字体反爬

字体反爬

1.字体反爬概述

随着css技术的发展,在CSS3中出现了一个自定义字体的新特性。在这之前,web设计师必须使用已在用户计算机上安装好的字体。现在,通过CSS3,web设计师可以使用它们喜欢的任意字体。浏览器会下载字体信息,然后动态渲染,html页面源码中,你看到的不再是正常字符, 或者unicode而是网站使用的自定义编码。因此它也被拿来作为一种反爬的手段,这就是字体反爬。

2.案例

2.1现象

使用谷歌浏览器访问58人才网,可以看到页面正常显示的文字,在开发者调试模式中是不能正常显示的。

在这里插入图片描述

查看源码,会发现它是类似unicode的编码。
在这里插入图片描述

2.2自定义字体

查看元素的css,果然使用了自定义的字体样式

在这里插入图片描述

左键点击字体引用,发现字体信息就在当前页面的style标签中,经过观察发现是一个base64编码的数据。

在这里插入图片描述

字体文件是二进制数据,复制这部分字符串,转换成二进制文件。

def make_font_file(base64_string):
    """
    创建字体文件
    :param base64_string: 页码base编码数据
    :return: 二进制数据
    """
    bin_data = base64.decodebytes(base64_string.encode())
    with open('new.ttf', 'wb') as f:
        f.write(bin_data)
    return bin_data

接下来要查看和处理这个字体文件,需要用到两个工具。一个软件是FontCreator,可以永利来打开ttf字体文件,查看每个字符对于的编码。打开刚才生成的字体文件。

[外链图片转存失败(img-TfElQJg1-1566994278829)(字体反爬.assets/1560060761751.png)]

打开后可以看到“王”,“生”,“男”的编码,跟html源码中的编码一致。

[外链图片转存失败(img-G1H2Dqxv-1566994278829)(字体反爬.assets/1560060958080.png)]

2.3字体文件分析

经过多次请求页面观察发现,自定义字体的字符集不变,也就是说总是那几个关键字符被编码,但是每次编码发生改变。所以需要进一步分析字体文件,这时需要使用到一个专门处理字符文件的python第三方库fontTools,利用它可以将字体文件转换成xml文件进行分析。

font = TTFont('new.ttf')
# 将解码字体保存为xml
font.saveXML("new.xml")

打开这个xml文件,它的结构如下

在这里插入图片描述

有兴趣可以去研究每个节点的具体含义,这里主要使用到GlyphOrderglyf两个元素节点,其中GlyphOrder是编码序号表,glyf是图元数据,也就是字体轮廓定义。

在这里插入图片描述

在这里插入图片描述

经过分析,发现虽然编码会不停的变换,但是每个字的图元是固定不变的,也即是字体形状是不变的。所以可以通过比较图元信息来判断两个编码是否表示同一个字符。

2.4解决步骤

经过上面的分析总结如下步骤:

  1. 首先下载一个字体文件作为基准,根据这个文件生成一个基准的编码和文字的映射。

  2. 访问页面,拿到字体数据

  3. 解码字体数据,生成字体文件

  4. 根据已有的基准字体文件和映射生成新的编码文字映射

  5. 替换数据中的编码

2.5代码

import base64
import json
import re
from io import BytesIO, StringIO
import requests
from fontTools.ttLib import TTFont
from lxml import etree

def get_base_map():
    """
    生成手动映射关系
    :return:
    """
    data = {}
    font = TTFont('base.ttf')
    res = font.getGlyphNames()
    for item in res:
        if 'uni' in item:
            data[item] = ''
    with open('base.map.json', 'w', encoding='utf-8') as f:

        json.dump(data, f, indent=4)


def make_font_file(base64_string):
    """
    创建字体文件
    :param base64_string: 页码base编码数据
    :return: 二进制数据
    """
    bin_data = base64.decodebytes(base64_string.encode())
    with open('new.ttf', 'wb') as f:
        f.write(bin_data)
    return bin_data


def convert_font_to_xml(font_bin):
    """
    创建字体xml文件
    :param font_bin:
    :return: font obj
    """
    # ByteIO把一个二进制内存块当成文件来操作,
    font = TTFont(BytesIO(font_bin))
    # 将解码字体保存为xml
    font.saveXML("new.xml")
    return font


def get_map(font):
    """
    生成新的映射关系
    :param font:
    :return: map
    """
    with open('base.map.json', 'r', encoding='utf-8') as f:

        base_map = json.load(f)

    map = {}

    base_font = TTFont('base.ttf')

    for name in font.getGlyphNames():
        if 'uni' in name:
            new_obj = font['glyf'][name]
            for base_name in base_font.getGlyphNames():
                if 'uni' in base_name:
                    old_obj = base_font['glyf'][base_name]
                    if new_obj == old_obj:
                        map[name] = base_map[base_name]

    return map


if __name__ == '__main__':
    session = requests.Session()
    session.headers.update({'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36'})

    # 1.请求页面
    response = session.get('https://bj.58.com/qzyewu/?PGTID=0d202409-0000-1aa8-92da-777b90a7dc73&ClickID=1')
    html = response.text

    # 2.解析网页中的字体信息,生成字体文件,和xml文件
    base_str = re.findall(r'base64,(.*?)\)  format\("woff"\)', html, re.S)
    if base_str:
        font_bin = make_font_file(base64_string=base_str[0])
        font = convert_font_to_xml(font_bin)
        # 3.根据basemap生成映射关系
        map = get_map(font)
        print(map)

        # 4.替换页面内容
        for item in map:
            old_str = '&#x%s;' % item[-4:].lower()
            print(old_str)
            html = html.replace(old_str, map[item])

        print(html)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值