Python 從零開始爬蟲(八)——動態爬取解決方案 之 selenium

selenium——自動化測試工具,專門爲Web應用程序編寫的一個驗收測試工具,測試其兼容性,功能什麼的。然而讓蟲師們垂涎的並非以上的種種,而是其經過驅動瀏覽器得到的解析JavaScript的能力。因此說這貨在動態爬取方面簡直是掛逼級別的存在,相較於手動分析更簡單易用,節省分析打碼時間。css

雖然selenium因其「超能力」被很多人吹上天了,可是認清利弊,根據需求來選擇爬蟲工具,仍是挺重要的,因此這裏簡單說下以供參考:python

  1. selenium無腦解決動態難題
  2. selenium更耐網頁變更
  3. selenium極大提高開發效率,但極大下降爬取效率(規模一大就明顯了)。
  4. selenium更佔用資源(cpu,內存,網絡)

理由:打開一個/多個瀏覽器,瀏覽器裏還有n個窗口.....
下面很少說,進入正題git

前置

本體直接pip安裝pip install seleniumgithub

驅動按需選擇:web

  • chrome (內核版本要對應,詳看其中的notes.txt)
  • firefox (相信各位的英文實力)
  • IE (不多用吧)

下載好後把解壓獲得的驅動放到設置了環境變量的路徑下就好了,若是你的python設置了環境變量,應該丟到python目錄下就好了。ajax

簡單嘗試

照例貼上文檔,2.53舊版中文,想學更多就給我啃
選好瀏覽器就建立實例,以後的操做都在這個實例之上,並介紹一些有用的option,本文以Chrome爲例。算法

from selenium import webdriver

#其餘瀏覽器把Chrome換名就行
#option = webdriver.ChromeOptions()
#option.set_headless() 設置無頭瀏覽器,就是隱藏界面後臺運行

driver = webdriver.Chrome() #建立driver實例
#driver = webdriver.Chrome(chrome_options=option)  建立實例並載入option

url = '**********'
driver.get(url)
#driver.maximize_window() 最大化窗口
#driver.set_window_size(width,height) 設置窗口大小

print(driver.page_source) #打印網頁源碼
driver.quit() # 關閉瀏覽器

對付低級的動態網頁(ajax較少)就是這麼簡單,基本不用考慮動態問題,徒手擼動態的人表示羨慕。然而更多時候並不像這樣簡單,直接套用的話很容易致使源碼所見非所得,並且不少爬蟲並無簡單到拿個源碼就走的程度,它還要交互,模擬點擊滾動等等。因此還請往下看,不要看到這就投入實戰啦chrome

定位

selenium提供多種方法對元素進行定位,返回WebElement對象,而上面提到的driver就至關於最大的WebElement對象npm

#如下都是單次定位,返回第一個定位到的。若是想屢次定位,給element加個s就行,返回的是符合元素的列表
element = driver.find_element_by_id() # 經過標籤的id定位,接收id屬性值

driver.find_element_by_name() # 經過標籤的name定位,接收name屬性值

driver.find_element_by_xpath() # 經過xpath定位,接收xpath表達式

driver.find_element_by_link_text() # 經過標籤的徹底文本定位,接收完整的文本

driver.find_element_by_partial_link_text() # 經過標籤的部分文本定位,接收部分文本

driver.find_element_by_tag_name() # 經過標籤名定位,接收標籤名

driver.find_element_by_class_name() # 經過標籤的class定位,接收class屬性值

driver.find_element_by_css_selector() # 經過css選擇器定位,接收其語法
#返回的WebElement對象能夠繼續往下定位

xpath和css就不展開講了,能實現精肯定位,必定要學其中一個,不知道的小夥伴們上網自學吧,不難windows


除了上面這些公有的方法,還有2個私有的方法來幫助頁面對象的定位。 這兩個方法就是 find_element 和 find_elements,須要導入By類輔助,接收一個By類屬性及其對應語法/值

