赞
踩
【注】1、2022年4月10日抓取的大众点评的评论数据
2、该网站采用了字体反爬,是抓取过程中的主要难点
3、网站反爬更新的速度很快,基本上几个礼拜,一个月就会更新一次,所以本文的代码直接copy的话,可能会执行不了。但是大概思路是没问题的
经过分析,发现网站的评论 被加密了。而且他不是全部加密他是有几个字进行了加密,有几个字又没有进行加密,这无疑是增加了我们爬取的工作量,但是,第一步应该还是先进行解密,即找到对应的css字体加密文件(.woff文件)
这里打开方式有两种:一种下载 FontCreator
软件。第二种使用在线字体编辑器
。这里两种方式都提供给大家,供大家参考使用.
FontCreator
链接:点击下载
提取码:dzd4
在线字体编辑器
链接:点击使用
处理woff文件,使用 fontTools
的模块去进行解析,但是需要注意几个点:
1、 woff_str_601 : 里面的文字来源于下载的woff文件,这里需要手动输入
不过这里面的字体好像一直都没有变化过,理论上直接copy即可。但是时间久的话,楼主也不太确定了。
2、注意两个方法的区别: getBestCmap()
与 getGlyphOrder()
getBestCmap()
# 获取code和name的关系
getGlyphOrder()
# 形状和字体的关系
【注】 为什么要提一个这个,因为之前用的值 getBestCmap() 转成的 十与十六进制的形式,方便与加密的字符进行匹配,最后发现其实不对,匹对的数据有误!!最后改用 getGlyphOrder() 用最原始的unicode 代码来进行匹配,可以见 下面的 步骤 第六步
最后说明一下,这里需要自己尝试,一开始谁也不能确定一定就行,所以还是需要多实践,尝试,从错误中寻找答案。
代码如下:
from fontTools.ttLib import TTFont
def woff_dict(key):
woff = TTFont(r'./字体文件/2850eeb5.woff') # 读取woff文件
# woff文件中ID编号为2~602的601个字符
woff_str_601 = '1234567890店中美家馆小车大市公酒行国品发电金心业商司超生装园场食有新限天面工服海华水房饰城乐汽香部利子老艺花专东肉菜学福饭人百餐茶务通味所山区门药银农龙停尚安广鑫一容动南具源兴鲜记时机烤文康信果阳理锅宝达地儿衣特产西批坊州牛佳化五米修爱北养卖建材三会鸡室红站德王光名丽油院堂烧江社合星货型村自科快便日民营和活童明器烟育宾精屋经居庄石顺林尔县手厅销用好客火雅盛体旅之鞋辣作粉包楼校鱼平彩上吧保永万物教吃设医正造丰健点汤网庆技斯洗料配汇木缘加麻联卫川泰色世方寓风幼羊烫来高厂兰阿贝皮全女拉成云维贸道术运都口博河瑞宏京际路祥青镇厨培力惠连马鸿钢训影甲助窗布富牌头四多妆吉苑沙恒隆春干饼氏里二管诚制售嘉长轩杂副清计黄讯太鸭号街交与叉附近层旁对巷栋环省桥湖段乡厦府铺内侧元购前幢滨处向座下臬凤港开关景泉塘放昌线湾政步宁解白田町溪十八古双胜本单同九迎第台玉锦底后七斜期武岭松角纪朝峰六振珠局岗洲横边济井办汉代临弄团外塔杨铁浦字年岛陵原梅进荣友虹央桂沿事津凯莲丁秀柳集紫旗张谷的是不了很还个也这我就在以可到错没去过感次要比觉看得说常真们但最喜哈么别位能较境非为欢然他挺着价那意种想出员两推做排实分间甜度起满给热完格荐喝等其再几只现朋候样直而买于般豆量选奶打每评少算又因情找些份置适什蛋师气你姐棒试总定啊足级整带虾如态且尝主话强当更板知己无酸让入啦式笑赞片酱差像提队走嫩才刚午接重串回晚微周值费性桌拍跟块调糕'
# ['cmap']为字符与Unicode编码的映射关系列表
woff_unicode = woff['cmap'].tables[0].ttFont.getGlyphOrder() # 获取603个字符对应的unicode编码
woff_character = ['.notdef', 'x'] + list(woff_str_601) # 添加编号为0、1的两个特殊字符
woff_dict = dict(zip(woff_unicode, woff_character))
#print(woff_dict)
return woff_dict[key]
最终字典数据如下:
经分析发现,该网站的评论数据并不是静态的,而是json数据,所以我们需要改变抓取策略。去响应一下json数据。
爬取之后的数据显示:
copy了一下,方便大家观察。
经分析发现,我们需要的是 
这种加密的字符,然后对其进行解析。所以我这里 直接使用replace 进行替换,(1、这里刚开始想的使用正则表达式,然后发现不太方便 如: 大董(环贸店):淮海中路999号环贸isomer商<svgmtsi class="review"></svgmtsi>6楼
像这种数据就不好处理,我们需要的是前面的 汉字
+ 后面的 
,实践得出失败。2、后面想到用 lxml 序列化 如 :
from lxml import etree
html=etree.HTML(all_comment[0]) # all_comment为抓取的评论数据
content=html.xpath('//text()')
print(content)
经测试发现结果较为理想,如下图所示:
但是,当时提取字符的时候,会发现乱码[就不截图了,感兴趣的小伙伴可以自己尝试一下,然后告诉一下楼主有没有解决方案],so,第二次尝试失败
)
正确处理:
用replace 替换之后,在用split分割
到了这里,已经快接近胜利了,回顾一下,我们已经得到了 woff的字典数据
、网页进行加密的字符数据
,那么如何把他们进行对应呢。
1、首先回顾一下: 第三步
2、找规律,为了看起来方便,我都写一起,省的看起来头疼。如下图:
到此,发现我们的爬取方式正确。接下来只要按照这种规律进行匹配即可
接下来就是大家心心念念的完整代码
把读取woff的模块封装成单独的 字典 模块
# 封装一个读取css字体的模块
from fontTools.ttLib import TTFont
def woff_dict(key):
woff = TTFont(r'./字体文件/2850eeb5.woff') # 读取woff文件
# woff文件中ID编号为2~602的601个字符
woff_str_601 = '1234567890店中美家馆小车大市公酒行国品发电金心业商司超生装园场食有新限天面工服海华水房饰城乐汽香部利子老艺花专东肉菜学福饭人百餐茶务通味所山区门药银农龙停尚安广鑫一容动南具源兴鲜记时机烤文康信果阳理锅宝达地儿衣特产西批坊州牛佳化五米修爱北养卖建材三会鸡室红站德王光名丽油院堂烧江社合星货型村自科快便日民营和活童明器烟育宾精屋经居庄石顺林尔县手厅销用好客火雅盛体旅之鞋辣作粉包楼校鱼平彩上吧保永万物教吃设医正造丰健点汤网庆技斯洗料配汇木缘加麻联卫川泰色世方寓风幼羊烫来高厂兰阿贝皮全女拉成云维贸道术运都口博河瑞宏京际路祥青镇厨培力惠连马鸿钢训影甲助窗布富牌头四多妆吉苑沙恒隆春干饼氏里二管诚制售嘉长轩杂副清计黄讯太鸭号街交与叉附近层旁对巷栋环省桥湖段乡厦府铺内侧元购前幢滨处向座下臬凤港开关景泉塘放昌线湾政步宁解白田町溪十八古双胜本单同九迎第台玉锦底后七斜期武岭松角纪朝峰六振珠局岗洲横边济井办汉代临弄团外塔杨铁浦字年岛陵原梅进荣友虹央桂沿事津凯莲丁秀柳集紫旗张谷的是不了很还个也这我就在以可到错没去过感次要比觉看得说常真们但最喜哈么别位能较境非为欢然他挺着价那意种想出员两推做排实分间甜度起满给热完格荐喝等其再几只现朋候样直而买于般豆量选奶打每评少算又因情找些份置适什蛋师气你姐棒试总定啊足级整带虾如态且尝主话强当更板知己无酸让入啦式笑赞片酱差像提队走嫩才刚午接重串回晚微周值费性桌拍跟块调糕'
# ['cmap']为字符与Unicode编码的映射关系列表
woff_unicode = woff['cmap'].tables[0].ttFont.getGlyphOrder() # 获取603个字符对应的unicode编码
woff_character = ['.notdef', 'x'] + list(woff_str_601) # 添加编号为0、1的两个特殊字符
woff_dict = dict(zip(woff_unicode, woff_character))
#print(woff_dict)
return woff_dict[key]
主代码模块
import requests import json import pandas as pd import font 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-Language": "zh-CN,zh;q=0.9", "Cache-Control": "max-age=0", "Connection": "keep-alive", "Upgrade-Insecure-Requests": "1", 'Cookie':'fspop=test; cy=1; cye=shanghai; _lxsdk_cuid=18006e84946c8-0edfb5359bc9cf-1a343370-144000-18006e8494683; _lxsdk=18006e84946c8-0edfb5359bc9cf-1a343370-144000-18006e8494683; _hc.v=26a2d697-b910-5ca3-2811-7cb9de72b061.1649383329; s_ViewType=10; dplet=54a75cdaf24b303d3f910da18f7722de; dper=4a8592a6f6069c93adacd0d4efa1208e88ad3f679ac3884773372a379298967e9afc526b24332076b0f0409a6044bd2f677d3ff6943921a4f97c10ac407f85f59c4531048bb3ff331e87b08beac013239986b3821d73a2d7ad662074a343d534; ua=%E5%B0%8F%E7%99%BD%E8%8F%9C; ctu=43203cd8c3de60575d04dea3547e7bc86b19a461a533987b78fafea2df8f4bd5; ll=7fd06e815b796be3df069dec7836c3df; Hm_lvt_602b80cf8079ae6591966cc70a3940e7=1649383329,1649421529,1649469353; _lx_utm=utm_source%3DBaidu%26utm_medium%3Dorganic; Hm_lpvt_602b80cf8079ae6591966cc70a3940e7=1649469364; _lxsdk_s=1800c08ea8e-63d-fe7-70e%7C%7C57', "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36" } url = "http://www.dianping.com/ajax/json/shopDynamic/allReview?shopId=Emnne5KsrYwPeJ4I&cityId=1&shopType=10&tcv=0x0xrqr5le&_token=eJxVj19vgjAUxb9LnxtooRQh8cG%2FiYBzo1Y0iw8MmSIWHe1EXPbdVzL3sOQm59zfvSe59wvUsx3wMUKIYAiueQ18gA1kUACBknpCiUeoRyxquy4E2X%2FmYs3e6tUY%2BK%2FYsSl0bbTtSKzBL%2BlRsoUPa1tbaBFd3c5Mr4CDUhffNJumMXZFWl2Kam9kZ2HKw%2FliTkRV5U4o603znAdkpk8COimWXdKhCLrI7UDZAa3pQ9VfP9fP6JAs9pV2eXBbMknkx3s8l8sVb1svZMxqowxHjNvRfaKeOLsu2lFvwOqjcD6Tk6jO6ylKkiGNqVgXoxuff4xwoMYvNi9Kdc9uiyi8IpbhRbwLNuXJK9PV8MCnQ56IPC3D%2FWRgDuQRN%2F0%2B%2BP4BbKFpyA%3D%3D&uuid=26a2d697-b910-5ca3-2811-7cb9de72b061.1649383329&platform=1&partner=150&optimusCode=10&originUrl=http%3A%2F%2Fwww.dianping.com%2Fshop%2FEmnne5KsrYwPeJ4I" response = requests.get(url, headers=headers).text data_json=json.loads(response) all_user=[] all_comment=[] for item in data_json['reviewAllDOList']: user=item['user']['userNickName'] comment=item['reviewDataVO']['reviewData']['reviewBody'] all_user.append(user) all_comment.append(comment) #print(all_comment) all_comData=[] # 处理所有的评论 for strr in all_comment: # 把获取到的字符串,去掉 标签 string=strr.replace('<svgmtsi class="review">','') string2=string.replace(';</svgmtsi>','') ingergrate_str=string2.split('&') intergrate=[] # 判断有没有 加密的字符串,有就替换,没有就,正常输出 for item in ingergrate_str: try: k=item[0] # 截取第一个字符,判断是不是这种 【#xe2da聚】 的格式 if k in ['#']: # 拼凑关键字,在下载的css文件里面 查找 key='uni'+item[2:6] integrate_str=font.woff_dict(key)+item[6::] # 只替换 【#xe2da】 剩下的 【聚】 保留 》》结果展示:【的聚】 intergrate.append(integrate_str) #print(item) else: intergrate.append(item) # 把单个评论的列表数据,整合成一条 data=''.join(intergrate) except : continue # 把for 循环的数据都 放在一个列表里面 all_comData.append(data) #输出至excel中 data=pd.DataFrame({'用户名':all_user,'评论':all_comData}) data.index+=1 data.to_excel(r'E:\pythonProject\spieder项目\da_comment\shuju\评论.xlsx') print(len(all_comData)) print('完成')
【总结】
1、这里有个小插曲,加了一个try()…except()…模块,它的评论中带有一下 表情、特殊符号 然后我们在截取的时候,会报错,这时我们可以选择忽略,其实也并不影响我们评论的整体爬取。
2、这里总结了整体的一个爬取流程,爬取的时候遇到了很多的坑,在这里也希望能帮到大家。然后,我在强调一下,这个网站的更新速度很快,遇到代码运行不了的情况也不要急,先把握整体思路,然后在逐个模块突破,解决,最后拿到我们所需要的数据。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。