爬虫之路: 字体文件反爬二(动态字体文件)

上一篇解决了但页面的字体反爬, 这篇记录下如何解决动态字体文件, 编码不同, 文字顺序不同的情况

源码在最后

冷静分析页面

打开一个页面, 发现字体文件地址是动态的, 这个倒是好说, 写个正则, 就可以动态匹配出来

先下载下来一个新页面的字体文件, 做一下对比, 如图

mmp, 发现编码, 字体顺序那那都不一样, 这可就过分了, 心里一万个xxx在奔腾

头脑风暴ing.gif

(与伙伴对话ing...)

不着急, 还是要冷静下来, 再想想哪里还有突破点

同一个页面的字体文件地址是动态的, 但是, 里面的字体编码和顺序是不会变的呀

可以使用某一个页面的字体文件做一个标准的字体映射表呀!

好像发现了新世界的大门, 可门还没开开, 就被自己堵死了, 就想 做出来映射表然后呢!(又要奔腾了)

想呀想呀想呀想, 最后叫上小伙伴一起想

突然就想到了, 虽然那么多不一样, 但是, 但是, 相同文字的坐标点相同呀! 突然又打开了大门

首先排除特别的文字的情况下, 只是在这个字体文件的情况下, 60%的字坐标点一样

那剩下的怎么办呢! 先不管了, 先把这60%给弄出来

提取60%的字体映射表

制作标准编码文字映射表(某页字体文件为准)

def extract_ttf_file(self, file_name, get_word_map=True):
        _font = TTFont(file_name)
        uni_list = _font.getGlyphOrder()[1:]

        # 被替换的字体的列表
        word_list = [
            "坏", "少", "远", "大", "九", "左", "近", "呢", "十", "高", "着",
            "矮", "八", "二", "右", "是", "得", "的", "小", "短", "很", "一", "了",
            "地", "好", "多", "七", "不", "长", "低", "三", "五", "六", "下", "更",
            "和", "四", "上"
        ]
        utf_word_map = {}
        utf_coordinates_map = {}

        for index, uni_code in enumerate(uni_list):
            utf_word_map[uni_code] = word_list[index]
            utf_coordinates_map[uni_code] = list(_font[‘glyf‘][uni_code].coordinates)

        if get_word_map:
            return utf_word_map, utf_coordinates_map
        return utf_coordinates_map

    # self.local_utf_word_map, self.local_utf_coordinates_map = self.extract_ttf_file(self.local_ttf_name)

制作新标准编码映射表

下载要破解的字体文件, 并替换标准编码字体映射表

    def replace_ttf_map(self):
        unicode_mlist_map = []
        new_utf_coordinates_map = self.extract_ttf_file(self.download_ttf_name, get_word_map=False)
        for local_unicode, local_coordinate in self.local_utf_coordinates_map.items():
            coordinate_equal_list = []
            for new_unicode, new_coordinate in new_utf_coordinates_map.items():
                if len(new_coordinate) == len(local_coordinate):
                    coordinate_equal_list.append({"norm_key": local_unicode, "norm_coordinate": local_coordinate, "new_key": new_unicode, "new_coordinate": new_coordinate})

            if len(coordinate_equal_list) == 1:
                unicode_mlist_map.append(coordinate_equal_list[0])for unicode_dict in unicode_mlist_map:
            self.new_unicode_map[unicode_dict["new_key"]] = self.local_utf_word_map[unicode_dict["norm_key"]]

        print("new unicode map extract success\n", self.new_unicode_map)

会得到22个字体的映射表, 共38个:

{‘uniED23‘: ‘坏‘, ‘uniED75‘: ‘少‘, ‘uniEC18‘: ‘九‘, ‘uniECB0‘: ‘左‘, ‘uniECF7‘: ‘呢‘, ‘uniEC7A‘: ‘高‘, ‘uniECA7‘: ‘矮‘, ‘uniEDD6‘: ‘八‘, ‘uniEDA0‘: ‘二‘, ‘uniEDF2‘: ‘右‘, ‘uniEC96‘: ‘的‘, ‘uniEDF0‘: ‘小‘, ‘uniEC8B‘: ‘短‘, ‘uniECDB‘: ‘一‘, ‘uniEDD5‘: ‘地‘, ‘uniED8F‘: ‘好‘, ‘uniECC1‘: ‘多‘, ‘uniEDBA‘: ‘七‘, ‘uniEC2A‘: ‘长‘, ‘uniED59‘: ‘下‘, ‘uniEC94‘: ‘和‘, ‘uniED73‘: ‘四‘}