from selenium.webdriver.common.by import By
driver.find_element(By.XPATH,'//a[text()="dark"]')
driver.find_elements(By.XPATH,'//a[text()="dark"]')
'''By類有如下屬性
ID = "id"
XPATH = "xpath"
LINK_TEXT = "link text"
PARTIAL_LINK_TEXT = "partial link text"
NAME = "name"
TAG_NAME = "tag_name"
CLASS_NAME = "class name"
CSS_SELECTOR = "css selector"
'''

frame切換

不少人在用selenium會遇到所見非所得,或者定位頁面元素的時候會定位不到的問題,這種狀況頗有多是frame在搞鬼,必須切換到相應frame中再進行定位。若是遇到以上問題,第一時間F12看下你所要的信息是否在frame標籤裏面。
frame標籤有frameset、frame、iframe三種,frameset跟其餘普通標籤沒有區別,不會影響到正常的定位,而frame與iframe對selenium定位而言是同樣的。

selenium提供了4種方法定位iframe並切換進去:

#對於<iframe name="frame1" id="dark">.....</iframe>
driver.switch_to_frame(0)  # 用frame的index來定位,第一個是0,以此類推
driver.switch_to_frame("frame1")  # 用id來定位
driver.switch_to_frame("dark")  # 用name來定位
driver.switch_to_frame(driver.find_element_by_tag_name("iframe"))  # 用WebElement對象來定位

一般經過id和name就能實現,無此屬性時能夠經過WebElement對象,即用find_element系列方法所取得的對象來定位。若是你肯定每一個目標frame都是固定第幾個,那也能夠用index定位

切到frame中以後,就不能繼續操做主文檔的元素了,這時若是想操做主文檔內容,則需切回主文檔。

driver.switch_to_default_content()

嵌套frame的切換
若是frame裏包着frame而你要的frame是後者,那麼須要一層一層切換進去,切換方法四選一

driver.switch_to_frame("frame1")
driver.switch_to_frame("frame2")

若是想回去上一個父frame,用driver.switch_to.parent_frame()

window 切換

有時候點開一個連接就會彈出一個新窗口,若是要對其操做就要切換過去,方法和frame的切換差很少,但只接收window_handle(至關於窗口的名字)來進行切換。driver.switch_to_window("windowName")

切換前最好保存以前的handle和全部的handles以便於來回切換。

current_window = driver.current_window_handle  # 獲取當前窗口handle name
all_windows = driver.window_handles  # 獲取全部窗口handle name

# 若是window不是當前window,則切換到該window,即切換到新窗口
for window in all_windows:
    if window != current_window:
        driver.switch_to_window(window)
        #....
driver.switch_to_window(current_window)  # 返回以前的窗口
driver.close()  # 窗口的關閉用close()

頁面交互

經常使用的頁面交互有點擊,輸入文本等。交互的原則是先定位,後交互,好比你F12找到了某個文本框或某個可點擊項的標籤,那就先定位到那,再用如下的交互方法

from selenium.webdriver.common.keys import Keys
# 首先定位到某個文本框或某個可點擊項(如超連接,按鈕),
element = driver.find_element_by_xpath('//a[text()="dark"]')

element.send_keys("some text")  # 往文本框輸入文本
#element.send_keys("some text", Keys.ARROW_DOWN)  可使用 Keys 類來模擬輸入方向鍵
element.clear()  # 清空文本框

#element.click() 若是此元素可點擊,可用click()方法

要注意的一點是,不是定位到就一定能交互,有時候目標會被網頁彈出來的東西覆蓋,致使沒法交互,因此要確保頁面乾淨無覆蓋

上下拉滾動

selenium能夠執行js,下拉滾動能夠經過此實現,所以就算不懂js也能夠記一些有用的js代碼

#driver.execute_script('js_str')
driver.execute_script('window.scrollTo(0,10000)')  # 移動到指定座標
driver.execute_script('window.scrollBy(0,10000)')  # 相對當前座標移動
driver.execute_script('window.scrollTo(0, document.body.scrollHeight)')  # 相對當前座標移動

等待

