python爬⾍实践-02-⼤众点评评论爬取-破解CSS字体加密
warning:⼤众点评反爬策略更新的很快,即使机制没变,也会在其它地⽅设置障碍,让你之前的爬⾍完全没法⽤。⽐如我在写这篇博客的前⼏天写好的爬⾍,解析出了映射表,结果写博客的时候就⽤不了。⽽且它不仅更换了相应的⽂件,还把⽂件的格式给改了,这样对⽂件的解析也得重新来,⽐如这次,它就把svg的⽂件格式给改了,解析的难度⼤了很多,我对这⽅⾯不太了解,只能猜+验证。
关键:破解字体加密CSS+SVG
⽹页:
⽹页源代码:
⼆者对⽐可知:
1. 完整的评论由HTML中的两部分组成
2. 评论中⼤量的字被加密了,⽐如“吃”就被加密为<svgmtsi class'qxszj'></svgmtsi>
要解决加密问题,我们再打开开发者⼯具进⾏查询,选中加密字1,查看style。
selenium获取cookie从上图可以知道,<svgmtsi class="qxszj"></svgmtsi>被background-image给替换了,并且是将back-ground-image上坐标为-42.0px,-1420.0px处的元素替换的。2号该URL对应着⼀个分布着⼀⾏⼀⾏汉字的SVG⽂件。⽽右上⾓的3号CSS⽂件则是⽤于根据 class的
值qxszj,来从background-image中根据坐标来选取某个汉字的,即其功能是映射。
由于其映射规则保存在CSS⽂件中,**因此我们需要将该CSS⽂件和SVG⽂件都下载下来进⾏分析。**根据CSS定位背景图⽚的规则:
x y⽤百分⽐或者px表⽰的时候,其值可以为负数。我们应⽤坐标规则就很容易理解负数表⽰的意义,x为负数时候表⽰图⽚左顶点在容器左顶点的左侧,y为负数时表⽰图⽚的左顶点在容器的左定点的上⽅
我们⾸先针对⼀个字进⾏分析,即<svgmtsi class="qxszj"></svgmtsi>映射为“吃”。
读取SVG⽂件【需要注意的是SVG⽂件不仅内容会变,格式也会变,写博客的时候,原来的SVG格式就变了】
其中“吃”字所⾏为:<textPath xlink:href="#36" textLength="322">纸盒秘吃净培杀侄掠留抚源甩宣脑邪蓬⾮勺吸洞极呼</textPath>即处于第36⾏,第4个字。
读取CSS⽂件,到字段.qxszj{background:-42.0px -1420.0px;}即“吃”字加密后的<svgmtsi class="qxszj"></svgmtsi>被SVG中坐标-42.0px -1420.0px的⽂字替代。为了到坐标和汉字所处⾏列的规则,我们查看SVG的头部信息:
该字段指明:font-size:14px,⽽“吃”字:x=-42.0px,第4个字,下标为3,显然:下标为abs(x)/14
⽽y坐标与⾏的关系,需要知道⾏⾼,但SVG头部并没有直接给出,猜测path id='1' d='M0 33 H600'表⽰的是id为1的⾏,其⾏所在的y坐标为33,那么的话,“吃”所在的href=36,⽽此处<path id="35" d="M0 1399 H600"/><path id="36" d="M0 1443 H600"/>'可以得出规律,要到第⼀个d⼤于y的path id,从⽽定位到⾏。
由于缺乏相关知识,上⾯只是猜测得到的规律,不⼀定正确,以后有需要再进⾏研究。
基于上述规律,可以根据CSS⽂件和SVG⽂件构建映射表:
import requests
from bs4 import BeautifulSoup
import re
with open('dianping.svg',mode='r',encoding='utf-8')as f:
svg_text = f.read()
# 提取⾏号与其所处y坐标的关系
svg_col_height = re.findall(r'<path id="(\d+)" d="M0 (\d+) H600"/>',svg_html)
svg_col_height =[(int(id),int(height))for id, height in svg_map]
print(svg_col_height[0])
# 提取SVG图⽚中的⽂字矩阵
svg_words =  re.findall(r'<textPath xlink:href="#\d+" textLength="\d+">(.+)</textPath>',svg_html)
print(svg_words[0])
with open('dianping.css',mode='r',encoding='utf-8')as f:
css_text = f.read()
#提取class_name与x,y坐标的对应关系
class_map = re.findall(r'\.(\w+)\{background:-(\d+)\.0px -(\d+)\.0px;\}',css_text)
class_map =[(cls_name,int(x),int(y))for cls_name,x,y in class_map]
print(class_map[0])
# 构建class_name到⽂字的映射
#不知道是我解析地有问题,还是⼤众点评好像很鸡贼地添加了⼀些⽆法解析的⼲扰项,CSS⽂件内的有些class_name是没有映射关系的。
parse_map={}
for class_name, x, y in class_map:
col =int(x/14)
row =0
for id, height in svg_col_height:
if height > y:
row =id
break
try:
parse_map[class_name]=svg_words[row-1][col]
#print((class_name,col,row),svg_words[row-1][col])
except Exception as e:
print('该字解析失败:',(class_name,col,row),svg_words[row-1])
print(parse_map['qxszj'])
# 打印为’吃‘,初步验证成功
需要注意的是:⼤众点评的字体映射关系是会变化的,因此构造好的映射表可能很快就失效了。5天前获取的CSS+SVG⽂件构造出来的映射表,已经不适⽤于解析当前评论信息了。eg.
⽤5天前构造的映射表解析出来的结果:
终下3钻餐,菜单上要尝试,奈帝每菜⼝,别欢再来⼝,我个⼆线城市阿,点个⾊菜,以燎胃瘠……必须原~没喝,来⾃⾷本⾝甜,总觉得盅……再来⼴州,她们⽐受宠哎,种烹制⽅法使得她们得百魅骄,要去宠幸……,没腥,股殊,在⽤东⽅精马杀龄…\n,⾊,还⾃⼰来品吧……
显然,⽜头不对马嘴。更隔应⼈的是,SVG⽂件⽂字排版还改了,导致得重新解析SVG⽂件。
模拟登录
⼤众点评在没有登录之前,是⽆法查看所有评价的,并且我发现,⼤众点评在登录与未登录的时候,虽然都是采取了字体加密的反爬⼿段,但是⽅式却不⼀样,未登录前好像是⽐较⽼版本的,采⽤的是CSS+woff⽂件进⾏字体映射,⽽登录后则是采⽤CSS+SVG⽂件进⾏字体映射。
⽐较奇怪的是,我是5天前获取的Cookie,结果现在还能拿来⽤,有点⼉迷惑,我在浏览器上访问⼤众点评是需要重新登录的。
模拟登录的⽅法有很多种,可以通过Selenium进⾏模拟登录,然后获得Cookie;或者通过requests库获取Cookie;或者在浏览器中进⾏登录,然后将获取的请求头部复制下来即可,其中包括Cookie
由于本次实践的重点是破解字体解密,因此我采⽤了⽐较简单的第三种,⽽且由于其Cookie失效的时间蛮长的,因此“性价⽐”⾼。获取的头部信息如下:
raw_headers ="""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
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7
Cache-Control: max-age=0
Connection: keep-alive
Cookie: s_ViewType=10; _lxsdk_cuid=17983edd8ed75-0a10f6120f599a-2363163-1fa400-17983edd8eec8; _lxsdk=17983edd8ed75-0a10f6120f599a-2363 163-1fa400-17983edd8eec8; _hc.v=b58e1d27-879d-6fb3-bb85-7c3d7975becc.1621416074; cy=4; cye=guangzhou; ua=%E5%8C%97%E6%9C%88%E5% 8D%97%E5%AE%89; ctu=e0d1fb9a8019d4f1b7f1e6fa069fc6735ca72ea96fa7930227688c08b5e7e438; fspop=test; _lx_utm=utm_source%3DBaidu%26ut m_medium%3Dorganic; thirdtoken=3a936f56-3646-4fa9-a718-94cb525cdebc; Hm_lvt_602b80cf8079ae6591966cc70a3940e7=1622095754,1622976283, 1623030832,1623033389; dper=6ea96f6ba58ffa2b1efb2841699855d852c5e48a0e9fec61e40f927cd3c9ef7ae45005cf7f5a42e43512a466fac84d0183bdf6 dd74d2eacb7fe2fada0aaec8333eea2ada6291028577b09cf8a9a81cce8750350bc5b99c979a5d5f4feac59e95; ll=7fd06e815b796be3df069dec7836c3df; ua mo=130********; dplet=778acd373e790e1ff7703c193ad541b5; Hm_lpvt_602b80cf8079ae6591966cc70a3940e7=1623038459; _lxsdk_s=179e48a3339-2a0-24-dc4%7C%7C226
Host: www.dianping
Referer: www.dianping/shop/jYLWhDRaJsHy5PAB
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36"""
# 将复制下来的头部转为字典形式
headers =dict([line.split(": ",1)for line in raw_headers.split("\n")])
获取评论数据并解析
response  = (url ="www.dianping/shop/jh631305VtnDuXpl/review_all",headers=headers)
webText =
for key,value in parse_map.items():
webText = place('<svgmtsi class="'+key+'"></svgmtsi>',value)
soup = BeautifulSoup(webText,'lxml')
review = soup.find_all(attrs={'class':repile('review-words.*')})
print(review[0])
其结果为:
与浏览器页⾯对⽐:
显然,字体加密已经解析成功了,剩下的就是需要进⼀步的信息整理了。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。