使用Selenium模擬瀏覽器,實現(xiàn)自動爬取數(shù)據(jù)

最近需要在一個網(wǎng)站下載一批數(shù)據(jù)。但是輸入一個查詢,返回三四萬條結(jié)果,每次只能導(dǎo)出500條,而且每次還得輸入下載條目的范圍!這樣點擊下載,還不要了我的老命。于是乎想自動化這個過程。

我的需求主要是兩點:1. 要求自動化程度高。最好有直接模擬瀏覽器鼠標(biāo)和鍵盤動作的成熟接口,比如在文本框輸入,選擇下拉列表,單選框,復(fù)選框,點擊按鈕等。2. 不要求效率。因為我要的數(shù)據(jù)量相對來說很小。3. python下的框架。因為平時幾乎主要用python。

我不太懂網(wǎng)站技術(shù),和網(wǎng)站沾邊的經(jīng)驗只有兩個:開發(fā)過一個很簡單安卓的客戶端,用python的scrapy框架寫過爬蟲來自動爬取新聞。所以了解一些客戶端和服務(wù)端基本的交互方式、了解如何分析網(wǎng)頁源代碼、了解xpath語法。

剛開始針對這個問題,我連搜啥都不太清楚。知乎的這篇文章提供了很多有用信息:“Python 爬蟲如何獲取 JS 生成的 URL 和網(wǎng)頁內(nèi)容?” 順著它我又權(quán)衡了很多方法,最后選擇了Selenium。主要優(yōu)點是學(xué)習(xí)成本極小,代碼實現(xiàn)快。缺點是爬取效率低。想要高效率的朋友,就要花一些時間學(xué)習(xí)更復(fù)雜的工具包了。

網(wǎng)站技術(shù)

想要自動爬取網(wǎng)頁,得了解一些基本的知識,這樣做起來更快。這里簡單介紹一下相關(guān)知識。

1. Request/response

request是客戶端向服務(wù)端發(fā)起請求。輸入一個網(wǎng)址對應(yīng)一個request動作,這是最直觀的。爬取靜態(tài)網(wǎng)頁的內(nèi)容,只要知道網(wǎng)址就可以了。但是現(xiàn)在的網(wǎng)頁很多都是動態(tài)的,鼠標(biāo)指向或者點擊網(wǎng)頁中某些元素也會觸發(fā)request動作,從而使網(wǎng)頁動態(tài)更新部分內(nèi)容,這部分內(nèi)容是不能直接從靜態(tài)網(wǎng)頁中獲取的。這種技術(shù)叫AJAX,不過我不太懂。這里的問題是我們可能根本不知道網(wǎng)址是什么,因此需要一些高級的接口,能處理動態(tài)內(nèi)容。

response是服務(wù)端給客戶端的返回內(nèi)容。想要獲取靜態(tài)網(wǎng)頁內(nèi)容的話,直接從requeson里取就好了。

2. 分析網(wǎng)頁源碼

我們想要爬取網(wǎng)頁上的某一部分信息,需要知道如何能定位到它。這里需要HTML,XPATH的知識。不知道的可以上w3school 在線教程:http://www.w3school.com.cn

查看網(wǎng)頁源代碼,鼠標(biāo)指針指向網(wǎng)頁任意地方,或者指向目標(biāo)元素。右鍵鼠標(biāo),在下拉列表選擇“檢查元素”即可。如下是我右鍵“百度一下”所顯示的網(wǎng)頁源代碼,是HTML格式的,我們可以看到對應(yīng)的HTML代碼。把它提取出來,我們可能需要div//@[class="head_wrapper"]//input[@type="submit"]的語句,這是XPATH語法,很好掌握。知道如何分析網(wǎng)頁,我們又進了一步。

3. 網(wǎng)頁基本元素操作

前進、后退、刷新、打開新選項卡、輸入網(wǎng)址等;

文本框輸入、選擇下拉列表、單選框、復(fù)選框、點擊按鈕等。

我這里需要模擬的操作也就這么多了,對應(yīng)的selenium接口可以參考 http://www.cnblogs.com/Ming8006/p/5727542.html。

4. Selenium介紹

一句話:Selenium是一個web應(yīng)用的自動化測試工具集

好多句話:Selenium 誕生于 2004 年,當(dāng)在 ThoughtWorks 工作的 Jason Huggins 在測試一個內(nèi)部應(yīng)用時。作為一個聰明的家伙,他意識到相對于每次改動都需要手工進行測試,他的時間應(yīng)該用得更有價值。他開發(fā)了一個可以驅(qū)動頁面進行交互的 Javascript 庫,能讓多瀏覽器自動返回測試結(jié)果。那個庫最終變成了 Selenium 的核心,它是 Selenium RC(遠程控制)和 Selenium IDE 所有功能的基礎(chǔ)。

