說(shuō)到python爬蟲,剛開始主要用urllib庫(kù)靡菇,雖然接口比較繁瑣重归,但也能實(shí)現(xiàn)基本功能。等見識(shí)了requests庫(kù)的威力后镰官,便放棄urllib庫(kù)提前,并且也不打算回去了。但對(duì)一些動(dòng)態(tài)加載的網(wǎng)站泳唠,經(jīng)常要先分析請(qǐng)求狈网,再用requests模擬,比較麻煩笨腥。直到遇到了selenium庫(kù)拓哺,才發(fā)現(xiàn)爬動(dòng)態(tài)網(wǎng)頁(yè)也可以這么簡(jiǎn)單,果斷入坑脖母!
selenium是python的一個(gè)第三方自動(dòng)化測(cè)試庫(kù)士鸥,雖然是測(cè)試庫(kù),卻也非常適合用來(lái)寫爬蟲谆级,而phantomJS是其子包webdriver下面的一個(gè)瀏覽器烤礁。phantomJS本身是一個(gè)無(wú)頭瀏覽器(headless browser),也稱無(wú)界面瀏覽器肥照〗抛校可以在通過(guò)官網(wǎng)下載運(yùn)行phantomjs.exe,簡(jiǎn)單幾行代碼也能訪問(wèn)網(wǎng)頁(yè)舆绎,爬取數(shù)據(jù)鲤脏。但本文主要討論通過(guò)python的selenium庫(kù)使用phantomJS。除了phantomJS瀏覽器吕朵,webdriver還整合了Chrome猎醇、Firefox、IE等瀏覽器努溃,并提供了操作這些瀏覽器的接口硫嘶。
由于phantomJS是無(wú)界面瀏覽器,不需要界面的同時(shí)占用的內(nèi)存也相對(duì)較小茅坛,更適用于大規(guī)模多進(jìn)程爬數(shù)據(jù)(試想音半,如果開幾十個(gè)Chrome進(jìn)程爬數(shù)據(jù),那真是內(nèi)存噩夢(mèng)9北汀)曹鸠。本文主要討論使用selenium phantomJS過(guò)程中遇到的bug,而不是selenium phantomJS使用教程斥铺,有需要了解selenium基本用法的同學(xué)彻桃,請(qǐng)移步官方文檔。
個(gè)人用phantomJS爬數(shù)據(jù)有一段時(shí)間了晾蜘,爬蟲程序也大致完工了邻眷,過(guò)程中遇到了很多坑眠屎,統(tǒng)一總結(jié)如下。
1. 查看phantomJS文檔
前面提到肆饶,phantomJS是selenium子包webdriver下面多個(gè)瀏覽器中的一個(gè)改衩,而selenium包對(duì)不同的瀏覽器都提供了統(tǒng)一的接口,所以直接查看selenium的官方文檔即可驯镊,也有對(duì)應(yīng)的中文文檔葫督。文檔內(nèi)容不多,但很全面板惑。遇到不懂的問(wèn)題橄镜,先看文檔肯定沒錯(cuò)。
這里需要注意的是冯乘,百度搜索phantomJS得到的結(jié)果只是phantomJS的官方文檔洽胶,而phantomJS是一個(gè)獨(dú)立的無(wú)界面瀏覽器,也稱JS模擬器裆馒,本來(lái)就獨(dú)立于python姊氓。我們需要的是phantomJS的python接口,也就是通過(guò)python調(diào)用phantomJS喷好,所以只需查看selenium的webdriver文檔他膳。
當(dāng)然,官方文檔很全面绒窑,但也相對(duì)繁雜。python有個(gè)查看文檔的小技巧舔亭,直接使用help()
就能查看某個(gè)對(duì)象的幫助文檔些膨。比如help(driver)
即可直接查看driver這個(gè)對(duì)象的文檔,包括其內(nèi)部函數(shù)钦铺、變量的說(shuō)明订雾。如果driver是一個(gè)phantomJS對(duì)象,那么會(huì)顯示phantomJS瀏覽器對(duì)象的函數(shù)和變量的文檔矛洞,具體內(nèi)容和官方文檔一樣洼哎。對(duì)所有的python對(duì)象都可以這樣干,非常便捷沼本。似乎現(xiàn)在各種IDE也有這個(gè)功能:當(dāng)鼠標(biāo)懸停在某個(gè)對(duì)象上噩峦,就顯示該對(duì)象的幫助文檔。不過(guò)多掌握個(gè)方法總歸沒錯(cuò)抽兆。
2. phantomJS的配置問(wèn)題
selenium官方文檔中识补,phantomJS對(duì)象的幫助文檔很詳細(xì),但當(dāng)涉及phantomJS瀏覽器的配置辫红,比如user-agent偽裝凭涂、代理祝辣、超時(shí)返回等選項(xiàng)時(shí),有用的信息就非常少了切油。結(jié)合網(wǎng)上的資料和自己遇到的各種坑蝙斜,我總結(jié)了常用的phantomJS配置選項(xiàng),對(duì)普通的爬蟲來(lái)說(shuō)澎胡,應(yīng)該夠用了孕荠。
from selenium import webdriver
# 引入配置對(duì)象DesiredCapabilities
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
dcap = dict(DesiredCapabilities.PHANTOMJS)
#從USER_AGENTS列表中隨機(jī)選一個(gè)瀏覽器頭,偽裝瀏覽器
dcap["phantomjs.page.settings.userAgent"] = (random.choice(USER_AGENTS))
# 不載入圖片滤馍,爬頁(yè)面速度會(huì)快很多
dcap["phantomjs.page.settings.loadImages"] = False
# 設(shè)置代理
service_args = ['--proxy=127.0.0.1:9999','--proxy-type=socks5']
#打開帶配置信息的phantomJS瀏覽器
driver = webdriver.PhantomJS(phantomjs_driver_path, desired_capabilities=dcap,service_args=service_args)
# 隱式等待5秒岛琼,可以自己調(diào)節(jié)
driver.implicitly_wait(5)
# 設(shè)置10秒頁(yè)面超時(shí)返回,類似于requests.get()的timeout選項(xiàng)巢株,driver.get()沒有timeout選項(xiàng)
# 以前遇到過(guò)driver.get(url)一直不返回槐瑞,但也不報(bào)錯(cuò)的問(wèn)題,這時(shí)程序會(huì)卡住阁苞,設(shè)置超時(shí)選項(xiàng)能解決這個(gè)問(wèn)題困檩。
driver.set_page_load_timeout(10)
# 設(shè)置10秒腳本超時(shí)時(shí)間
driver.set_script_timeout(10)
3. phantomJS的并發(fā)問(wèn)題
phantomJS爬數(shù)據(jù)比較慢,并發(fā)編程幾乎是必選項(xiàng)那槽。最初悼沿,我考慮采用多線程/協(xié)程的方式,畢竟對(duì)于這種IO密集型的程序骚灸,多線程/協(xié)程比較合適糟趾。但多次測(cè)試下來(lái),程序卻遇到各種問(wèn)題甚牲,有時(shí)能成功運(yùn)行义郑,有時(shí)卻不能。嘗試將phantomJS改成Chrome丈钙,程序居然能正常運(yùn)行非驮,這基本確定是phantomJS的鍋了。所以雏赦,如果需要并發(fā)編程提高效率劫笙,用Chrome比較好,雖然內(nèi)存占用相對(duì)較多星岗,況且經(jīng)下面簡(jiǎn)友提醒填大,在沒界面的主機(jī)上也可以跑Chrome,那自然更好了俏橘。
在網(wǎng)上仔細(xì)查找了相關(guān)資料(這玩意的中文資料極少栋盹,只能去國(guó)外技術(shù)論壇潛水),原來(lái)phantomJS本身在多線程方面還有很多bug,建議使用多進(jìn)程例获,具體什么原因有時(shí)間再去了解汉额。
關(guān)于多進(jìn)程,推薦使用multiprocessing庫(kù)榨汤,簡(jiǎn)潔蠕搜、高效!下面幾行代碼便實(shí)現(xiàn)了多進(jìn)程并發(fā)收壕。
from multiprocessing import Pool
pool = Pool(8)
data_list = pool.map(get, url_list)
pool.close()
pool.join()
4. phantomJS進(jìn)程不自動(dòng)退出問(wèn)題
話說(shuō)妓灌,一開始我寫好程序后,先在本地測(cè)試了一段時(shí)間蜜宪,確認(rèn)程序各方面都沒問(wèn)題后虫埂,直接扔阿里云主機(jī)上跑了。過(guò)了一段時(shí)間圃验,查了下程序運(yùn)行日志掉伏,很好,一切如常澳窑。于是我就高高興興地摸魚去了斧散。
第二天準(zhǔn)備登錄主機(jī)驗(yàn)收程序時(shí),卻發(fā)現(xiàn)居然無(wú)法登錄摊聋!啥鸡捐,無(wú)法登錄?難不成這個(gè)小爬蟲程序還能把主機(jī)搞崩麻裁?我先在阿里云后臺(tái)查看了主機(jī)的運(yùn)行日志箍镜,發(fā)現(xiàn)主機(jī)的內(nèi)存使用越來(lái)越高,應(yīng)該是內(nèi)存耗盡后煎源,強(qiáng)制關(guān)機(jī)了鹿寨。似乎是程序沒有回收內(nèi)存,導(dǎo)致占用的內(nèi)存越來(lái)越大薪夕。明確大致原因后,就是痛苦的查bug過(guò)程了赫悄。
由于bug涉及內(nèi)存的使用原献,我自然地想到了用top命令查看進(jìn)程的內(nèi)存使用情況。先運(yùn)行程序埂淮,然后運(yùn)行top命令姑隅,實(shí)時(shí)檢測(cè)程序的內(nèi)存使用情況。一開始程序占用內(nèi)存在正常范圍倔撞,只有一個(gè)phantomJS進(jìn)程在運(yùn)行讲仰,似乎沒有什么不對(duì)。但隨著時(shí)間的增長(zhǎng)痪蝇,內(nèi)存中居然同時(shí)有好幾個(gè)phantomJS進(jìn)程在運(yùn)行鄙陡,內(nèi)存所拭岱浚空間越來(lái)越小趁矾!但根據(jù)程序的邏輯耙册,任何時(shí)候都只有一個(gè)phantomJS進(jìn)程在爬數(shù)據(jù)。我意識(shí)到可能是由于phantomJS進(jìn)程沒有正常關(guān)閉毫捣,所以在內(nèi)存中駐留的phantomJS進(jìn)程越來(lái)越多详拙,最終吃光了內(nèi)存。
帶著這個(gè)問(wèn)題蔓同,我重新檢查了一次代碼饶辙,尤其在程序異常退出的地方。最終找到了類似下面的代碼:
try:
self.driver.get(url)
self.wait_()
return True
except Exception as e:
return False
程序的邏輯是:如果在打開url的過(guò)程中報(bào)錯(cuò)斑粱,那么就返回False弃揽,反之返回True。
似乎直接return False
的處理太粗心了珊佣。我嘗試著在return False前加上一行self.driver.quit()
蹋宦。再次運(yùn)行程序,并用top查看內(nèi)存使用情況咒锻,發(fā)現(xiàn)程序的內(nèi)存使用一直都在正常范圍內(nèi)冷冗,并沒有出現(xiàn)多個(gè)phantomJS進(jìn)程的情況,問(wèn)題搞定惑艇!后面在網(wǎng)上找到的資料也證實(shí)了我的猜想:主程序退出后蒿辙,selenium不保證phantomJS也成功退出,最好手動(dòng)關(guān)閉phantomJS進(jìn)程滨巴。
5. 其他問(wèn)題
5.1 不同frame間的轉(zhuǎn)換
有時(shí)思灌,phantomJS獲得的頁(yè)面源碼的確存在某元素,但通過(guò)find_element_by_xpath()
等定位函數(shù)卻無(wú)法獲得該元素對(duì)象恭取,總是提示“元素不存在”的錯(cuò)誤泰偿。遇到這種情況,除了檢查元素節(jié)點(diǎn)路徑是否正確外蜈垮,還應(yīng)該分析頁(yè)面源碼耗跛,檢查元素是否被包裹在一個(gè)特定的frame中,如果是后者攒发,那么在使用查找函數(shù)前调塌,需要額外的處理。
比如網(wǎng)頁(yè)源碼中有如下代碼:
<iframe id="topmenuFrame" width="100%" scrolling="no" height="100%" src="topmenu.aspx?>
<div id="haha">text</div>
</iframe>
假如你想要獲取id="haha"
的div標(biāo)簽惠猿,直接通過(guò)driver.find_element_by_id('haha')
就會(huì)提示“元素不存在“的錯(cuò)誤羔砾。
這時(shí)需要使用driver.switch_to_frame(driver.find_element_by_id``("topmenuFrame"))
,即先進(jìn)入id為topmenuFrame的frame,然后再執(zhí)行driver.find_element_by_id("haha")
姜凄,就能正確獲得該元素了政溃。
需要注意的是,切換到這個(gè)frame之后檀葛,只能訪問(wèn)當(dāng)前frame的內(nèi)容玩祟,如果想要回到默認(rèn)的內(nèi)容范圍,相當(dāng)于默認(rèn)的frame屿聋,還需要使用driver.switch_to_default_content()
空扎。
頁(yè)面中有多個(gè)frame時(shí),要注意frame之間的切換润讥。
5.2 implicit_wait转锈、WebDriverWait不一定靠譜
宿舍哥們用phantomJS爬數(shù)據(jù)時(shí),遇到了一個(gè)匪夷所思的bug楚殿。起初撮慨,他寫了個(gè)很簡(jiǎn)單的程序,從個(gè)方面來(lái)看都沒問(wèn)題脆粥,但實(shí)際運(yùn)行卻提示各種錯(cuò)誤砌溺,讓人十分費(fèi)解。折騰大半天之后变隔,他直接注釋掉自己不太熟悉的implicit_wait()
规伐,改用time.sleep()
作延時(shí),程序居然就能正確運(yùn)行了匣缘!原來(lái)implicit_wait()
有bug猖闪。同樣的,對(duì)于WebDriverWait
肌厨,大家使用時(shí)也要特別注意培慌。
看來(lái)python的selenium庫(kù)不是很成熟,還存在一些問(wèn)題柑爸,一些函數(shù)的實(shí)際運(yùn)行情況并不是預(yù)期的那樣吵护,在查bug時(shí),要留意這些問(wèn)題表鳍。
6. 總結(jié)
總的來(lái)說(shuō)馅而,selenium庫(kù)簡(jiǎn)單,容易上手进胯,是爬動(dòng)態(tài)網(wǎng)頁(yè)的殺手級(jí)武器,但對(duì)phantomJS瀏覽器的支持還不是特別完善原押。當(dāng)然胁镐,了解存在的問(wèn)題,并找到對(duì)應(yīng)的解決方法,就能發(fā)揮phantomJS的威力了盯漂。
以上就是我個(gè)人這段時(shí)間的phantomJS使用小結(jié)颇玷,雖然不是很全面,也不確保完全準(zhǔn)確就缆,算是對(duì)我這段學(xué)習(xí)歷程的總結(jié)吧帖渠,希望對(duì)大家有用。
非常感謝大佬的指點(diǎn)...
作者:Rabin_xie
鏈接:http://www.reibang.com/p/9d408e21dc3a