最近需要在一個(gè)網(wǎng)站下載一批數(shù)據(jù)。但是輸入一個(gè)查詢,返回三四萬條結(jié)果,每次只能導(dǎo)出500條,而且每次還得輸入下載條目的范圍!這樣點(diǎn)擊下載,還不要了我的老命。于是乎想自動(dòng)化這個(gè)過程。
我的需求主要是兩點(diǎn):1. 要求自動(dòng)化程度高。最好有直接模擬瀏覽器鼠標(biāo)和鍵盤動(dòng)作的成熟接口,比如在文本框輸入,選擇下拉列表,單選框,復(fù)選框,點(diǎn)擊按鈕等。2. 不要求效率。因?yàn)槲乙臄?shù)據(jù)量相對來說很小。3. python下的框架。因?yàn)槠綍r(shí)幾乎主要用python。
我不太懂網(wǎng)站技術(shù),和網(wǎng)站沾邊的經(jīng)驗(yàn)只有兩個(gè):開發(fā)過一個(gè)很簡單安卓的客戶端,用python的scrapy框架寫過爬蟲來自動(dòng)爬取新聞。所以了解一些客戶端和服務(wù)端基本的交互方式、了解如何分析網(wǎng)頁源代碼、了解xpath語法。
剛開始針對這個(gè)問題,我連搜啥都不太清楚。知乎的這篇文章提供了很多有用信息:“Python 爬蟲如何獲取 JS 生成的 URL 和網(wǎng)頁內(nèi)容?” 順著它我又權(quán)衡了很多方法,最后選擇了Selenium。主要優(yōu)點(diǎn)是學(xué)習(xí)成本極小,代碼實(shí)現(xiàn)快。缺點(diǎn)是爬取效率低。想要高效率的朋友,就要花一些時(shí)間學(xué)習(xí)更復(fù)雜的工具包了。
網(wǎng)站技術(shù)
想要自動(dòng)爬取網(wǎng)頁,得了解一些基本的知識,這樣做起來更快。這里簡單介紹一下相關(guān)知識。
1. Request/response
request是客戶端向服務(wù)端發(fā)起請求。輸入一個(gè)網(wǎng)址對應(yīng)一個(gè)request動(dòng)作,這是最直觀的。爬取靜態(tài)網(wǎng)頁的內(nèi)容,只要知道網(wǎng)址就可以了。但是現(xiàn)在的網(wǎng)頁很多都是動(dòng)態(tài)的,鼠標(biāo)指向或者點(diǎn)擊網(wǎng)頁中某些元素也會(huì)觸發(fā)request動(dòng)作,從而使網(wǎng)頁動(dòng)態(tài)更新部分內(nèi)容,這部分內(nèi)容是不能直接從靜態(tài)網(wǎng)頁中獲取的。這種技術(shù)叫AJAX,不過我不太懂。這里的問題是我們可能根本不知道網(wǎng)址是什么,因此需要一些高級的接口,能處理動(dò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)頁,我們又進(jìn)了一步。

3. 網(wǎng)頁基本元素操作
前進(jìn)、后退、刷新、打開新選項(xiàng)卡、輸入網(wǎng)址等;
文本框輸入、選擇下拉列表、單選框、復(fù)選框、點(diǎn)擊按鈕等。
我這里需要模擬的操作也就這么多了,對應(yīng)的selenium接口可以參考 http://www.cnblogs.com/Ming8006/p/5727542.html。
4. Selenium介紹
一句話:Selenium是一個(gè)web應(yīng)用的自動(dòng)化測試工具集。
好多句話:Selenium 誕生于 2004 年,當(dāng)在 ThoughtWorks 工作的 Jason Huggins 在測試一個(gè)內(nèi)部應(yīng)用時(shí)。作為一個(gè)聰明的家伙,他意識到相對于每次改動(dòng)都需要手工進(jìn)行測試,他的時(shí)間應(yīng)該用得更有價(jià)值。他開發(fā)了一個(gè)可以驅(qū)動(dòng)頁面進(jìn)行交互的 Javascript 庫,能讓多瀏覽器自動(dòng)返回測試結(jié)果。那個(gè)庫最終變成了 Selenium 的核心,它是 Selenium RC(遠(yuǎn)程控制)和 Selenium IDE 所有功能的基礎(chǔ)。
實(shí)戰(zhàn)練習(xí)
1.分析數(shù)據(jù)獲取的過程
我的數(shù)據(jù)獲取過程如下:
在A頁面輸入查詢語句,點(diǎn)擊submit;瀏覽器自動(dòng)新開一個(gè)頁面,跳轉(zhuǎn)到新頁面B,在文本框輸入下載條目的范圍;點(diǎn)擊Export彈出彈窗,然后在下拉列表、單選框、復(fù)選框做一些選擇,點(diǎn)擊下載。然后瀏覽器就開始下載文件了。
網(wǎng)頁A

網(wǎng)頁B