如今很多web都都有使用ajax技術,即異步加載,有時候你要的東西都還沒加載到你就去定位了,就會拋出異常。爲了不此等血淋淋的慘狀,必需要待其加載一段時間後再進行後面的操做。這裏付一張不等待就獲取page_source的後果
圖片描述

最粗暴的方法就是使用time.sleep(),這很笨,由於你還要設置合適的時間,而不一樣網站加載的速度有異,容易形成時間的浪費。因此最好仍是使用selenium提供的兩種等待方法

顯式Wiats

顯式Wiats容許你設置一個加載時間的上限和一個條件,每隔0.5s就判斷一下所設條件,條件成立就繼續執行下面的代碼,若是過了時間上限仍是沒有成立,默認拋出NoSuchElementException 異常。這種相對智能的等待方法能最大化地節省時間,應該優先選擇使用

selenium提供了多種expected_conditions(EC)來設置條件,可是要注意有定位時EC的方法接收的是定位信息的元組(locator_tuple)而不是兩個參數,正確用法如 EC.presence_of_element_located((By.ID,"dark"))
這個條件檢測是否有id='dark'的元素

selenium提供的EC有如下方法,意思如其名,這裏註釋一些易搞錯的,選擇使用

title_is(str)
title_contains(str)
presence_of_element_located(locator_tuple)
visibility_of_element_located(locator_tuple) # 判斷某個元素是否可見
visibility_of(WebElement) # 同上,傳參不一樣
presence_of_all_elements_located(locator_tuple)
text_to_be_present_in_element(str) # 判斷某個元素中的text是否包含了指定字符串
text_to_be_present_in_element_value(str) # 判斷某個元素中的value屬性是否包含了預期的字符串
frame_to_be_available_and_switch_to_it
invisibility_of_element_located(locator_tuple)
element_to_be_clickable(locator_tuple)
staleness_of(WebElement) # 等待某個元素從的移除
element_to_be_selected(WebElement)
element_located_to_be_selected(locator_tuple)
element_selection_state_to_be(WebElement)
element_located_selection_state_to_be(locator_tuple)
alert_is_present() # 判斷頁面上是否存在alert

使用上一般搭配try語句

from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait # 導入顯式等待
from selenium.webdriver.support import expected_conditions as EC # 導入EC
from selenium.common.exceptions import NoSuchElementException

# WebDriverWait(driver,time).until(EC) 顯式waits

try:
    element = WebDriverWait(driver,10).until(EC.presence_of_element_located((By.ID,"myDynamicElement")))
    
except NoSuchElementException:
    #.....
finally:
     driver.quit()

隱式等待

隱式等待是在嘗試定位某個元素的時候,若是沒能成功,就等待固定長度的時間,默認0秒。一旦設置了隱式等待時間,它的做用範圍就是Webdriver對象實例的整個生命週期。driver.implicitly_wait(10)

最後補充

這是用selenium幾分鐘弄出來的網易雲音樂單曲評論爬蟲,並且還模擬了評論翻頁,還截了圖。沒提取信息,提取的時候能夠用BeautifulSoup或正則提取,

from selenium import webdriver
import time
from selenium.common.exceptions import NoSuchElementException, TimeoutException
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait

driver = webdriver.Chrome()
driver.maximize_window()
driver.get("http://music.163.com/#/song?id=31877470")
driver.switch_to_frame("contentFrame")
time.sleep(5)
driver.execute_script('window.scrollTo(0, document.body.scrollHeight)')
driver.save_screenshot('E:/python3/gg.png')  # 截圖
b = driver.find_element_by_xpath("//a[starts-with(@class,'zbtn znxt')]")
b.click()
try:
    WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.XPATH,"//a[@data-type='reply']")))
    print(driver.page_source)
except NoSuchElementException:
    print('OMG')
finally:
        driver.quit()

短短二十行就能作到這種程度(並且很多仍是import的),足以見得selenium強大。若是要手動分析,你還要分析js加密算法,寫上n倍的代碼。二者相比起來selenium真的很吸引人,簡直是懶人救星。你們權衡利弊按需選擇吧

相關文章
相關標籤/搜索