Python爬蟲利器五之Selenium的用法
Selenium 是什麼?一句話,自動化測試工具。它支援各種瀏覽器,包括 Chrome,Safari,Firefox 等主流介面式瀏覽器,如果你在這些瀏覽器裡面安裝一個 Selenium 的外掛,那麼便可以方便地實現Web介面的測試。換句話說叫 Selenium 支援這些瀏覽器驅動。話說回來,PhantomJS不也是一個瀏覽器嗎,那麼 Selenium 支援不?答案是肯定的,這樣二者便可以實現無縫對接了。
然後又有什麼好訊息呢?Selenium支援多種語言開發,比如 Java,C,Ruby等等,有 Python 嗎?那是必須的!哦這可真是天大的好訊息啊。
嗯,所以呢?安裝一下 Python 的 Selenium 庫,再安裝好 PhantomJS,不就可以實現 Python+Selenium+PhantomJS 的無縫對接了嘛!PhantomJS 用來渲染解析JS,Selenium 用來驅動以及與 Python 的對接,Python 進行後期的處理,完美的三劍客!
有人問,為什麼不直接用瀏覽器而用一個沒介面的 PhantomJS 呢?答案是:效率高!
Selenium 有兩個版本,目前最新版本是 2.53.1(2016/3/22)
Selenium 2,又名 WebDriver,它的主要新功能是集成了 Selenium 1.0 以及 WebDriver(WebDriver 曾經是 Selenium 的競爭對手)。也就是說 Selenium 2 是 Selenium 和 WebDriver 兩個專案的合併,即 Selenium 2 相容 Selenium,它既支援 Selenium API 也支援 WebDriver API。
更多詳情可以檢視 Webdriver 的簡介。
嗯,通過以上描述,我們應該對 Selenium 有了大概對認識,接下來就讓我們開始進入動態爬取的新世界吧。
安裝
首先安裝 Selenium
pip install selenium
pip install selenium
或者下載原始碼
下載原始碼
然後解壓後執行下面的命令進行安裝
python setup.py install
python setup.py install
安裝好了之後我們便開始探索抓取方法了。
快速開始
初步體驗
我們先來一個小例子感受一下 Selenium,這裡我們用 Chrome 瀏覽器來測試,方便檢視效果,到真正爬取的時候換回 PhantomJS 即可。
from selenium import webdriver
browser = webdriver.Chrome()
browser.get('http://www.baidu.com/')
from selenium import webdriver
browser = webdriver.Chrome()
browser.get('http://www.baidu.com/')
執行這段程式碼,會自動開啟瀏覽器,然後訪問百度。
如果程式執行錯誤,瀏覽器沒有開啟,那麼應該是沒有裝 Chrome 瀏覽器或者 Chrome 驅動沒有配置在環境變數裡。下載驅動,然後將驅動檔案路徑配置在環境變數即可。
瀏覽器驅動下載
比如我的是 Mac OS,就把下載好的檔案放在 /usr/bin 目錄下就可以了。
模擬提交
下面的程式碼實現了模擬提交提交搜尋的功能,首先等頁面載入完成,然後輸入到搜尋框文字,點選提交。
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
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
同樣是在 Chrome 裡面測試,感受一下。
The driver.get method will navigate to a page given by the URL. WebDriver will wait until the page has fully loaded (that is, the “onload” event has fired) before returning control to your test or script. It’s worth noting that if your page uses a lot of AJAX on load then WebDriver may not know when it has completely loaded.
其中 driver.get 方法會開啟請求的URL,WebDriver 會等待頁面完全載入完成之後才會返回,即程式會等待頁面的所有內容載入完成,JS渲染完畢之後才繼續往下執行。注意:如果這裡用到了特別多的 Ajax 的話,程式可能不知道是否已經完全載入完畢。
WebDriver offers a number of ways to find elements using one of the find_element_by_* methods. For example, the input text element can be located by its name attribute using find_element_by_name method
WebDriver 提供了許多尋找網頁元素的方法,譬如 find_element_by_* 的方法。例如一個輸入框可以通過 find_element_by_name 方法尋找 name 屬性來確定。
Next we are sending keys, this is similar to entering keys using your keyboard. Special keys can be send using Keys class imported from selenium.webdriver.common.keys
然後我們輸入來文字然後模擬點選了回車,就像我們敲擊鍵盤一樣。我們可以利用 Keys 這個類來模擬鍵盤輸入。
最後最重要的一點
獲取網頁渲染後的原始碼。
輸出 page_source 屬性即可。
這樣,我們就可以做到網頁的動態爬取了。
測試用例
有了以上特性,我們當然可以用來寫測試樣例了。
import unittest
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
class PythonOrgSearch(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome()
def test_search_in_python_org(self):
driver = self.driver
driver.get("http://www.python.org")
self.assertIn("Python", driver.title)
elem = driver.find_element_by_name("q")
elem.send_keys("pycon")
elem.send_keys(Keys.RETURN)
assert "No results found." not in driver.page_source
def tearDown(self):
self.driver.close()
if __name__ == "__main__":
unittest.main()
import unittest
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
class PythonOrgSearch(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome()
def test_search_in_python_org(self):
driver = self.driver
driver.get("http://www.python.org")
self.assertIn("Python", driver.title)
elem = driver.find_element_by_name("q")
elem.send_keys("pycon")
elem.send_keys(Keys.RETURN)
assert "No results found." not in driver.page_source
def tearDown(self):
self.driver.close()
if __name__ == "__main__":
unittest.main()
執行程式,同樣的功能,我們將其封裝為測試標準類的形式。
The test case class is inherited from unittest.TestCase. Inheriting from TestCase class is the way to tell unittest module that this is a test case. The setUp is part of initialization, this method will get called before every test function which you are going to write in this test case class. The test case method should always start with characters test. The tearDown method will get called after every test method. This is a place to do all cleanup actions. You can also call quit method instead of close. The quit will exit the entire browser, whereas close will close a tab, but if it is the only tab opened, by default most browser will exit entirely.
測試用例是繼承了 unittest.TestCase 類,繼承這個類表明這是一個測試類。setUp方法是初始化的方法,這個方法會在每個測試類中自動呼叫。每一個測試方法命名都有規範,必須以 test 開頭,會自動執行。最後的 tearDown 方法會在每一個測試方法結束之後呼叫。這相當於最後的析構方法。在這個方法裡寫的是 close 方法,你還可以寫 quit 方法。不過 close 方法相當於關閉了這個 TAB 選項卡,然而 quit 是退出了整個瀏覽器。當你只開啟了一個 TAB 選項卡的時候,關閉的時候也會將整個瀏覽器關閉。
頁面操作
頁面互動
僅僅抓取頁面沒有多大卵用,我們真正要做的是做到和頁面互動,比如點選,輸入等等。那麼前提就是要找到頁面中的元素。WebDriver提供了各種方法來尋找元素。例如下面有一個表單輸入框。
<input type="text" name="passwd" id="passwd-id" />
<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("//input[@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("//input[@id='passwd-id']")
你還可以通過它的文字連結來獲取,但是要小心,文字必須完全匹配才可以,所以這並不是一個很好的匹配方式。
而且你在用 xpath 的時候還需要注意的是,如果有多個元素匹配了 xpath,它只會返回第一個匹配的元素。如果沒有找到,那麼會丟擲 NoSuchElementException 的異常。
獲取了元素之後,下一步當然就是向文字輸入內容了,可以利用下面的方法
element.send_keys("some text")
element.send_keys("some text")
同樣你還可以利用 Keys 這個類來模擬點選某個按鍵。
element.send_keys("and some", Keys.ARROW_DOWN)
element.send_keys("and some", Keys.ARROW_DOWN)
你可以對任何獲取到到元素使用 send_keys 方法,就像你在 GMail 裡面點擊發送鍵一樣。不過這樣會導致的結果就是輸入的文字不會自動清除。所以輸入的文字都會在原來的基礎上繼續輸入。你可以用下面的方法來清除輸入文字的內容。
element.clear()
element.clear()
這樣輸入的文字會被清除。
填充表單
我們已經知道了怎樣向文字框中輸入文字,但是其它的表單元素呢?例如下拉選項卡的的處理可以如下
element = driver.find_element_by_xpath("//select[@name='name']")
all_options = element.find_elements_by_tag_name("option")
for option in all_options:
print("Value is: %s" % option.get_attribute("value"))
option.click()
element = driver.find_element_by_xpath("//select[@name='name']")
all_options = element.find_elements_by_tag_name("option")
for option in all_options:
print("Value is: %s" % option.get_attribute("value"))
option.click()
首先獲取了第一個 select 元素,也就是下拉選項卡。然後輪流設定了 select 選項卡中的每一個 option 選項。你可以看到,這並不是一個非常有效的方法。
其實 WebDriver 中提供了一個叫 Select 的方法,可以幫助我們完成這些事情。
from selenium.webdriver.support.ui import Select
select = Select(driver.find_element_by_name('name'))
select.select_by_index(index)
select.select_by_visible_text("text")
select.select_by_value(value)
from selenium.webdriver.support.ui import Select
select = Select(driver.find_element_by_name('name'))
select.select_by_index(index)
select.select_by_visible_text("text")
select.select_by_value(value)
如你所見,它可以根據索引來選擇,可以根據值來選擇,可以根據文字來選擇。是十分方便的。
全部取消選擇怎麼辦呢?很簡單
select = Select(driver.find_element_by_id('id'))
select.deselect_all()
select = Select(driver.find_element_by_id('id'))
select.deselect_all()
這樣便可以取消所有的選擇。
另外我們還可以通過下面的方法獲取所有的已選選項。
select = Select(driver.find_element_by_xpath("xpath"))
all_selected_options = select.all_selected_options
select = Select(driver.find_element_by_xpath("xpath"))
all_selected_options = select.all_selected_options
獲取所有可選選項是
options = select.options
options = select.options
如果你把表單都填好了,最後肯定要提交表單對吧。怎嗎提交呢?很簡單
driver.find_element_by_id("submit").click()
driver.find_element_by_id("submit").click()
這樣就相當於模擬點選了 submit 按鈕,做到表單提交。
當然你也可以單獨提交某個元素
element.submit()
element.submit()
方法,WebDriver 會在表單中尋找它所在的表單,如果發現這個元素並沒有被表單所包圍,那麼程式會丟擲 NoSuchElementException 的異常。
元素拖拽
要完成元素的拖拽,首先你需要指定被拖動的元素和拖動目標元素,然後利用 ActionChains 類來實現。
element = driver.find_element_by_name(“source”)
target = driver.find_element_by_name(“target”)
from selenium.webdriver import ActionChains
action_chains = ActionChains(driver)
action_chains.drag_and_drop(element, target).perform()
element = driver.find_element_by_name(“source”)
target = driver.find_element_by_name(“target”)
from selenium.webdriver import ActionChains
action_chains = ActionChains(driver)
action_chains.drag_and_drop(element, target).perform()
這樣就實現了元素從 source 拖動到 target 的操作。
頁面切換
一個瀏覽器肯定會有很多視窗,所以我們肯定要有方法來實現視窗的切換。切換視窗的方法如下
driver.switch_to_window(“windowName”)
driver.switch_to_window(“windowName”)
另外你可以使用 window_handles 方法來獲取每個視窗的操作物件。例如
for handle in driver.window_handles:
driver.switch_to_window(handle)
for handle in driver.window_handles:
driver.switch_to_window(handle)
另外切換 frame 的方法如下
driver.switch_to_frame(“frameName.0.child”)
driver.switch_to_frame(“frameName.0.child”)
這樣焦點會切換到一個 name 為 child 的 frame 上。
彈窗處理
當你出發了某個事件之後,頁面出現了彈窗提示,那麼你怎樣來處理這個提示或者獲取提示資訊呢?
alert = driver.switch_to_alert()
alert = driver.switch_to_alert()
通過上述方法可以獲取彈窗物件。
歷史記錄
那麼怎樣來操作頁面的前進和後退功能呢?
driver.forward()
driver.back()
driver.forward()
driver.back()
嗯,簡潔明瞭。
Cookies處理
為頁面新增 Cookies,用法如下
- # Go to the correct domain
- # Now set the cookie. This one’s valid for the entire domain
cookie = {‘name’ : ‘foo’, ‘value’ : ‘bar’}
driver.add_cookie(cookie)
- # Go to the correct domain
- # Now set the cookie. This one’s valid for the entire domain
cookie = {‘name’ : ‘foo’, ‘value’ : ‘bar’}
driver.add_cookie(cookie)
獲取頁面 Cookies,用法如下
- # Go to the correct domain
- # And now output all the available cookies for the current URL
driver.get_cookies()
# Go to the correct domain
# And now output all the available cookies for the current URL
driver.get_cookies()
以上便是 Cookies 的處理,同樣是非常簡單的。
元素選取
關於元素的選取,有如下的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’)
from selenium.webdriver.common.by import By
driver.find_element(By.XPATH, ‘//button[text()=”Some text”]’)
driver.find_elements(By.XPATH, ‘//button’)
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”
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”
更詳細的元素選擇方法參見官方文件
元素選擇
頁面等待
這是非常重要的一部分,現在的網頁越來越多采用了 Ajax 技術,這樣程式便不能確定何時某個元素完全加載出來了。這會讓元素定位困難而且會提高產生 ElementNotVisibleException 的概率。
所以 Selenium 提供了兩種等待方式,一種是隱式等待,一種是顯式等待。
隱式等待是等待特定的時間,顯式等待是指定某一條件直到這個條件成立時繼續執行。
顯式等待
顯式等待指定某個條件,然後設定最長等待時間。如果在這個時間還沒有找到元素,那麼便會丟擲異常了。
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
driver = webdriver.Chrome()
driver.get(“http://somedomain/url_that_delays_loading“)
try:
element = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.ID, “myDynamicElement”))
)
finally:
driver.quit()
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
driver = webdriver.Chrome()
driver.get(“http://somedomain/url_that_delays_loading“)
try:
element = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.ID, “myDynamicElement”))
)
finally:
driver.quit()
程式預設會 500ms 呼叫一次來檢視元素是否已經生成,如果本來元素就是存在的,那麼會立即返回。
下面是一些內建的等待條件,你可以直接呼叫這些條件,而不用自己寫某些等待條件了。
title_is
title_contains
presence_of_element_located
visibility_of_element_located
visibility_of
presence_of_all_elements_located
text_to_be_present_in_element
text_to_be_present_in_element_value
frame_to_be_available_and_switch_to_it
invisibility_of_element_located
element_to_be_clickable – it is Displayed and Enabled.
staleness_of
element_to_be_selected
element_located_to_be_selected
element_selection_state_to_be
element_located_selection_state_to_be
alert_is_present
from selenium.webdriver.support import expected_conditions as EC
wait = WebDriverWait(driver, 10)
element = wait.until(EC.element_to_be_clickable((By.ID,’someid’)))
from selenium.webdriver.support import expected_conditions as EC
wait = WebDriverWait(driver, 10)
element = wait.until(EC.element_to_be_clickable((By.ID,’someid’)))
隱式等待
隱式等待比較簡單,就是簡單地設定一個等待時間,單位為秒。
from selenium import webdriver
driver = webdriver.Chrome()
driver.implicitly_wait(10) # seconds
driver.get(“http://somedomain/url_that_delays_loading“)
myDynamicElement = driver.find_element_by_id(“myDynamicElement”)
from selenium import webdriver
driver = webdriver.Chrome()
driver.implicitly_wait(10) # seconds
driver.get(“http://somedomain/url_that_delays_loading“)
myDynamicElement = driver.find_element_by_id(“myDynamicElement”)
當然如果不設定,預設等待時間為0。
程式框架
對於頁面測試和分析,官方提供了一個比較明晰的程式碼結構,可以參考。
到最後,肯定是放鬆最全最重要的API了,比較多,希望大家可以多加練習。
結語
以上就是 Selenium 的基本用法,我們講解了頁面互動,頁面渲染之後的原始碼的獲取。這樣,即使頁面是 JS 渲染而成的,我們也可以手到擒來了。就是這麼溜!