用fontTools和ddddocr库破解自定义字体反爬虫

破解自定义字体反爬虫

背景介绍

众所周知,常见的反爬虫策略有以下几种:

  • 限制IP限制访问频率等,这我们可以通过代理ip等方式解决
  • 验证码,其中滑块验证码可以参考这篇博客解决:链接

还有一种:通过css自定义字体,来隐藏关键数据,从而拦截爬虫

如下图片所示,你会发现电影评分是一个无法识别的字体:

在这里插入图片描述

这次来破解一下这样的自定义字体爬虫

自定义字体原理介绍

css加密的方式具体前端是怎么实现的,可以参考如下这篇博客:

css加密实现原理

知道原理之后,接下来就具体介绍如何破解

x眼电影具体案例

以猫眼电影的票房数据为例,介绍一个具体案例

下载字体

通过搜索stonefont,我们可以找到如下style

在这里插入图片描述

通过其中的xxx.woff这个链接,就可以下载到字体。

具体到代码中,我们通过正则拿到字体链接,然后下载到本地。保存三种格式的字体。

def get_font_url(url):
    """
    获取加密字体的下载地址
    :param url: 电影请求页面的url
    :return: 返回字体的名称
    """
    resp = requests.get(url, headers=header)
    resp_html = resp.text
    e_t = etree.HTML(resp_html)
    font_str = str(e_t.xpath('/html/head/style/text()'))
    font_file_name = re.findall(r'//s3plus.meituan.net/v1/mss_73a511b8f91f43d0bdae92584ea6330b/font/(\w+\.woff)',
                                font_str)[0]

    return font_file_name

def down_font(font_name):
    """
    下载字体,保存为font.woff font.ttf 和 font.xml
    :param font_name: 字体的名称
    :return:
    """
    font_url = f"//s3plus.meituan.net/v1/mss_73a511b8f91f43d0bdae92584ea6330b/font/{font_name}"
    r = requests.get('http:'+font_url)
    with open(f"font/font.woff", "wb") as code:
        code.write(r.content)
    font = TTFont(f"font/font.woff")
    font.saveXML(f'font/font.xml')
    decompress("font/font.woff", "font/font.ttf")

字体识别

将下载后的woff文件,通过如下网站可以进行预览,就能得到这个字体文件中,编码和数字的对应关系;还有一种方式是将woff转换成xml文件,然后通过pillow库对字体坐标进行绘图,也可以识别字体。

https://blog.luckly-mjw.cn/tool-show/iconfont-preview/index.html

如下所示:

在这里插入图片描述

字体对应关系如下:

font_dict = {
    'uniEB19': 9, 'uniE3EC': 2, 'uniF7D2': 4, 'uniED30': 1, 'uniF3E8': 5, 'uniF11C': 3,
    'uniEA60': 6, 'uniEF28': 8, 'uniEA6F': 0, 'uniE3DF': 7
}

