近日因为公司业务需要,领导要求我批量获取一款天猫商城上的商品链接下的评论。虽然我已有超过一年的爬虫实战经验,但是一想到我即将面对的是“阿里云”这一BUG级怪物,头皮不由自主地发麻了。但是秉着“万物皆可爬”的信念,我还是硬着头皮上了。
我需要获取的网页链接为天猫商城。
先来看看评论区的模样吧。
正如大家所熟知的,评论栏目十分工整,所有评论都整齐地摆放在了网页当中,而页面末尾还有翻页按钮。一切还似乎是简单的模样。
然而,爬虫界里有一句话可谓是“至理箴言”——你看得到不一定你就能爬得到。这不,当我打开浏览器监听器时就发现了天猫商品的网页采取的都是异步请求方式获得的,评论区的都内容都停放在了类似于
https://rate.tmall.com/list_detail_rate.htm?itemId=537259015354&spuId=694941313&sellerId=2386968451&order=3¤tPage=2&append=0&content=1&tagId=&posi=&picture=&groupId=&ua=098%23E1hvS9vovLIvUvCkvvvvvjiPRFzpsjnmRLFw0jrCPmPZlj3CnLMpljYWn2LwQj3bRphvCvvvvvmCvpvZz2staosNznswUGrfYgsw1aAv7IVrvpvEvCB%2FvNsGv2s22QhvCvvvMMGCvpvVvmvvvhCvKphv8vvvvvCvpvvvvvv2vhCvCjQvvvWvphvW9pvvvQCvpvs9vvv2vhCv2RmEvpvVmvvC9jamuphvmvvv9bcWRGhImphvLvCbXvvjna21Q8oaWLEc34z%2BFfmtEpcUTUoXKFwFxT7YK4vTHkGVqwzaiLu18vmYiR0n%2BbyDCw2IAXZTKFEw9Exrz8TJEcq9afknnbvtvpvhvvvvv8wCvvpvvUmm3QhvCvvhvvmrvpvpjvkJ9wjCvmvIFfwznHVt6OhCvvswMHna3nMwznQY3DItvpvhvvvvvUhCvvswNHBwEaMwzns%2FblItvpvhvvvvv86Cvvyv2h7n1GwvzWy%3D&needFold=0&_ksTS=1561438169812_1139&callback=jsonp1140
这样的链接当中。
可以注意到,链接当中包含着一个currentPage字段,只要通过更改currentPage的值即可实现翻页的效果。
上图为评论区内容的停放方式。实话说,到目前为止,我还没有体会到阿里云的强大之处到底在哪,根据个人经验,我仍然觉得只要听过递归算法实现翻页、逐个获取就可以完成老板的任务。然而,不到最后一刻真的不能轻易放松啊。
通常来讲,如果网站想要反爬,它们通常会从访问者的访问频率入手,只要识别到某个IP发出的请求在某一时间段内极度频繁网站便会认定(如果真的需要反爬的话)该访问者为不友好客户,将该IP封锁请求一段时间。
然而阿里云的高明之处在于它压根不需要设置反爬就可以屏蔽掉大部分不友好访问者了。至于原因如何,请看如下:
我在pycharm terminal当中利用scrapy shell对需要访问的网址进行测试
> scrapy shell "https://rate.tmall.com/list_detail_rate.htm?itemId=537259015354&spuId=694941313&sellerId=2386968451&order=3¤tPage=2&append=0&content=1&tagId=&posi=&picture=&groupId=&ua=098%23E1hvS9vovLIvUvCkvvvvvjiPRFzpsjnmRLFw0jrCPmPZlj3CnLMpljYWn2LwQj3bRphvCvvvvvmCvpvZz2staosNznswUGrfYgsw1aAv7IVrvpvEvCB%2FvNsGv2s22QhvCvvvMMGCvpvVvmvvvhCvKphv8vvvvvCvpvvvvvv2vhCvCjQvvvWvphvW9pvvvQCvpvs9vvv2vhCv2RmEvpvVmvvC9jamuphvmvvv9bcWRGhImphvLvCbXvvjna21Q8oaWLEc34z%2BFfmtEpcUTUoXKFwFxT7YK4vTHkGVqwzaiLu18vmYiR0n%2BbyDCw2IAXZTKFEw9Exrz8TJEcq9afknnbvtvpvhvvvvv8wCvvpvvUmm3QhvCvvhvvmrvpvpjvkJ9wjCvmvIFfwznHVt6OhCvvswMHna3nMwznQY3DItvpvhvvvvvUhCvvswNHBwEaMwzns%2FblItvpvhvvvvv86Cvvyv2h7n1GwvzWy%3D&needFold=0&_ksTS=1561438169812_1139&callback=jsonp1140"
在点击运行shell命令以后,程序实际上在不停地重定向,以至于最后请求得到的response.body为
response.body所指的链接包含了“login”一词,也就是说我们每一次访问都意味着需要登录一次个人密码,更何况,经本人验证,密码验证通过以后普通用户也无权查看网页内容。所以说,马爸爸能有今日的成就真的不是靠吹牛吹出来的。
正当我毫无头绪,准备等着去老总办公室领便当之时,我心血来潮地用火狐浏览器再次打开商品链接。正是这一次无心之举让我看到了曙光,因为不同于Google Chrome的浏览器监听,Firefox浏览器监听还给出了每个链接各自的请求cookies以及响应cookies。
而有了网页cookies的加持,代码实现自然便廓然开朗了。
scrapy的原理不多说,我的工作包括:在spider模块中定义爬取规则、在items模块定义所需信息以及在settings.py文件设置请求参数。
这一部分可以结合自身工作需要进行定义,不过建议初学者尽可能多地抓取信息,这样有利于对HTML的理解的长进以及后续爬虫能力的进步。
class TmallItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
init_comment = scrapy.Field() # 首次评论内容
init_images = scrapy.Field() # 首次评论所发布的照片
init_explanation = scrapy.Field() # 首次评论时店家的回复
append_comment = scrapy.Field() # 追评内容
append_images = scrapy.Field() # 追评所发布的图片
append_explanation = scrapy.Field() # 追评回复
category = scrapy.Field() # 商品类型
user = scrapy.Field() # 用户名
init_comment_date = scrapy.Field() # 首次评论日期
append_comment_date = scrapy.Field() # 追评日期
append_days = scrapy.Field() # 追评日期距离收货日期的天数
本次爬虫逻辑为垂直式爬取,所以首先需要定义种子链接的形式。
name = 'jisu'
url = 'https://rate.tmall.com/list_detail_rate.htm?itemId=537259015354&spuId=694941313&sellerId=2386968451&' \
'order=1¤tPage={page}&append=0&content=1&tagId=&posi=&picture=&groupId=&' \
'ua=098%23E1hv0pvxvchvUvCkvvvvvjiPRFsOljlnP2F9tjYHPmP9ljnHnLqv6j3URFLhAj1U9phvHHiaLxF3zHi4w17gtssR7TC4NrGB' \
'dphv219vhQ9wjVoKzYVtRkHL6OhCvv14cGJOEa1475PE7r%2FCvpvW7D%2FShUbw7Dis%2BtjN9phv2HiNsQ9bzHi4wTo2zsyCvvpvvvv' \
'vkphvC9QvvOC0p4yCvv9vvUmljyONNbyCvmFMMQ2GS6vvtQvvvQCvpvoKvvv2vhCv2UhvvvWvphvWgvvvvQavpvQXmphvLv3fYpvjcRCl' \
'dU9tK7ERiNLyzCyXfCuYiXVvVE6Fp%2B0x9W9OjLEc6acEKBm6NB3rQjcQ%2BulgEfk1DfesRk9cznsW1C0OwZFvgb2XrqpCvpvVvmvvv' \
'hCv2QhvCvvvMMGtvpvhvvvvv8wCvvpvvUmm3QhvCvvhvvmCvpvW7D%2FjM0Lw7Di4XLLNdphvmpvhYUWOVvCpjOhCvCB47Twpc1147DiA' \
'iKNG%2FHrz7IbNVLyCvvpvvvvvdphvmpvZL9nEop2nULyCvvpvvvvv&needFold=0' \
'&_ksTS=1560657691879_1614&callback=jsonp1615'
max_page = 8911
要注意带上Firefox浏览器上的cookies.
def start_requests(self):
headers = {
'User-Agent': USER_AGENT,
}
cookies = {
'_l_g_': 'Ug==',
'_m_h5_tk': '7fc115a6218e184230f2576214bddb9d_1560939254163',
'_m_h5_tk_enc': '2234531bca3e65e82c1c4f1e84a9ff8e',
'_nk_': '\u5409\u7C73\u591A\u7EF4\u5947\u7231\u6570\u5206',
'_tb_token_': 'e3e6b1b159e7b',
'ck1': '',
'cna': 'fRQMFZNC9lUCAd0EIk1wBCP3',
'cookie1': 'AVJ0BAH4k1O+4H25j50cTLQqmMXYIJqxzI6MZPtNeyc=',
'cookie17': 'UU6oL/V0LlWJdQ==',
'cookie2': '1905c0ac2025ad5fa6bb8b0a0bbb3edc',
'csg': '2ab2dcee',
'enc': 'ooH8602regPsWM1QfY3QSxjCZFzz5o8Cc3HlKJU7zZGHQIGiyDbVU7tl3OLBBy8xg8tux6KZM6Dx6O7t+LoYFA==',
'hng': 'CN|zh-CN|CNY|156',
'isg': 'BCMjGH3segMKazekY19AtEN1sWdhBFppX-siTVWA5gJslEq23evtqnuGjqQ_Lw9S',
'l': 'bBr5SYxqv46OYL1BBOfZqQFf8hQTgIRVCkPP2PW6kICPOLCX5xhCWZh60VYWC31VZ1DyR3JceFJJB8TNpyCV.',
'lgc': '\u5409\u7C73\u591A\u7EF4\u5947\u7231\u6570\u5206',
'lid': '�米�维����',
'login': 'true',
'otherx': 'e=1&p=*&s=0&c=0&f=0&g=0&t=0',
'skt': '4445413dc568a647',
't': '9d720f184875d4c25562b0a93e4f5996',
'tk_trace': '1',
'tracknick': '\u5409\u7C73\u591A\u7EF4\u5947\u7231\u6570\u5206',
'uc1': 'cookie16=Vq8l+KCLySLZMFWHxqs8fwqnEw==&cookie21=VFC/uZ9aiKCaj7AzMHh1&cookie15=UtASsssmOIJ0bQ==&'
'existShop=false&pas=0&cookie14=UoTaGOHiwTZ1YA==&tag=8&lng=zh_CN',
'uc3': 'vt3=F8dBy3kbj5HhJvR1epM=&id2=UU6oL/V0LlWJdQ==&nk2=3zMxvthgoDYtGUAVjC8HCg==&lg2=UIHiLt3xD8xYTw==',
'unb': '2696829666',
'uss': '',
'whl': '-1&0&0&0',
'x': '__ll=-1&_ato=0',
'x5sec': '7b22726174656d616e616765723b32223a223639643865623637336663346530323431313935653338623730383565'
'366237434d2f57702b6746454958316d346a64684b69364e686f4d4d6a59354e6a67794f5459324e6a7378227d'
}
for page in range(1, self.max_page + 1):
yield scrapy.Request(self.url.format(page=page), headers=headers,
cookies=cookies, callback=self.parse, dont_filter=True)
从监听器对链接的格式化显示可以看出,评论区的内容是以json格式储存在网站中的。因此,parse函数的关注焦点便是如何解析各网页的json内容。
def parse(self, response):
item = TmallItem()
comments = re.findall('"rateList":(\[.*\]),"searchinfo"', response.text)
comments = ''.join(comments)
comments = json.loads(comments)
for comment in comments:
item['init_comment'] = comment['rateContent']
item['init_images'] = comment['pics']
item['init_explanation'] = comment['reply']
item['category'] = ''.join(re.findall('颜色分类:(.*)', comment['auctionSku']))
item['user'] = comment['displayUserNick']
item['init_comment_date'] = ''.join(re.findall('\d{4}\D\d{1,2}\D\d{1,2}', comment['rateDate']))
if comment['appendComment']:
item['append_comment'] = comment['appendComment']['content']
item['append_images'] = comment['appendComment']['pics']
item['append_explanation'] = comment['appendComment']['reply']
item['append_days'] = comment['appendComment']['days']
try:
item['append_comment_date'] = ''.join(re.findall('\d{4}\D\d{1,2}\D\d{1,2}',
comment['appendComment']['commentTime']))
finally:
pass
else:
item['append_comment'] = None
item['append_images'] = None
item['append_explanation'] = None
item['append_comment_date'] = None
item['append_days'] = None
yield item
以上工作全部都需要在spider类当中完成。
为了尽可能规避反爬机制的识别,我还在该文件中进行了以下操作。
ROBOTSTXT_OBEY = False
CONCURRENT_REQUESTS = 32
DOWNLOAD_DELAY = 1
CONCURRENT_REQUESTS_PER_DOMAIN = 16
CONCURRENT_REQUESTS_PER_IP = 16
FEED_EXPORT_ENCODING = 'GBK' # 这里表示信息文本是GBK编码,避免了汉字数据输出时出现乱码的情况
至此,我的代码编写工作就算大功告成了。接下来便是需要等待输出结果了。
经过长达10分钟的等待,我终于等到了最终结果。然而结果却没有我想象当中那般理想——超过八成的数据竟然是重!复!的!
实际上,我观察到我的程序在运行到一段时间过后,请求返回的数据都是一样的,这大概率是我用写死cookies这种饮鸩止渴的方式的锅了。不过考虑到我手头上至少能够拿到1992条数据,起码可以给老板交个差,所以也就没有继续深究下去了。
这一部分的技术难度实话说并不高,唯一的痛点在于要用到的第三方模块又多又难安装,希望各位小伙伴们注意一下。
from wordcloud import WordCloud
import jieba
import pandas as pd
import matplotlib.pyplot as plt
from scipy.misc import imread
接下来的工作步骤,我已上传至我的主页,有兴趣了解的可前往下载(里面还有前述的爬虫代码哦!),我只展示结果:
至此我的工作就算完毕了,有收获,更有不足。我觉得这一次爬虫过后我最大的收获是自信,之前总想着阿里巴巴的网站防爬虫是多么多么的高级,自己一个区区本科学历小职员螳臂当车岂不是作死之举?而现在看来,一切困难只不过是只纸老虎,只要有恒心,就总有法子搞得定。
不过,我更在意的是这一编程工作所暴露出来的不足。尽管学习、实操Python爬虫已经超过一年了,但是还会时常发现自己对网络方面的知识有所欠缺。所获取的数据尚不完整就是最扎心的明证。但这恰恰是我敢于、乐意公开代码、分享经验的根本原因,我欢迎各路高手为我指点,相互切磋交流,一起进步!
祝福各位认真读完这篇文章的程序猿与程序媛们生活愉快,程序无BUG!