實戰(zhàn)練習(xí)

1.分析數(shù)據(jù)獲取的過程

我的數(shù)據(jù)獲取過程如下:

在A頁面輸入查詢語句,點擊submit;瀏覽器自動新開一個頁面,跳轉(zhuǎn)到新頁面B,在文本框輸入下載條目的范圍;點擊Export彈出彈窗,然后在下拉列表、單選框、復(fù)選框做一些選擇,點擊下載。然后瀏覽器就開始下載文件了。

網(wǎng)頁A

網(wǎng)頁B

2. 爬取過程

?A. 安裝Selenium

Selenium支持多種瀏覽器,我選用google chrome。下載地址:https://sites.google.com/a/chromium.org/chromedriver/。同時,當(dāng)然要在python中安裝selenium。 命令行輸入pip install senenium 即可安裝。

?B. 配置環(huán)境變量

這一步需要將chromedriver的保存路徑配置到操作系統(tǒng)的環(huán)境變量中,好讓selenium能找到chromedriver。windows下配置環(huán)境變量PATH,linux或者mac可以選擇配置到 .bash_rc中。配置方法很多,自行百度。

我用的是mac,不知為什么配置了不起作用!后來發(fā)現(xiàn)只有在代碼里設(shè)置才能起作用。

C. 核心代碼(python)

# 設(shè)置下載路徑,將路徑配置到ChromeOptions。

chromeptions = webdriver.ChromeOptions()

prefs = {'profile.default_content_settings.popups':0,'download.default_directory': query_dir}

chromeptions.add_experimental_option('prefs', prefs)

# 設(shè)置環(huán)境變量,啟動瀏覽器。

chromedriver = CHROMEDRIVER_DIR ? ?# 設(shè)置成你自己的路徑

os.environ["webdriver.chrome.driver"] = chromedriver

driver = webdriver.Chrome(executable_path=chromedriver,chrome_options=chromeptions)

# 設(shè)置隱形等待時間,因為點擊后網(wǎng)站一段時間后才能返回內(nèi)容,如果不等待會報超時異常。

driver.implicitly_wait(IMPLICIT_WAIT_TIME)

# 請求網(wǎng)頁A

driver.get("http://demo.ovid.com/demo/ovidsptools/launcher.htm")

# 在網(wǎng)頁A的兩個文本框輸入,并提交。

driver.find_element_by_name('D').clear()

driver.find_element_by_name('D').send_keys('mesz')

driver.find_element_by_name('SEARCH').clear()

driver.find_element_by_name('SEARCH').send_keys(str_search_query)

driver.find_element_by_name('ovid').click()

# ?跳轉(zhuǎn)到新窗口,并將焦點定位到該窗口。

current_window_handle = driver.current_window_handle

for hdl in driver.window_handles: ??# selenium總是有兩個handle

? ? if hdl != current_window_handle:

? ? ? ? new_window_handle = hdl

driver.switch_to.window(new_window_handle)

driver.implicitly_wait(IMPLICIT_WAIT_TIME)

# 獲取到網(wǎng)頁。首先獲取返回的總條目數(shù),然后提取文本框輸入下載條目的范圍,如1-500。然后點擊Export。

# 注意:等待頁面加載完成后再計算下載次數(shù)

search_ret_num = WebDriverWait(driver, EXPLICIT_WAIT_TIME, ?EXPLICIT_WAIT_INTERVAL).until(EC.presence_of_element_located((By.XPATH,'//*[@id="searchaid-numbers"]')))

search_ret_num =int(re.findall(r'\d+', search_ret_num.text.encode('utf-8'))[0])

?list_range = chunks_by_element(range(1, search_ret_num+1), DOWNLOAD_NUM_PER_TIME)

for item in list_range:

? ? download_range = driver.find_element_by_xpath('//*[@id="titles-display"]//input[@title="Range"]')

? ? download_range.clear()

? ? download_range.send_keys('{}-{}'.format(item[0], item[-1]))

# 點擊 Export

export = driver.find_element_by_xpath('//*[@id="titles-display"]//input[@value="Export"]')

export.click()

# 獲取到彈窗。進行一些設(shè)置。

driver.switch_to.alert

WebDriverWait(driver, EXPLICIT_WAIT_TIME, EXPLICIT_WAIT_INTERVAL).until(EC.presence_of_element_located((By.XPATH,'//div[@id="export-citation-popup"]')))

# 設(shè)置下載文件的一些配置

