数据抓取的艺术(一):Selenium+Phantomjs数据抓取环境配置
数据抓取是一门艺术,和其他 软件不同,世界上不存在完美的、一致的、通用的抓取工具。为了不同的目的,需要定制不同的代码。不过,我们不必Start from Scratch,已经有许多的基本工具、基本方法和基础框架可供使用。不同的工具、不同的方法、不同的框架的特点也不同。了解这些工具、方法和框架是首要 任务,接下来就需要明白它们的差异都在哪里、什么情境该用什么东东,最后才是析出规则、编写代码、运行程序来抓取数据。所以说,其实数据抓取的学习路线, 不但很长而且很杂。
为了一个特定的目的,我需要爬取Google的搜索数,和其他情况不同:人家是特定关键词,一页一页地爬结果;我的是N多关键词,一次一个地搜索,只需要 返回的搜索条数。事实上,一共有153个关键词,但每个关键词都需要和所有关键词握手组配成一对待检词组。于是,大家可以试想一下,一个153行、153 列的大表格,每一个空白都等着填,这就将是153*153=23409次,也就是约23409/2=11704次,经测试每爬取一个共词页面的结果并存入 Excel,需要花费4秒的时间。这意味着,以单人单线程的方式需要11704*4/3600=13个小时,才能跑完。
这些内容我还会在后续博文中详述,现在先介绍上述目的情境下我使用的技术框架及其安装配置过程。
一、技术架构
[Python2.7 + Pip + Selenium + Phantomjs]
Selenium+Phantomjs,最初这对兄弟不是一家的,后来发现二者志趣相投、互有好感,于是结拜为兄弟,住进了Selenium家里。(这种说法有待商榷)
看看介绍:
Selenium是一个用于Web应用程序测试的工具。Selenium测试直接运行在浏览器中,就像真正的用户在操作一样。支持的浏览器包括IE、Mozilla Firefox、Chrome等。
Phantom JS是一个服务器端的 JavaScript API 的 WebKit。其支持各种Web标准: DOM 处理, CSS 选择器, JSON, Canvas, 和 SVG。
二、环境搭建
(1)安装Python从略,我用的版本是2.7.4(WinXP和Win7的32位平台)。
(2)由于发现Pip比easy_install优秀,我便使用我已经安装的easy_install来安装pip。
- easy_install pip
如图所示:
(3)安装Phantomjs。
到Phantomjs的官方网站http://phantomjs.org/download.html,下载“Download phantomjs-1.9.0-windows.zip (7.1 MB) ”。随后打开这个压缩包,将phantomjs.exe这一个文件解压到系统路径所能找到的地方,由于之前我已经将“C:\Python27\Scripts”目录添加入PATH之中,所以我就直接解压到这个目录。如图所示:
至此,就已经在Win的环境下配置好了环境。
三、测试
随意新建一个文件,并加入如下代码:
- from selenium import webdriver
- driver = webdriver.PhantomJS()
- driver.get(‘www.baidu.com’)
- data = driver.find_element_by_id(‘cp’).text
- print data
Check Out,看看是不是这个结果:
其实,我一直不喜欢弹出来的DOS黑框,觉得这东东太影响视觉而且可能更耗时,但是在我看了官方DOC后:
我发现,是不能直接藏匿DOS弹框的。So,只好这般啦。
数据抓取的艺术(二):数据抓取程序优化
续前文:《数据抓取的艺术(一):Selenium+Phantomjs数据抓取环境配置》。
程序优化:第一步
开始:
- for i in range(startx,total):
- for j in range(starty,total):
- BASE_URL = createTheUrl([item[i],item[j]])
- driver.get(BASE_URL)
- driver = webdriver.PhantomJS()
- html = driver.page_source
- output = filterOutcome(html)
- driver.quit()
- print ‘i=’+str(i)+’tj=’+str(j)+’tresult=’+str(output)
- j += 1
每个耗时约27秒。
修改后:
- driver = webdriver.PhantomJS()
- for i in range(startx,total):
- for j in range(starty,total):
- BASE_URL = createTheUrl([item[i],item[j]])
- driver.get(BASE_URL)
- html = driver.page_source
- output = filterOutcome(html)
- print ‘i=’+str(i)+’tj=’+str(j)+’tresult=’+str(output)
- if output == -1:
- driver.quit()
- exit(0)
- j += 1
- driver.quit()
这回只分析了3个,共52秒,每个耗时约17秒,只是因为避免了重复PhantomJS的开启、运行和关闭这一过程。
程序优化:第二步
减少对角线重复请求次数:
- driver = webdriver.PhantomJS()
- for i in range(startx,total):
- if starty != -1:
- k = i
- else:
- k = starty
- for j in range(k,total):
- BASE_URL = createTheUrl([item[i],item[j]])
- driver.get(BASE_URL)
- html = driver.page_source
- output = filterOutcome(html)
- print ‘i=’+str(i)+’tj=’+str(j)+’tresult=’+str(output)
- if output == -1:
- driver.quit()
- exit(0)
- #toexcel(“C:catchoutput.xlsx”,”Sheet1″,output,i,j)
- j += 1
- driver.quit()
和上面的待分析的个数一样,花费21秒,每个耗时约7秒。如果开启excel存储,则共花费25秒,每个耗时约8秒。
程序优化:第三步
减少写入Excel的次数,提高硬盘性能。当然,数据量越大,次数越多,效果越明显。这次把Excel一直打开,每隔20个保存一次。
- #打开Excel插件
- xlsApp = win32com.client.Dispatch(“Excel.Application”)
- xlsBook = xlsApp.Workbooks.Open(‘C:catchoutput.xlsx’)
- xlsSheet = xlsBook.Sheets(‘Sheet1’)
- #开启webdirver的PhantomJS对象
- driver = webdriver.PhantomJS()
- #main()
- for i in range(startx,total):
- if starty != -1:
- k = i
- else:
- k = starty
- for j in range(k,total):
- BASE_URL = createTheUrl([item[i],item[j]])
- driver.get(BASE_URL)
- html = driver.page_source
- output = filterOutcome(html)
- print ‘i=’+str(i)+’tj=’+str(j)+’tresult=’+str(output)
- mycounter += 1
- if output == -1:
- driver.quit()
- xlsBook.Save()
- xlsBook.Close()
- xlsApp.Quit()
- exit(0)
- xlsSheet.Cells(j+1,i+1).Value = xlsSheet.Cells(i+1,j+1).Value = output
- #每隔20个保存一次,并重新清零
- if mycounter%20 == 0:
- print “~~~~~~ SAVED HERE ~~~~~~”
- xlsBook.Save()
- mycounter = 0
- j += 1
- #程序结束前的清扫工作
- driver.quit()
- xlsBook.Save()
- xlsBook.Close()
- xlsApp.Quit()
结果如下:
- >>>
- 请输入起始XaaS的序号X:0
- 请输入起始XaaS的序号Y:0
- 待处理数据记录总数:8 条
- 待处理握手总数:36 次
- 读取info.txt文件成功
- 计时开始!
- —————-
- i=0 j=0 result=14000000
- i=0 j=1 result=2
- i=0 j=2 result=8
- i=0 j=3 result=1
- i=0 j=4 result=80400
- i=0 j=5 result=2
- i=0 j=6 result=3
- i=0 j=7 result=8470
- i=1 j=1 result=394000
- i=1 j=2 result=3140
- i=1 j=3 result=9
- i=1 j=4 result=57
- i=1 j=5 result=7
- i=1 j=6 result=3790
- i=1 j=7 result=718
- i=2 j=2 result=7110000
- i=2 j=3 result=7
- i=2 j=4 result=4
- i=2 j=5 result=232000
- i=2 j=6 result=382000
- i=2 j=7 result=7970
- i=3 j=3 result=981000
- i=3 j=4 result=7
- i=3 j=5 result=1
- i=3 j=6 result=2
- i=3 j=7 result=10
- i=4 j=4 result=398000
- i=4 j=5 result=4
- i=4 j=6 result=3850
- i=4 j=7 result=1390
- i=5 j=5 result=275000
- i=5 j=6 result=32100
- i=5 j=7 result=8
- i=6 j=6 result=8050000
- i=6 j=7 result=67800
- i=7 j=7 result=738000
- —————-
- 执行成功!
- 程序耗时:72 秒
相当于每次握手,花费2秒。但这还存在一个致命伤,那就是在随着数据量的激增,以后经常要保存上万个值,每次都保存,那么次数越多写入量就会越大。只是希望微软的Excel已经能够知道:哪些是未改动数据就不必再次写入,哪些数据改动过需要写入。
程序优化:第四步
使用多线程+使用数据库。如果不用数据库,就靠读写一个单机版的Excel,效率太低,因此我考虑用Mysql或Sqlite。最后再将结果转出来。
本来是想把这部分内容放到前一篇《数据抓取的艺术(二):数据抓取程序优化》之中。但是随着任务的完成,我越来越感觉到其中深深的趣味,现总结如下:
(1)时间
时间是一个与抓取规模相形而生的因素,数据规模越大,时间消耗往往越长。所以程序优化变得相当重要,要知道抓取时间越长,出错的可能性就越大,这还不说 程序需要人工干预的情境。一旦运行中需要人工干预,时间越长,干预次数越多,出错的几率就更大了。在数据太多,工期太短的情况下,使用多线程抓取,也是一 个好办法,但这会增加程序复杂度,对最终数据准确性产生一定影响。
所以,千万不能看不起小小的调优,那怕只是0.02秒一次的改进,对于上万乃至千万级别的数据量而言,都可以节省数小时甚至数天的时间,同时也能在某种程度上降低出错的概率。于是,我得出一个原则:“程序优化要力求极致”。
这是“软”的因素。
(2)效率
呵呵,效率可以被视为是时间的另一种说辞,虽然这种说法并不全面。在这里我所谓的效率,指的是I/O效率。一旦算法优化已经完善,那么还有什么可以提 高效率以缩短程序完成的时间呢?这回抓取,我使用的是SSD硬盘,与实验室的机械硬盘相比(可惜用的是笔记本),速度得到了明显的提升。
我感觉以后处理大规模数据(或“大数据”),需要使用内存数据库。首先,使用数据库而非Excel就已经能够提高效率,如果再采用内存数据库的话,效率将得到更大幅度的攀升。当然,如果你机器内存足够大的话,可以试试Redis。
SSD硬盘与内存数据库是特别重要的两个方面。但我们也不能忽略CPU,一个好的CPU就是一颗“强劲的心”,但是笔记本的CPU一般都做过“调优” (想想吧:有限的电力,为了延时,只好吃一口“蓝色小丸药”),我觉得I3处理器性能有些低下,如果不是I5+还不如直接用奔腾双核结构。当然最好是运行 在台式机、工作站、服务器甚至云系统中。嘿嘿,下次我要试试Sae。
这是“硬”的因素。
(3)了解你的对手
常言道,知己知彼百战不殆。做数据抓取,最重要的就是了解你的对手。这一轮,我很倒霉,我碰到了Google姐,由于我的数据只能从Google上析出,所以没的选择,不过虽说她也把我折腾地够呛,但最终我还是享受到了“神秘的”幸福。
这是“人”(目标对手)的因素。
八卦一下我和Google姐一夜大战的六个回合吧。
第一回合, 简单地使用urllib2或其它库是不行的,我的血泪史说明,搜索超不过8个Google就强行和你失去连接了。重新搜这几个还可以,但接着第8个往后 走,她就又不干了,还是不能连接。姐姐一脸坏笑地看着我,就这点小把戏,还想过来扒我裤子?我十分汗颜,责怪自己没能正确估计出她那雄壮的腰围。
第二回合,我开始设计欺骗Google。由于人类访问网站,不会有极短而均匀的点击行为,所以我设计了一个小函数,每次搜索完毕,随机暂停1~3秒,这是在模仿人的访问模式。不幸再次降临到我身上,姐姐的坏笑依旧,我也没能触动到她的一根毫毛。
第三回合, 我生气了。我准备上代理,我的战术很简单,弄一大堆代理站成一队,排得一眼望不到边,失去连接我就换一个代理,再失去就再换,吓死她!结果这条路仍没走 通,这种硬碰硬的方式不是好方式,姐姐其实是过来人,从小吓大的,这招没用:一方面是我找到的可用代理IP实在有限,根本不够换的;另一方面,代理的引入 大大增加了程序出错的可能性,因为代理本来就不都靠谱。好几次,不是Google断掉的而是代理跑着跑着给断的。
第四回合, 我没气可生了。我开始深入研究,静下心来耐心学习这个领域的知识,仔细欣赏、研究Google这个熟女。功夫不负有心人,我找到了终极解决方案:浏览器。 不必再模拟手工行为(Mechanize),咱就直接用浏览器访问,看她还有什么话说。其实这样的方法很多,核心就是使用测试工具。所以你也可以使用 selenium或Windmill,也可以使用QT+Webkit等等就不一一列举了。当然,我用的是 Selenium+Phantomjs+BeautifulSoup,后来想想也不用BS,直接正则算了,但毕竟BS比较简单哈。
第五回合,我编写好程序,进行优化,特意选择深夜悄然开始执行。我用的校园网,晚上网速极快,所以嗖嗖地抽取出结果,但有个问题,就是Google对单IP短时持续访问请求是有限制的,这个限制不论是蜘蛛还是人都绕不开,她会证给你一个填写验码的证明页面,上书“不能未婚先孕”,到街道办开了证明才能让你继续。唉,对此,一开始,我太年轻,真没什么办法,只能在程序中识别这个页面,然后保存结果退出。再重新启动继续这个断点接着执行。不过由于每隔70、80个就会因验证码页面停掉一次,我的抓取速度一直不高,因为需要手工干预。
[虚拟第六回合,我用一个新线程执行抓取,当这个线程遇到验证码时,保存、退出并回馈断点参数,然后带入参数重启一个新的线程。时间原因,还没如此实现。]
第六回合, 较长的抓取时间,我在傻傻等待,时不时断点续抓一次。我受不了了,我开始做点其他的事情,比如浏览新闻、听听音乐、看看电影什么的。终于,我发现,竟然碰 不到验证码页面了。这点十分惊喜,仔细研究了这个过程,我得出结论,抓取时其他程序对抓取程序网速的影响,正好让Google认为这似乎就是人在搜索,所 以从95个左右开始,到最后的152个的共词搜索,还是那个程序,但由于我的其他休闲活动导致后半程一次都没有遇到验证码。Oh Yeah~神马重启一个新线程,NND都是浮云,一边爬取数据一边轻松生活才是王道。(只是别因下片把网速全占满了,使你的抓取程序一直处于一个等待状 态,就像《潜伏》最后一幕一样,翠平抱着孩子一直在等待一个永远不能归来的人)。所以要向普天下的男人们大声说:不要那么直接,一上来就只知道抓,一起多 看看电影、多听听歌,效果会更好!难道不是么?
最后,我站在高岗上,手握着Google的内裤,让它尽情随风舞动……
这就是技术,这也是生活!
2013年5月16日,下午4点
转自:http://blog.chinaunix.net/uid/22414998/cid-17896-list-1.html