题目地址: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() 
涉及到的知识点:
运行结果