快手H5逆向,字体反爬,互动量获取
目标
获取快手视频点赞数、评论数、 播放数
分析视频接口
https://m.gifshow.com/fw/photo/5232901352181146092
请求头:#
sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="98", "Google Chrome";v="98"
sec-ch-ua-mobile: ?1
sec-ch-ua-platform: "Android"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Mobile Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: didv=1644894188000; did=web_160a5eac2ed14dbda625edcf277953d1; sid=9995d540410efc97b28229c2; Hm_lvt_86a27b7db2c5c0ae37fee4a8a35033ee=1644894193; Hm_lpvt_86a27b7db2c5c0ae37fee4a8a35033ee=1645090238
响应(关键信息window.pageData=):略#
互动量相关JSON.obfuseData:#
{
"id": 263,
"fontCdnUrl": "https://tx2.a.kwimgs.com/kos/nlav10312/mdata/pkg/kwai-font/fontscn_263f9585.ttf",
"fontCdnStyle": "<style>\n @font-face {\n font-family: kwaiFont;\n src: url(https://tx2.a.kwimgs.com/kos/nlav10312/mdata/pkg/kwai-font/fontscn_263f9585.ttf) format(\"truetype\");\n }\n .font {\n font-family: \"kwaiFont\";\n font-style: normal;\n font-weight: normal;\n font-variant: normal;\n text-transform: none;\n line-height: 1;\n -webkit-font-smoothing: antialiased;\n }\n </style>",
"commentCount": "<SPAN STYLE='FONT-FAMILY: kwaiFont;'></SPAN>",
"likeCount": "<SPAN STYLE='FONT-FAMILY: kwaiFont;'></SPAN>",
"viewCount": "<SPAN STYLE='FONT-FAMILY: kwaiFont;'></SPAN>"
}
页面中显示:#
<p class="video-like"> <span style="FONT-FAMILY: kwaiFont;"></span> </p>
可以明显看出是有字体反爬处理,需要下载字体文件进行解密
什么是字体反爬?
使用自定义的ttf文件来渲染网页中的文字,而网页中的文字不再是文字,而是相应的字体编码,通过复制或者简单的采集是无法采集到编码后的文字内容
字体反爬实现原理#
厂商会随机生成成千上万套字库,并且保存好编码、字库文件、字的映射关系。文章显示的时候会从库中随机查询一套字库,并把文章中的替换成unicode编码,以达到字体加密的效果。
字体反爬的前世今生
初级难度:一套字体做加密#
做好映射解密即可
中级难度:生成多套字体和编码#
比如:猫眼电影,58同城等等,汽车之家
虽然生成了多套,但是每个字对应的字体信息是不变
比如一个字的笔画数,和x,y 变化都很小,可以利用这个特征解析字体的xml文件,做特征映射
高级难度:字体变形,字体信息随机化#
比如:快手
静态字体解密
下载字体用FontCeator打开#
将字体文件转为xml#
先用FontCreator将字体转为woff文件,再用python转为xml
<GlyphID id="0" name=".notdef"/>
<GlyphID id="1" name="uniEAC6"/>
<GlyphID id="2" name="uniEC96"/>
<GlyphID id="3" name="uniF38C"/>
<GlyphID id="4" name="uniF088"/>
<GlyphID id="5" name="uniEC7E"/>
<GlyphID id="6" name="uniF5D6"/>
<GlyphID id="7" name="uniF20E"/>
<GlyphID id="8" name="uniF7A6"/>
<GlyphID id="9" name="uniF5B9"/>
<GlyphID id="10" name="uniE9D5"/>
<GlyphID id="11" name="uniE43E"/>
<GlyphID id="12" name="uniE729"/>
<GlyphID id="13" name="uniE47F"/>
<GlyphID id="14" name="uniE7CA"/>
<GlyphID id="15" name="uniF6C7"/>
注意:这个xml表示的是id和值的映射关系。比如
uniE43E字体对应真实的值:4 , 对应的glyph11
查看字体解密规则#
"commentCount": "<SPAN STYLE='FONT-FAMILY: kwaiFont;'></SPAN>",
规则:&#+16进制加上;号
解密#
逆推字体加密
将字体的key 转为16进制,&#+16进制与加密的对比,得到对应的值
[ #编写对应的列表
'w', '5', 'm', 'k', '6', '.', '1', '3', '+', '0', '4', '8', '9',
'7', '2'
]
fontCdnUrl = "https://tx2.a.kwimgs.com/kos/nlav10312/mdata/pkg/kwai-font/fontscn_263f9585.ttf"
key_map = {}
font_content = requests.get(fontCdnUrl).content
font = TTFont(BytesIO(font_content))
code = font.getGlyphOrder()[1:]
nums = [
'w', '5', 'm', 'k', '6', '.', '1', '3', '+', '0', '4', '8', '9',
'7', '2'
]
temp = dict(zip(code, nums))
res = font.getBestCmap()
for k, v in res.items(): #解析字体
print("字体的原始:" +str(k))
kk = str(hex(k))
print("字体的转换成16进制:" + str(kk))
kk = kk[1:]
print("去除前缀:" + str(kk))
kk ='&#' + kk
print("拼接&#得到最后加密的:" + str(kk))
print(str(v)+"字体对应真实的值:" + str(temp[v]))
key_map[kk] = temp[v]
re_html_code = re.compile(r'&#x[\da-f]{4}')
text = "" #7751confusionLikeCount=
words = re_html_code.findall(text)
result = ''.join(key_map[i] for i in words)
print(result)
得到7762, 静态字体破解搞定~
动态字体解密
可以发现字体的形状不同,无法通过笔画来映射
<pt x="-10158" y="550" on="1"/>
<pt x="-10154" y="545" on="0"/>
<pt x="-10161" y="534" on="1"/>
<pt x="-10160" y="350" on="0"/>
<pt x="-10490" y="54" on="1"/>
<pt x="-10492" y="27" on="1"/>
<pt x="-10492" y="5" on="1"/>
<pt x="-10285" y="0" on="1"/>
<pt x="-10156" y="4" on="1"/>
<pt x="-10031" y="-5" on="1"/>
<pt x="-10035" y="16" on="1"/>
<pt x="-10029" y="58" on="1"/>
<pt x="-10034" y="87" on="1"/>
<pt x="-10232" y="75" on="1"/>
<pt x="-10236" y="87" on="1"/>
<pt x="-10256" y="83" on="0"/>
<pt x="-10300" y="83" on="1"/>
<pt x="-10323" y="77" on="0"/>
<pt x="-10356" y="78" on="1"/>
<pt x="-10116" y="306" on="0"/>
<pt x="-10077" y="472" on="1"/>
<pt x="-10071" y="499" on="0"/>
<pt x="-10066" y="529" on="1"/>
<pt x="-10070" y="610" on="0"/>
<pt x="-10110" y="665" on="1"/>
<pt x="-10119" y="688" on="0"/>
<pt x="-10124" y="692" on="1"/>
<pt x="-10156" y="716" on="0"/>
<pt x="-10201" y="737" on="1"/>
<pt x="-10237" y="742" on="0"/>
<pt x="-10282" y="743" on="1"/>
<pt x="-10394" y="740" on="0"/>
<pt x="-10497" y="632" on="1"/>
<pt x="-10442" y="594" on="1"/>
<pt x="-10441" y="592" on="1"/>
<pt x="-10372" y="671" on="0"/>
<pt x="-10293" y="672" on="1"/>
<pt x="-10226" y="677" on="0"/>
<pt x="-10191" y="630" on="1"/>
<pt x="-10163" y="603" on="0"/>
<pt x="-9192" y="740" on="0"/>
<pt x="-9129" y="740" on="1"/>
<pt x="-9114" y="755" on="0"/>
<pt x="-9099" y="746" on="1"/>
<pt x="-9037" y="747" on="0"/>
<pt x="-8991" y="706" on="1"/>
<pt x="-8986" y="698" on="0"/>
<pt x="-8973" y="696" on="1"/>
<pt x="-8973" y="695" on="0"/>
<pt x="-8975" y="695" on="1"/>
<pt x="-8917" y="624" on="0"/>
<pt x="-8920" y="525" on="1"/>
<pt x="-8920" y="337" on="0"/>
<pt x="-9203" y="69" on="1"/>
<pt x="-9180" y="74" on="0"/>
<pt x="-9159" y="75" on="1"/>
<pt x="-9102" y="79" on="0"/>
<pt x="-9086" y="83" on="1"/>
<pt x="-9016" y="81" on="1"/>
<pt x="-8878" y="77" on="1"/>
<pt x="-8881" y="66" on="1"/>
<pt x="-8882" y="13" on="1"/>
<pt x="-8881" y="-3" on="1"/>
<pt x="-8917" y="-1" on="1"/>
<pt x="-9346" y="2" on="1"/>
<pt x="-9340" y="50" on="1"/>
<pt x="-9345" y="58" on="1"/>
<pt x="-9008" y="348" on="0"/>
<pt x="-9006" y="534" on="1"/>
<pt x="-9003" y="552" on="0"/>
<pt x="-9008" y="574" on="1"/>
<pt x="-9016" y="607" on="0"/>
<pt x="-9036" y="635" on="1"/>
<pt x="-9049" y="639" on="0"/>
<pt x="-9063" y="648" on="1"/>
<pt x="-9072" y="660" on="0"/>
<pt x="-9080" y="658" on="1"/>
<pt x="-9077" y="662" on="0"/>
<pt x="-9083" y="669" on="1"/>
<pt x="-9107" y="671" on="0"/>
<pt x="-9144" y="676" on="1"/>
<pt x="-9163" y="678" on="0"/>
<pt x="-9187" y="672" on="1"/>
<pt x="-9206" y="651" on="0"/>
<pt x="-9229" y="648" on="1"/>
<pt x="-9263" y="626" on="0"/>
<pt x="-9293" y="586" on="1"/>
<pt x="-9314" y="600" on="1"/>
<pt x="-9326" y="628" on="1"/>
<pt x="-9349" y="639" on="1"/>
<pt x="-9304" y="686" on="0"/>
<pt x="-9259" y="716" on="1"/>
<pt x="-9251" y="717" on="0"/>
<pt x="-9242" y="719" on="1"/>
经过下载多个字体文件查看,发现如下:
- 每次请求都是不同的字体,字体库不是简单的几套
- 笔画数不同
- x,y坐标不同,并且移动很大
所以无法通过解析字体的xml利用有规律的特征来做映射
思路一:#
将获取到的加密字段自己组装成html显示后,截图,ocr识别出来
<style>
@font-face {
font-family: kwaiFont;
src: url(https://tx2.a.kwimgs.com/kos/nlav10312/mdata/pkg/kwai-font/fontscn_28789ff8.ttf) format("truetype");
}
.font {
font-family: "kwaiFont";
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
-webkit-font-smoothing: antialiased;
}
</style>
<div>
<SPAN STYLE='FONT-FAMILY: kwaiFont;'></SPAN>
</div>
保存为html#
略
截图#
略
OCR 识别#
略
思路二#
将字体文件字体分割为png,ocr识别成出来。放入集合按顺序排列
下载安装tesseract-ocr#
下载语言包#
https://tesseract-ocr.github.io/tessdoc/Data-Files
放入Tesseract-OCR\tessdata
配置环境变量#
将字体切割成图片代码#
def uni_2_png_stream(txt, font, img_size=512):
"""将字形转化为图片流
Args:
txt ([type]): [description]
font ([type]): [description]
img_size (int, optional): [description]. Defaults to 512.
Returns:
[type]: [description]
"""
img = Image.new('1', (img_size, img_size), 255)
draw = ImageDraw.Draw(img)
font = ImageFont.truetype(font, int(img_size * 0.7))
txt = chr(txt)
x, y = draw.textsize(txt, font=font)
draw.text(((img_size - x) // 2, (img_size - y) // 2), txt, font=font, fill=0)
# draw.text((0,0), txt, font=font, fill=0)
return img
对比ocr效果#
uniE43E字体对应真实的值:4
uniE47F字体对应真实的值:9
uniE729字体对应真实的值:8
uniE7CA字体对应真实的值:7
uniE9D5字体对应真实的值:0
uniEAC6字体对应真实的值:w
uniEC7E字体对应真实的值:6
uniEC96字体对应真实的值:D
uniF088字体对应真实的值:k
uniF20E字体对应真实的值:1
uniF38C字体对应真实的值:m
uniF5B9字体对应真实的值:+
uniF5D6字体对应真实的值:.
uniF6C7字体对应真实的值:2
uniF7A6字体对应真实的值:3
可以看出来识别率90%,数字5识别成D了。
优化数字识别率 和ocr执行时间#
pytesseract.pytesseract.tesseract_cmd = filename
# text = pytesseract.image_to_string(image, lang='chi_sim', config='--psm 10')
# 注意 这里替换语言库,提升纯数字识别准确率 和 执行时间 11秒 提升到6秒
text = pytesseract.image_to_string(image, config='--psm 10')
测试了几个font文件,数字识别率100%
继续优化
tesseract:#
nums:['Ww', '+', '4', '9', '2', '6', 'mM', '8', '', '7', '3', 'k', '5', '0', '1']
INFO: 127.0.0.1:2629 - "GET /api/ks_interactive_data/3xdyxk622whqx2q HTTP/1.1" 200 OK
2022-03-01 12:28:14.721 | WARNING | utils:ocr_processor:146 - 识别耗时:2227.240800857544
2022-03-01 12:28:14.722 | WARNING | main:request:123 - result:0_2_65
2022-03-01 12:28:14.723 | WARNING | main:request:124 - 耗时:3415.691375732422
更换ocr识别后:#
2022-03-01 12:31:36.279 | WARNING | utils:ocr_processor:146 - 识别耗时:131.30450248718262
nums:['', '7', 'w', '2', '0', '9', '6', '3', '十', '4', '8', '5', 'k', '1', 'm']
INFO: 127.0.0.1:2923 - "GET /api/ks_interactive_data/3xdyxk622whqx2q HTTP/1.1" 200 OK
2022-03-01 12:31:36.290 | WARNING | main:request:123 - result:0_2_65
2022-03-01 12:31:36.290 | WARNING | main:request:124 - 耗时:1669.9013710021973
DID 风控解决
待下篇分解
大规模测试:#
10个线程,单IP,5个小时一共跑了 62264次,失败次数3266,成功率94%
平均一个小时12452次。
单个请求2秒以内完成。1秒4和1秒8居多,通过redis预存did的话,可以进一步提升耗时到1秒以内。
警告:仅供学习和参考,不可用于非法用途!
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· 【.NET】调用本地 Deepseek 模型
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库