【首发字体反爬】猫X眼YingShi,我们又来欺负你了,用到了 OCR 识别技术

语言: CN / TW / HK

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第29天,点击查看活动详情

📢📢📢📢📢📢 💗 你正在阅读 【梦想橡皮擦】 的博客

⛳️ Python 反爬实战场景

本篇博客开始,我们正式进入字体反爬的战场,今天的目标站点是猫眼,一个很经典的字体实例,案例仅供学习使用,请勿用于非法用途。

进入网站首页之后,随机选择一部影视作品,进入详情页。

在页面的响应中可以看到,数字相关信息无法直接获取。

image.png

如果在爬虫分析逻辑中,出现上述内容,都属于字体反爬类站点。

我们在用 Element 抓取一下元素内容,可看到目标数据引用了一个特殊的字体样式 mtsi-font

image.png

在网页源码中检索该文本,得到如下内容:

js @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 软件打开该文件。

直接拖拽字体文件到软件中,即可进行查阅,得到如下结果。

image.png

请注意其中的编码和数字的对应关系,后续我们就是在 Python 中解析这些关系,从而获取文字信息的。

在查阅刚才字体加密的文件,得到下图所示内容。

image.png

上面的代码 <span class="stonefont">&#xe36c;.&#xf8df;</span> 就是要解除反爬的位置,其中

&#xe36c; 主要看后 4 位,两张图片对比就可以知道,这个值对应的字体编码是 uniE36C,即数字 9 ,所以在后续编写字体解密的代码时,需要将获取的文字编码转换成字体编码。

&#xe36c; - > uniE36C

接下来我们使用 Python 中的模块读取该字体,尝试获取字体编码内容。

安装 fontTools 模块,用于解析字体文件。

python pip install fontTools -i https://pypi.tuna.tsinghua.edu.cn/simple

手动下载 2 个字体文件,然后运行下述代码:

```python from fontTools.ttLib import TTFont

TTFont('03337c30.woff').saveXML('font1.xml') TTFont('e5389ac6.woff').saveXML('font2.xml') ```

得到 2 个 XML 文件,其中包含字体解析之后的数据。

image.png

这里是字体编号的缩写,字体具体的绘制内容在代码下侧,对比一下可以发现如下内容。

对比两个文件中关于数字 3 的编码,可以看到出现了细节差异,此时得到的一个结论就是,MAOYAN 除了字体编码变化外,字体渲染的坐标也发生了变化。

image.png

接下来完成本案例的第一步,下载一个参考字体文件,例如上文得到的内容。

image.png

然后建立一个字典,用于创建数字与编码的对应关系。

python font_dict = { 'uniE415': '3', 'uniF41A': '8', 'uniF078': '7', 'uniE5BF': '0', 'uniE36C': '9', 'uniF8DF': '1', 'uniE5A5': '6', 'uniEF4D': '4', 'uniE6E0': '2', 'uniED3D': '5', }

下面编写数字提取代码即可。

```python 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 文件,后续通过该文件进行渲染。

字体文件格式转换

```python 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 模块绘制一张字体图片。

绘制一张空图片

```python

图片宽度和高度

img_size = 512

实例化一个图片对象

img = Image.new('1', (img_size, img_size), 255)

绘制图片

draw = ImageDraw.Draw(img) img.show() ```

image.png

解析字体文件中的编码对应关系。

```python font = TTFont('./font.ttf')

for cmap_code, glyph_name in font.getBestCmap().items(): print(cmap_code,glyph_name) ```

代码运行之后,得到对应关系如下所示

txt 120 x 58220 uniE36C 58389 uniE415 58789 uniE5A5 58815 uniE5BF 59104 uniE6E0 60733 uniED3D 61261 uniEF4D 61560 uniF078 62490 uniF41A 63711 uniF8DF

此时又发现了 uni 开头的编码,而且前面出现了一堆数字,接下来就将这个数字进行绘制。

```python 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 识别即可完成。

接下来就是识别部分的代码了,将数字生成的二进制字节流读取到程序中,直接进行识别。

python bytes_io = BytesIO() img.save(bytes_io, format="PNG") word = ocr.classification(bytes_io.getvalue()) # 识别字体 print(word)

完整代码如下所示:

```python 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)

```

运行之后,得到如下输出内容

txt 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

此时在结合上文,将两部分代码(第一部分是将 &#xe6e0; 转换为 uni编码 )集成到一起 ,则完成猫眼的字体反爬识别案例。

再次去猫眼随机获取一个字体文件,成功解析如下结果,本次反爬案例成功解决。

image.png

📣📣📣📣📣📣 右下角有个大拇指,点赞的漂亮加倍

「其他文章」