爬虫反爬系列2-字体加密
一个练习爬虫技巧的网站: http://glidedsky.com/ 镀金的天空是一个互联网技能认证网站(侵权删)
那么这种网站就算是通过selenium也是无法获取真是数据的
字体反爬分析流程
- 字体加密一般是通过字体文件进行映射,所以我们只需要找到对应的字体文件,然后通过一些手段找到他们字体对应的映射关系就可以破解字体反爬
1. 找到字体加密文件
- 当我们发现字体反爬之后,首先可以找一下是通过哪一个类进行的字体映射,因为字体映射都会在具体的css样式上进行体现
- 我们可以先删除掉自身的一些样式,观察页面变化,当我们删除自身样式或者父类样式之后页面恢复了乱码,就可以确定是哪一个类进行的字体加密
- 通过搜索的方式找到对应文件,这个页面中关键的参数刚好是font-family: glided_sky; 那么部分网站会将这个属性写入到一个css类中,流程还是相同,搜索找到相关css类的定义,跳转进入,可能是两种情况
- base64格式的字体文件,这种文件可以通过python的base64模块进行解密
- 通过url的方式引入字体文件,这种比较简单,直接请求对应的url就可以了
2.找到字体文件后,进行下载
- url文件的下载方式直接请求就可以
- base64文件的请求方式
- 通过请求base64文件所在的url, 提取出这部分内容
import base64
from fontTools.ttLib import TTFont
# 从每一个页面获取的base64编码
a = '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'
# 经过b64decode 获取到文件内容的字节
res = base64.b64decode(a)
# 写入到woff文件中
f = open('1.woff','wb')
f.write(res)
f.close()
#将数据保存为woff
font = TTFont('1.woff')
# 将数据转换为xml
font.saveXML('1.xml')
3. 寻找字体的映射关系
- 打开保存的1.xml
- 按照我之前的分析流程是通过cmap来找到源码中的数据和页面展示数据的对应关系,这个问题就解决了
- 给出一个以前遇到的字体反爬
- 这种类型的,只需要到软件中对应一次,就可以得出name所对应的值
- 只需要每次获得 code 和 name 的对应关系就好
- 这种是比较小的字体文件,有些字体文件会包括中文加密,有几百个对应关系,可以通过文字识别的方式直接提取出里面的值,一般name的值不会随意更改
- 回归正题发现他并不是使用cmap的映射方式之后,还发现了一个有意思的点就是他的GlyphOrder中的参数是变化的,经过了一番测试后得出了他就是字体映射的部分
- 经过几番比较之后发现了对应关系
- 源码中数字 对应name 中的英文数字
- 在从name 对应id数字 - 1 就是实际数值
# 给出关键代码
from fontTools.ttLib import TTFont
from xml.dom import minidom
from lxml import etree
import requests
import base64
import re
def map_table(ba64_woff):
woff_bytes = base64.b64decode(ba64_woff)
f = open('1.woff', 'wb')
f.write(woff_bytes)
f.close()
font = TTFont('1.woff')
font.saveXML('1.xml')
dom = minidom.parse('1.xml')
root = dom.documentElement
GlyphID = root.getElementsByTagName('GlyphOrder')[0].getElementsByTagName('GlyphID')
name_map_id = {}
for name in GlyphID[1:]:
name_map_id[name.getAttribute("name")] = int(name.getAttribute("id")) - 1
return name_map_id
def get_num(html):
tree = etree.HTML(html)
div = tree.xpath('//div[@class="row"]')
div_list = div[0].xpath('./div')
num_list = []
for div in div_list:
num = div.text.strip()
num_list.append(num)
return num_list
def main(url):
html_text = requests.get(url, headers=header).text
ba64_woff = re.findall('data:font;charset=utf-8;base64,(.*?)\) format',html_text, re.S)[0]
name_map_id = map_table(ba64_woff)
num_list = get_num(html_text)
for num in num_list:
print(''.join([str(name_map_id[num_map_name[i]]) for i in num]))
header = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36',
'Cookie': ''
}
num_map_name = {'0': 'zero', '1': 'one', '2': 'two', '3': 'three', '4': 'four', '5': 'five', '6': 'six',
'7': 'seven', '8': 'eight', '9': 'nine'}
if __name__ == '__main__':
main('http://glidedsky.com/level/web/crawler-font-puzzle-1')
完成