Python爬虫利器五Selenium用法
在上一節我們學習了 PhantomJS 的基本用法,歸根結底它是一個沒有界面的瀏覽器,而且運行的是 JavaScript 腳本,然而這就能寫爬蟲了嗎?這又和 Python 有什么關系?說好的 Python 爬蟲呢?別急,接下來我們介紹的這個工具,統統解決掉你的疑惑。
簡介
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 的簡介。 Webdriver 嗯,通過以上描述,我們應該對 Selenium 有了大概對認識,接下來就讓我們開始進入動態爬取的新世界吧。 本文參考內容來自 Selenium 官網 和SeleniumPython 文檔
安裝
首先安裝 Selenium
pip install selenium或者下載源碼然后解壓后運行下面的命令進行安裝
python setup.py install初步體驗
我們先來一個小例子感受一下 Selenium,這里我們用 Chrome 瀏覽器來測試,方便查看效果,到真正爬取的時候換回 PhantomJS 即可。
from selenium import webdriverbrowser = webdriver.Chrome() browser.get('http://www.baidu.com/')運行這段代碼,會自動打開瀏覽器,然后訪問百度。 如果程序執行錯誤,瀏覽器沒有打開,那么應該是沒有裝 Chrome 瀏覽器或者 Chrome 驅動沒有配置在環境變量里。下載驅動,然后將驅動文件路徑配置在環境變量即可。
我將chromedriver.exe文件放在谷歌瀏覽器安裝目錄C:\Program Files (x86)\Google\Chrome\Application下(其它路徑也行),然后將此路徑添加到環境變量
瀏覽器驅動下載
chromedriver
谷歌、火狐、IE瀏覽器驅動
模擬提交
下面的代碼實現了模擬提交提交搜索的功能,首先等頁面加載完成,然后輸入到搜索框文本,點擊提交。
from selenium import webdriver from selenium.webdriver.common.keys import Keysdriver = 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 findelement_by* methods. For example, the input text element can be located by its name attribute using find_element_by_name method
WebDriver 提供了許多尋找網頁元素的方法,譬如 findelement_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 Keysclass PythonOrgSearch(unittest.TestCase):def setUp(self):self.driver = webdriver.Chrome()def test_search_in_python_org(self):driver = self.driverdriver.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_sourcedef 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" /> 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")同樣你還可以利用 Keys 這個類來模擬點擊某個按鍵。
element.send_keys("and some", Keys.ARROW_DOWN)你可以對任何獲取到到元素使用 send_keys 方法,就像你在 GMail 里面點擊發送鍵一樣。不過這樣會導致的結果就是輸入的文本不會自動清除。所以輸入的文本都會在原來的基礎上繼續輸入。你可以用下面的方法來清除輸入文本的內容。這樣輸入的文本會被清除。
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()首先獲取了第一個 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)如你所見,它可以根據索引來選擇,可以根據值來選擇,可以根據文字來選擇。是十分方便的。 全部取消選擇怎么辦呢?很簡單
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獲取所有可選選項是
options = select.options如果你把表單都填好了,最后肯定要提交表單對吧。怎嗎提交呢?很簡單
driver.find_element_by_id("submit").click()這樣就相當于模擬點擊了 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()這樣就實現了元素從 source 拖動到 target 的操作。
頁面切換
一個瀏覽器肯定會有很多窗口,所以我們肯定要有方法來實現窗口的切換。切換窗口的方法如下
driver.switch_to_window("windowName")另外你可以使用 window_handles 方法來獲取每個窗口的操作對象。例如
for handle in driver.window_handles:driver.switch_to_window(handle)另外切換 frame 的方法如下
driver.switch_to_frame("frameName.0.child")這樣焦點會切換到一個 name 為 child 的 frame 上。
彈窗處理
當你出發了某個事件之后,頁面出現了彈窗提示,那么你怎樣來處理這個提示或者獲取提示信息呢?
alert = driver.switch_to_alert()通過上述方法可以獲取彈窗對象。
歷史記錄
那么怎樣來操作頁面的前進和后退功能呢?
driver.forward() driver.back()Cookies 處理
為頁面添加 Cookies,用法如下
# Go to the correct domain driver.get("http://www.example.com")# 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 driver.get("http://www.example.com")# And now output all the available cookies for the current URL driver.get_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 Bydriver.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"更詳細的元素選擇方法參見官方文檔 元素選擇
等待頁面加載
現在的大多數的Web應用程序是使用Ajax技術。當一個頁面被加載到瀏覽器時, 該頁面內的元素可以在不同的時間點被加載。這使得定位元素變得困難, 如果元素不再頁面之中,會拋出 ElementNotVisibleException 異常。 使用 waits, 我們可以解決這個問題。waits提供了一些操作之間的時間間隔- 主要是定位元素或針對該元素的任何其他操作。
Selenium Webdriver 提供兩種類型的waits - 隱式和顯式。 顯式等待會讓WebDriver等待滿足一定的條件以后再進一步的執行。 而隱式等待讓Webdriver等待一定的時間后再才是查找某元素。
顯示等待
顯式等待是你在代碼中定義等待一定條件發生后再進一步執行你的代碼。 最糟糕的案例是使用time.sleep(),它將條件設置為等待一個確切的時間段。 這里有一些方便的方法讓你只等待需要的時間。WebDriverWait結合ExpectedCondition 是實現的一種方式。
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 ECdriver = webdriver.Firefox() 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()在拋出TimeoutException異常之前將等待10秒或者在10秒內發現了查找的元素。 WebDriverWait 默認情況下會每500毫秒調用一次ExpectedCondition直到結果成功返回。 ExpectedCondition成功的返回結果是一個布爾類型的true或是不為null的返回值。
預期的條件
下面是一些內置的等待條件,你可以直接調用這些條件,而不用自己寫某些等待條件了。
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
隱式等待
如果某些元素不是立即可用的,隱式等待是告訴WebDriver去等待一定的時間后去查找元素。 默認等待時間是0秒,一旦設置該值,隱式等待是設置該WebDriver的實例的生命周期。
from selenium import webdriverdriver = webdriver.Firefox() driver.implicitly_wait(10) # seconds driver.get("http://somedomain/url_that_delays_loading") myDynamicElement = driver.find_element_by_id("myDynamicElement")頁面對象
一個頁面對象表示在你測試的WEB應用程序的用戶界面上的區域。
使用頁面對象模式的好處:
- 創建可復用的代碼以便于在多個測試用例間共享
- 減少重復的代碼量
- 如果用戶界面變化,只需要修改一處
測試用例
下面是一個在python.org網站搜索一個詞并保證一些結果可以找到的測試用例
import unittest from selenium import webdriver import pageclass PythonOrgSearch(unittest.TestCase):"""A sample test class to show how page object works"""def setUp(self):self.driver = webdriver.Firefox()self.driver.get("http://www.python.org")def test_search_in_python_org(self):"""Tests python.org search feature. Searches for the word "pycon" then verified that some results show up.Note that it does not look for any particular text in search results page. This test verifies thatthe results were not empty."""#Load the main page. In this case the home page of Python.org.main_page = page.MainPage(self.driver)#Checks if the word "Python" is in titleassert main_page.is_title_matches(), "python.org title doesn't match."#Sets the text of search textbox to "pycon"main_page.search_text_element = "pycon"main_page.click_go_button()search_results_page = page.SearchResultsPage(self.driver)#Verifies that the results page is not emptyassert search_results_page.is_results_found(), "No results found."def tearDown(self):self.driver.close()if __name__ == "__main__":unittest.main()頁面對象類
頁面對象為每個網頁模擬創建出一個對象。 按照此技術,在測試代碼和技術實施之間的一個分離層被創建。
這個 page.py 看起來像這樣:
頁面元素
這個 element.py 看起來像這樣:
from selenium.webdriver.support.ui import WebDriverWaitclass BasePageElement(object):"""Base page class that is initialized on every page object class."""def __set__(self, obj, value):"""Sets the text to the value supplied"""driver = obj.driverWebDriverWait(driver, 100).until(lambda driver: driver.find_element_by_name(self.locator))driver.find_element_by_name(self.locator).send_keys(value)def __get__(self, obj, owner):"""Gets the text of the specified object"""driver = obj.driverWebDriverWait(driver, 100).until(lambda driver: driver.find_element_by_name(self.locator))element = driver.find_element_by_name(self.locator)return element.get_attribute("value")定位器
其中一個做法是,從它們正在使用的地方分離定位字符。在這個例子中,同一頁面的定位器屬于同一個類。
這個 locators.py 看起來像這樣:
from selenium.webdriver.common.by import Byclass MainPageLocators(object):"""A class for main page locators. All main page locators should come here"""GO_BUTTON = (By.ID, 'submit')class SearchResultsPageLocators(object):"""A class for search results locators. All search results locators should come here"""passAPI
到最后,肯定是放松最全最重要的 API 了,比較多,希望大家可以多加練習。
API
以上就是 Selenium 的基本用法,我們講解了頁面交互,頁面渲染之后的源代碼的獲取。這樣,即使頁面是 JS 渲染而成的,我們也可以手到擒來了。就是這么溜!
總結
以上是生活随笔為你收集整理的Python爬虫利器五Selenium用法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux安装openssl
- 下一篇: 微服务:知识点梳理(SOA、服务拆分、服