python爬虫解决字体加密问题

字体文件的后缀名大多是woff、tff两种

有些网站在前端显示的是正确的文字,我们在获取网页源代码的时候,发现源代码中有些字是乱码

猫眼验证中心为例:

可以看到显示这样,这种就是字体进行了加密

当前文字运用的是这种字体,我们在全局搜索,可以找到定义该字体的CSS文件

在这里进行定义的

我们可以在抓包中看到这个字体文件发送的请求,直接双击进行下载就可以了

每次请求的时候会有一个后缀为woff的文件,这个就是乱码字体的文件,把文件下载下来

这种woff文件,下载完成之后是打不开的,我们需要用专门的软件打开,软件地址:Download font software进行下载就可以了

woff文件打开之后是这样的:

红色方框对应的是字体的Unicode,绿色方框中的内容是字体,Unicode和字体的搭配是不固定的,每次请求woff文件都会改变,所以不能用固定的方法得到它们的对应关系

我们可以将woff文件转化成xml文件,便于找到Unicode和字体之间的关联,我们需要使用字体处理的库fontTools,直接在控制台输入pip install fontTools下载模块

会生成一个这样的文件

在文件里找到<glyf>标签,标签里存放的是当前字体文件中Unicode对应的字体的坐标轨迹

name里面的是字体的Unicode,x、y表示字体通过哪些坐标绘画成的,on的内容表示画的黑线还是白线

理论上来说我们可以通过字体的绘画轨迹确定规律,从而制定一套模板,把woff文件里字体的Unicode在模板里寻找,找到对应的字体,但是绘画的时候点的轨迹是会有差别的,这种方法并不可行,作为爬虫,我们要学会投机,如果硬刚的话,只能使用机器学习进行训练

投机的方法就是确定一个字体对应的点的黑线、白线,也就是on的值,用一个woff文件制定一个模板,当使用其它的woff文件时,用模板进行比对,找到Unicode对应的正确字体,这样不管怎么打乱顺序,都能找到正确的搭配,这种方法只能适用于加密字体种类少的(都是数字0~9)

代码的主要思路是,先下载一个woff文件,转化成xml,然后找到<glyf>标签,使用flags方法获取<glyf>中每个字体的on属性,将on属性的b'\x01'进行替换成空字符串,只留下b'\x00'更能提高准确性,声明一个字典,将字体和on属性进行key-value映射,制定一套模板,后续请求的woff文件进行循环遍历字体的Unicode,和模板中的on属性进行比对,找到对应的字体

代码实现:

import requests
import re								# 正则表达式
from fontTools.ttLib import TTFont      # 字体处理相关库
from urllib.parse import urljoin        # 进行url拼接
from lxml import etree                  # xpath

# 获取一个woff文件,制定一个字体模板
def get_same():
    font1 = TTFont("2a70c44b.woff")
    # 处理成xml,在xml中找规律
    font1.saveXML("font1.xml")

    dic = {
        '0':'uniEBA2',
        '1':'uniED8F',
        '2':'uniE886',
        '3':'uniE583',
        '4':'uniF16B',
        '5':'uniE83D',
        '6':'uniF23F',
        '7':'uniE132',
        '8':'uniF05A',
        '9':'uniEC4B',
    }
    for k,v in dic.items():
        on = font1['glyf'][v].flags.replace(b'\x01', b'')
        dic[k] = on
    return dic

# 获取网页源代码
def get_page(url):
    headers = {
        "User-Agent":'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36',
        'Referer':'https://www.maoyan.com/board?',
        "Cookie":'__mta=20289031.1691468350838.1691484912827.1691484918225.13; uuid_n_v=v1; uuid=BA861EB035A211EE83DDF5FAAAD1D78DA74E30FA828944D6A5ED149C4737DB76; _csrf=5477ed82cf6b809219afe73fd94994eb65baee610e144ea243816b1496754f0e; _lxsdk_cuid=189d35ec51dc8-03da99cc5a2a7-26031e51-1fa400-189d35ec51dc8; _lxsdk=BA861EB035A211EE83DDF5FAAAD1D78DA74E30FA828944D6A5ED149C4737DB76; Hm_lvt_703e94591e87be68cc8da0da7cbd0be2=1691468351; __mta=20289031.1691468350838.1691468416974.1691475725237.4; Hm_lpvt_703e94591e87be68cc8da0da7cbd0be2=1691484918; _lxsdk_s=189d45b7aec-868-d2c-c90%7C%7C5',
    }
    resp = requests.get(url=url, headers=headers)
    resp.encoding = 'utf-8'
    return resp.text

# 下载当前网页使用的woff字体文件
def get_font(page, url):
    obj = re.compile(r'@font-face.*?,url\("(?P<font_url>.*?)"\);', re.S)
    font_url = obj.search(page, re.S).group('font_url')
    font_url = urljoin(url, font_url)
    name = font_url.split('/')[-1]
    font_resp = requests.get(url=font_url)
    with open(name, mode='wb') as f:
        f.write(font_resp.content)
    return name

# 将当前woff文件的的Unicode通过模板找到正确的字体,返回一个Unicode和正确的字体相对应的字典
def get_data(name, dic):
    font = TTFont(name)
    orders = font.getGlyphOrder()[2:]	# 获取woff字体文件的Unicode列表
    data_dic = {}
    for unicode in orders:
        for k,v in dic.items():
            on = font['glyf'][unicode].flags.replace(b'\x01', b'')
            if on == v:
                data_dic[unicode] = k
                break
    if len(data_dic) == 9:      # 有概率会缺少几个字体,缺少一个还可以补上,超过一个的话进行循环重新进行请求,得到另一个woff文件
        for unicode in orders:
            if unicode not in data_dic:
                result = list(set(dic.keys()) - set(data_dic.values()))[0]
                data_dic[unicode] = result
                break
    return data_dic

if __name__ == '__main__':
    for i in range(3):
        url = 'https://www.maoyan.com/board/6?timeStamp=1691475822817&channelId=40011&index=8&signKey=d32ebd787d8d3cdd9ba1e6651bbdee42&sVersion=1&webdriver=false'
        dic = get_same()
        page_text = get_page(url)
        name = get_font(page_text,url)
        result = get_data(name, dic)
    if len(result) != 10:
    print("重新请求!!!")
    continue
    page_text = page_text.replace('&#x', 'uni')
    for k,v in result.items():
    k = k.lower()
    page_text = page_text.replace(k+';', v)
    tree = etree.HTML(page_text)
    dd_list = tree.xpath("//dl[@class='board-wrapper']/dd")
    for dd in dd_list:
    name = dd.xpath("./div/div/div[1]/p[1]/a/text()")[0]
    num1 = dd.xpath("./div/div/div[2]/p[1]/span/span/text()")[0]
    num2 = dd.xpath("./div/div/div[2]/p[2]/span/span/text()")[0]
    print(f"=======================\n{name}\n本月新增想看:{num1}人\n总想看:{num2}人")
    break
  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值