爬虫技术-字体反爬
文本混淆章节
1. 文本混淆简介
简单而言就是利用前端技术干扰,页面可以正常展示,而使用爬虫下载后无法提取正常的数据。
1.1 常见的干扰方式
- 字体反爬
2. 字体反爬
2.1 字体反爬简介
在 CSS3
之前,Web
开发者必须使用用户计算机上已有的字体。目前的技术开发者可以使用@font-face
为网页指定字体,开发者可将心仪的字体文件放在 Web 服务器上,并在 CSS
样式中使用它。用户使用浏览器访问 Web应用
时,对应的字体会被浏览器下载到用户的计算机上。
注:使用自动化selenium
也无法获取正常的数据
3. 某习字体反爬实践
3.1 逆向目标
- 首页:https://www.shixiseng.com/
- 目标:https://www.shixiseng.com/interns?keyword=产品&city=全国&type=intern&from=menu
- 逆向:薪酬字体
3.2 逆向分析
3.2.1 网页分析
- 打开网站可以发现,价格的字体是乱码
3.2.2 页面处理
- 在页面源代码中搜索
font-face
关键字,可以发现字体文件在网页源代码中
- 可以在网络抓包里面进行筛选,可以发现这里面有对应的字体文件加载地址,由后端返回
- 对于字体文件,可以直接使用工具解析
- 在线地址:http://font.qqe2.com/
- 使用方式:
- 下载字体文件到本地目录
- 访问在线工具网站,点击左上角打开,找到本地目录字体文件即可
- 使用方式:
- 在线地址:http://font.qqe2.com/
3.3.3 字体分析
正常在网页里面展示的薪酬是:
- 下载后的页面元素
- 解析的字体文件
woff
文件转化成xml
文件进行分析cmap
是关键 原来是unicode
码
3.3 逆向结果
3.3.1 完整code
class Sxs(): def __init__(self): self.headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36" } def get_html(self): # 第1步:获取html,且存为html文件以便后面研究使用 url = 'https://www.shixiseng.com/interns?keyword=%E4%BA%A7%E5%93%81&city=%E5%85%A8%E5%9B%BD&type=intern&from=menu' ret = requests.get(url=url, headers=self.headers).text with open('index.html', 'w', encoding='utf8') as f: f.write(ret) return ret def get_font(self,ret): # 第2步:下载html配套的ttf文件 font_url = re.findall('src: url\((.*?)\);', ret) f_url = 'https://www.shixiseng.com' + font_url[0] if font_url else font_url font_data = requests.get(f_url) with open('file.woff', 'wb') as f: f.write(font_data.content) def get_font_data(self,ttf): font_dict = {} # font = TTFont("file.woff") font = TTFont(ttf) cmap = font.get("cmap").getBestCmap() for k, v in cmap.items(): if v[3:]: content = "\\u00" + v[3:] if len(v[3:]) == 2 else "\\u" + v[3:] real_content = content.encode('utf-8').decode('unicode_escape') k_hex = hex(k) # 网页返回的字体是以&#x开头 ,换成以这个开头,下面代码就是直接替换 real_k = k_hex.replace("0x", "&#x") font_dict[real_k] = real_content return font_dict def put_html(self,ttf_dict): with open("index.html", "r", encoding="utf-8") as f: html = f.read() for k, v in ttf_dict.items(): html = html.replace(k, v) return html def get_data(self,html): html = etree.HTML(html) li_list = html.xpath("//div[@class='intern-wrap intern-item']") for li in li_list: title = "".join(li.xpath(".//div[@class='f-l intern-detail__job']//a/text()")[0].split()) price = "".join( li.xpath(".//div[@class='f-l intern-detail__job']//span[@class='day font']/text()")[0].split()) name = li.xpath('.//a[@class="title ellipsis"]/text()')[0] print(title, price,name) def main(self): # 第1步:获取html,且存为html文件以便后面研究使用 ret = self.get_html() # 第2步:下载html配套的ttf文件 self.get_font(ret) # 第3步:提取ttf中摄影的数据 font_dict = self.get_font_data('file.woff') # 第4步:对下载(HTML内容)进行替换 html = self.put_html(font_dict) # 第5步:使用xpath提取想要的数据 data = self.get_data(html) print(data) if __name__ == '__main__': Sxs().main()
4. 字体解析工具
4.1 工具安装
pip install fontTools # 使用这个包处理字体文件
4.2 字体读取
from fontTools.ttLib import TTFont # 加载字体文件: font = TTFont('file.woff') # 转为xml文件: font.saveXML('file.xml')
4.3 节点读取
from fontTools.ttLib import TTFont # 加载字体文件: font = TTFont('file.woff') kv = font.keys() print(kv)
字体文件不仅包含字形数据和点信息,还包括字符到字形映射、字体标题、命名和水平指标等,这些信息存在对应的表中:
表 | 作用 |
---|---|
cmap | 字符到字形映射 |
glyf | 字形数据 |
head | 字体标题 |
hhea | 水平标题 |
hmtx | 水平指标 |
loca | 索引到位置 |
maxp | 最大限度的 |
name | 命名 |
post | 后记 |
4.4 获取请求到的字体code和name的对应关系
code_name_map = font_aa.getBestCmap()
4.5 获取字体坐标信息
font_aa = TTFont('file.woff') # 获取请求到的字体形状 glyf = font_aa['glyf'] #font['glyf'][字体编码].coordinates font_aa['glyf']['uni4E94'].coordinates
5. 某车字体反爬
5.1 逆向目标
- 首页:https://www.renrenche.com/
- 目标:https://www.renrenche.com/bj/ershouche/p2/?&plog_id=838083390d4b077a45852d11065f60ad
- 逆向:标题字体
5.2 逆向分析
-
打开网页源代码搜索
font-face
,查找字体文件 -
分析字形关系
5.2.1 原字形还原
请求字体链接,获取字体code和name的对应关系,然后遍历,获取网页中反爬文字的真实文字。
relation_table = {"zero": "0", "one": "1", "two": "2", "three": "3", "four": "4", "five": "5", "seven": "6","eight": "7", "six": "8", "nine": "9"} def woff_font(font_url): '''获取字体真实对应关系''' newmap = {} resp = session.get(font_url) # 请求字体链接 woff_data = BytesIO(resp.content) font = TTFont(woff_data) # 读取woff数据 cmap = font.getBestCmap() # 获取字体对应关系 font.close() for k, v in cmap.items(): value = v key = str(k - 48) # 获取真实的key try: get_real_data = relation_table[value] except: get_real_data = '' if get_real_data != '': newmap[key] = get_real_data # 将字体真实结果对应 return newmap
5.3 逆向代码
from io import BytesIO import requests from fontTools.ttLib import TTFont from lxml import etree import re import execjs session = requests.session() maps = lambda x:x[0] if x else x # relation_table = {"zero": "0", "one": "1", "two": "2", "three": "3", "four": "4", "five": "5", "seven": "6","eight": "7", "six": "8", "nine": "9"} def woff_font(font_url): '''获取字体真实对应关系''' newmap = {} resp = session.get(font_url) # 请求字体链接 woff_data = BytesIO(resp.content) # 保存字体数据 font = TTFont(woff_data) # 读取woff数据 glyf = font['glyf'] # 获取请求到的字体形状 # 建立基础的字体和字体形状的对应关系 base_font_map = { 0: glyf['zero'], 1: glyf['four'], 2: glyf['five'], 3: glyf['three'], 4: glyf['seven'], 5: glyf['one'], 6: glyf['two'], 7: glyf['six'], 8: glyf['nine'], 9: glyf['eight'] } code_name_map = font.getBestCmap() # 获取请求到的字体code和name的对应关系 font.close() for code, name in code_name_map.items(): codestr = str(code - 48) # 根据分析结果需要减去48 current_shape = glyf[name] # 根据name获取字体形状 for number, shape in base_font_map.items(): # 遍历基础字体形状对应关系 if shape == current_shape: # 判断,如果两个字体形状相等 newmap[codestr] = str(number) # 将字体编码和字体添加到字典 # print(newmap) return newmap def font_file(r): # 从网页里面获取字体 font_url = re.search(r"url\('(.*\.woff)'\)", r).group(1) return font_url def get_index(): headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36" } url = 'https://www.renrenche.com/bj/ershouche/p2/?&plog_id=838083390d4b077a45852d11065f60ad' complete_cookie = {} # 第一次不带参数访问首页,获取 acw_tc 和 acw_sc__v2 response = requests.get(url=url, headers=headers) complete_cookie.update(response.cookies.get_dict()) arg1 = re.findall("arg1='(.*?)'", response.text)[0] with open('tiaoshi.js', 'r', encoding='utf-8') as f: acw_sc_v2_js = f.read() acw_sc__v2 = execjs.compile(acw_sc_v2_js).call('ss', arg1) complete_cookie.update({"acw_sc__v2": acw_sc__v2}) # 第二次访问首页,获取其他 cookies response2 = requests.get(url=url, headers=headers, cookies=complete_cookie) font_url = font_file(response2.text) # print(font_url) html = etree.HTML(response2.text) items = html.xpath('//ul[@class="row-fluid list-row js-car-list"]/li') for i in items: title = maps(i.xpath('.//h3/text()')) font = woff_font(font_url) trans_title = "".join([i if not i.isdigit() else font[i] for i in title]) # 替换错误字体,获取真实标题 print(trans_title) get_index()
运行结果:
/usr/local/bin/python3.9 "/Volumes/shifeng/人人车.py" 斯巴鲁-森林人 2016款 2.5i 豪华导航版 雪铁龙-雪铁龙C4L 2013款 1.6THP 自动劲智版 日产-骐达TIIDA 2011款 1.6L CVT智能型 大众-途观 2012款 1.8TSI 自动四驱菁英版 本田-飞度 2018款 1.5L CVT舒适天窗版 北京-北京BJ20 2016款 1.5T CVT豪华型 东风风光-风光ix5 2019款 360TGDI CVT智尊型 广汽传祺-传祺GS3 2017款 150N 自动精英版 英菲尼迪-英菲尼迪M系 2008款 M35 尊尚版 奥迪-奥迪A4L 2015款 45 TFSI quattro个性运动型 大众-宝来 2016款 1.6L 自动时尚型 日产-轩逸 2018款 经典 1.6XE+ CVT豪华版 捷豹-捷豹F-PACE 2019款 2.0T 四驱R-Sport运动版 奥迪-奥迪Q5(进口) 2010款 3.2FSI 运动版 玛莎拉蒂-Levante 2019款 3.0T 标准版 福特-福克斯 6004款 三厢 5.9L 手动舒适型 日产-逍客 2012款 2.0XV 雷 CVT 2WD 宝马-宝马3系 2016款 316Li 时尚型 现代-悦动 2020款 1.6L 自动GL悦目版 现代-北京现代ix35 2012款 2.0L 自动两驱精英版GLS 福特-福克斯 2011款 三厢 1.8L 自动时尚型 本田-飞度 2016款 1.5L LX CVT舒适型 奥迪-奥迪A4L 2011款 1.8 TFSI 舒适型 日产-奇骏 2012款 2.5L CVT豪华版 4WD 广汽埃安-AION V 2020款 80 智享科技版 丰田-亚洲龙 2019款 2.5L Touring尊贵版 荣威-荣威Ei5 2021款 超爽版 宝马-宝马5系 2011款 523Li 豪华型 本田-杰德 2017款 1.8L 自动舒适版 5座 凯迪拉克-凯迪拉克SRX 2014款 3.0L 豪华型 丰田-卡罗拉 2017款 改款 1.2T S-CVT GL 保时捷-Macan 2014款 Macan S 3.0T 丰田-汉兰达 2011款 2.7L 两驱7座豪华版 大众-迈腾 2013款 1.8TSI 豪华型 别克-君越 2018款 28T 豪华型 日产-纳瓦拉 2017款 2.5L自动两驱豪华版QR25 别克-凯越 6009款 5.7LE-MT 奥迪-奥迪A3 2014款 Sportback 35 TFSI 自动时尚型 宝马-宝马4系 2017款 425i M运动套装 宝马-宝马5系 2018款 530Li 领先型 M运动套装 保时捷-Panamera 2016款 Panamera 4 Edition 3.0T 奔驰-奔驰V级 2017款 V 260 L 尊贵加长版 Process finished with exit code 0
如果您觉得本篇文章还不错,欢迎点赞,转发分享(转发请注明出处),感谢~~
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!