前段时间,我家妹子公司老板叫她去将法国亚马逊评论列表的前100页共1000个评论用户的联系方式找出来。1000个用户,要一个个的去看再记录下来,而且并不是每个评论用户都会将个人的联系方式留下来。那么问题来了,这样费时费力的工作如果人工去做的话,那么就是花了两天的时间也就找了前30页的数据(还有别的工作要做),然后累的够呛的。本着心疼的原则(程序猿能找到妹子就很不错了,所以得心疼着),就想帮着她做点事。
我本身的工作是做游戏客户端开发的,主要使用的开发语言是lua和c++,并没有接触过网页、网站相关的工作。只是工作中有用到过python脚本,然后有一次在网上查python的相关资料的时候,有看到网友用python写爬虫干点事的。所以我就想,我是否也能够实用python来写爬虫去亚马逊的网站抓取数据呢?就这样现学现用的开始敲起代码来了。
环境:
windows7
python:2.7
利用的python插件:
urllib2、urllib插件,用了打开网页链接;
re插件,用来做正则匹配;
codecs插件,用来做编码转换及数据保存。
目前实现的功能:
抓取法国亚马逊top-viewer列表前100页共1000个用户的姓名、联系方式(网站链接或者邮箱)、国籍(可能有卢森堡、瑞士的买家在法国亚马逊购买)、用户评论详细页面的链接等数据。
通过抓取法国亚马逊top-viewer列表数据,进而扩展到抓取中国亚马逊、美国亚马逊top-viewer列表的数据。理论上通过简单的修改可以抓取不同国家亚马逊top-viewer列表的数据。
需改进的地方:
代码写好之后,抓取数据的过程中发现效率好低,1000个数据需要花费很长的时间才能抓取完,而且抓取了几页或者几十页之后程序就跑不动了,卡住只能关掉再开。当时在没有扩展到抓取中国、美国亚马逊的数据之前,我想到的可能原因有:
- 正则表达式有优化空间,因为我之前没有接触过正则表达式,没有使用过;
- 法国亚马逊网站在国内访问速度慢,影响到了数据的抓取;
- python没有系统的学过,在一些语法或者第三方辅助插件的使用上不够熟悉。
以上三点是我想到的造成抓取效率低下的可能的原因。后来我把同一套代码扩展到抓取中国、美国亚马逊的数据,以验证第二条原因对整个抓取工作的影响程度,结果发现影响非常大!同样的带宽、硬件条件下,中国、美国的前100页共1000个评论用户,抓取大概花了半个多小时,而抓取法国的那1000个数据,花了我近一个下午的时间(因为总是卡住不动,我想应该是urllib打开网页未响应而我的程序没有做判断)才陆陆续续的抓取完,不过也总比妹子一个一个的打开网页再记录下来好,至少人不会烦!然后对中国、美国数据抓取花了半个小时时间,对这个时间花费我个人不好评判是花多了还是说差不多了。但是作为一个开发人员来说,程序总是可以做优化的!
思路:
当时看到网友写的爬虫,思路就是打开网页、匹配自己需要的信息。因此我的思路也是跟着这个来的:通过python的urllib和urllib2插件打开页面,再转换为html数据,利用python的re正则插件去做正则匹配,得到页面、用户详细信息页面、用户联系方式等信息。
具体实现:
1、法国亚马逊的top评论列表大概有1000个网页,每个页面有10个用户数据。每个页面,除了第一页的链接外,其它页面的链接都跟页面数相关,如http://xxx.page23.xxx 是代表23页的数据,因此可以通过简单的字符串拼接就可以得到1000个页面的页面链接了。这是关于得到各个页面链接的方法;示例代码如下:
a、拼接页面链接,因为第一页和其余页的格式稍微有点不同,所以分开来处理:
if 1 == i: html_link = "http://www.amazon.fr/review/top-reviewers/ref=cm_cr_tr_link_" + str(i); else: html_link = "http://www.amazon.fr/review/top-reviewers/ref=cm_cr_tr_link_" + str(i) + "?ie=UTF8&page=" + str(i);
b、将页面转为html:
try: page = urllib.urlopen(url) html = page.read() return html except: print "getHtml2 error"
我使用了try、except就是想看能不能处理打开不开网页的问题(我猜法国亚马逊抓取卡死是因为网站未响应了),但是没有效果;
2、每个页面有10个用户的数据,点开一个用户会跳转到其详细信息页面,通过查看不同详细信息页面的链接形式,发现都是类似的:一个可能是用户名的变量,一个表示该用户在评论列表中的排名值。因此我可以想办法得到用户名(猜想是用户名、或者是亚马逊保存的一个唯一标示)、用户在评论列表中的排名,然后拼接出用户详细信息页面的链接;通过查看页面的源码,发现每个页面中,用户信息的形式类似,都是诸如:/gp/pdp/profile/xxxx/这样的形式,因此可以通过简单的正则匹配得到xxxx这个数据,暂且称为用户唯一标示符。这是关于得到详细页面链接的方法;示例代码:
a、匹配每个用户的唯一标示符:
reg = r‘href="(/gp/pdp/profile/.+?)"><b>‘ captureRe = re.compile(reg) cpList = re.findall(captureRe,html)
b、拼凑链接:
num = (i - 1) * 10 + index; subLink = "http://www.amazon.fr" + cp + "/ref=cm_cr_tr_tbl_" + str(num) + "_name";
index指的是10个数据中具体哪个,num其实就是用户在评论列表中的排名,一页10个,所以能根据页码及index来算出具体的排名;
c、转为html:
headers = { #伪装为浏览器抓取 ‘User-Agent‘:‘Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6‘ } req = urllib2.Request(url,headers=headers) page = ""; try: page = urllib2.urlopen(req) html = page.read() return html except: print "getHtml error"
可以看到这个跟前面的那个转换形式不一样,因为我发现利用前一种转换方式得到的页面数据,跟我们之间右键浏览器查看源代码的格式是有差异的,然后我在匹配联系方式的时候一直匹配失败,就以为是这个差异造成的。因此就查找了资料使用了上述形式来转换,网友也说这样能防止亚马逊针对频繁访问的ip做封ip处理;
3、不是每个评论者都提供了联系方式,但是提供了联系方式的只有两种形式:一个网站链接(博客或者啥);一个邮箱。通过查看源码发现:提供网站链接的,在源码中会有一个nofollow标签,而提供邮箱的则有一个mailto:关键词。有了这两个信息,那么我就可以据此来做正则匹配了。这就是关于得到联系方式的方法。这里也是两个正则表达式做正则匹配,跟上述代码类似就不上代码了;
4、后面又通过正则匹配得到了评论人姓名、国籍等信息。操作也跟3中提到的类似。
缺陷:
- 正如我在问题中提到的,抓取效率是一个大问题。1000个数据快都需要花费半个小时才能匹配抓取完,自我感觉效率上还可以提升;
- 在抓取法国亚马逊top-viewer数据的时候,程序一直卡死,但是我不能据此做问题处理,需再查查。
写在最后:
这个小脚本大概花了一个下午的时间才折腾出来,然后又花了些时间去满足妹子提的其他需求,第二天才把最终抓取到的数据发送给妹子使用。但总的来说,比起妹子可能要花1个礼拜才能做完,我也是帮了点小忙了。而我自己本身也学到了东西:数据抓取!虽然我这只是简单得不能再简单的一种实现,但是也相当于稍微入了门,积累了点知识!