严重声明:本文仅用于学习交流,不得用于商业用途,同时希望大家遵循robots协议,维护网络和谐。
本猿最近在逛一些网站的时间。在打开浏览器的f12查看人家前端代码咋写的时候,经常会发现就是页面上显示的内容和源码里面的不一样,然后自己请求一遍也还是不一样,奇怪,猿,妙不可言?本着猿精神,上网查了下,这种属于字体反爬策略。应用的还是不少的,所以,在这里将在下对字体反爬的见解写一下。
- 先观察人人车网站,看看人家的实现: 人人车
-
打开一个详情页,f12看页面标签,可以发现标题里面年款啥的页面上展示给我们的和我们看到的不一样。在查看了很多页面后发现,人人车字体变化的都是数字:
-
查看标题css样式的时候发现,标题标签和一个css样式利用属性名关联上了,所以这个属性名肯定有问题:
-
查看服务器给我们发的响应包,直接发现一个跟上面标签属性名一样的字体文件,利用谷歌浏览器的规范输出,直接查看,发现最下面的一行数字顺序是被打乱的。另外还有一个字体文件,但是数字顺序是正常的:
-
我们看这两串数字,因为i标题采用了个乱序数字字体文件名的样式,暂时总结出个规律:就是本应该在1的位置上他换上了2,2>1,3>4,4>3,5>8,8>5:
-
按照4中的规律再看首页的源码和展示给我们的标题,猿来如此:
-
应用上面过程查看其它页面,发现都是上面这样的套路,但是这个字体文件的url和内容是变化的。
-
那么,作为一个coder,怎么会被这些迷惑操作遮住我们睿智的双眼,下面是本猿利用python代码实现的字体复原过程(完整测试代码贴在最下面):
测试代码,完整:
# !/usr/bin/python3 # -*- coding: utf-8 -*- """ # @Time : 2020/4/25 13:32 # @Author : hupoc # @File : second_hand_car_renrenche.py # @Desc : """ import io import os import requests from fontTools.ttLib import TTFont from scrapy import Selector def font_transfor(translate_text, html, url): """ 网页上使用字体文件的文本转换 :param translate_text: 需要转换的文本 :param html: 经过scrapy.Selector()转换后的响应对象 :param url: 请求的url :return: 转换后的文本 """ # 获取字体名所在标签文本 font_interface_str = html.xpath("//div[@class='title']/h1/@class").extract_first() if font_interface_str and 'title-name' in font_interface_str: # 提取字体文件名称 font_interface_name = font_interface_str.replace('title-name', '').strip() else: # 如果没有title-name表示没有应用字体文件 print('没有提取到字体名所在标签文本,url:{0}'.format(html.url)) return file_name = url.rsplit("/", 1)[-1] + '_' + font_interface_str # 拼接字体文件 在本地保存路径 file_ttf = '/tmp/renrenche_font/{0}.woff'.format(file_name) if not os.path.exists(os.path.dirname(file_ttf)): os.makedirs(os.path.dirname(file_ttf)) # 下载字体文件,保存到本地路径 url = 'https://misc.rrcimg.com/ttf/{0}.woff'.format(font_interface_name) resp = requests.get(url) if not resp: print('请求字体文件失败') try: font = TTFont(io.BytesIO(resp.content)) except Exception as e: return font_obj = font['cmap'] font_tables = font['cmap'].tables uni_list = font['cmap'].tables[0].ttFont.getGlyphOrder() # 生成转换规则 base_num_list = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'] base_eng_list = {'zero': '0', 'one': '1', 'two': '2', 'three': '3', 'four': '4', 'five': '5', 'six': '6', 'seven': '7', 'eight': '8', 'nine': '9'} mapping_list = [base_eng_list[_] for _ in uni_list[1:]] font_dict = dict(zip(mapping_list, base_num_list)) # 对需要转换的文本进行转换 transfor_str = [_ if not _.isdigit() else font_dict[_] for _ in translate_text] # 关闭字体文件 font.close() # 返回正确的文本 return ''.join(transfor_str) def get_renrenche_detail(): # 请求页面获取响应 detail_url = 'https://www.renrenche.com/bj/car/c2d4fdd2902df36b' response = requests.get(detail_url) if not response or response.status_code != 200: print('人人车详情页请求失败') return # 将响应文本转为lxml对象,为了方便,使用的是scrapy.Selector() html = Selector(text=response.text) # 获取标题,并规格化标题文本 titles = html.xpath("//div[@class='title']/h1/text()").extract() title = ' '.join(titles).replace('\n', '').strip() # 转换标题文字,获取正确文本 true_title = font_transfor(title, html, detail_url) # 打印文本 print(true_title) if __name__ == '__main__': # 请求人人车的二手车详情页 get_renrenche_detail()
测试代码运行结果: