【字体反爬】猫X眼YingShi,我们又来欺负你了,用到了 OCR 识别技术 一9
📢📢📢📢📢📢
💗 你正在阅读 【梦想橡皮擦】 的博客
⛳️ Python反爬实战场景
本篇博客开始,我们正式进入字体反爬的战场,今天的目标站点是猫眼,一个很经典的字体实例,案例仅供学习使用,请勿用于非法用途。
进入网站首页之后,随机选择一部影视作品,进入详情页。
在页面的响应中可以看到,数字相关信息无法直接获取。
如果在爬虫分析逻辑中,出现上述内容,都属于字体反爬类站点。
我们在用 Element
抓取一下元素内容,可看到目标数据引用了一个特殊的字体样式 mtsi-font
在网页源码中检索该文本,得到如下内容:
@font-face{font-family: "mtsi-font";src:url("//s3plus.meituan.net/v1/mss_73a511b8f91f43d0bdae92584ea6330b/font/e5389ac6.eot");src:url("//s3plus.meituan.net/v1/mss_73a511b8f91f43d0bdae92584ea6330b/font/e5389ac6.eot?#iefix") format("embedded-opentype"),url("//s3plus.meituan.net/v1/mss_73a511b8f91f43d0bdae92584ea6330b/font/e5389ac6.woff");}
.stonefont {
font-family: mtsi-font;
}
这里面包含了2个字体文件,后缀名依次是 .eot
,.woff
,后续突破点也在这里,同时发现了美xxx团相关信息,看来美xxx团系的站点,都有类似的字体反爬,后续可以研究下。
优先选择 .woff
文件下载分析,还文件的名字应该是动态的,后续爬虫的编写还需要动态获取该文件名。
woff 全名叫 Web 开发字体格式(Web Open Font Format),是一种网页字体格式标准。
下载文件之后,可以使用 FontCreator
软件打开该文件。
直接拖拽字体文件到软件中,即可进行查阅,得到如下结果。
请注意其中的编码和数字的对应关系,后续我们就是在 Python 中解析这些关系,从而获取文字信息的。
在查阅刚才字体加密的文件,得到下图所示内容。
上面的代码 <span class="stonefont">.</span>
就是要解除反爬的位置,其中

主要看后4位,两张图片对比就可以知道,这个值对应的字体编码是 uniE36C
,即数字 9 ,所以在后续编写字体解密的代码时,需要将获取的文字编码转换成字体编码。

