精华内容
下载资源
问答
  • python-字体反爬(一)

    2020-12-21 08:40:31
    网上关于这网页的详细解析挺多的,就不一一说明了。 1.ttf文件是被加密,需要解密再下载到本地。 2.观察得到编码是英文的one,two…..,需要转变为数字0,1…..,然后取数字列表的下标。 直接上代码。...
  • 58同城字体反爬

    2020-12-21 07:34:38
    对那些被编成乱码的文字进行爬取。次卧(龤室) 餼閏㎡<(次卧3室 15平方米),,你能看出来吗 所以我们要去破解这些乱七八糟的数据 先了解一下 StringIO and BytesIO StringIO 很多时候,数据读写不一定是文件,也...
  • 经过奋斗一个通宵,20200113可以正常运行,因猫眼更新频繁,所以有问题可以,一起交流,具体内容见文件。里面含测试文件及采集的2019、2018、2017的部分数据,字段含电影名、上演时间、上演地区、市场、评分、票房、...
  • 猫眼电影字体反爬

    2020-12-22 04:43:29
    猫眼电影字体反爬 我们再爬取猫眼电影的时候,会遇到如下情况: 我们想要其中想看人数的数据,但是在网页源代码中并不是直接显示数字而是这一串东西。 这一串,其实是猫眼本身的一种字体,目的是不想每个人都获取...
  • 通过knn算法来识别网站的字体。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
  • 字体反爬

    千次阅读 2020-07-20 20:21:54
    字体反爬 网页开发者自己创造一种字体,因为在字体中每个文字都有其代号,那么以后在网页中不会直接显示这个文字的最终的效果,而是显示他的代号,因此即使获取到了网页中的文本内容,也只是获取到文字的代号,而...

    字体反爬

    1. 网页开发者自己创造一种字体,因为在字体中每个文字都有其代号,那么以后在网页中不会直接显示这个文字的最终的效果,而是显示他的代号,因此即使获取到了网页中的文本内容,也只是获取到文字的代号,而不是文字本身。
    2. 因为创造字体费时费力,并且如果把中国3000多常用汉字都实现,那么这个字体将达到几十兆,也会影响网页的加载。一般情况下为了反爬虫,仅会针对0-9以及少数汉字进行自己单独创建,其他的还是使用用户系统中自带的字体。

    字体反爬解决方法-寻找字体

    1. 一般情况下为了考虑网页渲染性能,通常网页开发者会把字体编码成base64的方式,因此我们可以到网页中找到@font-face属性,然后获取里面的base64代码,再用Python代码进行解码,然后再保存本地。示例: view-source:https://www.shixiseng.com/intern/inn_a7xabqqr4f9u
    2. 如果没有使用base64,还有另外一种方式,就是直接把字体文件放到服务器上,然后前端通过@font-face中的url函数进行加载。示例: https://developer.mozilla.org/zh-CN/docs/Web/CSS/@font-face
    3. 分析字体需要将字体转换成xml文件,然后查看其中的cmap和glyf中的属性。其中cmap存储的是code和name的映射,而glyf下存储的是每个name下的字体绘制规则。
    4. 从第1步中我们知道了name对应的字体的绘制规则,但是还是不知道字体是长什么样子,那么可以通过一款叫做FontCreator的软件来打开.tff的字体文件,这样就可以看到每个name对应的字体最终的呈现效果。(FontCreator是一款制作字体的工具,下载地址:https://www.high-logic.com/FontCreatorSetup-x64.exe 这款软件有30天的试用期)。
      在这里插入图片描述

    字体反爬解决方法-根据映射关系获取真实内容

    1. 在网页中,直接显示的是字体的code,而不是name。并且网页开发者为了增加爬虫的难度,有可能在多次请求之间code->name->最终字体的映射会发生改变。但是最终字体的形状是不会改变的,因此我们可以通过形状对比来进行判断。

    2. 我们可以通过分析字体,得出每个字体形状对应的文字,然后保存到一个字典中。以后再请求网页的时候,就进行反向解析,先获取字体的形状,再通过字体形状反向获取代号所对应的具体文字内容。

    在这里插入图片描述
    在这里插入图片描述

    实例 58同城:

    import requests
    import re
    from lxml import etree
    import io
    import base64
    from fontTools.ttLib import TTFont
    
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36',
        'Referer': 'https://www.shixiseng.com/interns?k=python&p=1',
    }
    
    # font_face代表的是经过base64编码后的字符串,他本是是一个字体文件
    font_face = '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'
    # 所以我们需要通过base64进行解码,还原回这个字体文件
    font_bytes = io.BytesIO(base64.b64decode(font_face))
    # 有这个字体bytes数据后,就可以使用TTFont来创建一个可以操作这个字体的对象
    baseFont = TTFont(font_bytes)
    # 获取所有字体的形状对象
    baseGlyf = baseFont['glyf']
    
    # 建立一个内容和字体形状的映射
    baseFontMap = {
        0: baseGlyf['uni30'],
        1: baseGlyf['uni31'],
        2: baseGlyf['uni32'],
        3: baseGlyf['uni33'],
        4: baseGlyf['uni34'],
        5: baseGlyf['uni35'],
        6: baseGlyf['uni36'],
        7: baseGlyf['uni37'],
        8: baseGlyf['uni38'],
        9: baseGlyf['uni39']
    }
    
    # 去爬取网页
    url = "https://www.shixiseng.com/intern/inn_a7xabqqr4f9u"
    resp = requests.get(url,headers=headers)
    text = resp.text
    # 抓取出当前网页的字体文件
    result = re.search(r'font-family:myFont; src: url\("data:application/octet-stream;base64,(.+?)"\)',text)
    font_face = result.group(1)
    b = base64.b64decode(font_face)
    currentFont = TTFont(io.BytesIO(b))
    # 获取当前网页的字体的所有字体的形状
    currentGlyf = currentFont['glyf']
    # 获取字体的code和name的映射
    codeNameMap = currentFont.getBestCmap()
    # 循环code和name
    for code,name in codeNameMap.items():
        # 先获取到当前网页,某个name下的形状
        currentShape = currentGlyf[name]
        # currentShape.coordinates
        # 循环内容和形状的字典
        for number,shape in baseFontMap.items():
            # 看下循环后的shape是否和当前的shape相当
            # 如果是相等,那么就可以找到code与内容的映射
            if shape == currentShape:
                # 构建网页中的code
                webcode = str(hex(code)).replace("0","&#",1)
                # 把网页中的code值替换成数字
                text = re.sub(webcode,str(number),text)
    
    print(text)
    
    
    
    
    
    展开全文
  • 这篇文章是公众号《云爬虫技术研究笔记》的《2019年末逆向复习系列》的第六篇:《从猫眼字体反爬分析谈谈字体反爬的前世今生》 本次案例的代码都已上传到Review_Reverse上面,后面会持续更新,大家可以Fork一波。 ...

    郑重声明:本项目的所有代码和相关文章, 仅用于经验技术交流分享,禁止将相关技术应用到不正当途径,因为滥用技术产生的风险与本人无关。

    这篇文章是公众号《云爬虫技术研究笔记》的《2019年末逆向复习系列》的第六篇:《从猫眼字体反爬分析谈谈字体反爬的前世今生》

    本次案例的代码都已上传到Review_Reverse上面,后面会持续更新,大家可以Fork一波。

    背景分析

    “字体反爬”我相信大多数从事爬虫工作的工程师都接触过,这其实不是一种常规的反爬手段,它其实是页面和前端字体文件想配合完成的一个反爬策略。像最早使用字体反爬的58同城、汽车之家到现在很多App的web页面也开始使用,例如美团、猫眼、快手抖音等等。随着爬虫工程师和反爬工程师的不断对抗,字体反爬从一开始的单纯依靠一个写死的字体文件升级成现在最新动态的字体文件,而字体反爬的攻克也有一个开始的解析字体文件做数据映射到现在依靠KNN来做动态映射,算是经历了一个又一个光辉的“升级阶段”,这篇文章就是简单讲述一下字体反爬的演变史以及最新依靠KNN来做动态映射的破解思路。

    历史分析

    PS: 关于历史分析的项目都可以在Review_Reverse项目下的fonts目录看到
    使用方法:python -m fonts.xxxx即可

    首先我们先理解字体反爬的原理,就是前端工程师通过自定义的字体来替换页面中某些关键的数据,那在HTML中如何使用自定义字体呢?答案就是使用@font-face,我们举个例子看看@font-face

    @font-face {
     font-family: <identifier>; 
     src: <fontsrc> [, <fontsrc>]*; <font>;
      }
    

    里面的font-family也就是一个特定的名字,src就表示你需要引用的具体的文件,而这个文件就是字体文件,一般是ttf类型,eot类型,当然,现在因为ttf文件过大,在移动端使用的时候会导致加载速度过慢,woff类型的文件最近也广泛会用,所以一般大家现在碰到的都是woff类型的文件。那woff文件中的内容是什么呢?它是怎样把数据进行替换的呢?下面我们先简单的看个例子。
    我们先把woff文件打开,需要使用两种工具打开:

    • FontCreator工具:https://www.high-logic.com/font-editor/fontcreator
    • 在线FontEditor工具:http://fontstore.baidu.com/static/editor/index.html

    这里我们使用FontCreator,我们把FontCreator下载下来,传来一个我们之前准备好的woff文件看看效果

    我们可以看到woff文件中每个字符都有一个编码对应,woff实际上就是编码和字符的映射表。我们再来看看页面中的被替换的词是什么形式

    我们对比下可以发现,页面源码中的被替换字的就是woff文件中字符的编码加上$#x,所以大家可以发现字体替换的原理就是这样,我们使用一个简单的等式来表现

    “替换数据”=“$#x{woff文件中被替换数据的编码}”
    

    现在我们懂得了原理,下面开始回顾下字体反爬的演变历程

    1. 阶段一:通过固定的字体文件进行数据替换

    反爬方:一开始的时候,字体反爬还没有发展的很成熟,所以大部分网站使用字体反爬的方式是使用固定的字体文件来做数据替换,固定的字体文件就表明每个数据的编码是写死的,不变的,那么每次网站引用这个woff文件之后,都可以用相同的编码来替换想要替换的数据,这就是最初的时候的字体反爬。
    应对方:既然他们的字体文件不变,那我们就直接解析他们的固定的woff文件就行,我们使用Python的fontTool库的ttLib包,代码如下:

    from pathlib import Path
    from fontTools.ttLib import TTFont
    woff_path = Path(__file__).absolute().parent/"base64 (1).woff"
    font = TTFont(woff_path)
    font_names = font.getGlyphOrder()
    font_str = [
        "8", "验", "杨", "女", "3", "届", "7", "男", "高", "赵", "6", "2", "下", "以", "技", "黄", "周", 
        "4", "经", "专", "硕", "刘", "吴", "陈", "士", "E", "5", "中", "博", "1", "科", "大", "9", "本",
         "王", "B", "无", "李", "应", "生", "校", "A", "0", "张","M"
    ]
    print(dict(zip(font_names[2:],font_str)))
    

    我们解析woff文件得到一定顺序的编码集再结合在FontCreator中的字符集得到字符编码字典,在我们解析HTML源码的时候替换就行了。

    {'uniE032': '8', 'uniE200': '验', 'uniE267': '杨', 'uniE2DF': '女', 'uniE34E': '3', 'uniE39C': '届', 
    'uniE42A': '7', 'uniE481': '男', 'uniE51F': '高', 'uniE555': '赵
    ', 'uniE595': '6', 'uniE608': '2', 'uniE6CD': '下', 'uniE72D': '以', 'uniE7C1': '技', 'uniE7C6': '黄', 
    'uniE7D3': '周', 'uniE841': '4', 'uniE84B': '经', 'uniE8A4': '专', 'uniE8E6': '硕', 'uniE8F4': '刘', 
    'uniE906': '吴', 'uniE9CF': '陈', 'uniEA8F': '士', 'uniEB2C': 'E', 'uniEBBA': '5', 'uniEBE2': '中', 'uniED0E': '博',
     'uniEF3E': '1', 'uniF003': '科', 'uniF012': '大', 'uniF01A': '9', 'uniF02F': '本', 
    'uniF0D7': '王', 'uniF160': 'B', 'uniF180': '无', 'uniF205': '李', 'uniF2A0': '应', 'uniF3B5': '生', 'uniF501': '校',
     'uniF6E9': 'A', 'uniF71C': '0', 'uniF76F': '张', 'uniF877': 'M'}
    

    2. 阶段二:字体信息不换,动态更换字符编码

    反爬方:既然写死的woff文件太容易让人解析,那就每次都更换新的woff文件,woff文件不更换字体信息,只更换字符编码,这样,每次的字符编码都不一样,解析的时候就不能使用同一套字符编码字典去解析了。
    应对方:每次同一字符的编码都不一样的情况是什么样呢?可以看看下面两个图所示


    我们连续两次请求的同一个字符却有不同的编码,换个思路,同一个的字符它们的字体的关键点的坐标是不变的,就像我们在FontCreator点开某个字符看的的一样

    为了得到每个字的坐标点参数,我们需要把woff文件转换成xml文件

    from pathlib import Path
    from fontTools.ttLib import TTFont
    font1_path = Path(__file__).absolute().parent/"font_1.xml"
    font2_path = Path(__file__).absolute().parent/"font_2.xml"
    woff1_path = Path(__file__).absolute().parent/"base64 (1).woff"
    woff2_path = Path(__file__).absolute().parent/"base64 (2).woff"
    font_1 = TTFont(woff1_path)
    font_2 = TTFont(woff2_path)
    font_1.saveXML(font1_path)
    font_2.saveXML(font2_path)
    

    得到文件是这样的

    我们根据刚才生字的两个不同编码寻找,得到下面这两个结构

    我们可以看到,虽然这两个字符的坐标不一样,但是从旧字符根据一定的偏移量可以得到新字符,所以我们破解这一代字体反爬的手段可以是把最先的字符和字符的坐标保留下来,之后请求得到的字符和字符坐标,根据一定量的偏移去匹配是否是同一个字,类似这样

    from pathlib import Path
    from fontTools.ttLib import TTFont
    woff1_path = Path(__file__).absolute().parent/"base64 (1).woff"
    woff2_path = Path(__file__).absolute().parent/"base64 (2).woff"
    font_1 = TTFont(woff1_path)
    font_2 = TTFont(woff2_path)
    font_old_order = font_1.getGlyphOrder()[2:]
    font_new_order = font_2.getGlyphOrder()[2:]
    
    
    def get_font_flags(font_glyphorder, font_ttf):
        f = {}
        for i in font_glyphorder:
            flags = font_ttf['glyf'][i]
            if "flags" in flags.__dict__:
                f[tuple(list(flags.flags))] = i
        return f
    
    
    def comp(arr1, arr2):
        if len(arr1) != len(arr2):
            return 0
        for i in range(len(arr2)):
            if arr1[i] != arr2[i]:
                return 0
        return 1
    
    
    def get_old_new_mapping():
        old, new = get_font_flags(font_glyphorder=font_old_order, font_ttf=font_1), get_font_flags(
            font_glyphorder=font_new_order, font_ttf=font_2)
        result_dict = {}
        for key1, value1 in old.items():
            for key2, value2 in new.items():
                if comp(key1, key2):
                    result_dict[value1] = value2
        return result_dict
    
    
    print(get_old_new_mapping())
    

    我们会得到新旧两个字符的映射

    {'uniE032': 'uniF889', 'uniE595': 'uniEB52', 'uniF01A': 'uniF07A', 'uniF71C': 'uniEBDE'}
    

    3. 阶段三:有了动态的编码,再搞个动态字体坐标?

    反爬方:动态更换字符编码集也能根据字体坐标来破解,要是新旧两个字符的坐标不是按照一定的偏移量来做的呢?例如我们新的字符和旧的字符的字体不一样,新的字体做了一定量的变形,导致某些坐标的缺少以及坐标的偏移量不一致,所以可以做几百套不同字体坐标,不同字符编码的动态字体集(真的变态!)。
    应对方:
    这一阶段的反爬看到过很多大佬的实现:

    • 有使用阈值来做的,不过阈值是写死的,也就是说明成功其实有点靠运气,有时候返回的两个字符坐标差值在阈值内,有时不在,所以这一个方案有点不太靠谱。
    • 有使用ocr来做的,哈哈,真的是秀,利用ocr来做的原理就是先利用坐标勾勒出汉字图样,接着识别出汉字,再把相同汉字的不同编码做对应,这样也能得出结果,效果没有具体去测算,不过使用ocr来识别汉字应该相对于tf,pytorch等来说效果会差点。
    • 之前看到大壮哥使用KNN来做,是个好想法,而且也不用去识别图片成汉字,资源消耗和速率上相对来说会小点,原理就是如果一个样本在特征空间中的k个最相邻的样本中的大多数属于某一个类别,则该样本也属于这个类别,并具有这个类别上样本的特性。放在字体这个例子中,就是新字体文件中哪个字符离旧字体文件中的某个字符距离较近,它就属于这个字符的类别,也就是和这个字符是一样的。

    4. 阶段四:展望未来。。。

    目前还没有新的字体反爬的手段出现,更多的@font-face的加密上面,比如对字体文件的地址做基本的Js加密等等什么的,其他的我就暂时没发现,有发现的大佬可以透露一下。

    整个字体反爬的演变历程就是上面介绍的这样,下面我们开始做实战分析。

    猫眼实战分析

    猫眼国内票房榜地址

    https://maoyan.com/board/1

    猫眼字体反爬分析

    首先我们进入页面


    我们可以看到票房数据被替换了,是被stonefont这个@font-face的名称给替换了,我们去搜索这个stonefont

    我们通过这个woff地址去下载woff文件,在利用FontCreator打开,看到这样



    和我们之前看到的字体反爬方式是一样的,动态字体文件,以及动态的字体坐标,接下来,我们用KNN的思路去破解它。

    猫眼字体KNN思路分析

    KNN算法比较简单,我们划分测试集和训练集,先用测试集得到每个数字的距离,然后在测试的时候我们对不同的输入(也就是数字)就是距离计算,距离最近的即为相同值。

    代码实战

    PS: 关于历史分析的项目都可以在Review_Reverse项目下的maoyan目录看到
    使用方法:python -m maoyan.xxxx即可

    我们这次使用Sklearn来做KNN

    1. 收集猫眼的多套字体文件

    这里我会获取10套字体文件(越多越好),然后将所有字符对应的字形坐标信息保存到一个列表当中(注意做好字符与字形坐标的对应关系)

    def get_font_content() -> str:
        response = requests.get(
            url=_brand_url,
            headers=_headers
        )
        woff_url = re.findall(r"url\('(.*?\.woff)'\)", response.text)[0]
        font_url = f"http:{woff_url}"
        return requests.get(font_url).content
    
    
    def save_font() -> None:
        for i in range(5):
            font_content = get_font_content()
            with open(f'./fonts/{i+1}.woff', 'wb') as f:
                f.write(font_content)
    
    
    def get_coor_info(font, cli):
        glyf_order = font.getGlyphOrder()[2:]
        info = list()
        for i, g in enumerate(glyf_order):
            coors = font['glyf'][g].coordinates
            coors = [_ for c in coors for _ in c]
            coors.insert(0, cli[i])
            info.append(coors)
        return info
    

    把FontCreator的字符补充上

    def get_font_data() -> List[List[List[int]]]:
        font_1 = TTFont('./fonts/1.woff')
        cli_1 = [6, 7, 4, 9, 1, 2, 5, 0, 3, 8]
        coor_info_1 = get_coor_info(font_1, cli_1)
    
        font_2 = TTFont('./fonts/2.woff')
        cli_2 = [1, 3, 2, 7, 6, 8, 9, 0, 4, 5]
        coor_info_2 = get_coor_info(font_2, cli_2)
    
        font_3 = TTFont('./fonts/3.woff')
        cli_3 = [5, 8, 3, 0, 6, 7, 9, 1, 2, 4]
        coor_info_3 = get_coor_info(font_3, cli_3)
    
        font_4 = TTFont('./fonts/4.woff')
        cli_4 = [9, 3, 4, 8, 7, 5, 2, 1, 6, 0]
        coor_info_4 = get_coor_info(font_4, cli_4)
    
        font_5 = TTFont('./fonts/5.woff')
        cli_5 = [1, 5, 8, 0, 7, 9, 6, 3, 2, 4]
        coor_info_5 = get_coor_info(font_5, cli_5)
    
        infos = coor_info_1 + coor_info_2 + coor_info_3 + coor_info_4 + coor_info_5
        return infos
    

    2. 使用knn算法训练数据

    通常情况下,拿到样本数据,先进行缺失值处理,然后取出特征值和目标值,再对样本数据进行分割,分为训练集和测试集,然后再对样本数据进行标准化处理,最后进行训练预测。由于采集的字体数据不多,如果按随机分割的方式,训练集容易缺失某些字符,导致预测测试集的结果误差率较大,所以在此固定前40个样本为训练集,最后10个样本为测试集合。另外,多次测试发现,此处进行标准化,会影响成功率,所以不采用,另外k值取1, 也就是说,我判定当前样本跟离它最近的那个样本属于同一类型,即同一个字符,这个值取多少合适经过调试才知道,最后预测10个样本,包含了0-9 10个字符,成功率为100%。

    import numpy as np
    import pandas as pd
    from maoyan.font import get_font_data
    from sklearn.impute import SimpleImputer
    from sklearn.model_selection import train_test_split
    from sklearn.neighbors import KNeighborsClassifier
    from sklearn.preprocessing import StandardScaler
    
    
    def main() -> None:
        # 处理缺失值
        imputer = SimpleImputer(missing_values=np.nan, strategy='mean')
        data = pd.DataFrame(imputer.fit_transform(pd.DataFrame(get_font_data())))
        # 取出特征值\目标值
        x = data.drop([0], axis=1)
        y = data[0]
        # 分割数据集
        # x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=0)
        x_train = x.head(30)
        y_train = y.head(30)
        x_test = x.tail(10)
        y_test = y.tail(10)
        # 标准化
        # std = StandardScaler()
        # x_train = std.fit_transform(x_train)
        # x_test = std.transform(x_test)
        # 进行算法流程
        knn = KNeighborsClassifier(n_neighbors=1)
        # 开始训练
        knn.fit(x_train, y_train)
        # 预测结果
        y_predict = knn.predict(x_test)
        print(y)
        # 得出准确率
        print(knn.score(x_test, y_test))
    

    3. 得到训练好的流程之后我们进行测试

    def get_board() -> None:
        map_dict = get_map(
            text=requests.get(
                url=_board_url,
                headers=_headers
            ).text
        )
        for uni in map_dict.keys():
            text = text.replace(uni, map_dict[uni])
        html = etree.HTML(text)
        dd_li = html.xpath('//dl[@class="board-wrapper"]/dd')
        for dd in dd_li:
            p_li = dd.xpath(
                './div[@class="board-item-main"]//div[@class="movie-item-info"]/p')
            title = p_li[0].xpath('./a/@title')[0]
            star = p_li[1].xpath('./text()')[0]
            releasetime = p_li[2].xpath('./text()')[0]
            p_li = dd.xpath(
                './div[@class="board-item-main"]//div[@class="movie-item-number boxoffice"]/p')
            realtime_stont = ''.join(
                list(map(lambda x: x.strip(), p_li[0].xpath('.//text()'))))
            total_stont = ''.join(
                list(map(lambda x: x.strip(), p_li[1].xpath('.//text()'))))
            print(title)
            print(star)
            print(releasetime)
            print(realtime_stont)
            print(total_stont)
            print('-' * 50)
    
    
    get_board()
    



    把训练好的结果和官网对比一下,是不是感觉美滋滋,连最新的字体反爬也被我们破解啦!

    复习要点

    1. 重新梳理下字体反爬的整个演变历程
    2. 对于最新的字体反爬转化思路,从识别图片到分类算法,提高效率

    作者相关

    号主介绍

    多年反爬虫破解经验,AKA“逆向小学生”,沉迷数据分析和黑客增长不能自拔,虚名有CSDN博客专家和华为云享专家。

    私藏资料

    呕心沥血从浩瀚的资料中整理了独家的“私藏资料”,公众号内回复“私藏资料”即可领取爬虫高级逆向教学视频以及多平台的中文数据集

    小学生都推荐的好文

    2019年末逆向复习系列之知乎登录formdata加密逆向破解

    2019年末逆向复习系列之今日头条WEB端_signature、as、cp参数逆向分析

    2019年末逆向复习系列之百度指数Data加密逆向破解

    2019年末逆向复习系列之努比亚Cookie生成逆向分析

    2019年末逆向复习系列之淘宝M站Sign参数逆向分析

    展开全文
  • 最早使用字体反爬技术的有58同城、汽车之家等,发展到现在很多主流APP也使用了字体反爬技术和爬虫工作者进行对抗,字体反爬从一开始的单纯依靠一个写死的字体文件升级成现在最新动态的字体文件。而字体反爬的功课也...

    本项目的所有代码和相关文章, 仅用于经验技术交流分享,禁止将相关技术应用到不正当途径,因为滥用技术产生的风险与本人无关。

    字体反爬的背景

    字体反爬,也是一种常见的反爬技术,相信很多爬虫工作者都接触过,这其实不是一种常规的反爬手段,它其实是页面和前端字体文件想配合完成的一个反爬策略。
    最早使用字体反爬技术的有58同城、汽车之家等,发展到现在很多主流APP也使用了字体反爬技术和爬虫工作者进行对抗,字体反爬从一开始的单纯依靠一个写死的字体文件升级成现在最新动态的字体文件。而字体反爬的功课也有一个开始的解析字体文件做数据映射到现在依靠KNN来做动态映射,算是经历了一个又一个光辉的“升级阶段”。

    本篇文章主要讲如何攻克入门级的字体反爬技术。

    入门级别的字体反爬技术是通过固定的字体文件进行数据替换的

    字体反爬原理

    首先我们先理解字体反爬的原理,就是前端工程师通过自定义的字体来替换页面中某些关键的数据

    因为是自定义的字体,所以如果不使用正确的解码方式来进行匹配,我们爬取的时候就无法获得正确的内容

    那么在HTML中如何使用自定义字体呢?
    答案就是使用@font-face

    @font-face {
     font-family: <identifier>; 
     src: <fontsrc> [, <fontsrc>]*; <font>;
      }
    

    里面的font-family也就是一个特定的名字,src就表示你需要引用的具体的文件,而这个文件就是字体文件,一般是ttf类型,eot类型或者是woff类型,woff类型的文件运用比较广泛,所以大家一般碰到的都是woff类型的文件。

    那么woff文件中的内容是什么呢?他是怎么把数据进行数据替换的呢?

    我们打开一个woff字体文件,需要使用工具辅助。

    1. FontCreator工具 下载地址
    2. 在线FontEditor工具地址

    这里我先随便给一个woff字体文件
    复制连接打开 //k3.autoimg.cn/g1/M00/D2/8E/wKgHGFsUz0eAJlMWAABj9Bn3RMw39…ttf

    我们把FontCreator下载下来,传来一个我们之前准备好的woff文件看看效果

    在这里插入图片描述
    我们可以看到woff文件中每个字符都有一个编码对应,woff实际上就是编码和字符的映射表。

    在对抗反爬的时候,解析得到的woff文件得到一定顺序的编码集再结合在FontCreator中的字符集得到字符编码字典,在我们解析HTML源码的时候替换就行了。

    这里补充一点就是你每次访问加载的字体文件中的字符的编码可能是变化的,就是说网站有多套的字体文件。

    既然编码是不固定的,那就不能用编码的一一对应关系来处理字体反爬。这里要用到上面说的三方库fontTools,利用fontTools可以获取每一个字符对象,这个对象你可以简单的理解为保存着这个字符的形状信息。而且编码可以作为这个对象的id,具有一 一对应的关系。
    安装第三方库 fontTools命令: pip install fontTools

    模拟对抗字体反爬

    引用第三方库

    from fontTools.ttLib import TTFont
    

    这里我们手动把一组编码和字符的对应关系提出来,在实际情况中这种关系在网站自身的字体文件里,要通过程序存进列表里,这里省略一些步骤。

    uni_list = ['uniF848', 'uniE2A8', 'uniEA5D', 'uniE51B', 'uniE335', 'uniE42F', 'uniF373', 'uniF649', 'uniE052', 'uniE7A3']
    word_list = ['9', '2', '8', '4', '3','1','6','7','5','0']
    

    加载字体文件

    font1 = TTFont('02.ttf')
    

    获取cmap节点code与name值映射,返回字典

    print(font1.getBestCmap())#可以看到字体和对应的编码信息
    

    下面我们把编码逐个提出来,获取这个编码对应文字的 x、y坐标信息(每个元素是(x,y)元组),放进一个列表中。
    在遍历word_list中的每个文字,通过自定义的comp函数比较其坐标,对于一个字符如果每组坐标都相同则说明这个编码与这个字符相对应。

    from fontTools.ttLib import TTFont
    def comp(l1, l2):  # 定义一个比较函数,比较两个列表的坐标信息是否相同
        if len(l1) != len(l2):
            return False
        else:
            mark = 1
            for i in range(len(l1)):
                    if abs(l1[i][0]-l2[i][0]) < 40 and abs(l1[i][1]-l2[i][1]) < 40:
                        pass
                    else:
                        mark = 0
                        break 
            return mark
    
    # 手动确定一组编码和字符的对应关系
    uni_list = ['uniF848', 'uniE2A8', 'uniEA5D', 'uniE51B', 'uniE335', 'uniE42F', 'uniF373', 'uniF649', 'uniE052', 'uniE7A3']
    word_list = ['9', '2', '8', '4', '3','1','6','7','5','0']
    font1 = TTFont('02.ttf') #加载字体文件
    print(font1.getBestCmap())#可以看到字体和对应的编码信息
    be_p1 = []  # 用来存38个字符的(x,y)信息
    for uni in uni_list:
        p1 = []  # 保存一个字符的(x,y)信息
        #  获取对象的x,y信息,返回的是一个GlyphCoordinates对象,可以当作列表操作,每个元素是(x,y)元组
        p = font1['glyf'][uni].coordinates
        # p=font1['glyf'][i].flags #获取0、1值,实际用不到
        for f in p:  # 把GlyphCoordinates对象改成一个列表
            p1.append(f)#append 在列表末尾添加新的对象
        be_p1.append(p1)
    
    font2 = TTFont('02.ttf')
    # 获取getGlyphOrder节点的name值,返回为列表
    uni_list2 = font2.getGlyphOrder()[1:]
    on_p1 = []
    for i in uni_list2:
        pp1 = []
        #获取对象的x,y信息,
        p = font2['glyf'][i].coordinates
        for f in p:
            pp1.append(f)
        on_p1.append(pp1)
    
    n2 = 0
    x_list = []
    for d in on_p1:
        n2 += 1
        n1 = 0
        for a in be_p1:
            n1 += 1
            if comp(a, d):#比较坐标
                #如果每组坐标都一样,则说明这个编码和这个字符是对应的,输出
                print(uni_list2[n2-1], word_list[n1-1])
    
         
    

    输出结果

    {120: 'x', 57426: 'uniE052', 58024: 'uniE2A8', 58165: 'uniE335', 58415: 'uniE42F', 58651: 'uniE51B', 59299: 'uniE7A3', 59997: 'uniEA5D', 62323: 'uniF373', 63049: 'uniF649', 63560: 'uniF848'}
    uniF848 9
    uniE2A8 2
    uniEA5D 8
    uniE51B 4
    uniE335 3
    uniE42F 1
    uniF373 6
    uniF649 7
    uniE052 5
    uniE7A3 0
    
    展开全文
  • 解决猫眼字体反爬

    千次阅读 2019-02-26 23:32:44
    猫眼字体反爬 具体下来,这两个字体反爬都没有58同城的反爬那样繁琐。58同城反爬 具体解决思路: 1.获取对应加载的字体文件(可能是对网页上的base64加密字段进行解密,也能是去获得字体文件的url,以.ttf或.woff...

    猫眼字体反爬

    具体下来,这两个字体反爬都没有58同城的反爬那样繁琐。58同城反爬

    具体解决思路:
    1.获取对应加载的字体文件(可能是对网页上的base64加密字段进行解密,也能是去获得字体文件的url,以.ttf或.woff结尾的文件)
    2.用1的步骤先下载好一个字体文件并用FontCreater进行查看,按照顺序写出对应字体
    3.新请求下载一个字体文件并解析这个字体文件(注意这一次请求要与下面的解析网页保持同一个会话,不然字体文件对应的编码就不管用了)貌似每个文字是通过某种方式绘画出来的,所有同一个文字在不同的字体文件里面的参数是一样的,只不过对应的网页上的编码不一样而已,即对比前面下载好的字体文件的某个字体对应的参数去与新的字体文件的字体的参数进行匹配,匹配上了,就用对应的字体替换就好了。

    先把下载好的第一个字体文件按照显示设置好映射
    在这里插入图片描述

    map_list = ['1','2','8','0','7','6','3','5','9','4']
    

    后面用新的动态加载的字体文件的参数匹配前面的那个文件的字体的参数,并对应于这个列表。

    在此之前
    我们先测试一下,先下载两个字体文件并存储为xml格式

    file_url = re.search("src:.*?,.*?url\('(.*?)'\) format\('woff'\);",response.text,re.S).group(1)
        print(file_url)
        with open('D:/maoyan_new1.woff','wb') as fp:
            content = session.get('http:' + file_url, headers=headers).content
            fp.write(content)
            
    #上面那个先执行两次,修改一下文件名获取对应的xml文件进行比对字体参数
    font = TTFont('D:/maoyan_base1.woff')
    font.saveXML('D:/maoyan_base1.xml')
    
    font_new = TTFont('D:/maoyan_new1.woff')
    font_new.saveXML('D:/maoyan_new.xml')
    

    下面对比一下字体的xml文件
    在这里插入图片描述
    在这里插入图片描述
    可以看到只是name不一样

    import requests
    import re
    from fontTools.ttLib import TTFont
    
    url = 'https://maoyan.com/'
    headers = {
        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36',
    }
    
    session = requests.session()
    response = session.get(url,headers=headers)
    
    def get_woff_file():
        file_url = re.search("src:.*?,.*?url\('(.*?)'\) format\('woff'\);",response.text,re.S).group(1)
        print(file_url)
        with open('D:/maoyan_new2.woff','wb') as fp:
            content = session.get('http:' + file_url, headers=headers).content
            fp.write(content)
    
    def parse_font():
        font = TTFont('D:/maoyan_base1.woff')
        font.saveXML('D:/maoyan_base1.xml')
        font_base_order = font.getGlyphOrder()[2:]
        print(font_base_order)
        # 根据第一次下载的文件写出对应
        map_list = ['1','2','8','0','7','6','3','5','9','4']
    
        font_new = TTFont('D:/maoyan_new2.woff')
        font_new.saveXML('D:/maoyan_new2.xml')
        font_new_order = font_new.getGlyphOrder()[2:]
        print(font_new_order)
    
        base_flag_list = list()
        new_flag_list = list()
        # 得到两个二维列表,对里面没个一维列表进行内容的比对,得到对应的字体
        for i,j in zip(font_base_order,font_new_order):
            flag_base = font['glyf'][i].flags
            flag_new = font_new['glyf'][j].flags
            base_flag_list.append(list(flag_base))
            new_flag_list.append(list(flag_new))
    
        memory_dict = dict()
        for index1,x in enumerate(base_flag_list):
            for index2,y in enumerate(new_flag_list):
                if common(x,y):
                    key = font_new_order[index2]
                    key = '&#x' + key.replace('uni','').lower() + ';'
                    memory_dict[key] = map_list[index1]
        print(memory_dict)
        return memory_dict
    
    def get_data(memory_dict):
        all_data = re.findall('<span class="stonefont">(.*?)</span>万', response.text, re.S)
        print(all_data)
        for data in all_data:
            for key,value in memory_dict.items():
                data = data.replace(key,value)
            print(data)# 只是做测试用就不提取那么多东西了
    
    def common(list1,list2):
        len1 = len(list1)
        len2 = len(list2)
        if len1 != len2:
            return False
        for i in range(len1):
            if list1[i] != list2[i]:
                return False
        return True
    
    if __name__ == '__main__':
        get_woff_file()
        memory_dict = parse_font()
        get_data(memory_dict)
        
    

    于是就可以获得结果了
    在这里插入图片描述

    展开全文
  • CSS字体反爬实战,10分钟就能学会

    千次阅读 2019-08-24 15:16:57
    本次来解锁新姿势——CSS字体反爬。 在解决这个字体反爬的路上,当我以为解决这个反爬手段的时候, 最后验证总的答案的时候,被打脸了!!! 又被默默设埋伏了,踩了一个坑,巨大的,为何悲伤辣么大 <(-︿-)>...
  • 起点字体反爬

    2021-06-26 14:39:18
    x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.106 Safari/537.36', } page_text = requests.get(url=url,headers=headers).text 接下里我们要字体文件 这个字体文件就是一个文件链接,用着这提取...
  • 58 字体反爬

    2020-03-07 19:58:55
    ClickID=120 此链接是深圳的二手车: ...爬取一页中的所有二手车详情链接,从中爬取基本信息,其中的交易价字体加密了,需要处理才能获得正确的数字: 查看源代码,找到这个数字,但源代码中和右键检查看...
  • KNN算法及其用KNN解决字体反爬关于KNN算法概要简介原理KNN算法Python实现KNN解决字体反爬web-font介绍例子最后 关于KNN算法 概要 K最近邻(kNN,k-NearestNeighbor)分类算法是数据挖掘分类技术中最简单的方法之一。...
  • woff字体反爬实战,10分钟就能学会(ttf字体同理)

    万次阅读 多人点赞 2019-08-26 23:21:07
    声明:本帖子仅是用于...经过分析,当前这种字体反爬机制是:通过获取指定链接的woff字体文件,然后根据html源码的数字 去woff字体文件里面查找真正的数字,讲到底就是一个映射关系/查找字典。如html源码是123,去w...
  • 下面两张图是保存的自定义字体文件的xml格式文件! 此图是文章中:unicode编码(图中的code,16进制格式),和glyph编号,和 id(对应的就是下面第三个图中每个字符,第一个id为0,以此类推!)之间存在对应关系 ...
  • 58同城招聘字体反爬

    千次阅读 2019-02-25 23:15:34
    可以发现对应的某些字体是以某种编码的形式存在的 对应的,在网页里面搜索不到ttf文件,但可以通过network发现@font-face这个东西于是找这个东西 发现是经过base64加密的,于是要用正则把这一段提取出来并进行base...
  • 如何破解字体反爬机制

    千次阅读 2018-10-27 17:39:14
    这几天爬取58租房信息的时候意外发现了它是一个字体反爬的网站,所谓的字体反爬就是网站将一些关键字替换为网站自己的字体,这样在网页上字体会正常显示,但是当爬取下来的时候,经过字体加密的字符都是乱码的,根本...
  • 爬虫进阶-- 字体反爬终极解析

    千次阅读 2019-06-14 16:59:10
    这种一般是网站设置了字体反爬 什么是字体反爬? 字体反爬虫:在网页中的关键部分中采用自定义的字体来显示,防止爬虫爬取到关键信息。 采用自定义字体文件是CSS3特性,可参考CSS3字体。 这是网友的见解。(ps...
  • 目标:猫眼电影票房、汽车之家字体反爬的处理 ---全部文章: 京东爬虫 、链家爬虫、美团爬虫、微信公众号爬虫、字体反爬、Django笔记、阿里云部署、vi\vim入门---- 前言:字体反爬,也是一种常见的反爬技术,...
  • python 破解字体反爬 (一)

    千次阅读 2019-11-25 17:08:37
    这种一般是网站设置了字体反爬 这里我们以58同城为例: 点击进入https://sz.58.com/chuzu/链接: 网页显示数据为: 网页原码数据为: 从上面可以看出,生这个字变成了乱码,请大家特别注意箭头所指的数字。...
  • 分析有哪些反爬操作(字体反爬) 所需要的模块: # 所需要的模块 pip install requests pip install beautifulsoup4 # 内置模块 time random 反爬操作总流程: 确认该网站存在字体反爬 寻找字体文件 在...
  • 这两天一直在看字体反爬方面的文章,现在难一点的还没摸清怎么搞,但是58的品牌公寓的字体反爬相对简单一些,已经自己做出来了,特此记下来,也可以帮刚在这方面入门的小伙伴更快熟悉起来。整体代码我会在文末发出来...
  • 该页面文章不是固定的,为动态生成,并且字体做了反爬措施。 该页面结果简单,爬取提取数据基本上一行代码就可以解决。但是爬取下来的为字体加密后的字符。 所以我现在要做的就是,怎么去将&#x…;字符转为...
  • 过程 突然接了个需求,爬一个网站的经销商数据 ...问题 网站比较简单,其中有一个手机号获取是js渲染出来的。 正常展示是这样的: 而代码源码是这样的: 可以看到,手机号并没有在 span 标签里,而这个标签里面有个和很...
  • 字体反爬详解

    2019-05-17 10:05:09
    目前网页的字体多样化完全可以自定义,常见有eot,woff,ttf格式类型 本次爬虫就针对以woff格式的字体而言: 先看看不做措施造成的影响: http://maoyan.com/ 以猫*票房信息为例 调试模式下票房数字是乱码的,在...
  • k-近邻算法 解决 动态字体反爬

    千次阅读 2019-12-21 22:49:44
    k-近邻算法 解决 猫眼电影字体反爬。 建议不要点击进来,你会发现看了没用且浪费你的时间。

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,964
精华内容 785
关键字:

字体反爬