一、环境搭建
开发环境python2.7,开发工具pycharm。
关于python2.7环境搭建,使用搜索引擎搜索“anaconda”,在anaconda官网下载python版本,有两个版本可供选择,分别是anaconda2(python2)和anaconda3(python3),建议选择anaconda3,由于发展必须,python2.7终究要被淘汰,由于本人电脑只安装anaconda2,故用python2.7进行开发。
在这里说一下选择anaconda的理由,anaconda中集成了大部分python中常用的库,可以在使用的时候直接进行调用,不必重新进行安装,故选择anaconda。对于anaconda的下载及安装不在赘述,各大搜索引擎中均可搜到详细安装过程。
pycharm作为python专用开发工具,其好处自然不必多说,例如可以随时终止/暂停进程、各种强大的提示功能(错误提示、变量是否用到等等)、索引功能很强等,当然也可以使用其他的开发工具,例如sublime等。本文使用pycharm作为开发工具。
二、爬虫入门
网络爬虫(又称为网页蜘蛛,网络机器人,在FOAF社区中间,更经常的成为网页追逐者),是一种按照一定的规则,自动的抓取万维网信息的程序或脚本。另外一些补偿使用的名字还有蚂蚁、自动索引、模拟程序或者蠕虫。【引用自:点击打开链接】
以上是对网络爬虫的简单介绍,接下来介绍网络爬虫经常使用的库。
1、requests库
2、beautifulsoup库
3、json库
4、random库
5、re库
6、MySQLdb库(用于将数据存到mysql数据库)
注:这些库的介绍网上太多了,读者可自行搜索。
三、对于天猫的网络爬虫
说了很多,终于回到主题,接下来对天猫中商品用户评论进行爬虫,为什么不说淘宝呢,请看标题四、所遇到的问题。
首先介绍爬虫思路:
(1)从搜索栏开始,找到相关的url地址,此时需要“F12”,进入开发者工具,在Network中寻找相关url(此过程务必仔细);
(2)找到存储信息的url之后,爬取信息;
(3)将信息存入数据库;
(4)大功告成。
其实爬虫是个简单的过程,但是其实需要细心细心再细心,毕竟网页的设计者不希望我们不费吹灰之力就拿到他写的数据,大大小小的网站都是存在反爬机制的,为什么要设置登录注册,仅仅是为了让你方便购物吗?虽然这是其中很重要的一个内容,但是更多是为了防止你爬取网页信息。我们都明白,对一台服务器来说,访问量暴增是很危险的事情,这会导致服务器崩溃,从而使整个网站陷入瘫痪,试想:当你所选中的商品正在疯狂打折中,而且距离抢购结束仅剩10分钟,服务器突然崩溃,我想你的内心也要崩溃了吧,之前鹿晗恋情曝光之后微博服务器崩溃,导致我们的程序猿新郎官在婚礼中紧急修复微博服务器,所以,一个好的爬虫,他的目的就是为了拿到网络数据,但是不能影响网络的正常运行,当你大肆爬取网络信息时,被对方检测到你的ip正在频繁访问他,那么对方就会选择禁掉你的ip,从而阻止你的访问,使得你需要通过验证等操作才可访问,这就是反爬机制。
废话说了这么多,下面进去具体的爬虫操作。
1、url初窥
每个url,几乎都是通过一系列的拼接形成的,所以需要你对你看到的url进行简化之后访问,这样可以使你的url具有一定的规律,你看着也会赏心悦目。
没有图片确实太单调了点,但是在截图时发现不知道截哪里的图。。。只能这样截了三张。。。
2、上有政策,下有对策
之前说过对反爬的理解,正所谓上有政策下游对策(当然这个是不好的,我们要积极遵守所出的政策!!!),为了防止浏览器封掉我们的ip,目前常用的几种应对方法:
(1)伪装浏览器头等信息:这些东西在network的headers中有,仅需把这些ctrl+c,然后ctrl+v进你的代码里;
(2)设置延时:我们明白,爬虫就是模拟人的浏览习惯进行网页中数据的下载,你会一动不动的保持上一个网页刚刚打开就打开下一个网页一个小时吗?显然是不会的,我们会等打开之后浏览几秒钟,几分钟,发现里面没有我们需要的信息之后再选择打开下一个网页,但是计算机不懂这些呀,所以我们就需要设置一些延时,使浏览器看起来是一个“活生生”的人在访问它,从而放松对我们的警惕,使我们的爬虫有一个细水长流的过程。
(3)添加ip池:这个方法虽然各种教程都说过,但是你要明白一个道理,天下没有免费的ip,就算你偶尔找到一个可用的免费ip,但是想想地球上辣么多亿人口,你能发现这个ip,那么发现这个ip的人大有人在,所以要想使用伪造ip,辣么最对的方法就是去租!!!没错,是要你掏腰包滴。
(4)其他:当然,现在还是有很多其他的方法,据我几个月前了解,数据海洋的服务器似乎是可以自动更换ip的,但是服务器还是需要自掏腰包进行租赁,总而言之,有钱真的可以为所欲为!!!但是我这样的穷ds还是自食其力吧。
光说不练假把式,下面贴丢丢代码,写的很难看,大佬们表介意。。。
self.search_url = "http://list.tmall.com/search_product.htm?&s={page_num}&q=%BF%E3%D7%D3&sort=s&style=g&type=pc#J_Filter" # 地址,搜索内容为 裤子
self.detail_url = "http://detail.tmall.com/item.htm?id=16351748398&cm_id=140105335569ed55e27b&abbucket=18&on_comment=1" # 详情地址,需重写
self.comment_url = "http://rate.tmall.com/list_detail_rate.htm?itemId=16351748398&sellerId=729426279¤tPage=1&_ksTS=1515553207394_471&callback=jsonp472"
# 访问搜索页面的headers
self.search_headers = {'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
'accept-encoding': 'gzip, deflate, br',
'accept-language': 'zh-CN,zh;q=0.9',
'cache-control': 'no-cache',
'pragma': 'no-cache',
'upgrade-insecure-requests': '1',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36'}
# 访问评论页面的headers
self.comment_url_headers = {'accept': '*/*',
'accept-encoding': 'gzip, deflate, br',
'accept-language': 'zh-CN,zh;q=0.9',
'referer': self.comment_url,
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36',}
上面大致是写的一些关于url,还有访问头的一些构造(这里采取最常用的模拟浏览器访问头的办法避开反爬机制),对于url,我本人不善言辞,一两句说不清楚,只能贴出来了。
当搜索条件为“裤子”时,你访问的url经过简化之后和上述代码相同;中间的url为每个商品详情的url(我记得代码中最后似乎没怎么用到);最后的url就是评论存在的url。
3、先run一下
当你访问到你需要的这些信息之后,就需要开始对网页的源码进行分析了,对于网页的源码,我的建议分析两部分,一部分是f12之后Elements中的源码,这些源码让你快速的找到你需要的内容,另一部分是鼠标右键-->查看网页源代码 之后打开的源码,这部分让你清楚的认识到你之前找到的都是没有的,虽然有点崩溃,但是事情总有解决的办法。
对于网页的加载,有静态的也有动态的,对于静态网页来说,它天生就是这些内容;但是对于动态网页,它天生只是一个框架而已,当后台的数据库进行变动时,网页内容会跟着数据库一起变化,所以这部分变化的东西程序猿一般都会进行一系列的动态加载,目前常用的有ajax动态加载json和jsonp数据,这就是你为什么在源码中找不到这部分内容的原因了。聪明的程序猿利用动态加载使得我们找不到我们需要的数据,但是更加聪明的我们应该想到,你最终是要将你的数据丢到页面上去啊,这样你的数据库和你的web端必然产生数据的交互啊,那么肯定会有数据包产生啊,这就需要用到“抓包”的知识了。
浏览器的开发者工具(f12)自带抓包工具,network中存在的东西就是数据包(笔者感觉这种说法似乎有些不妥,手懒不想查了。。。),当然你也可以用fiddle等工具进行抓包。数据包中的response内容就是我们需要查找的“真相”(我们需要的数据不出意外应该是在这里的)!!!
经过笔者仔细的查找,终于找到我们所需要的数据包的所在了,接下来就开始“手脚并用”的爬虫了!!!
4、阿哦,我们的数据这样来
我们已经找到这些数据包,需要访问这些数据包仍然需要访问头,带着访问头去访问网页我自认为是一个好习惯,虽然有时候这样做显得很臃肿,但是这样可以减少很多问题。在你成功访问到内容后,利用beautifulsoup、正则表达式等就可以拿到你所需要的内容了。
5、让你的数据有个家
拿到数据之后可以选择存储到文本(.txt)文件中,也可以选择存储到数据库内,笔者不对这两种方法进行评价,按照你的需求来,如果存储到txt中方便你的读取就存到txt中,数据库也是一样的道理,你甚至可以存到Excel中去。
四、所遇到的问题
1、在爬取淘宝搜索页面中的数据时,requests.get返回200,但是print时没有数据显示。
不算解决方案的解决方案:关于这点,我查阅了大量前辈们所写的东西,发现虽然有提问,但是并没有给出具体的解决方案,由于本人小白一枚,技术有限,虽然百思不得其解,但是毫无办法,所以直接改爬天猫,二者本是一家,数据量还是比较可观。
2、对评论url的分析拼接
http://rate.tmall.com/list_detail_rate.htm?itemId=16351748398&sellerId=729426279¤tPage=1&_ksTS=1515553207394_471&callback=jsonp472
这是上面说过的评论url地址,在这里做一下说明:
itemId:商品id
sellerId:卖家id
page:页码
_ksTS:"_"前是当前时间戳(关于时间戳读者请自行百度),"_"后是jsonp后的数字
callback:作为回调函数的一部分,这部分不加其实无伤大雅,但是对取数据会有一定的影响(就是你不能取json数据,取起来比较麻烦,数据不会很整齐),这部分经过我的测试发现,“callback=jsonp”这部分是固定不变的,后面的数字利用random函数生成一个随机数拼接上去就可以了,当然这个随机数尽可能给个大的范围(我就给了100到1800之间随机生成)。
你的url就这样拼接而成了。
五、源码
下面附上源码,虽然写的很难看,但是希望对各位有所帮助(笔者是将数据存到txt中):
# -*- encoding:utf-8 -*-
import requests
import json
import time
import random
import re
import datetime
import save_commdity_json
from bs4 import BeautifulSoup
class Tianmao:
def __init__(self):
self.scj = save_commdity_json.Json_commdity()
self.search_url = "http://list.tmall.com/search_product.htm?&s={page_num}&q=%BF%E3%D7%D3&sort=s&style=g&type=pc#J_Filter" # 地址,搜索内容为 裤子
self.detail_url = "http://detail.tmall.com/item.htm?id=16351748398&cm_id=140105335569ed55e27b&abbucket=18&on_comment=1" # 详情地址,需重写
self.comment_url = "http://rate.tmall.com/list_detail_rate.htm?itemId=16351748398&sellerId=729426279¤tPage=1&_ksTS=1515553207394_471&callback=jsonp472"
# 访问搜索页面的headers
self.search_headers = {'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
'accept-encoding': 'gzip, deflate, br',
'accept-language': 'zh-CN,zh;q=0.9',
'cache-control': 'no-cache',
'pragma': 'no-cache',
'upgrade-insecure-requests': '1',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36'}
# 访问店铺详情的headers
self.request_url_headers = {'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
'accept-encoding': 'gzip, deflate, br',
'accept-language': 'zh-CN,zh;q=0.9',
'cache-control': 'max-age=0',
'upgrade-insecure-requests': '1',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36'}
self.comment_url_headers = {'accept': '*/*',
'accept-encoding': 'gzip, deflate, br',
'accept-language': 'zh-CN,zh;q=0.9',
'referer': self.comment_url,
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36',}
def open_url(self): # 下载所有的商品url,存到数据库中
main_page_num = 0
for page_num in xrange(2, 50): # 50页之内
list_commdity = []
list_price = []
list_sale = []
list_name = []
list_commdity_id = []
dic_commdity = {}
real_search_url = self.search_url.format(page_num=page_num)
try:
response = requests.get(real_search_url, headers=self.search_headers)
main_page_num = main_page_num + 1
print str(datetime.datetime.now()) + " sleep one minutes" + " This is Page " + str(main_page_num)
time.sleep(60)
result_ = response.content
result = result_.decode("gbk")
# result = result_.decode(chardet.detect(result_).get('encoding')) # 获取页面编码格式并按照编码格式解码
# print result
except Exception, e:
print e
else:
soup = BeautifulSoup(result, "lxml")
all_link_commdity = soup.find_all("p", attrs={"class": "productTitle"})
all_link_price = re.findall('<em title=".+">', result) # 正则表达式匹配价格信息
all_link_sale = re.findall(u'<span>月成交 <em>.+', result) # 正则表达式匹配月成交量信息
for link_sale in all_link_sale:
link_sale = link_sale.split(">")[2].split("<")[0]
list_sale.append(link_sale)
# print link_sale
for link_price in all_link_price:
link_price = link_price.split('"')[1]
list_price.append(link_price)
# print link_price
for link_commdity in all_link_commdity:
link_commdity = str(link_commdity.find_all("a", attrs={"target": "_blank"})).split('"')
list_commdity.append(link_commdity[3])
commdity_id = link_commdity[3].split("=")[1].split("&")[0] # 商品id
list_commdity_id.append(commdity_id)
commdity_name = link_commdity[7].decode("unicode-escape") # 商品名称
list_name.append(commdity_name)
# print commdity_name
# 将数据存到数据库中
# self.conn_sql.insert_tianmao_list(id=commdity_id, commdityUrl=link_commdity[3], commdityName=commdity_name)
# self.conn_sql.close_sql()
dic_commdity[0] = list_name
dic_commdity[1] = list_commdity_id
dic_commdity[2] = list_commdity
dic_commdity[3] = list_price
dic_commdity[4] = list_sale
Tianmao.save_commdity(self, dic_commdity=dic_commdity) # 商品信息存入txt
Tianmao.open_comment_url(self, list_commdity=list_commdity) # 评论信息存入txt
# return self.dic_commdity
def save_commdity(self, dic_commdity):
dic_comm = dic_commdity
list_name = dic_comm[0] # 名称
list_commdity_id = dic_comm[1] # id
list_commdity = dic_comm[2] # 地址
list_price = dic_comm[3] # 价格
list_sale = dic_comm[4] # 月销量
for num in xrange(len(list_name)):
self.scj.save_commdity(name=list_name[num],
commdity_id=list_commdity_id[num],
url=list_commdity[num],
price=list_price[num],
sale=list_sale[num])
print "SAVE No." + str(num) + " OVER"
def comment_url(self, item): # 保存信息到txt
link_itemId = item.split("=")[1].split("&")[0]
link_sellerId = item.split("=")[3].split("&")[0]
millis = int(round(time.time() * 1000))
random_num = random.randint(100, 1800)
random_num_ = str(random_num + 1)
comment_url = "https://rate.tmall.com/list_detail_rate.htm?itemId=" + link_itemId + "&sellerId=" + link_sellerId + "¤tPage={page_num}&_ksTS:" + str(millis) + "_" + str(random_num) + "&callback=jsonp" + random_num_
return comment_url
def open_comment_url(self, list_commdity):
page_num_start = 0
comment_num = 0
for item in list_commdity:
list_comment_all = []
comment_url_test = Tianmao.comment_url(self, item)
link_itemId = comment_url_test.split("=")[1].split("&")[0]
item_url_test = comment_url_test.strip("\n").format(page_num=page_num_start)
item_url_str_test = item_url_test.split("=")[-1] + "("
try:
comment_response_test = requests.get(item_url_test, headers=self.comment_url_headers)
print "Please sleep 2 seconds"
time.sleep(2) # 休息2s
comment_result_test = comment_response_test.content.decode('gbk').replace(item_url_str_test, "")[:-1]
json_comment_page_num = json.loads(comment_result_test)['rateDetail']['paginator']['lastPage']
except Exception, e:
print e
else:
for i in xrange(1, json_comment_page_num):
comment_url = Tianmao.comment_url(self, item)
item_url = comment_url.strip("\n").format(page_num=i)
item_url_str = item_url.split("=")[-1] + "("
try:
comment_response = requests.get(item_url, headers=self.comment_url_headers)
comment_result = comment_response.content.decode('gbk').replace(item_url_str, "")[:-1]
print str(datetime.datetime.now()) + " sleep 2 seconds"
time.sleep(2)
json_comment_result = json.loads(comment_result)['rateDetail']['rateList']
time.sleep(1)
len_comment = len(json_comment_result)
except Exception, e:
print e
else:
for item_comment in xrange(len_comment): # 得到一个完整评论
dic_comment_page = {}
displayUserNick = json_comment_result[item_comment]["displayUserNick"] # 用户名
id = json_comment_result[item_comment]["id"] # 订单id
auctionSku = json_comment_result[item_comment]["auctionSku"] # 所购买款式
goldUser = json_comment_result[item_comment]["goldUser"] # 是否超级会员
rateContent = json_comment_result[item_comment]["rateContent"] # 得到用户评论数据
rateDate = json_comment_result[item_comment]["rateDate"] # 评论时间
dic_comment_page["username"] = displayUserNick # 将一个完整的用户评论信息存入字典
dic_comment_page["order_id"] = id
dic_comment_page["style"] = auctionSku
dic_comment_page["super"] = goldUser
dic_comment_page["ratecontent"] = rateContent
dic_comment_page["ratedate"] = rateDate
list_comment_all.append(dic_comment_page)
# print list_comment_all
comment_num = comment_num + 1
print "Comment: This is No." + str(comment_num)
self.scj.save_comment(commdity_id=link_itemId, comment_all=list_comment_all)
def main():
down_tianmao = Tianmao()
down_tianmao.open_url()
if __name__ == '__main__':
main()
save_commdity_json 的源码
# -*- encoding:utf-8 -*-
import json
import os
import sys
reload(sys)
sys.setdefaultencoding("utf-8")
class Json_commdity:
def __init__(self):
pass
def save_commdity(self, commdity_id, name, url, price, sale):
try:
# 商品名称,商品id,商品地址,商品价格,月销量,评论
data = {"commdity_id": commdity_id, "name": name, "url": url, "price": price, "sale": sale, "comment": ""}
json_ = json.dumps(data, indent=4).decode("unicode-escape") # 将中文写入需要格式转换
os.chdir("G:/tianmaoshuju")
txt_name = commdity_id + ".txt"
with open(txt_name, "w") as f:
f.writelines(json_)
f.close()
except Exception, e:
print e
def save_comment(self, commdity_id, comment_all):
try:
# 评论者(匿名),订单id,所购买款式,超级会员,评论内容,评论时间
comment = {"comment": comment_all}
json_comment = json.dumps(comment, indent=4).decode("unicode-escape")
os.chdir("G:/tianmaopinglun")
txt_name = commdity_id + ".txt"
with open(txt_name, "w") as fo:
fo.writelines(json_comment)
fo.close()
except Exception, e:
print e
def main():
json_c = Json_commdity()
commdity_id = "commdity_id"
name = "name"
url = "url"
price = "price"
sale = "sale"
json_c.save_commdity(commdity_id, name, url, price, sale)
if __name__ == '__main__':
main()
讲道理这部分代码写的是真的难看,笔者自己贴出来感觉很汗颜啊,代码功底是硬伤,以后还需要勤加练习,希望小白的我这些东西可以帮到同是小白的你们,唉,不说了,这个代码是真的丑,要继续练习代码了。。。
还有,目前经过笔者测试,发现搜索条件为“裤子”的url似乎发生了改变。。。
当然,有问题可以问我,虽然我很可能,但是还是会尽力解答的。。。
本文原创,转载请注明出处,谢谢。
最后的几点说明:淘宝天猫和其他的网站一样,很可能会让你登录后才可以访问他们的数据,这个是习以为常的,但是笔者目前精力有限,没有做这部分的登录,当然,就笔者自身的水平而言目前写这些东西还较为困难(毕竟对其中的加密算法不太了解),往大佬们勿喷。写本文是笔者在写毕设中的一些归纳、整理。。再加上总结吧,仅仅是希望可以认真的对待本次毕设,好吧,就是为了一个字,过!!!再次希望各位大佬不喜勿喷。。。(虽然没有人看,礼貌还是有的)谢谢大家