- >uniE36C
接下来我们使用 Python 中的模块读取该字体,尝试获取字体编码内容。
安装 fontTools
模块,用于解析字体文件。
pip install fontTools -i https://pypi.tuna.tsinghua.edu.cn/simple
手动下载2个字体文件,然后运行下述代码:
from fontTools.ttLib import TTFont
TTFont('03337c30.woff').saveXML('font1.xml')
TTFont('e5389ac6.woff').saveXML('font2.xml')
得到2个XML文件,其中包含字体解析之后的数据。
这里是字体编号的缩写,字体具体的绘制内容在代码下侧,对比一下可以发现如下内容。
对比两个文件中关于数字 3 的编码,可以看到出现了细节差异,此时得到的一个结论就是,MAOYAN 除了字体编码变化外,字体渲染的坐标也发生了变化。
接下来完成本案例的第一步,下载一个参考字体文件,例如上文得到的内容。
然后建立一个字典,用于创建数字与编码的对应关系。
font_dict = {
'uniE415': '3',
'uniF41A': '8',
'uniF078': '7',
'uniE5BF': '0',
'uniE36C': '9',
'uniF8DF': '1',
'uniE5A5': '6',
'uniEF4D': '4',
'uniE6E0': '2',
'uniED3D': '5',
}
下面编写数字提取代码即可。
from fontTools.ttLib import TTFont
base_font = TTFont('03337c30.woff')
print(base_font)
base_list = base_font.getGlyphOrder()[2:]
print(base_list)
# 假设抓取到的评分字符串如下所示
# 
font_str = ''
font_list = font_str.split(';')[:-1]
font_list = ['uni' + _[3:].upper() for _ in font_list]
font_dict = {
'uniE415': '3',
'uniF41A': '8',
'uniF078': '7',
'uniE5BF': '0',
'uniE36C': '9',
'uniF8DF': '1',
'uniE5A5': '6',
'uniEF4D': '4',
'uniE6E0': '2',
'uniED3D': '5',
}
real_numm = [font_dict[f] for f in font_list]
print(real_numm)
输出内容为 ['2', '0', '3', '7', '7']
,该内容可以与网页显示的数据对应上,表示基础的解析已经完成,如果网页中字体文件编码无变化,此时我们已经了问题,但是猫眼难度要高2个级别,它每次刷新页面都发生了变更,自然编码逻辑难度要大很多。
接下来就进入数字的识别环节,由于猫眼的字体文件动态加载,并且编码和顺序都是会变的,所以我们采用OCR图片识别技术。
⛳️ 数字识别实战场景
测试前下载一个 woff
文件,然后将其转换为 ttf
文件,后续通过该文件进行渲染。
字体文件格式转换
from fontTools.ttLib import TTFont
from fontTools.ttLib.woff2 import decompress
# 目标站点下载的字体文件
woff_path = "./03337c30.woff"
# ttf 格式文件
ttf_path = './font.ttf'
# 将 woff文件转成 ttf 文件
decompress(woff_path, ttf_path)
font = TTFont(ttf_path)
print(font)
运行代码之后,得到 ttf 文件。
接下来使用 PIL 模块绘制一张字体图片。
绘制一张空图片
# 图片宽度和高度
img_size = 512
# 实例化一个图片对象
img = Image.new('1', (img_size, img_size), 255)
# 绘制图片
draw = ImageDraw.Draw(img)
img.show()
解析字体文件中的编码对应关系。
font = TTFont('./font.ttf')
for cmap_code, glyph_name in font.getBestCmap().items():
print(cmap_code,glyph_name)
代码运行之后,得到对应关系如下所示
120 x
58220 uniE36C
58389 uniE415
58789 uniE5A5
58815 uniE5BF
59104 uniE6E0
60733 uniED3D
61261 uniEF4D
61560 uniF078
62490 uniF41A
63711 uniF8DF
此时又发现了 uni
开头的编码,而且前面出现了一堆数字,接下来就将这个数字进行绘制。
from fontTools.ttLib import TTFont
from fontTools.ttLib.woff2 import decompress
from PIL import ImageFont, Image, ImageDraw
# 图片宽度和高度
img_size = 512
font = TTFont('./font.ttf')
font_img = ImageFont.truetype('./font.ttf', img_size)
for cmap_code, glyph_name in font.getBestCmap().items():
# print(cmap_code,glyph_name)
# 实例化一个图片对象
img = Image.new('1', (img_size, img_size), 255)
# 绘制图片
draw = ImageDraw.Draw(img)
# 将编码读取成字节
txt = chr(cmap_code)
x, y = draw.textsize(txt, font=font_img)
draw.text(((img_size - x) // 2, (img_size - y) // 2), txt, font=font_img, fill=0)
img.show()
运行上述代码,程序会依次展示各种绘制数字的图片,后续我们使用 ddddocr
识别即可完成。
接下来就是识别部分的代码了,将数字生成的二进制字节流读取到程序中,直接进行识别。
bytes_io = BytesIO()
img.save(bytes_io, format="PNG")
word = ocr.classification(bytes_io.getvalue()) # 识别字体
print(word)
完整代码如下所示:
from fontTools.ttLib import TTFont
from fontTools.ttLib.woff2 import decompress
from PIL import ImageFont, Image, ImageDraw
from io import BytesIO
import ddddocr
"""
# 目标站点下载的字体文件
woff_path = "./03337c30.woff"
# ttf 格式文件
ttf_path = './font.ttf'
# 将 woff文件转成 ttf 文件
decompress(woff_path, ttf_path)
font = TTFont(ttf_path)
"""
# 图片宽度和高度
img_size = 512
font = TTFont('./font.ttf')
font_img = ImageFont.truetype('./font.ttf', img_size)
ocr = ddddocr.DdddOcr()
for cmap_code, glyph_name in font.getBestCmap().items():
# print(cmap_code,glyph_name)
# 实例化一个图片对象
img = Image.new('1', (img_size, img_size), 255)
# 绘制图片
draw = ImageDraw.Draw(img)
# 将编码读取成字节
txt = chr(cmap_code)
x, y = draw.textsize(txt, font=font_img)
draw.text(((img_size - x) // 2, (img_size - y) // 2), txt, font=font_img, fill=0)
bytes_io = BytesIO()
img.save(bytes_io, format="PNG")
word = ocr.classification(bytes_io.getvalue()) # 识别字体
print(cmap_code, glyph_name, word)
运行之后,得到如下输出内容
2 extra bytes in post.stringData array
120 x
58220 uniE36C 9
58389 uniE415 3
58789 uniE5A5 6
58815 uniE5BF 0
59104 uniE6E0 2
60733 uniED3D 5
61261 uniEF4D 4
61560 uniF078 7
62490 uniF41A 8
63711 uniF8DF 1
此时在结合上文,将两部分代码(第一部分是将 
转换为 uni编码
)集成到一起 ,则完成猫眼的字体反爬识别案例。
再次去猫眼随机获取一个字体文件,成功解析如下结果,本次反爬案例成功解决。
📣📣📣📣📣📣
右下角有个大拇指,点赞的漂亮加倍
- 点赞
- 收藏
- 关注作者
评论(0)