替换40%的字体映射表

重组新标准映射表

接下来, 就用坐标点来解决, 以下为思路

使用两点坐标差来判断, 但是这个偏差值拿不准

相同文字, 坐标点几乎一致, 即所有坐标点相差的绝对值的和最小的就为同一个字

来先试试

    def get_distence(self, norm_coordinate, new_coordinate):
        distance_total = 0
        for index, coordinate_point in enumerate(norm_coordinate):
            distance_total += abs(new_coordinate[index][0] - coordinate_point[0]) + abs(new_coordinate[index][1] - coordinate_point[1])
        return distance_total

然后在重组标准编码, 标准坐标, 新的编码, 和新坐标

(这是想, 找出最相近的坐标, 使用新坐标提取出标准编码, 然后用标准编码提取对应的文字, 在替换成使用本页用的编码映射表)

# 准备替换的编码坐标映射表
{"norm_key": local_unicode, "norm_coordinate": local_coordinate, "new_key": new_unicode, "new_coordinate": new_coordinate}

提取所有坐标点加起来最小的元素

    def handle_subtraction(self, coordinate_equal_list):
        coordinate_min_list = []
        for coordinate_equal in coordinate_equal_list:
            n = self.get_distence(coordinate_equal.get(‘norm_coordinate‘), coordinate_equal.get(‘new_coordinate‘))
            coordinate_min_list.append(n)

        return coordinate_equal_list[coordinate_min_list.index(min(coordinate_min_list))]

替换, 生成新的标准映射表

self.new_unicode_map[unicode_dict["new_key"]] = self.local_utf_word_map[unicode_dict["norm_key"]]

加入判断

在以上替换60%的字体映射表再加入一个判断, 改成如下

    def replace_ttf_map(self):
        unicode_mlist_map = []
        new_utf_coordinates_map = self.extract_ttf_file(self.download_ttf_name, get_word_map=False)
        for local_unicode, local_coordinate in self.local_utf_coordinates_map.items():
            coordinate_equal_list = []
            for new_unicode, new_coordinate in new_utf_coordinates_map.items():
                if len(new_coordinate) == len(local_coordinate):
                    coordinate_equal_list.append({"norm_key": local_unicode, "norm_coordinate": local_coordinate, "new_key": new_unicode, "new_coordinate": new_coordinate})

            if len(coordinate_equal_list) == 1:
                unicode_mlist_map.append(coordinate_equal_list[0])
            elif len(coordinate_equal_list) > 1:
                min_word = self.handle_subtraction(coordinate_equal_list)
                unicode_mlist_map.append(min_word)
        for unicode_dict in unicode_mlist_map:
            self.new_unicode_map[unicode_dict["new_key"]] = self.local_utf_word_map[unicode_dict["norm_key"]]

        print("new unicode map extract success\n", self.new_unicode_map)