2. 爬取過程
?A. 安裝Selenium
Selenium支持多種瀏覽器,我選用google chrome。下載地址:https://sites.google.com/a/chromium.org/chromedriver/。同時(shí),當(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)境變量,啟動(dòng)瀏覽器。
chromedriver = CHROMEDRIVER_DIR ? ?# 設(shè)置成你自己的路徑
os.environ["webdriver.chrome.driver"] = chromedriver
driver = webdriver.Chrome(executable_path=chromedriver,chrome_options=chromeptions)
# 設(shè)置隱形等待時(shí)間,因?yàn)辄c(diǎn)擊后網(wǎng)站一段時(shí)間后才能返回內(nèi)容,如果不等待會(huì)報(bào)超時(shí)異常。
driver.implicitly_wait(IMPLICIT_WAIT_TIME)
# 請求網(wǎng)頁A
driver.get("http://demo.ovid.com/demo/ovidsptools/launcher.htm")
# 在網(wǎng)頁A的兩個(gè)文本框輸入,并提交。

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)到新窗口,并將焦點(diǎn)定位到該窗口。
current_window_handle = driver.current_window_handle
for hdl in driver.window_handles: ??# selenium總是有兩個(gè)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。然后點(diǎn)擊Export。

# 注意:等待頁面加載完成后再計(jì)算下載次數(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]))
# 點(diǎn)擊 Export
export = driver.find_element_by_xpath('//*[@id="titles-display"]//input[@value="Export"]')
export.click()
# 獲取到彈窗。進(jìn)行一些設(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()
# 點(diǎn)擊下載。
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. 每次啟動(dòng)一個(gè)瀏覽器,桌面就會(huì)真的彈出一個(gè)瀏覽器。你可以清晰地看到自動(dòng)化過程是如何的。看來selenium真的就是為web程序的自動(dòng)化測試準(zhǔn)備的。另外,爬取過程中要注意屏幕保持打開。如果進(jìn)入休眠或者屏保,也會(huì)拋出異常的。
B. 模擬網(wǎng)頁操作的時(shí)候,網(wǎng)頁跳轉(zhuǎn)是很常見的場景。因此要注意網(wǎng)頁響應(yīng)時(shí)間。selenium不會(huì)等待網(wǎng)頁響應(yīng)完成再繼續(xù)執(zhí)行代碼,它會(huì)直接執(zhí)行。二者應(yīng)該是不同的進(jìn)程。這里可以選擇設(shè)置隱性等待和顯性等待。在其他操作中,隱性等待起決定性作用,在WebDriverWait..中顯性等待起主要作用,但要注意的是,最長的等待時(shí)間取決于兩者之間的大者,如果隱性等待時(shí)間 > 顯性等待時(shí)間,則該句代碼的最長等待時(shí)間等于隱性等待時(shí)間。
C. 設(shè)置下載路徑時(shí),剛開始怎么都不起作用。我懷疑是key “download.default_directory”不對,于是通過查看網(wǎng)頁源代碼,找到了key,依然一樣的。問題出在其他地方。不過這里提醒了我,以后在代碼中用字典做相關(guān)的配置時(shí),可以通過查看源代碼的方式來猜測。

D. 原以為實(shí)現(xiàn)整個(gè)過程最起碼的兩三天,因?yàn)槲艺娴牟欢?b>從開始學(xué)習(xí)到做完不到一個(gè)白天就完成了。估計(jì)是因?yàn)槲覄?dòng)手之前搜了很長時(shí)間,反復(fù)比對之后,找了個(gè)最得心應(yīng)手的工具。
E. 完成后我在github上搜了一圈,發(fā)現(xiàn)了一個(gè)神器https://github.com/voliveirajr/seleniumcrawler。 對于想爬取大量內(nèi)容的朋友,如果還不想浪費(fèi)時(shí)間學(xué)習(xí)太多web應(yīng)用底層的知識,可以結(jié)合使用Selenium+scrapy。scrapy可以負(fù)責(zé)搜索網(wǎng)頁,selenium負(fù)責(zé)處理每個(gè)網(wǎng)頁上的內(nèi)容,尤其是動(dòng)態(tài)內(nèi)容。下次我如果有需求,打算用這個(gè)思路了!
F. 分享一句話?!?b>關(guān)于爬蟲,漲經(jīng)驗(yàn)最快的方式是:學(xué)學(xué)怎么寫網(wǎng)站,你知道網(wǎng)站是什么發(fā)請求的,就知道怎么爬網(wǎng)站了!” 很簡單吧,不過這么簡單的一句話給我很大的啟發(fā)。之前就是感覺太難,一直停留在scrapy爬取靜態(tài)網(wǎng)頁的水平。而且像cookies之類的技術(shù)也看了,看一次忘一次。現(xiàn)在看來,還是因?yàn)闆]有把網(wǎng)站的整體流程梳理清楚。另一方面,也是畏懼那些繁雜的網(wǎng)站技術(shù)名詞。其實(shí)只要上網(wǎng)查查相關(guān)的概念怎么回事,慢慢就打通了。
G. 最后,完全不懂編程的人可以用一些可視化的爬蟲工具,這里有一些介紹:https://www.sdk.cn/news/4801。懂編程的且想要高效率的就需要參考其他工具了。