最近需要在一個網(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)頁搅荞,我們又進(jìn)了一步。
3. 網(wǎng)頁基本元素操作
前進(jìn)、后退咕痛、刷新痢甘、打開新選項卡、輸入網(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)用時。作為一個聰明的家伙绷雏,他意識到相對于每次改動都需要手工進(jìn)行測試头滔,他的時間應(yīng)該用得更有價值。他開發(fā)了一個可以驅(qū)動頁面進(jìn)行交互的 Javascript 庫涎显,能讓多瀏覽器自動返回測試結(jié)果坤检。那個庫最終變成了 Selenium 的核心,它是 Selenium RC(遠(yuǎn)程控制)和 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()
# 獲取到彈窗与帆。進(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()
# 點擊下載。
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)備的逢并。另外,爬取過程中要注意屏幕保持打開郭卫。如果進(jìn)入休眠或者屏保砍聊,也會拋出異常的。
B. 模擬網(wǎng)頁操作的時候贰军,網(wǎng)頁跳轉(zhuǎn)是很常見的場景玻蝌。因此要注意網(wǎng)頁響應(yīng)時間。selenium不會等待網(wǎng)頁響應(yīng)完成再繼續(xù)執(zhí)行代碼,它會直接執(zhí)行俯树。二者應(yīng)該是不同的進(jìn)程帘腹。這里可以選擇設(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。懂編程的且想要高效率的就需要參考其他工具了叹括。