输出一个标准的坐标值, 这里我就不上图进行对比了, 经过对比, 发现没什么问题

 {‘uniED23‘: ‘坏‘, ‘uniED75‘: ‘少‘, ‘uniEDC5‘: ‘远‘, ‘uniED5A‘: ‘大‘, ‘uniEC18‘: ‘九‘, ‘uniECB0‘: ‘左‘, ‘uniECA5‘: ‘近‘, ‘uniECF7‘: ‘呢‘, ‘uniED3F‘: ‘十‘, ‘uniEC7A‘: ‘高‘, ‘uniEC44‘: ‘着‘, ‘uniECA7‘: ‘矮‘, ‘uniEDD6‘: ‘八‘, ‘uniEDA0‘: ‘二‘, ‘uniEDF2‘: ‘右‘, ‘uniED09‘: ‘是‘, ‘uniEC32‘: ‘得‘, ‘uniEC96‘: ‘的‘, ‘uniEDF0‘: ‘小‘, ‘uniEC8B‘: ‘短‘, ‘uniED3D‘: ‘很‘, ‘uniECDB‘: ‘一‘, ‘uniEC60‘: ‘了‘, ‘uniEDD5‘: ‘地‘, ‘uniED8F‘: ‘好‘, ‘uniECC1‘: ‘多‘, ‘uniEDBA‘: ‘七‘, ‘uniED2D‘: ‘不‘, ‘uniEC2A‘: ‘长‘, ‘uniED11‘: ‘低‘, ‘uniEC5E‘: ‘三‘, ‘uniECDD‘: ‘五‘, ‘uniEDBC‘: ‘六‘, ‘uniED59‘: ‘下‘, ‘uniEE02‘: ‘更‘, ‘uniEC94‘: ‘和‘, ‘uniED73‘: ‘四‘, ‘uniED6A‘: ‘上‘}

补充

如有以上有错误, 恳求大神指出

其余的就和上篇的一致了, 点击这里查看

源码

# -*- coding: utf-8 -*-
# @Author: Mehaei
# @Date:   2020-01-10 14:51:53
# @Last Modified by:   Mehaei
# @Last Modified time: 2020-01-13 10:10:13
import re
import os
import requests
from lxml import etree
from fontTools.ttLib import TTFont

class NotFoundFontFileUrl(Exception):
    pass