export_to_options = driver.find_element_by_xpath('//select[@id="export-citation-export-to-options"]')

export_to_options.find_element_by_xpath('//option[@value="xml"]').click()# XML

# 設(shè)置 citation content radio

citation_options = driver.find_element_by_xpath('//ul[@id="export-citation-options"]')

citation_options.find_element_by_xpath('//input[@value="ALL"]').click()#? Complete Reference

# 設(shè)置 include check-box

citation_include = driver.find_element_by_xpath('//div[@id="export-citation-include"]')

ifcitation_include.find_element_by_xpath('//input[@name="externalResolverLink"]').is_selected():# Link to External Resolver

citation_include.find_element_by_xpath('//input[@name="externalResolverLink"]').click()

ifcitation_include.find_element_by_xpath('//input[@name="jumpstartLink"]').is_selected():# Include URL

citation_include.find_element_by_xpath('//input[@name="jumpstartLink"]').click()

ifcitation_include.find_element_by_xpath('//input[@name="saveStrategy"]').is_selected():# Search History

citation_include.find_element_by_xpath('//input[@name="saveStrategy"]').click()

# 點擊下載。

download = driver.find_element_by_xpath('//div[@class ="export-citation-buttons"]')

download.click()

finally:

sleep(30)# wait for finishing downloading the last file

# driver.implicitly_wait(30) # doesn't work!

driver.quit()

return

3. 小貼士

A. 每次啟動一個瀏覽器,桌面就會真的彈出一個瀏覽器。你可以清晰地看到自動化過程是如何的。看來selenium真的就是為web程序的自動化測試準(zhǔn)備的。另外,爬取過程中要注意屏幕保持打開。如果進入休眠或者屏保,也會拋出異常的。

B. 模擬網(wǎng)頁操作的時候,網(wǎng)頁跳轉(zhuǎn)是很常見的場景。因此要注意網(wǎng)頁響應(yīng)時間。selenium不會等待網(wǎng)頁響應(yīng)完成再繼續(xù)執(zhí)行代碼,它會直接執(zhí)行。二者應(yīng)該是不同的進程。這里可以選擇設(shè)置隱性等待和顯性等待。在其他操作中,隱性等待起決定性作用,在WebDriverWait..中顯性等待起主要作用,但要注意的是,最長的等待時間取決于兩者之間的大者,如果隱性等待時間 > 顯性等待時間,則該句代碼的最長等待時間等于隱性等待時間。

C. 設(shè)置下載路徑時,剛開始怎么都不起作用。我懷疑是key “download.default_directory”不對,于是通過查看網(wǎng)頁源代碼,找到了key,依然一樣的。問題出在其他地方。不過這里提醒了我,以后在代碼中用字典做相關(guān)的配置時,可以通過查看源代碼的方式來猜測。

D. 原以為實現(xiàn)整個過程最起碼的兩三天,因為我真的不懂。從開始學(xué)習(xí)到做完不到一個白天就完成了。估計是因為我動手之前搜了很長時間,反復(fù)比對之后,找了個最得心應(yīng)手的工具。

E. 完成后我在github上搜了一圈,發(fā)現(xiàn)了一個神器https://github.com/voliveirajr/seleniumcrawler。 對于想爬取大量內(nèi)容的朋友,如果還不想浪費時間學(xué)習(xí)太多web應(yīng)用底層的知識,可以結(jié)合使用Selenium+scrapy。scrapy可以負(fù)責(zé)搜索網(wǎng)頁,selenium負(fù)責(zé)處理每個網(wǎng)頁上的內(nèi)容,尤其是動態(tài)內(nèi)容。下次我如果有需求,打算用這個思路了!

F. 分享一句話。“關(guān)于爬蟲,漲經(jīng)驗最快的方式是:學(xué)學(xué)怎么寫網(wǎng)站,你知道網(wǎng)站是什么發(fā)請求的,就知道怎么爬網(wǎng)站了!” 很簡單吧,不過這么簡單的一句話給我很大的啟發(fā)。之前就是感覺太難,一直停留在scrapy爬取靜態(tài)網(wǎng)頁的水平。而且像cookies之類的技術(shù)也看了,看一次忘一次。現(xiàn)在看來,還是因為沒有把網(wǎng)站的整體流程梳理清楚。另一方面,也是畏懼那些繁雜的網(wǎng)站技術(shù)名詞。其實只要上網(wǎng)查查相關(guān)的概念怎么回事,慢慢就打通了。

G. 最后,完全不懂編程的人可以用一些可視化的爬蟲工具,這里有一些介紹:https://www.sdk.cn/news/4801。懂編程的且想要高效率的就需要參考其他工具了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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