题目地址:http://www.glidedsky.com/level/crawler-font-puzzle-1
写一下之前处理过的两个字体反爬实战,也是很常见的一种反爬类型,这是第一篇
先来看一下题目
源码拿到的数字,和实际显示在网页的数字,明显不一样的
注意到两个现象
每一次刷新,源码中的数字就跟着变动,说明每请求一次页面,就使用了新的ttf字体文件
数字看起来无序,其实是有映射关系的,比如 122
变成了 277
,226
变成了 773
所以这里的解题思路就是,解析ttf文件,得到数字之间的映射关系,然后结合网页源码提取到的数字,就能获取真实的数字了
作者已经提示,ttf文件内嵌在源码的base64中
复制那一大串字符,进行解码,然后保存成 ttf文件 :
1 2 3 4 content = base64.b64decode(b64_str) with open ("page-1.ttf" , "wb" ) as f: f.write(content)
使用专门的字体查看工具打开文件,这里我使用的是 FontCreator 9.1
通过比对网页数字、源码数字,我们发现映射关系是一致的,比如:8 显示成 1,4 显示成 0
接下来就是要怎么拿到这些映射关系了?根据ttf文件提示,我们只要拿到上面那一栏小标题 four one eight ...
,然后按顺序找到就能对应的真实数字,比如 four 对应 0, one 对应 1
那么怎么拿到解析 ttf文件拿到小标题呢?有两种方式,一种是解析xml,一种是使用专门的字体解析库 fontTools
这里使用 fontTools,后面其他的文章我会介绍xml的
参考:https://fonttools.readthedocs.io/en/latest/ttLib/ttFont.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 from fontTools.ttLib import TTFontdef 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
拿到映射表后,我们提取源码中的数字就能得到真实的数字了
代码整理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 import base64import requestsfrom io import BytesIOfrom fontTools.ttLib import TTFontfrom parsel import Selectordef 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) 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()
涉及到的知识点:
运行结果