爬虫逆向之字体反爬(一)、镀金的天空-字体反爬-1

题目地址:http://www.glidedsky.com/level/crawler-font-puzzle-1

写一下之前处理过的几个字体反爬实战,也是很常见的一种反爬类型,这是第一篇

先来看一下题目

image.png

源码拿到的数字,和实际显示在网页的数字,明显不一样的

image.png

注意到两个现象

  • 每一次刷新,源码中的数字就跟着变动,说明每请求一次页面,就使用了新的ttf字体文件
  • 数字看起来无序,其实是有映射关系的,比如 122 变成了 277226 变成了 773

所以这里的解题思路就是,解析ttf文件,得到数字之间的映射关系,然后结合网页源码提取到的数字,就能获取真实的数字了

作者已经提示,ttf文件内嵌在源码的base64中

image.png

复制那一大串字符,进行解码,然后保存成 ttf文件

content = base64.b64decode(b64_str)

with open("page-1.ttf", "wb") as f:
	f.write(content)

使用专门的字体查看工具打开文件,这里我使用的是 FontCreator 9.1

image.png

通过比对网页数字、源码数字,我们发现映射关系是一致的,比如:8 显示成 1,4 显示成 0

image.png

接下来就是要怎么拿到这些映射关系了?根据ttf文件提示,我们只要拿到上面那一栏小标题 four one eight ...,然后按顺序找到就能对应的真实数字,比如 four 对应 0, one 对应 1

那么怎么拿到解析 ttf文件拿到小标题呢?有两种方式,一种是解析xml,一种是使用专门的字体解析库 fontTools

这里使用 fontTools,后面其他的文章我会介绍xml的

参考:https://fonttools.readthedocs.io/en/latest/ttLib/ttFont.html

from fontTools.ttLib import TTFont

def parse_ttf():
    """
    return
        {
            源码数字:真实数字
        }
    """
    font = TTFont("page-1.ttf")
    # 获取节点名列表
    name_lst = font["cmap"].tables[0].ttFont.getGlyphOrder()
    en2num = {
        "nine": 9,
        "eight": 8,
        "two": 2,
        "six": 6,
        "three": 3,
        "four": 4,
        "seven": 7,
        "one": 1,
        "five": 5,
        "zero": 0,
    }
    # 生成映射表
    map_list = {
        str(en2num[word]): str(index) for index, word in enumerate(name_lst[1:])
    }
    return map_list

拿到映射表后,我们提取源码中的数字就能得到真实的数字了

代码整理

# -*- coding:utf-8 -*-

import base64
import requests

from io import BytesIO
from fontTools.ttLib import TTFont
from parsel import Selector


def glidedsky_login():
    """
    网站登录,才能看到题目
    注意题目域名也必须是 www.glidedsky.com
    """
    EMAIL = ""
    PASSWORD = ""
    LOGIN_URL = "http://www.glidedsky.com/login"

    session = requests.session()
    resp = session.get(LOGIN_URL)
    dom = Selector(resp.text)
    _token = dom.css("meta[name='csrf-token']::attr(content)").get()
    form_data = {
        "_token": _token,
        "email": EMAIL,
        "password": PASSWORD,
    }
    session.post(LOGIN_URL, data=form_data)
    return session


def parse_ttf(b64_str):
    """
    return
        {
            源码数字:真实数字
        }
    """
    content = base64.b64decode(b64_str)
    # 使用 BytesIO 构建一个临时文件对象
    font = TTFont(BytesIO(content))

    # 获取节点名列表
    name_lst = font["cmap"].tables[0].ttFont.getGlyphOrder()
    en2num = {
        "nine": 9,
        "eight": 8,
        "two": 2,
        "six": 6,
        "three": 3,
        "four": 4,
        "seven": 7,
        "one": 1,
        "five": 5,
        "zero": 0,
    }
    # 生成映射表
    map_list = {
        str(en2num[word]): str(index) for index, word in enumerate(name_lst[1:])
    }
    return map_list


def parse_html(html_text):
    dom = Selector(text=html_text)
    b64_str = dom.css("style::text").re_first(r"base64,(.+?)\)")
    mappings = parse_ttf(b64_str)

    fake_nums = dom.css(".col-md-1::text").re(r"\d+")
    for num in fake_nums:
        real_num = "".join(mappings[n] for n in num)
        yield real_num


def main():
    session = glidedsky_login()

    for page in range(1, 11):
        url = "http://www.glidedsky.com/level/web/crawler-font-puzzle-1?page=" + str(page)
        resp = session.get(url)
        print(f"page {page}: ", [num for num in parse_html(resp.text)])


if __name__ == "__main__":
    main()

涉及到的知识点:

运行结果

image.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值