上学期和同学一块儿接了个爬虫的活儿,赚了人生第一桶金。本来打算寒假好好整理一下,但是,但是寒假整整玩儿了一个月,说好的总结博客没有写,想学的新东西也没有看,真是不思进取啊。现在提前几天回学校,一来准备一下实验室新开的项目,二来对这学期做一个小的规划吧,顺带整理一下上学期的工作。这篇博客主要总结了我在这个爬虫项目中学习到的一些新知识,在此要感谢我的两位同学killers Deathyohoho

这个项目主要是抓取某几个网站(其中包括淘宝)的商品列表和商品详情。我们的整体思路是将抓取到的商品列表和商品详情分别保存在MySQL中,并且在其中加入了类似『断点续传』的功能——即爬虫在断掉后重新抓取时只抓取那些未抓取的商品详情。

BeautifulSoup和Xpath的比较

解析网页的方法有好几种,我以前只会使用beautifulsoup,写起来既麻烦,速度又慢,这次我们使用了xpath和正则表达式。借助chrome中的开发者工具我们可以迅速拿到页面中任意信息对应的xpath语句,节省了使用beautifulsoup中的find方法时的一次次调试。另外xpath解析网页的速度要比beautifulSoup快十几倍,二者具体的比较见python中的beautifulsoup和xpath有什么异同点? - 彭泉鑫的回答 - 知乎

如何爬取JSON动态加载的网站

我们爬的第一个网站是工品汇,这个网站看起来很规整啊。上来按照常规方法去抓取商品详情,结果什么都没有拿到,这就有点儿懵逼了。查了一下了解到有些网站是通过json动态加载的,然后我们查到了Phantomjs,通过这个工具成功抓到了商品详情。下面介绍一下如何使用这个:

  • 安装Phantomjs
  • 安装tornado
  • 在工作路径下添加文件phantonjs_fetcher.js,然后在控制台用命令”phantomjs phantomjs_fetcher.js [port]”来启动它(这里的port随便填一个即可)

做完上面的工作后,我们再用下边的这个函数就可以拿到json动态加载之后的信息了。之后再按照常规的方法就可以拿到整个网站的三级列表,可以正常的抓取商品详情了。

1
2
3
4
5
6
7
8
9
10
11
12
13
def getHtmlFromJs(url):
'''
获取js加载数据后的html
:param url:请求url
:return:
'''
fetcher=Fetcher(
user_agent='phantomjs', # user agent
phantomjs_proxy='http://localhost:4444', # phantomjs url
pool_size=10, # max httpclient num
async=False
)
return fetcher.phantomjs_fetch(url)

一般在抓取某一个目录下的所有商品的url列表时,网站会通过接口来返回url列表,我们只需要通过不断改变要请求的接口链接中的参数就可以实现网页的翻页,从而拿到整个分类下的所有商品url。我之前在爬取知乎大V的follower列表时就是这个样子,具体可以阅读我前面的那篇博客。但是这个网站明显不是这样的,我们用常规方法只能抓到第一页,拿不到什么接口,也没有什么下一页的链接,这就又懵逼了。另外一个问题是每个三级目录下最多只显示7页x15条/页=105条商品详情(网页只显示有100件相关商品),如果想拿到所有的数据(如135条)的话,我们就需要在爬虫时自动的遍历选择条件(如下图所示),以前的方法里并没有这个功能。

经过又一番搜索,我们找到了Selenium。Selenium是一个自动化测试工具,支持Chrome,Firefox等主流界面式浏览器,只要在浏览器里边安装一个Selenium的插件,然后就可以实现Web界面的测试。另外Selenium支持多种语言开发,如JAVA,C,Python。这里我们便开始借助Selenium来解决上面遇到的两个问题。首先是完成如下准备工作:

  • 安装Selenium
  • 安装Firefox及Selenium插件
  • 安装Selenium库(通过pip命令安装即可)
  • 快速学习Selenium官方文档

