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

举报
梦想橡皮擦 发表于 2022/04/30 21:40:35 2022/04/30
【摘要】 📢📢📢📢📢📢💗 你正在阅读 【梦想橡皮擦】 的博客 ⛳️ Python反爬实战场景本篇博客开始,我们正式进入字体反爬的战场,今天的目标站点是猫眼,一个很经典的字体实例,案例仅供学习使用,请勿用于非法用途。进入网站首页之后,随机选择一部影视作品,进入详情页。在页面的响应中可以看到,数字相关信息无法直接获取。如果在爬虫分析逻辑中,出现上述内容,都属于字体反爬类站点。我们在用 Ele...

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

⛳️ 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">&#xe36c;.&#xf8df;</span> 就是要解除反爬的位置,其中

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

&#xe36c; - > 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)

# 假设抓取到的评分字符串如下所示
# &#xe6e0;&#xe5bf;&#xe415;&#xf078;&#xf078;
font_str = '&#xe6e0;&#xe5bf;&#xe415;&#xf078;&#xf078;'

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

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

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

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

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。