一、爬取过程中遇到的困难
01.css字体防爬机制
- css字体渲染文件 ,@font-face { content } content里面是一些.eot、.woff后缀的文件
因为各个浏览器对字体的不兼容,所以需要考虑全面,将各个字体文件都包含(WOFF、EOT、SVG / SVGZ、OTF / TTF) 具体查看url:https://www.cnblogs.com/cangqinglang/p/10101230.html;
// 例如
@font-face {
font-family: 'yourfontname';
src: url('../fonts/singlemalta-webfont.eot');
src: url('../fonts/singlemalta-webfont.eot?#iefix') format('embedded-opentype'),
url('../fonts/singlemalta-webfont.woff') format('woff'),
url('../fonts/singlemalta-webfont.ttf') format('truetype'),
url('../fonts/singlemalta-webfont.svg#defineName') format('svg');
font-weight: normal;
font-style: normal;
}
- 使用FontCreator 查看.woff文件 ;
3.每个字体对应的Code-point值,没有认出是十六进制格式 4.设置映射过程中,需要按woff文件下字体的glyph顺序提取字体,不知道如何便捷的且有顺序的获取到全部字体
02._token防爬机制
1.请求评论信息的url中有一个_token值,这个值会过期,导致代码一段时间后需要重新在浏览器中重新复制粘贴url(这是也是一种反爬机制:token反爬【类似于cookies】)
二、未完成的部分
1、对解密后的评论进行处理,并进行可持续化储存 2、token值的获取,实现每次请求都成功的操作(全自动化) 3、登录操作 4、基于登录对店铺的请求爬取登录后的评论(获取更多信息)
三、爬取分析及困难解决(Chrome浏览器)
01.分析页面中的用户评论信息
F12,到element栏,定位到某一条评论的标签,发现页面源码中加载出来的评论信息不能直接爬取,个别字体用<svgmtsi class="review"></svgmtsi> 标签显示出来,而标签里面存放的是乱码 进一步分析和思考: 在确认是基于字体库的css反爬机制后, 思考:为什么页面中能看到评论信息,而在页面源码看不到完整的评论信息呢? ?????? 带着思考继续分析: 评论信息是如何生成的(页面源码还是ajax): 通过F12的抓包工具(Network栏), 可以发现评论信息是通过ajax请求得到的, 且用json信息返回给客户端, 返回的数据也有svgmtsi标签,但是标签里的内容不是乱码,而是 现在可以总结出: 服务器发送了一份加密的评论信息给客户端、而且每个字都有对应的编码
回到开始的问题(思考题): 页面源码能显示出正确的评论信息, 想必客户端这边一定有解开编码的文件,从而然后页面中显示出正确的评论信息
02.验证思考
为了验证思考结论的正确性,下面对该文件进行查找、搜索并且选一个字体进行解密。
👇👇👇 这里对应的是困难1:css反爬机制 为了获取到该解密文件,上网查阅了资料,发现该网站是采用css反爬,利用字体库对应的编码替换为正确的字体 学过js的知道,渲染标签就需要定位到标签位置 所以根据js渲染标签规则: 1、通过右侧,发现有一个font-family唯一一个与字体相关的描述,猜测该标签是通过该属性和值来找到对应的woff文件,替换为正确文本的; 2、在点击评论中其他加密标签后,发现每个标签中都有这个属性和值; 3、在抓包工具中搜索 PingFangSC-Regular-review 惊喜的发现,页面请求了一个css文件; 4、打开该请求(css文件),发现里面存放着渲染字体的信息,从而确定该文件是所需要的; 5.里面分别对几种类进行了字体渲染,这里我们需要的是review对应的字体库; 6.找到review渲染,下载对应的woff文件。
👇👇👇 这里对应的是困难2、3、4、5:woff文件的打开、解读 1、在第一次进行<svgmtsi> 标签里的编码和woff文件里的Code-point(s)值进行对比后,发现对应字体中 $ 后四位与 &#x 后四位 完全相同; 2、由上述分析,需要获取到字体的id和字体,且要一一对应; 3、查阅资料后,使用fonttools模块获取字体文件id;
from fontTools.ttLib import TTFont
# 字体文件名字
font_name = 'views.woff'
# 读取woff文件
base_font = TTFont(font_name)
# 获取glyph顺序的id,返回一个列表['glyph00000', 'x', unie9e9, .... ]
woff_font_id = base_font.getGlyphOrder()
print(woff_font_id)
4、获取到id后,发现不是软件显示的$前缀,而是uni前缀,当然只要后四位没有变,都不影响我们继续处理; 5、获取id后,需要获取字体,并使其一一对应上; 6、在查了大量资料后,仍然没有找到便捷方便的方式,这里只能通过pytesseract(需要安装tesseract且安装中文包)和pill模块来进行图片的文字提取 (如果有更好的方法请大佬告知!!!)
import pytesseract
from PIL import Image
# 存放字体的列表(对应获取到id是列表)
data_list = []
# tesseract 安装路径
pytesseract.pytesseract.tesseract_cmd = r'D:\Tesseract-OCR\tesseract'
for i in range(1, 8):
print('第' + str(i) + '张')
# 图片路径
img = Image.open('font_image/page_%s.png' % i)
text = pytesseract.image_to_string(img, lang='chi_sim')
for j in range(0, len(text)):
# 对字符串进行切片并添加到列表
data_list.append(text[j])
print(data_list)
该方法获取的字体只有75%的准确率(需要后期处理) 7、最后就是处理映射关系了,因为id和字体都是按照glyph顺序获取的,所以两个列表的索引一一对应即可 代码如下:
data_dic = {}
# 去除列表前两个非Order内容,剩下的就是keys
keys = woff_font_id[2::]
# 通过zip()方法将列表中对应索引封装为元组,再由for循环分别取元组的两值,最后用字典实现一一对应
for k, v in zip(keys, data_list):
data_dic[k] = v
print(data_dic)
到这里困难1~5以及迎刃而解了
03.实现评论解码
在上面已经实现了id与字体一一对应了,接下来需要做的是:
正则
替换前三位
爬取用户评论
获取 & #xe9e9
unie9e9
判断
替换
判断
输出
unie9e9
unie9e9在data_dic字典里
unie9e9对应的字体
不在字典里
替换失败提示
代码如下:
# 获取用户评论(评论时间和详细评论)
num = 1
for user_review in reviews_json['reviewAllDOList']:
review_text = user_review['reviewDataVO']['reviewBody']
# print(review_text)
reviews_dic[num] = review_text
num += 1
# print('reviews_dic:', reviews_dic)
# 处理替代文本
for index in reviews_dic:
# 存放被替换的字体
correct_list = []
# 定义正则获取svgmtsi标签
sub_methods = re.compile('<svgmtsi class="review">(.*?);</svgmtsi>')
# print(reviews_dic[review])
reviews = reviews_dic[index]
encode_list = sub_methods.findall(reviews)
# 处理encode,取后四位
for encode in encode_list:
# print(encode[3::])
code = 'uni' + encode[3::]
if code in data_dic:
# print('encode:', encode)
correct_text = data_dic[code]
# print(correct_text)
correct_list.append(correct_text)
sub_str = '<svgmtsi class="review">' + encode + ';</svgmtsi>'
# replace()方法,不会修改原变量值
sub_reviews = reviews.replace(sub_str, correct_text)
reviews = sub_reviews
else:
print('no in the dic')
print('------------------------爬取评论开始'------------------------')
print(reviews)
print('encode_list:', encode_list)
print('correct_list', correct_list)
print(''------------------------爬取评论结束'------------------------')
print()
04.爬取结果
结果 结果对比
|