class CarHomeFont(object):
    def __init__(self, url, *args, **kwargs):
        self.local_ttf_name = "norm_font.ttf"
        self.download_ttf_name = ‘new_font.ttf‘
        self.new_unicode_map = {}
        self._making_local_code_map()
        self._download_ttf_file(url, self.download_ttf_name)

    def _download_ttf_file(self, url, file_name):
        self.page_html = self.download(url) or ""
        # 获取字体的连接文件
        font_file_name = (re.findall(r",url\(‘(//.*\.ttf)?‘\) format", self.page_html) or [""])[0]
        if not font_file_name:
            raise NotFoundFontFileUrl("not found font file name")
        # 下载字体文件
        file_content = self.download("https:%s" % font_file_name, content=True)
        # 讲字体文件保存到本地
        with open(file_name, ‘wb‘) as f:
            f.write(file_content)
        print("font file download success")

    def _making_local_code_map(self):
        if not os.path.exists(self.local_ttf_name):
            # 这个url为标准字体文件地址, 如要更改, 请手动更改字体列表
            url = "https://club.autohome.com.cn/bbs/thread/62c48ae0f0ae73ef/75904283-1.html"
            self._download_ttf_file(url, self.local_ttf_name)
        self.local_utf_word_map, self.local_utf_coordinates_map = self.extract_ttf_file(self.local_ttf_name)
        print("local ttf load done")

    def get_distence(self, norm_coordinate, new_coordinate):
        distance_total = 0
        for index, coordinate_point in enumerate(norm_coordinate):
            distance_total += abs(new_coordinate[index][0] - coordinate_point[0]) + abs(new_coordinate[index][1] - coordinate_point[1])
        return distance_total

    def handle_subtraction(self, coordinate_equal_list):
        coordinate_min_list = []
        for coordinate_equal in coordinate_equal_list:
            n = self.get_distence(coordinate_equal.get(‘norm_coordinate‘), coordinate_equal.get(‘new_coordinate‘))
            coordinate_min_list.append(n)

        return coordinate_equal_list[coordinate_min_list.index(min(coordinate_min_list))]

    def replace_ttf_map(self):
        unicode_mlist_map = []
        new_utf_coordinates_map = self.extract_ttf_file(self.download_ttf_name, get_word_map=False)
        for local_unicode, local_coordinate in self.local_utf_coordinates_map.items():
            coordinate_equal_list = []
            for new_unicode, new_coordinate in new_utf_coordinates_map.items():
                if len(new_coordinate) == len(local_coordinate):
                    coordinate_equal_list.append({"norm_key": local_unicode, "norm_coordinate": local_coordinate, "new_key": new_unicode, "new_coordinate": new_coordinate})

            if len(coordinate_equal_list) == 1:
                unicode_mlist_map.append(coordinate_equal_list[0])
            elif len(coordinate_equal_list) > 1:
                min_word = self.handle_subtraction(coordinate_equal_list)
                unicode_mlist_map.append(min_word)
        for unicode_dict in unicode_mlist_map:
            self.new_unicode_map[unicode_dict["new_key"]] = self.local_utf_word_map[unicode_dict["norm_key"]]

        print("new unicode map extract success\n", self.new_unicode_map)

    def extract_ttf_file(self, file_name, get_word_map=True):
        _font = TTFont(file_name)
        uni_list = _font.getGlyphOrder()[1:]

        # 被替换的字体的列表
        word_list = [
            "坏", "少", "远", "大", "九", "左", "近", "呢", "十", "高", "着",
            "矮", "八", "二", "右", "是", "得", "的", "小", "短", "很", "一", "了",
            "地", "好", "多", "七", "不", "长", "低", "三", "五", "六", "下", "更",
            "和", "四", "上"
        ]
        utf_word_map = {}
        utf_coordinates_map = {}

        for index, uni_code in enumerate(uni_list):
            utf_word_map[uni_code] = word_list[index]
            utf_coordinates_map[uni_code] = list(_font[‘glyf‘][uni_code].coordinates)

        if get_word_map:
            return utf_word_map, utf_coordinates_map
        return utf_coordinates_map

    def repalce_source_code(self):
        replaced_html = self.page_html
        for utf_code, word in self.new_unicode_map.items():
            replaced_html = replaced_html.replace("&#x%s;" % utf_code[3:].lower(), word)
        return replaced_html

    def get_subject_content(self):
        normal_html = self.repalce_source_code()
        # 使用xpath 获取 主贴
        xp_html = etree.HTML(normal_html)
        subject_text = ‘‘.join(xp_html.xpath(‘//div[@xname="content"]//div[@class="tz-paragraph"]//text()‘))
        return subject_text

    def download(self, url, *args, try_time=5, method="GET", content=False, **kwargs):
        kwargs.setdefault("headers", {})
        kwargs["headers"].update({"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36"})
        while try_time:
            try:
                response = requests.request(method.upper(), url, *args, **kwargs)
                if response.ok:
                    if content:
                        return response.content
                    return response.text
                else:
                    continue
            except Exception as e:
                try_time -= 1
                print("download error: %s" % e)

if __name__ == "__main__":
    url = "https://club.autohome.com.cn/bbs/thread/34d6bcc159b717a9/85794510-1.html#pvareaid=6830286"
    car = CarHomeFont(url)
    car.replace_ttf_map()
    text = car.get_subject_content()
    print(text)

原文地址:https://www.cnblogs.com/mswei/p/12175505.html

时间: 2024-10-31 02:00:18

爬虫之路: 字体文件反爬二(动态字体文件)的相关文章

Python小白学习之路(二十)—【打开文件的模式二】【文件的其他操作】

打开文件的模式(二) 对于非文本文件,我们只能使用b模式,"b"表示以字节的方式操作(而所有文件也都是以字节的形式存储的,使用这种模式无需考虑文本文件的字符编码.图片文件的jgp格式.视频文件的avi格式) rb:   以字节方式读文件 wb: 以字节方式写文件ab: 以字节方式追加文件 注:以b方式打开时,读取到的内容是字节类型,写入时也需要提供字节类型,所以不能指定编码 1. rb #错误举例 f = open ('test1.py', 'rb', encoding = 'utf

爬虫毕设(三):爬取动态网页

动态网页分析 按照上一篇的分析,直接使用XPath找到该标签,然后通过parse提取出数据,在写入到item中就完事了.但是,当信心满满的写完代码后却发现,控制台输入了一个简简单单的[]. 小问号你是否有很多朋友. 一顿操作猛如虎,一看输出数据无.那么这到底是怎么回事呢?我们从头开始分析. 打开NetWork,找到tv/,点开Preview,结果发现只有一个框架,内容却是空白的. 这是由于网页执行js代码,通过Ajax请求数据来重新渲染页面的.所以我们需要找到有数据的那一个请求,然后再对该请求的

字体反爬----字体加密

学习用字体对数据进行加密来反爬 字体加密首先得准备一个字体库,测试的话可以在网上随便找,本例字体是本墨悠圆,链接:http://www.zhaozi.cn/html/fonts/china/benmo/2019-02-01/25085.html 字体重命名成bmyy.ttf 从字体库提取要加密的字符需要用到fonttools fonttools安装: pip install fonttools 使用方法: pyftsubset <字体文件> --text=<需要的字形> --out

Makefile 编译动态库文件及链接动态库

本文为原创文章,转载请指明该文链接 文件目录结构如下 1 dynamiclibapp.c 2 Makefile 3 comm/inc/apue.h 4 comm/errorhandle.c 5 dynamiclib/Makefile 6 dynamiclib/dynamiclib_add.c 7 dynamiclib/dynamiclib_mul.c 8 dynamiclib/inc/dynamiclibs.h 9 dynamiclib/libs/ 1. dynamiclib目录 dynamic

NGUI动态字体的创建

1,打开font maker 2,在font maker中创建选择动态字体 PS: 1,动态字体相对静态字体来说,动态字体比较消耗性能 2,静态字体是创建在图集中,这也是没那么消耗性能的原因 原文地址:https://www.cnblogs.com/May-day/p/8456348.html

Python爬虫入门教程 64-100 反爬教科书级别的网站-汽车之家,字体反爬之二

说说这个网站 汽车之家,反爬神一般的存在,字体反爬的鼻祖网站,这个网站的开发团队,一定擅长前端吧,2019年4月19日开始写这篇博客,不保证这个代码可以存活到月底,希望后来爬虫coder,继续和汽车之间对抗. CSDN上关于汽车之家的反爬文章千千万万了,但是爬虫就是这点有意思,这一刻写完,下一刻还能不能用就不知道了,所以可以一直不断有人写下去.希望今天的博客能帮你学会一个反爬技巧. 今天要爬去的网页 https://car.autohome.com.cn/config/series/59.htm

Python爬虫进阶 | 某音字体反爬分析

字体反爬案例 爬取一些网站的信息时,偶尔会碰到这样一种情况:网页浏览显示是正常的,用 python 爬取下来是乱码,F12用开发者模式查看网页源代码也是乱码.这种一般是网站设置了字体反爬. 1. 准备url 网址: https://www.iesdouyin.com/share/user/88445518961 2. 获取数据 分析字体加密方式 任务:爬取个人信息展示页中的关注.粉丝人数和点赞数据,页面内容如图 下 所示. 在编写代码之前,我们需要确定目标数据的元素定位.定位时,我们在 HTML

Python爬虫|深入请求(四)常见的反爬机制以及应对方法

作者:David Qian 链接:https://zhuanlan.zhihu.com/p/21558661 来源:知乎 著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 大家好!我是厦门大学王亚南经济研究院的大一学生,今天将由我来为大家介绍一下常见的反爬机制以及应对方法. 注:非商业转载注明作者即可,商业转载请联系作者授权并支付稿费.本人已授权"维权骑士"网站(http://rightknights.com)对我在知乎发布文章的版权侵权行为进行追究与维权. ---

爬虫与反爬

爬虫与反爬 (1) 基本的概念 爬虫: 自动获取网站数据的程序 关键是 定时,定量的,批量的获取 反爬虫: 使用技术手段 防止爬虫程序的方法 存在误伤,即 反爬技术 将普通用户识别为爬虫 如果误伤高 --- 效果再好也不能使用 例子: 比如 限制 ip === 用户的ip 一般都是 局域网内动态分配的, 一个爬虫的ip 可能分配给 另一个 非爬虫的用户 有效的方法: 可以在一段时间内 限制 ip,过一段时间 再把 ip释放 反爬的成本: 成功率越高成本越大,拦截率越高,误伤率越高 反爬虫的目的: