字形相同的字体反爬问题解析
- 问题所在:部分数据加载时使用网站自定义的字体,浏览器访问网页时字体文件会加载到浏览器中,爬虫访问时没有对应的自定义字体,所以就得不到那部分数据,如图1,加密的这部分数据在源码中以字体编码的形式存在,如图2。
- 分析问题:下载字体用字体分析工具查看字体后发现字体编码的后4位与源码代替加密数据的编码的后四位是相同的,再与实际数据进行比对后发现他们表示的是同一个文字,如下图所示,所以可利用编码与字形对应关系来替换源码中的编码。
- 解决问题
3.1 因大众点评不同字体的字形是相同的,所以首先可以自己创建一套对应的字体对照表,如下:
font=['1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '店', '中', '美', '家', '馆', '小', '车', '大', '市', '公', '酒','行', '国', '品', '发', '电', '金', '心', '业', '商', '司', '超', '生', '装', '园', '场', '食', '有', '新', '限', '天', '面','工', '服', '海', '华', '水', '房', '饰', '城', '乐', '汽', '香', '部', '利', '子', '老', '艺', '花', '专', '东', '肉', '菜','学', '福', '饭', '人', '百', '餐', '茶', '务', '通', '味', '所', '山', '区', '门', '药', '银', '农', '龙', '停', '尚', '安','广', '鑫', '一', '容', '动', '南', '具', '源', '兴', '鲜', '记', '时', '机', '烤', '文', '康', '信', '果', '阳', '理', '锅','宝', '达', '地', '儿', '衣', '特', '产', '西', '批', '坊', '州', '牛', '佳', '化', '五', '米', '修', '爱', '北', '养', '卖','建', '材', '三', '会', '鸡', '室', '红', '站', '德', '王', '光', '名', '丽', '油', '院', '堂', '烧', '江', '社', '合', '星','货', '型', '村', '自', '科', '快', '便', '日', '民', '营', '和', '活', '童', '明', '器', '烟', '育', '宾', '精', '屋', '经','居', '庄', '石', '顺', '林', '尔', '县', '手', '厅', '销', '用', '好', '客', '火', '雅', '盛', '体', '旅', '之', '鞋', '辣','作', '粉', '包', '楼', '校', '鱼', '平', '彩', '上', '吧', '保', '永', '万', '物', '教', '吃', '设', '医', '正', '造', '丰','健', '点', '汤', '网', '庆', '技', '斯', '洗', '料', '配', '汇', '木', '缘', '加', '麻', '联', '卫', '川', '泰', '色', '世','方', '寓', '风', '幼', '羊', '烫', '来', '高', '厂', '兰', '阿', '贝', '皮', '全', '女', '拉', '成', '云', '维', '贸', '道','术', '运', '都', '口', '博', '河', '瑞', '宏', '京', '际', '路', '祥', '青', '镇', '厨', '培', '力', '惠', '连', '马', '鸿','钢', '训', '影', '甲', '助', '窗', '布', '富', '牌', '头', '四', '多', '妆', '吉', '苑', '沙', '恒', '隆', '春', '干', '饼','氏', '里', '二', '管', '诚', '制', '售', '嘉', '长', '轩', '杂', '副', '清', '计', '黄', '讯', '太', '鸭', '号', '街', '交','与', '叉', '附', '近', '层', '旁', '对', '巷', '栋', '环', '省', '桥', '湖', '段', '乡', '厦', '府', '铺', '内', '侧', '元','购', '前', '幢', '滨', '处', '向', '座', '下', '県', '凤', '港', '开', '关', '景', '泉', '塘', '放', '昌', '线', '湾', '政','步', '宁', '解', '白', '田', '町', '溪', '十', '八', '古', '双', '胜', '本', '单', '同', '九', '迎', '第', '台', '玉', '锦','底', '后', '七', '斜', '期', '武', '岭', '松', '角', '纪', '朝', '峰', '六', '振', '珠', '局', '岗', '洲', '横', '边', '济','井', '办', '汉', '代', '临', '弄', '团', '外', '塔', '杨', '铁', '浦', '字', '年', '岛', '陵', '原', '梅', '进', '荣', '友','虹', '央', '桂', '沿', '事', '津', '凯', '莲', '丁', '秀', '柳', '集', '紫', '旗', '张', '谷', '的', '是', '不', '了', '很','还', '个', '也', '这', '我', '就', '在', '以', '可', '到', '错', '没', '去', '过', '感', '次', '要', '比', '觉', '看', '得','说', '常', '真', '们', '但', '最', '喜', '哈', '么', '别', '位', '能', '较', '境', '非', '为', '欢', '然', '他', '挺', '着','价', '那', '意', '种', '想', '出', '员', '两', '推', '做', '排', '实', '分', '间', '甜', '度', '起', '满', '给', '热', '完','格', '荐', '喝', '等', '其', '再', '几', '只', '现', '朋', '候', '样', '直', '而', '买', '于', '般', '豆', '量', '选', '奶','打', '每', '评', '少', '算', '又', '因', '情', '找', '些', '份', '置', '适', '什', '蛋', '师', '气', '你', '姐', '棒', '试','总', '定', '啊', '足', '级', '整', '带', '虾', '如', '态', '且', '尝', '主', '话', '强', '当', '更', '板', '知', '己', '无','酸', '让', '入', '啦', '式', '笑', '赞', '片', '酱', '差', '像', '提', '队', '走', '嫩', '才', '刚', '午', '接', '重', '串','回', '晚', '微', '周', '值', '费', '性', '桌', '拍', '跟', '块', '调', '糕']
3.2 下载字体并创建基准对照字典
import requests
from fontTools.ttLib import TTFonturl=""
headers={}
def get_base_dict():html=requests.get(url,headers=headers).text# 找出网站自建字体url进行字体下载font_url="http:"+''.join(re.findall(r'<link.*type="text/css" href="(//s3plus.*?)">',text,re.S))font_html=requests.get(font_url).content# 提取不同字体的url及名称(有效的3种字体对应不同的页面数据)urls=re.findall(r',url\("(.*?\.woff)',text,re.S)names=re.findall(r'font-family: "(.*?)";',text,re.S)for url,name in zip(urls,names):woff_url="http:"+''.join(url)name= name.split('-')[-1]# print(woff_url)res_woff=(requests.get(url=woff_url)).contentwith open(f'./{name}.woff','wb') as f:f.write(res_woff)# 打开一个字体文件font=TTFont('./name.woff')# 以xml格式保存查看字体的各种对应关系font.saveXML('name.xml')font_unicode = base_font.getGlyphOrder()[2:]for unicode, font in zip(font_unicode, basefont_char):# print(unicode, font)# 获取字体对应的像素坐标base_contour = base_font['glyf'][unicode].coordinates# 将像素坐标信息加密成32位字符串base_contour_md5 = hashlib.md5(bytes(str(base_contour), encoding='utf-8')).hexdigest()# 设置成基准对照字典base_glyph[base_contour_md5] = {'value': font, 'unicode': unicode}
3.3 得到对照字典后再将网址事实获取的字体信息处理成与对照字典相同的形式
# 根据不同字体以基准字典的形式创建各自的对照字典
def address_font(base_glyph):"""网站所有新字体解析出坐标信息与基准信息对照并替换"""address_font = TTFont('address.woff')# tagName_font = TTFont('tagName.woff')# reviewTag_font = TTFont('reviewTag.woff')# 获取名称与字体间的对应关系address_unicodes = address_font.getGlyphOrder()[2:]# print(address_unicodes)# 拿出基准字典的MD5加密keyfor key in base_glyph.keys():# 取出每个字体的名称for address_unicode in address_unicodes:# 根据字体名称获取字体对应的坐标信息address_contour = address_font['glyf'][address_unicode].coordinates# 获取字体坐标的MD5加密值address_contour_md5 = hashlib.md5(bytes(str(address_contour), encoding='utf-8')).hexdigest()# print(address_contour_md5)# 对比基准字典与网站自建字体坐标加密信息if key == address_contour_md5:# 将自建字体的信息以基准字典的形式进行替换创建新的对照字典address_glyph[address_contour_md5] = {'value': base_glyph[key]['value'],'unicode':'&#x'+ address_unicode[3:]}# print(address_glyph)return address_glyph
3.4 得到实时的真实字体与编码的对应关系后,将源码中的编码替换成对应的文字即可,然后再进行数据提取。
# 根据字体对照字典对获取到的网页源码的加密信息进行替换
def replace_shopNum(html):# 取出对应字体的名称与实际文字的对应关系for uni_dic in shopNum_glyph.values():old =uni_dic['unicode']+';'new = uni_dic['value']# 判断源码中是否存在对应的字体名称,再进行替换if old in html:html=re.sub(old,new,html)return html
- 整体思路总结
4.1 字形相同的网址先需要获取到左右文字信息,可使用ocr识别或者只用字体分析模块输出打印;
4.2 再根据获取到的字体文件创建各个文字与Unicode之间的关系,得到基准对照字典;
4.3 用同样的方式创建实时获取的不同字体文件与基准字典形式相同的字典,并与基准字典就MD5加密的key进行对比,将相同的进行替换,以此得到最终的文字与unicode的关系;
4.4 得到文字与Unicode的关系后用文字替换源码中相对应的编码,即得到解密后的数据。