最近需要在一個網(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。懂編程的且想要高效率的就需要參考其他工具了。