前景摘要
最近一个朋友问我,字体反爬虫的事,他发给我一个快手网站,我由于比较忙没回他,于是后来他说解决不了就要上scrapy框架,我是正义的程序员,这么小的事情就上框架,这也太残忍了,无故增加人家服务器压力多不好,人家网站维护者也是为了讨生活的程序员,咱们也是,因该相互体贴。于是我挺身而出,对他说,请给我10分钟让我破了他。
字体反爬虫
字体反爬虫给的来源呢?
答:我们了解到html是单个网站的骨架,而css是用来修饰html的,虽然页面渲染加载css样式的时候是不会改变html的内容,但是字体的加载与映射工作是由css来完成的,所以我们可能使用Splash,selenium和puppereer,都无法解决,字体反爬虫就是利用这个特点
比如下图,红色框框的地方,我们发现数字都被加密了。有点像韩文。
突破字体反爬虫思路的探索
1.探索字体被改变的原因
如下图,我们之前说过了,字体的加载与映射关系是通过css样式设置的,于是我们在css样式中看到了设置字体代码如下:
总结:在代码中我们可以看到eto,woff等格式的字体文件,于是我们可以判断,网页中的数字就是被这些文件所改变的,并且这么多文件,只有一套字体,可能是为了兼容各种系统把。
2.探索字体文件的映射规律
于是我们下载后,使用百度编辑器FontEditor来进行字体实时预览的功能,请点击我,界面如下图所示
先将这个https://static.yximgs.com/udata/pkg/kuaishou-front-end-live/fontscn_h57yip2q.eot文件下载下来,并使用百度编辑器FontEditor进行打开,打开后字体如下:
我们可以发现,这里有14个字体块,并且包含所有的数字并且每个数字下面都包括了一个美刀符号跟四个字母,于是我在快手页面随便找了三个特殊字符。进行使用unicode编码。
代码示例:
special_character = '뷍껝뾭'
new_special_character = (list(map(lambda x: x.encode('unicode_escape'), special_character)))
print(new_special_character)
'''
运行结果:
[b'\\ubdcd', b'\\uaedd', b'\\ubfad']
'''
我们将结果与上图的数字图片下的一个美刀符号跟四个字母组成的字符串进行对比
经过对比我们发现
b'\\ubdcd' $BDCD 1
b'\\uaedd' $AEDD 8
b'\\ubfad' $BFAD 9
根据与原来的网页数据进行对比我们发现189就是正确的,那么说明这个映射关系显而易见了。
总结:于是我们判定这个编码映射关系需要我们事先根据文件样式规则来制定,就是图片上面的字符串去掉$再在前面加上\u,然后全变小写就行。有五个文件,于是我们需要弄5次来应对突发情况。
最终突破快手反爬虫代码
代码示例:
import requests
def deal_with_character(special_character):
# #将映射关系写入字典
fontscn_h57yip2q = {
r'\\uabcf': '4',
r'\\uaced': '3',
r'\\uaedd': '8',
r'\\uaede': '0',
r'\\uafcd': '6',
r'\\ubdaa': '5',
r'\\ubdcd': '1',
r'\\ubfad': '9',
r'\\uccda': '2',
r'\\ucfbe': '7',
}
#进行unicode编码
new_special_character = (list(map(lambda x: x.encode('unicode_escape'), special_character)))
#定义处理过后的字符列表
character=[]
#进行匹配
for i in new_special_character:
try:
character.append(fontscn_h57yip2q[str(i)[2:-1:]])
except:
character.append(str(i)[2:-1:])
continue
return "".join(character)
def main():
header={
"Cookie": "clientid=3; did=web_4ebab7dfa63d79cba7ebe0fdf6330b28; client_key=65890b29; kuaishou.live.bfb1s=9b8f70844293bed778aade6e0a8f9942; userId=1218439726; userId=1218439726; kuaishou.live.web_st=ChRrdWFpc2hvdS5saXZlLndlYi5zdBKgAQxtC5tRiuxaK7exGY2Es00UuzlMfobuAuxGfkK6x-tk7pWojDEw5aQtUDZPvv4hYhCGE1orwpglwzxRhPRuYMwdN-RVzF9hvPItDsc0oGl9IhKnm6Q6XXf9BAWyskjSdlDgr1uW0NSTPaMcyvEQJTWIPluziQ5AX7q4oJEzkn2rVts8QOORyllrNbydXRyLiQu-H6iQ2uji-R4-NmAmJA8aEoJNhwQ4OUDtgURWN6k9Xgm8PSIgD-8O23y2QtSaLlc_WCgf4Ev2ugqnxbObwRXH46NWAWsoBTAB; kuaishou.live.web_ph=5c4e3fa8b45ada2084298dec8946ba78971e",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.87 Safari/537.36",
}
data={"operationName":"sensitiveUserInfoQuery","variables":{"principalId":"wudadala"},"query":"query sensitiveUserInfoQuery($principalId: String) {\n sensitiveUserInfo(principalId: $principalId) {\n kwaiId\n userId\n constellation\n cityName\n countsInfo {\n fan\n follow\n photo\n liked\n open\n playback\n private\n __typename\n }\n __typename\n }\n}\n"}
#进行post请求
response_json=requests.post('https://live.kuaishou.com/graphql',headers=header,json=data).json()
response_json1=response_json["data"]["sensitiveUserInfo"]["countsInfo"]
#进行取出数据,并进行匹配
for i,j in response_json1.items():
print("{}:{}".format(i,deal_with_character(j)))
if __name__ == '__main__':
main()
'''
运行结果:
fan:31.6w
follow:189
photo:538
liked:0
open:0
playback:0
private:0
__typename:CountsInfo
'''
总结:经过代码的匹配我们就完成任务了