如下图所示:(由于每次刷新页面字体都会随机变化,截图字体和我下载的不是同一款

在这里插入图片描述

字体本质上是由一系列的坐标定义的,将xml文件中的坐标,也就是标签中的内容复制出来,通过以下代码就可以绘图了:

import matplotlib.pyplot as plt
import re

code_str = """
      <contour>
        <pt x="258" y="536" on="1"/>
        <pt x="106" y="592" on="0"/>
        <pt x="106" y="741" on="1"/>
        <pt x="106" y="853" on="0"/>
        <pt x="263" y="1000" on="0"/>
        <pt x="526" y="1000" on="0"/>
        <pt x="686" y="844" on="0"/>
        。。。此处省略一些代码

      </contour>
"""

x = [int(i) for i in re.findall(r'<pt x="(.*?)" y=', code_str)]
y = [int(i) for i in re.findall(r'y="(.*?)" on=', code_str)]

print(x)
print(y)

plt.plot(x, y)
plt.show()

字体处理

对于字体的处理,参考多个博客后,大致有以下两种思路

  • 虽然每次字体都不一样,但字体的坐标信息是一样的,所以可以通过比较坐标来找出字体的对应关系(尝试了很久没成功
  • 每次通过绘图,然后图形识别,来识别字体(推荐该做法

下面分别对这两种方式进行介绍。

方案一:比较字体坐标(失败

制作模板字体字典

首先手动下载一个字体,把他作为模板字体,可以参考上文的方法得到,比如:

font_dict = {
    'uniEB19': 9, 'uniE3EC': 2, 'uniF7D2': 4, 'uniED30': 1, 'uniF3E8': 5, 'uniF11C': 3,
    'uniEA60': 6, 'uniEF28': 8, 'uniEA6F': 0, 'uniE3DF': 7
}

比较字体

每次访问新页面时,下载新的字体,然后进行比较,找出新的字体词典,代码如下:

import requests
from fontTools.ttLib import TTFont

def comp_font(font_name):
    if font_name == "e3dfe524.woff":
        print("本次字体和第一次一样")
        return font_dict

    font_url = f"//s3plus.meituan.net/v1/mss_73a511b8f91f43d0bdae92584ea6330b/font/{font_name}"
    r = requests.get('http:'+font_url)
    with open(f"font/{font_name}", "wb") as code:
        code.write(r.content)
    print("字体不一致,下载新的字体")

    # 打开基准字体
    demo_font = TTFont("font/e3dfe524.woff")
    demo_list = demo_font.getGlyphNames()[1:-1]
    demo_uni_list = demo_font.getGlyphOrder()[2:]

    # 打开新字体
    new_font = TTFont(f"font/{font_name}")
    new_font.saveXML(f"font/{font_name}.xml")
    # new_font = TTFont("font/20a70494.woff")
    new_list = new_font.getGlyphNames()[1:-1]
    new_uni_list = new_font.getGlyphOrder()[2:]

    # 通过两层循环进行比较,找出新的对应关系
    new_dict = dict()
    for n_uni in new_uni_list:
        n_obj = new_font['glyf'][n_uni]
        for demo_uni in demo_uni_list:
            demo_obj = demo_font['glyf'][demo_uni]
            if n_obj == demo_obj:
                new_dict[n_uni] = font_dict[demo_uni]

    print(new_dict)
    return new_dict

结论:失败

在网上很多都是推荐这种方法,但,结论是问题出在这一步:

if n_obj == demo_obj:

我猜测可能TTFont的[’glyf’]对象不能直接这样简单粗暴的比较吧。如果有小伙伴知道原因,麻烦告诉我。谢谢。

方案二:通过ddddocr库进行图片识别(推荐

简单粗暴,代码如下:

def read_num_by_draw(woff_font):
    """
    绘制图片通过ddddocr库进行图片识别
    :param woff_font: 字体文件
    :return: 字体字典
    """
    ttf_font = "font/font.ttf"
    img_size = 512
    font = TTFont(woff_font)
    font_img = ImageFont.truetype(ttf_font, img_size)
    ocr = ddddocr.DdddOcr(show_ad=False)
    font_dict = dict()
    for cmap_code, glyph_name in font.getBestCmap().items():

        # 实例化一个图片对象
        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)
        font_dict[glyph_name.replace('uni', '&#x').lower()] = word

    return font_dict

结语

拿到字体和编码的对应字典后,就可以对网页爬取的内容进行替换了。不过这里还有另一个问题需要解决,直接访问电影链接的话,实际上得到的是如下的母版页,并不包含电影信息:

在这里插入图片描述

电影详细信息是ajax请求得到的,这又是另一个问题了。

参考链接

完整代码请参考如下链接:

https://github.com/h-kayotin/hanayo_pixiv/tree/master/maoyan

本文还参考了如下博客:

https://mp.weixin.qq.com/s?__biz=Mzg2NzYyNjg2Nw==&mid=2247489977&idx=1&sn=83dcaee1ecbfd79048b38ab5b006cee2&source=41#wechat_redirect

https://github.com/CasterWx/python-maoyan-spider

https://mp.weixin.qq.com/s?__biz=Mzg2NzYyNjg2Nw==&mid=2247489991&idx=1&sn=54c11b1685d4cf7755a83f75cda84601&source=41#wechat_redirect

https://www.cnblogs.com/tjp40922/p/10603768.html

用图像识别解决:https://juejin.cn/post/7091958812378136590

  • 16
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值