Python之Selenium與PhantomJS的坑

說(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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末竭宰,一起剝皮案震驚了整個(gè)濱河市空郊,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌切揭,老刑警劉巖狞甚,帶你破解...
    沈念sama閱讀 222,000評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異廓旬,居然都是意外死亡哼审,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門孕豹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)涩盾,“玉大人,你說(shuō)我怎么就攤上這事励背〈夯簦” “怎么了?”我有些...
    開封第一講書人閱讀 168,561評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵椅野,是天一觀的道長(zhǎng)终畅。 經(jīng)常有香客問(wèn)我,道長(zhǎng)竟闪,這世上最難降的妖魔是什么离福? 我笑而不...
    開封第一講書人閱讀 59,782評(píng)論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮炼蛤,結(jié)果婚禮上妖爷,老公的妹妹穿的比我還像新娘。我一直安慰自己理朋,他們只是感情好絮识,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著嗽上,像睡著了一般次舌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上兽愤,一...
    開封第一講書人閱讀 52,394評(píng)論 1 310
  • 那天彼念,我揣著相機(jī)與錄音挪圾,去河邊找鬼。 笑死逐沙,一個(gè)胖子當(dāng)著我的面吹牛哲思,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播吩案,決...
    沈念sama閱讀 40,952評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼棚赔,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了徘郭?” 一聲冷哼從身側(cè)響起靠益,我...
    開封第一講書人閱讀 39,852評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎崎岂,沒想到半個(gè)月后捆毫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,409評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡冲甘,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評(píng)論 3 341
  • 正文 我和宋清朗相戀三年绩卤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片江醇。...
    茶點(diǎn)故事閱讀 40,615評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡濒憋,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出陶夜,到底是詐尸還是另有隱情凛驮,我是刑警寧澤,帶...
    沈念sama閱讀 36,303評(píng)論 5 350
  • 正文 年R本政府宣布条辟,位于F島的核電站黔夭,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏羽嫡。R本人自食惡果不足惜本姥,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評(píng)論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望杭棵。 院中可真熱鬧婚惫,春花似錦、人聲如沸魂爪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)滓侍。三九已至蒋川,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間撩笆,已是汗流浹背捺球。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工街图, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人懒构。 一個(gè)月前我還...
    沈念sama閱讀 49,041評(píng)論 3 377
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像耘擂,于是被迫代替她去往敵國(guó)和親胆剧。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容