目錄
- 前言
- 問題分析
- Selenium簡介
- Selenium安裝
- Selenium基礎(chǔ)知識
- Xpath
- 動手實(shí)戰(zhàn)
- 總結(jié)
前言
大家應(yīng)該都有過從百度文庫下載東西的經(jīng)歷,對于下載需要下載券的文章,我們可以辦理文庫VIP;又或者使用“冰點(diǎn)文庫”這樣的下載軟件辱挥,但是對于會爬蟲的人來說,當(dāng)然就是把他爬下來。
問題分析
我們以如何下載下面這篇文章為例乳蓄,分析問題:
URL:https://wenku.baidu.com/view/aa31a84bcf84b9d528ea7a2c.html
如果只是純粹爬取這種文章還是挺好爬的,但是我們翻到文章的最下方夕膀,我們可以看到如下內(nèi)容:
我們可以看到我們需要點(diǎn)擊繼續(xù)閱讀才能顯示后續(xù)的內(nèi)容虚倒。照之前的思路,我們當(dāng)然是抓包分析产舞,但是抓包后我們卻發(fā)現(xiàn):
Request URL太長魂奥,而且除了后面expire時間信息外其他信息不好解決,所以我們果斷放棄這個方法易猫。
問題:獲取當(dāng)前頁好辦耻煤,怎么獲取接下來頁面的內(nèi)容?
帶著這個思考准颓,Selenium神器走入了我的視線哈蝇。
預(yù)備知識
Selenium簡介
Selenium是什么?一句話攘已,自動化測試工具炮赦。它支持各種瀏覽器,包括Chorome,Safari,Firefox等主流界面式瀏覽器样勃,如果你在這些瀏覽器里面安裝一個Selenium的插件吠勘,那么便可以方便地實(shí)現(xiàn)Web界面的測試。換句話說叫Selenium支持這些瀏覽器驅(qū)動峡眶。Selenium支持多種語言開發(fā)剧防,比如Java,C辫樱,Ruby等等诵姜,而對于Python,當(dāng)然也是支持的搏熄。
安裝
pip3 install selenium
基礎(chǔ)知識
詳細(xì)內(nèi)容可查看官方文檔http://selenium-python.readthedocs.io/index.html
小試牛刀
我們先來一個小例子感受一下Selenium棚唆,我們用Chorme瀏覽器來測試。
from selenium import webdriver
browser = webdriver.Chrome()
browser.get('http://www.baidu.com/')
運(yùn)行這段代碼心例,會自動打開瀏覽器宵凌,然后訪問百度。
如果程序執(zhí)行錯誤止后,瀏覽器沒有打開瞎惫,那么應(yīng)該是沒有安裝Chrome瀏覽器或者Chrome驅(qū)動沒有配置在環(huán)境變量里溜腐,大家自行下載驅(qū)動,然后將驅(qū)動文件路徑配置在環(huán)境變量即可瓜喇。
驅(qū)動下載地址:https://sites.google.com/a/chromium.org/chromedriver/downloads
當(dāng)然挺益,你不設(shè)置環(huán)境變量也是可以的,程序也可以這樣寫:
from selenium import webdriver
browser = webdriver.Chrome('path\to\your\chromedriver.exe')
browser.get('http://www.baidu.com/')
上面的path\to\your\chromedriver.exe
是你爹chrome驅(qū)動文件位置乘寒,可以使用絕對路徑望众。我們通過驅(qū)動的位置傳遞參數(shù),也可以調(diào)用驅(qū)動伞辛,結(jié)果如下圖所示:
模擬提交
下面的代碼實(shí)現(xiàn)了模擬提交搜索的功能烂翰,首先等頁面加載完成,然后輸入到搜索框文本蚤氏,點(diǎn)擊提交甘耿,然后使用page_source打印提交后的頁面的信息。
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
driver = webdriver.Chrome()
driver.get("http://www.python.org")
assert "Python" in driver.title
elem = driver.find_element_by_name("q")
elem.send_keys("pycon")
elem.send_keys(Keys.RETURN)
print(driver.page_source)
全自動效果圖如下所示:
其中driver.get方法會打開請求的URL,WebDriver會等待頁面完全加載完成之后才會返回竿滨,即程序會等待頁面的所有內(nèi)容加載完畢佳恬,JS渲染完畢之后才繼續(xù)往下執(zhí)行。注意于游,如果這里用到了特別多的Ajax的話殿怜,程序可能不知道是否已經(jīng)完全加載完畢。
WebDriver提供了許多尋找網(wǎng)頁元素的方法曙砂,譬如find_element_by_*
的方法。例如一個輸入框可以通過find_element_by_name
方法尋找name屬性來確定骏掀。
然后我們輸入文本并模擬了點(diǎn)擊回車鸠澈,就像我們敲擊鍵盤一樣。我們可以利用Keys這個類來模擬鍵盤輸入截驮。
最后最重要的一點(diǎn)是可以獲取網(wǎng)頁渲染后的源代碼笑陈。通過輸出page_source
屬性即可。這樣葵袭,我們就可以做到網(wǎng)頁的動態(tài)爬去了涵妥。
元素選取
關(guān)于元素選取,有如下的API:
單個元素選绕挛:
find_element_by_id
find_element_by_name
find_element_by_xpath
find_element_by_link_text
find_element_by_partial_link_text
find_element_by_tag_name
find_element_by_class_name
find_element_by_css_selector
多個元素選扰钔:
find_elements_by_name
find_elements_by_xpath
find_elements_by_link_text
find_elements_by_partial_link_text
find_elements_by_tag_name
find_elements_by_class_name
find_elements_by_css_selector
另外還可以利用By類來確定那種選擇方式:
from selenium.webdriver.common.by import By
driver.find_element(By.XPATH, '//button[text()="Some text"]')
driver.find_elements(By.XPATH, '//button')
By類的一些屬性如下:
find_elements_by_name
find_elements_by_xpath
find_elements_by_link_text
find_elements_by_partial_link_text
find_elements_by_tag_name
find_elements_by_class_name
find_elements_by_css_selector
這些方法跟JS的一些方法有相似之處,find_element_by_id
鹉勒,就是根據(jù)標(biāo)簽的id屬性查找元素帆锋,find_element_by_name
,就是根據(jù)標(biāo)簽的name屬性查找元素禽额。舉個簡單的例子锯厢,比如我想找到下面這個元素:
<input type="text" name="passwd" id="passwd-id" />
我們可以這樣獲取他:
element = driver.find_element_by_id("passwd-id")
element = driver.find_element_by_name("passwd")
element = driver.find_elements_by_tag_name("input")
element = driver.find_element_by_xpath("http://input[@id='passwd-id']")
前三個都很好理解皮官,最后一個xpath是什么意思?xpath是一個非常強(qiáng)大的元素查找方式实辑,使用這種方法幾乎可以定位到頁面上的任意元素捺氢,在后面我會單獨(dú)講解。
界面交互
通過元素選取剪撬,我們能夠找到元素的位置摄乒,我們可以根據(jù)這個元素的位置進(jìn)行相應(yīng)的事件操作,例如輸入文本框內(nèi)容婿奔、鼠標(biāo)點(diǎn)擊缺狠、填充表單、元素拖拽等等萍摊。由于篇幅原因挤茄,我就不一個一個講解了,主要講解本次實(shí)戰(zhàn)用到的鼠標(biāo)點(diǎn)擊冰木,更詳細(xì)的內(nèi)容可以查看官方文檔穷劈。
elem = driver.find_element_by_xpath("http://a[@data-fun='next']")
elem.click()
比如上面這句話,我使用find_element_by_xpath()
找到元素位置踊沸,暫且不用理會這句話是什么意思歇终,暫且理解為找到了一個按鍵的位置。然后我們使用click()
方法逼龟,就可以觸發(fā)鼠標(biāo)左鍵點(diǎn)擊時間评凝。是不是很簡單?但是有一點(diǎn)需要注意腺律,就是在點(diǎn)擊的時候奕短,元素不能有遮擋。什么意思匀钧?就是說我在點(diǎn)擊這個按鍵之前翎碑,窗口最好移動到那里,因?yàn)槿绻@個按鍵被其他元素遮擋之斯,click()
就觸發(fā)異常日杈。因此穩(wěn)妥起見,在觸發(fā)鼠標(biāo)左鍵單擊事件之前佑刷,滑動窗口莉擒,移動到按鍵上方的一個元素位置:
page = driver.find_elements_by_xpath("http://div[@class='page']")
driver.execute_script('arguments[0].scrollIntoView();', page[-1]) #拖動到可見的元素去
上面的代碼,就是將窗口滑動到page這個位置瘫絮,在這個位置啰劲,我們能夠看到我們需要點(diǎn)擊的按鍵。
添加User-Agent
使用webdriver,是可以更改User-Agent的檀何,代碼如下:
from selenium import webdriver
options = webdriver.ChromeOptions()
options.add_argument('user-agent="Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19"')
driver = webdriver.Chrome(chrome_options=options)
driver.get('https://www.baidu.com/')
Xpath
Xpath是很強(qiáng)大的元素查找方式蝇裤,使用這種方法幾乎可以定位到頁面上的任意元素廷支。在正式開始使用之前,我們先了解下什么是Xpath栓辜。XPath是XML Path的簡稱恋拍,由于HTML文檔本身就是一個標(biāo)準(zhǔn)的XML頁面,所以我們可以使用XPath的語法來定位頁面元素藕甩。
假設(shè)我們現(xiàn)在以圖所示HTML代碼為例施敢,要引用對應(yīng)的對象,XPath語法如下:
絕對路徑寫法(只有一種)狭莱,寫法如下:
引用頁面上的form元素(即源碼中的第3行):
/html/body/form[1]
注意:
- 元素的xpath絕對路徑可通過firebug直接查詢僵娃。
- 般不推薦使用絕對路徑的寫法,因?yàn)橐坏╉撁娼Y(jié)構(gòu)發(fā)生變化腋妙,該路徑也隨之失效默怨,必須重新寫。
- 絕對路徑以單/號表示骤素,而下面要講的相對路徑則以//表示匙睹,這個區(qū)別非常重要。另外需要多說一句的是济竹,當(dāng)xpath的路徑以/開頭時痕檬,表示讓Xpath解析引擎從文檔的根節(jié)點(diǎn)開始解析。當(dāng)xpath路徑以//開頭時送浊,則表示讓xpath引擎從文檔的任意符合的元素節(jié)點(diǎn)開始進(jìn)行解析梦谜。而當(dāng)/出現(xiàn)在xpath路徑中時,則表示尋找父節(jié)點(diǎn)的直接子節(jié)點(diǎn)袭景,當(dāng)//出現(xiàn)在xpath路徑中時唁桩,表示尋找父節(jié)點(diǎn)下任意符合條件的子節(jié)點(diǎn),不管嵌套了多少層級(這些下面都有例子浴讯,大家可以參照來試驗(yàn))。弄清這個原則蔼啦,就可以理解其實(shí)xpath的路徑可以絕對路徑和相對路徑混合在一起來進(jìn)行表示榆纽,想怎么表示就怎么表示。
下面是相對路徑的引用寫法:
- 查找頁面根元素:
//
- 查找頁面上所有的input元素:
//input
- 查找頁面上第一個form元素內(nèi)的直接子input元素(即只包括form元素的下一級input元素捏肢,使用絕對路徑表示奈籽,單/號):
//form[1]/input
- 查找頁面上第一個form元素內(nèi)的所有子input元素(只要在form元素內(nèi)的input都算,不管還嵌套了多少個其他標(biāo)簽鸵赫,使用相對路徑表示衣屏,雙//號):
//form[1]//input
- 查找頁面上第一個form元素:
//form[1]
- 查找頁面上id為loginForm的form元素:
//form[@id='loginForm']
- 查找頁面上具有name屬性為username的input元素:
//input[@name='username']
- 查找頁面上id為loginForm的form元素下的第一個input元素:
//form[@id='loginForm']/input[1]
- 查找頁面具有name屬性為contiune并且type屬性為button的input元素:
//input[@name='continue'][@type='button']
- 查找頁面上id為loginForm的form元素下第4個input元素:
//form[@id='loginForm']/input[4]
Xpath功能很強(qiáng)大,所以也可以寫的更負(fù)責(zé)一些辩棒,如下圖html源碼:
如果我們現(xiàn)在要引用id為“J_password”的input元素狼忱,該怎么寫呢膨疏?我們可以像下面這樣寫:
//*[@id='J_login_form']/dl/dt/input[@id='J_password']
也可以寫成:
//*[@id='J_login_form']/*/*/input[@id='J_password']
這里解釋一下,其中//*[@id=’ J_login_form’]
這一段是指在根元素下查找任意id為J_login_form
的元素钻弄,此時相當(dāng)于引用到了form元素佃却。后面的路徑必須按照源碼的層級依次往下寫。按照代碼窘俺,我們要找的input元素包含在一個dt標(biāo)簽里面饲帅,而dt又包含在dl標(biāo)簽內(nèi),所以中間必須寫上dl和dt兩層瘤泪,才到input這層灶泵。當(dāng)然我們也可以用*號省略具體的標(biāo)簽名稱,但元素的層級關(guān)系必須體現(xiàn)出來对途,比如我們不能寫成//*[@id='J_login_form']/input[@id='J_password']
赦邻,這樣肯定會報錯的。
前面講的都是xpath中基于準(zhǔn)確元素屬性的定位掀宋,其實(shí)xpath作為定位神器也可以用于模糊匹配深纲。本次實(shí)戰(zhàn),可以進(jìn)行準(zhǔn)確元素定位劲妙,因此就不講模糊匹配了湃鹊。如果有興趣,可以自行了解镣奋。
動手實(shí)戰(zhàn)
以上面提到的文章為例币呵,進(jìn)行爬取講解。
頁面切換
由于網(wǎng)頁的百度文庫負(fù)責(zé)侨颈,可能抓取內(nèi)容不全余赢,因此使用User-Agent,模擬手機(jī)登錄哈垢,然后打印文章標(biāo)題妻柒,文章頁數(shù),并進(jìn)行翻頁耘分。先看下這個網(wǎng)站举塔。
我們需要找到兩個元素的位置,一個是頁碼元素的位置求泰,我們根據(jù)這個元素的位置央渣,將瀏覽器的滑動窗口移動到這個位置,這樣就可以避免click()下一頁元素的時候渴频,有元素遮擋芽丹。然后找到下一頁元素的位置,然后根據(jù)下一頁元素的位置卜朗,觸發(fā)鼠標(biāo)左鍵單擊事件拔第。
我們審查元素看一下咕村,這兩個元素:
我們根據(jù)這兩個元素,就可以通過xpath查找元素位置楼肪,代碼分別如下:
page = driver.find_elements_by_xpath("http://div[@class='page']")
nextpage = driver.find_element_by_xpath("http://a[@data-fun='next']")
由于page元素有很多培廓,所以我們使用find_elements_by_xpath()方法查找,然后使用page[-1]春叫,也就是鏈表中的最后一個元素的信息進(jìn)行瀏覽器窗口滑動肩钠,代碼如下:
from selenium import webdriver
options = webdriver.ChromeOptions()
options.add_argument('user-agent="Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19"')
driver = webdriver.Chrome(chrome_options = options)
driver.get('https://wenku.baidu.com/view/aa31a84bcf84b9d528ea7a2c.html')
page = driver.find_elements_by_xpath("http://div[@class='page']")
driver.execute_script('arguments[0].scrollIntoView();', page[-1]) #拖動到可見的元素去
nextpage = driver.find_element_by_xpath("http://a[@data-fun='next']")
nextpage.click()
內(nèi)容爬取
爬取內(nèi)容使用的是BeautifulSoup,這里不細(xì)說,審查元素暂殖,自己分析一下就可以价匠。代碼如下:
from selenium import webdriver
from bs4 import BeautifulSoup
options = webdriver.ChromeOptions()
options.add_argument('user-agent="Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19"')
driver = webdriver.Chrome(chrome_options=options)
driver.get('https://wenku.baidu.com/view/aa31a84bcf84b9d528ea7a2c.html')
html = driver.page_source
bf1 = BeautifulSoup(html, 'lxml')
result = bf1.find_all(class_='rtcspage')
for each_result in result:
bf2 = BeautifulSoup(str(each_result), 'lxml')
texts = bf2.find_all('p')
for each_text in texts:
main_body = BeautifulSoup(str(each_text), 'lxml')
for each in main_body.find_all(True):
if each.name == 'span':
print(each.string.replace('\xa0',''),end='')
elif each.name == 'br':
print('')
爬取結(jié)果如下:
整體代碼
我們能夠翻頁,也能夠爬取當(dāng)前頁面內(nèi)容呛每,代碼稍作整合踩窖,就可以爬取所有頁面的內(nèi)容了。找下網(wǎng)頁的規(guī)律就會發(fā)現(xiàn)晨横,5頁文章放在一個網(wǎng)頁里洋腮。思路:爬取正文內(nèi)容,再根據(jù)爬取到的文章頁數(shù)手形,計算頁數(shù)/5.0啥供,得到一個分?jǐn)?shù),如果這個分?jǐn)?shù)大于1库糠,則翻頁繼續(xù)爬伙狐,如果小于或等于1,代表到最后一頁了瞬欧。停止翻頁贷屎。有一點(diǎn)注意一下,翻頁之后艘虎,等待延時一下唉侄,等待頁面加載之后在爬取內(nèi)容,這里野建,我們使用最簡單的辦法属划,用sleep()進(jìn)行延時。因此總體代碼如下:
from selenium import webdriver
from bs4 import BeautifulSoup
import re
if __name__ == '__main__':
options = webdriver.ChromeOptions()
options.add_argument('user-agent="Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19"')
driver = webdriver.Chrome(chrome_options=options)
driver.get('https://wenku.baidu.com/view/aa31a84bcf84b9d528ea7a2c.html')
html = driver.page_source
bf1 = BeautifulSoup(html, 'lxml')
result = bf1.find_all(class_='rtcspage')
bf2 = BeautifulSoup(str(result[0]), 'lxml')
title = bf2.div.div.h1.string
pagenum = bf2.find_all(class_='size')
pagenum = BeautifulSoup(str(pagenum), 'lxml').span.string
pagepattern = re.compile('頁數(shù):(\d+)頁')
num = int(pagepattern.findall(pagenum)[0])
print('文章標(biāo)題:%s' % title)
print('文章頁數(shù):%d' % num)
while True:
num = num / 5.0
html = driver.page_source
bf1 = BeautifulSoup(html, 'lxml')
result = bf1.find_all(class_='rtcspage')
for each_result in result:
bf2 = BeautifulSoup(str(each_result), 'lxml')
texts = bf2.find_all('p')
for each_text in texts:
main_body = BeautifulSoup(str(each_text), 'lxml')
for each in main_body.find_all(True):
if each.name == 'span':
print(each.string.replace('\xa0',''),end='')
elif each.name == 'br':
print('')
print('\n')
if num > 1:
page = driver.find_elements_by_xpath("http://div[@class='page']")
driver.execute_script('arguments[0].scrollIntoView();', page[-1]) #拖動到可見的元素去
nextpage = driver.find_element_by_xpath("http://a[@data-fun='next']")
nextpage.click()
time.sleep(3)
else:
break
運(yùn)行結(jié)果:
總結(jié)
這樣的爬取只是為了演示Selenium使用贬墩,缺點(diǎn)很明顯:
- 沒有處理圖片
- 代碼通用性不強(qiáng)
- 等待頁面切換方法太out榴嗅,可以使用顯示等待的方式妄呕,等待頁面加載
參考:Jack-Cui https://cuijiahua.com/