在写这篇博客时看到了大神崔庆才的博客,这里也贴一下。下面直接给出两个函数,第一个函数解决的是翻页的问题,第二个函数则实现了条件选择,拿到了每一种条件选择对应的url。值得一提的是,代码跑起来时,网页自己进行翻页和条件选择的画面看着很magic啊,哈哈哈。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
def goodsUrlList(home_url):
'''
根据三级目录商品首页获取所有详情页url
:param home_url: http://www.vipmro.com/search/?&categoryId=501110
:return:url列表
'''
'''
# 所有条件下的列表
all_group_list = parseOptional(home_url)
# 保存所有goods的详情页url
url_list = []
for url in all_group_list:
# url = 'http://www.vipmro.com/search/?ram=0.9551325197768372&categoryId=501110&attrValueIds=509805,509801,509806,509807'
# 解析html
home_page = getHtmlFromJs(url)['content'].encode('utf-8')
html = HtmlResponse(url=url,body=str(home_page))
urls = html.selector.xpath('/html/body/div[7]/div[1]/ul/li/div[2]/a/@href').extract()
if not urls:
continue
url_list.extend(urls)
return url_list
'''
urls = [] # 存储种子url
driver = webdriver.Firefox()
driver.get(url)
path = '/html/body/div[7]/div[2]/a[@page-id="{0}"]'
time.sleep(3)
page = driver.page_source
html = HtmlResponse(url=url, body=str(page))
url_tmp = html.selector.xpath('/html/body/div[7]/div[1]/ul/li/div[2]/a/@href').extract()
urls.extend(url_tmp)
print len(urls)
num = int(re.findall(r'\d+', html.xpath('/html/body/div[6]/div/div[4]/span/text()').extract()[0])[0])
if num <= 15: # 只有一页
return urls
else:
import math
page_num = int(math.ceil(num / 15.0))
for i in range(1, page_num):
# 模拟翻页动作
driver.find_element_by_xpath(path.format(1 + i)).click()
time.sleep(2)
# 提取html语句,解析
page = driver.page_source
html = HtmlResponse(url=url, body=str(page))
url_tmp = html.selector.xpath('/html/body/div[7]/div[1]/ul/li/div[2]/a/@href').extract()
urls.extend(url_tmp)
print len(urls)
driver.quit()
return urls
def parseOptional(url):
'''
解析url下页面各种选择项组合的url
:param url: http://www.vipmro.com/search/?&categoryId=501110
:return:['http://www.vipmro.com/search/?categoryId=501110&attrValueIds=509801,512680,509807,509823']
'''
# 解析html
home_page = getHtmlFromJs(url)['content'].encode('utf-8')
html = HtmlResponse(url=url,body=str(home_page))
# 系列参数
xi_lie = html.selector.xpath('/html/body/div[5]/div[6]/ul/li/a/@href').re(r'ValueIds=(\d+)')
# 额定极限分断能力
fen_duan = html.selector.xpath('/html/body/div[5]/div[10]/ul/li/a/@href').re(r'ValueIds=(\d+)')
# 脱扣器形式
tuo_kou_qi = html.selector.xpath('/html/body/div[5]/div[14]/ul/li/a/@href').re(r'ValueIds=(\d+)')
# 安装方式
an_zhuang = html.selector.xpath('/html/body/div[5]/div[12]/ul/li/a/@href').re(r'ValueIds=(\d+)')
# 获取所有参数组合
all_group = list(itertools.product(xi_lie,fen_duan,tuo_kou_qi,an_zhuang))
_url = url + '&attrValueIds='
url_list = map(lambda x:_url+','.join(list(x)),all_group)
return url_list

由于当时有几个网站要处理,这个网站被放到了最后去爬。后来我们又发现这个网站数据比较少,便放弃了爬这个网站。(这篇博客纯粹是从技术实现的角度来讲的,应该不会对这个网站造成侵权吧)

另外学到的新知识就是如何将数据存储到数据库中,这一方面就不再赘述,等以后学完了MongoDB再一块儿总结吧。