0x00字体反爬
通过自定义字符集来改变html源码中的某些字符的表现形式。这里不采用ocr识别的方式,采取直接构建对应的映射词典的方式来解决。
0x01常见映射方式
(1)font-face拼凑式
(2)blackgroud拼凑式
(3)字符穿插式
(4)伪元素隐藏式
(5)元素定位覆盖式
(6)IFRAME异步加载式
(7)字符分割式
(8)字符集替换式
通常是几种方式的结合。目前字体反爬最难的应该是某眼。
0x02接口分析
url :aHR0cHM6Ly93d3cuZGlhbnBpbmcuY29tL2ppbmh1YS9jaDEwL2cxMTA=
源码里啥都有,也没有需要逆向的地方。
但是拿小箭头去spect一下可以看到
目标就是解析出串串香。
可以阅读下:
https://blog.csdn.net/qq_44969651/article/details/124428024?spm=1001.2014.3001.5502
0x03解决思路
定位一下串,去源码复制
点击tagName,访问对应的.css文件
转到这个.woff文件
之后打开 https://font.qqe2.com/index-en.html 搜索到如下图
根据映射的关系我们根据以下步骤构建大字典:
1.观察字体反爬所在标签位置,根据被加密字体的class定位css文件
2.访问字体地址(.woff结尾的地址),下载所有字体文件(不止一个woff文件,需要对应class属性)
2.对css地址,发送请求,获取响应,从响应中,提取出来这一个woff字体地址
然后通过代码请求字体地址,下载字体文件
3.通过字体应用,打开字体文件,从而获取字体译文
4.构建加密字体与解密内容大字典,例:{“加密字体源码”:“解密字体译文”}
之后,请求网页后,直接利用字典的特性,key取value值,获取所有译文
0x04代码及测试结果
from fontTools.ttLib import TTFont
from requests_html import HTMLSession
import re, os
session = HTMLSession()
class FontSpider(object):
"""创建保存字体的文件夹"""
os_path = os.getcwd() + '/字体/'
if not os.path.exists(os_path):
os.mkdir(os_path)
def __init__(self):
"""准备解析字体文件的数据(地址)"""
self.font_url = 'https://s3plus.meituan.net/v1/mss_0a06a471f9514fc79c981b5466f56b91/svgtextcss/b3fbec1328dfef833038fcc375ff0d90.css'
self.headers = {
"xxxxxxx"
}
"""创建存放解密字体大字典的大列表"""
self.big_font_list = []
def parse_font_url(self):
"""
解析字体css文件,提取字体地址
:return:
"""
response = session.get(self.font_url, headers=self.headers).content.decode()
font_big_list = re.findall(r'font-family: "(.*?)";src.*?(//s3plus\.meituan\.net/v1/mss_73a511b8f91f43d0bdae92584ea6330b/font/\w+.woff)', response)
"""遍历大列表字体地址"""
for font_data in font_big_list:
"""将遍历出的字体数据传递到解密字体地址方法中"""
self.parse_font_data(font_data)
"""循环解析字体结束后,解析网页加密字体"""
self.parse_url_font()
def parse_font_data(self, font_data):
"""
解析字体地址
:param font_data:
:return:
"""
"""取出字体地址,拼接完整,添加协议部分"""
woff_url = 'https:' + font_data[1]
"""取出字体地址,对应的字体加密标签的class属性名称"""
name = font_data[0].split('-')[-1]
"""下标取出保存字体文件所需的字体文件名"""
title = woff_url[-13:]
# 对字体地址发送请求
response = session.get(woff_url)
with open(self.os_path + name + title, 'wb')as f:
# 保存字体的二进制文件数据
f.write(response.content)
"""开始解析,解析字体文件"""
font_json = TTFont(self.os_path + name + title)
font_json.saveXML(self.os_path + title.replace('woff', 'xml'))
"""从xml中,拿取所有的name属性"""
gly_list = font_json.getGlyphOrder()
"""这一段话如果报错,第一行引入utf-8编码"""
words = ' .1234567890店中美家馆小车大市公酒行国品发电金心业商司超生装园场食有新限天面工服海华水房饰城乐汽香部利子老艺花专东肉菜学福饭人百餐茶务通味所山区门药银农龙停尚安广鑫一容动南具源兴鲜记时机烤文康信果阳理锅宝达地儿衣特产西批坊州牛佳化五米修爱北养卖建材三会鸡室红站德王光名丽油院堂烧江社合星货型村自科快便日民营和活童明器烟育宾精屋经居庄石顺林尔县手厅销用好客火雅盛体旅之鞋辣作粉包楼校鱼平彩上吧保永万物教吃设医正造丰健点汤网庆技斯洗料配汇木缘加麻联卫川泰色世方寓风幼羊烫来高厂兰阿贝皮全女拉成云维贸道术运都口博河瑞宏京际路祥青镇厨培力惠连马鸿钢训影甲助窗布富牌头四多妆吉苑沙恒隆春干饼氏里二管诚制售嘉长轩杂副清计黄讯太鸭号街交与叉附近层旁对巷栋环省桥湖段乡厦府铺内侧元购前幢滨处向座下臬凤港开关景泉塘放昌线湾政步宁解白田町溪十八古双胜本单同九迎第台玉锦底后七斜期武岭松角纪朝峰六振珠局岗洲横边济井办汉代临弄团外塔杨铁浦字年岛陵原梅进荣友虹央桂沿事津凯莲丁秀柳集紫旗张谷的是不了很还个也这我就在以可到错没去过感次要比觉看得说常真们但最喜哈么别位能较境非为欢然他挺着价那意种想出员两推做排实分间甜度起满给热完格荐喝等其再几只现朋候样直而买于般豆量选奶打每评少算又因情找些份置适什蛋师气你姐棒试总定啊足级整带虾如态且尝主话强当更板知己无酸让入啦式笑赞片酱差像提队走嫩才刚午接重串回晚微周值费性桌拍跟块调糕'
"""创建替换大字典"""
dict_data = {}
# 使用枚举方法遍历
for index, value in enumerate(words):
# 下标清洗值,构建大字典
dict_data[gly_list[index][3:]] = value
"""构造保存解密后的字体字典格式,加密字体所在的标签位置class属性作为key值,解密的字体字典作为value值"""
data_big_dict = {name: dict_data}
# 打印字体大字典,观察特征
print(data_big_dict)
"""往存放字体大列表中添加解密的字体大字典"""
self.big_font_list.append(data_big_dict)
def parse_url_font(self):
"""
开始解析字体
:return:
"""
# 大众点评火锅地址,城市金华
url = 'https://www.dianping.com/jinhua/ch10/g110'
response = session.get(url, headers=self.headers).html
print("==========================================================================================")
font_data = response.xpath('//*[@id="shop-all-list"]/ul/li[1]/div[2]/div[3]/a[1]/span//text()')
print("font_data",font_data)
# 提取被加密字体所在标签的属性
class_name = ''.join(response.xpath('//*[@id="shop-all-list"]/ul/li[1]/div[2]/div[3]/a[1]/span/svgmtsi[1]/@class'))
# 推导式替换\u字符串
font_result_data = [repr(i).replace(r'\u', '').replace("'", '') for i in font_data]
# 打印输出清洗后的数据,观察特征
print(font_result_data)
# 套路替换被加密的字体
# 创建存放解密后的字体的列表
font_list = []
"""遍历解密的字体的大列表"""
for data in self.big_font_list:
"""当加密字体位置的标签属性与解密字体的属性一致时,开始解密"""
if class_name in data:
"""遍历xapth提取出来的特殊字体列表"""
for font in font_result_data:
try:
# 解密与查找,添加
result = data[class_name][font]
font_list.append(result)
except:
font_list.append(font)
"""解密后,终止循环"""
break
print(''.join(font_list))
print('LuckForYou,我的哥')
if __name__ == '__main__':
f = FontSpider()
f.parse_font_url()
这样我们就拿到串串香了,将xpath规则改下就可以提取多条数据了。
0x05总结
先这样吧,批量采取还是需要采用代理,