一、簡介
接着幾個月之前的(數據科學學習手札31)基於Python的網絡數據采集(初級篇),在那篇文章中,我們介紹了關於網絡爬蟲的基礎知識(基本的請求庫,基本的解析庫,CSS,正則表達式等),在那篇文章中我們只介紹了如何利用urllib、requests這樣的請求庫來將我們的程序模擬成一個請求網絡服務的一端,來直接取得設置好的url地址中朴素的網頁內容,再利用BeautifulSoup或pyspider這樣的解析庫來對獲取的網頁內容進行解析,在初級篇中我們也只了解到如何爬取靜態網頁,那是網絡爬蟲中最簡單的部分,事實上,現在但凡有價值的網站都或多或少存在着自己的一套反爬機制,例如利用JS腳本來控制網頁中部分內容的請求和顯示,使得最原始的直接修改靜態目標頁面url地址來更改頁面的方式失效,這一部分,我在(數據科學學習手札47)基於Python的網絡數據采集實戰(2)中爬取馬蜂窩景點頁面下蜂蜂點評區域用戶評論內容的時候,也詳細介紹過,但之前我在所有爬蟲相關的文章中介紹的內容,都離不開這樣的一個過程:
整理url規則(靜態頁面直接訪問,JS控制的動態頁面通過瀏覽器的開發者工具來找到真實網址和參數)
|
偽裝瀏覽器
|
利用urllib.urlopen()或requests.get()對目標url發起訪問
|
獲得返回的網頁原始內容
|
利用BeautifulSoup或PySpider對網頁原始內容進行解析
|
結合觀察到的CSS標簽屬性等信息,利用BeautifulSoup對象的findAll()方法提取需要的內容,利用正則表達式來完成精確提取
|
存入數據庫
而本文將要介紹的一種新的網絡數據采集工具就不再是偽裝成瀏覽器端,而是基於自動化測試工具selenium來結合對應瀏覽器的驅動程序,開辟真實的、顯性的瀏覽器窗口,來完成一系列動作,以應對更加動態靈活的網頁;
二、selenium
2.1 介紹
selenium也是一個用於Web應用程序測試的工具。selenium測試直接運行在瀏覽器中,就像真正的用戶在操作一樣。支持的瀏覽器包括IE、Mozilla Firefox、Mozilla Suite、Chrome等。這個工具的主要功能是測試與瀏覽器的兼容性,但由於其能夠真實模擬瀏覽器,模擬網頁點擊、下拉、拖拽元素等行為的功能,使得其在網絡數據采集上開辟出一片天地;
2.2 環境搭建
要想基於Python(這里我們說的是Python3,Python2,就讓它在歷史的長河里隱退吧。。。)來使用selenium創建爬蟲程序,我們需要:
1.安裝selenium包,直接pip安裝即可
2.下載瀏覽器(廢話-_-!),以及對應的驅動程序,本文選擇使用的瀏覽器為Chrome,需要下載chromedriver.exe,這里提供一個收錄所有版本chromedriver.exe資源的地址:
http://npm.taobao.org/mirrors/chromedriver/
需要注意的是,要下載與你的瀏覽器版本兼容的資源,這里給一個建議:將你的Chrome瀏覽器更新到最新版本,再到上述地址中下載發布時間最新的chromedriver.exe;在下載完畢后,將chromedriver.exe放到你的Python根目錄下,和python.exe放在一起,譬如我就將其放在我的anaconda環境下的對應位置:
3.測試一下~
在完成上述操作之后,我們要檢驗一下我們的環境有沒有正確搭建完成,在你的Python編輯器中,寫下如下語句:
from selenium import webdriver '''創建一個新的Chrome瀏覽器窗體''' browser = webdriver.Chrome() '''在browser對應的瀏覽器中訪問百度首頁''' browser.get('http://www.baidu.com')
如果在執行上述語句之后,順利地打開了Chrome瀏覽器並訪問到我們設置的網頁,則selenium+Chrome的開發環境配置完成;
2.3 利用selenium進行網絡數據采集的基本流程
在本文的一開始我們總結了之前進行網絡數據采集的基本流程,下面我們以類似的形式介紹一下selenium進行網絡數據采集的基本流程:
創建瀏覽器(可能涉及對瀏覽器一些設置的預配置,如不需要采集圖片時設置禁止加載圖片以提升訪問速度)
|
利用.get()方法直接打開指定url地址
|
利用.page_source()方法獲取當前主窗口(瀏覽器中可能同時打開多個網頁窗口,這時需要利用頁面句柄來指定我們關注的主窗口網頁)頁面對應的網頁內容
|
利用BeautifulSoup或pyspider等解析庫對指定的網頁內容進行解析
|
結合觀察到的CSS標簽屬性等信息,利用BeautifulSoup對象的findAll()方法提取需要的內容,利用正則表達式來完成精確提取
|
存入數據庫
可以看出,利用selenium來進行網絡數據采集與之前的方法最大的不同點在於對目標網頁發起請求的過程,在使用selenium時,我們無需再偽裝瀏覽器,且有了非常豐富的瀏覽器動作可以設置,譬如說之前我們需要對頁面進行翻頁操作,主要是通過修改url中對應控制頁面值的參數來完成,所以在遇到JS控制的動態網頁時,可以不需要去費心尋找控制對應資源翻頁的真實url地址,只需要在selenium中,通過其內置的豐富的定位方法對頁面中的翻頁按鈕進行定位 ,再通過對定位到的元素運用.click(),即可實現真實的翻頁操作,下面我們根據上述過程中列出的selenium部分,涉及到的常用方法進行介紹以及舉例說明:
三、selenium常用操作
3.1 瀏覽器配置部分
在調出一個真實的瀏覽器對象之前,我們可以結合實際需要對瀏覽器的設置進行參數配置,這在selenium中是通過對應瀏覽器的XXXOptions類來設置的,例如本文只介紹Chrome瀏覽器,則我們通過ChromeOptions類中的方法來實現瀏覽器預配置,下面我們來了解一下ChromeOptions類:
ChromeOptions:
ChromeOptions是一個在selenium創建Chrome瀏覽器之前,對該瀏覽器對象進行預配置的類,其主要功能有添加Chrome啟動參數、修改Chrome設置、添加擴展應用等,如:
1.禁止網頁中圖片加載
from selenium import webdriver '''創建一個新的Chrome瀏覽器窗體,通過add_experimental_option()方法來設置禁止圖片加載''' chrome_options = webdriver.ChromeOptions() prefs = {"profile.managed_default_content_settings.images": 2} chrome_options.add_experimental_option("prefs", prefs) browser = webdriver.Chrome(chrome_options=chrome_options) '''在browser對應的瀏覽器中,以禁止圖片加載的方式訪問百度首頁''' browser.get('http://www.baidu.com') '''查看當前瀏覽器中已設置的參數''' chrome_options.experimental_options
可以看出,在進行如上設置后,我們訪問的網頁中所有圖片都沒有加載,這在不需要采集圖片資源的任務中,對於提升訪問速度有着重要意義;
2.設置代理IP
有些時候,在面對一些對訪問頻率有所限制的網站時,一旦我們爬取頻率過高,就會導致我們本機的IP地址遭受短暫的封禁,這時我們可以通過收集一些IP代理來建立我們的代理池,關於這一點我們會在之后單獨開一篇博客來詳細介紹,下面簡單演示一下如何為我們的Chrome()瀏覽器對象設置IP代理:
from selenium import webdriver '''設置代理IP''' IP = '106.75.9.39:8080' '''為Chrome瀏覽器配置chrome_options選項''' chrome_options = webdriver.ChromeOptions() chrome_options.add_argument('--proxy-server=http://{}'.format(IP)) '''將配置好的chrome_options選項傳入新的Chrome瀏覽器對象中''' browser = webdriver.Chrome(chrome_options=chrome_options) '''嘗試訪問百度首頁''' browser.get('http://www.baidu.com')
但是如果你不是付費購買的高速IP代理,而是從網上所謂的免費IP代理網站扒下來的一些IP地址,那么上述設置之后打開的瀏覽器中不一定能在正常時間內顯示目標網頁(原因你懂的);
另一種思路:
除了使用ChromeOptions()中的方法來設置,還有一種簡單直接粗暴的方法,我們可以直接訪問對應當前瀏覽器設置頁面的地址:chrome://settings/content:
from selenium import webdriver browser = webdriver.Chrome() '''直接訪問設置頁面''' browser.get('chrome://settings/content')
接着再使用自己編寫的模擬點擊規則,即可完成對應的設置內容,這里便不再多說;
3.2 瀏覽器運行時的實用方法
經過了3.1中介紹的方式,對瀏覽器進行預配置,並成功打開對應的瀏覽器之后,selenium中還存在着非常豐富的瀏覽器方法,下面我們就其中實用且常用的一些方法和類內的變量進行介紹:
假設我們構造了一個叫做browser的瀏覽器對象,可以使用的方法如下:
browser.get(url):在瀏覽器主窗口中打開url指定的網頁;
browser.title:獲得當前瀏覽器中主頁面的網頁標題:
from selenium import webdriver browser = webdriver.Chrome() '''直接訪問設置頁面''' browser.get('https://hao.360.cn/?wd_xp1') '''打印網頁標題''' print(browser.title)
browser.current_url:返回當前主頁面url地址
browser.page_source:獲取當前主界面的頁面內容,相當於requests.get(url).content
browser.close():關閉當前主頁面對應的網頁
browser.quit():直接關閉當前瀏覽器
browser.maximize_window():將瀏覽器窗口大小最大化
browser.fullscreen_window():將瀏覽器窗口全屏化
browser.back():控制當前主頁面進行后退操作(前提是它有上一頁面)
browser.forward():控制當前主頁面進行前進操作(前提是它有下一頁面)
browser.refresh():控制當前主頁面進行刷新操作
browser.set_page_load_timeout(time_to_wait):為當前瀏覽器設置一個最大頁面加載耗時容忍閾值,單位秒,類似urllib.urlopen()中的timeout參數,即當加載某個界面時,持續time_to_wait秒還未加載完成時,程序會報錯,我們可以利用錯誤處理機制捕捉這個錯誤,此方法適用於長時間采樣中某個界面訪問超時假死的情況
browser.set_window_size(width, height, windowHandle='current'):用於調節瀏覽器界面長寬大小
關於主頁面:
這里要額外介紹一下,我們在前面一大段中提到過很多次主頁面這個概念,是因為在selenium控制瀏覽器時,無論瀏覽器中開了多少個網頁,都只將唯一一個網頁視為主頁面,相應的很多webdriver()方法也都是以該主頁面為對象,下面是一個示例,我們以馬蜂窩地方游記頁面為例:
from selenium import webdriver browser = webdriver.Chrome() '''訪問馬蜂窩重慶游記匯總頁''' browser.get('http://www.mafengwo.cn/search/s.php?q=%E9%87%8D%E5%BA%86&t=info&seid=71F18E8D-AA90-4870-9928-2BE01E53DDBD&mxid=&mid=&mname=&kt=1')
打開目標頁面如下:
這里我們手動點開一篇游記(模擬點擊的方法下文會介紹),瀏覽器隨即跳轉到一個新的頁面:
這時我們運行下列代碼:
'''打印網頁標題''' print(browser.title)
可以看到,雖然在我們的視角里,通過點擊,進入到一個新的界面,但當我們利用對應方法獲取當前頁面標題時,仍然是以之前的頁面作為對象,這就涉及到我們之前提到的主頁面的問題,當在原始頁面中,因為點擊事件而跳轉到另一個頁面(這里指的是新開一個窗口顯示新界面,而不是在原來的窗口覆蓋掉原頁面),瀏覽器中的主頁面依舊是鎖定在原始頁面中,即get()方法跳轉到的網頁,這種情況我們就需要用到網頁的句柄來唯一標識每一個網頁;
在selenium中,關於獲取網頁句柄,有以下兩個方法:
browser.current_window_handle:獲取主頁面的句柄,以上面馬蜂窩的為例:
'''打印主頁面句柄''' print(browser.current_window_handle)
browser.window_handles:獲取當前瀏覽器中所有頁面的句柄,按照打開的時間順序:
'''打印當前瀏覽器下所有頁面的句柄''' print(browser.window_handles)
既然句柄相當於網頁的身份證,那么我們可以基於句柄切換當前的主網頁到其他網頁之上,延續上面的例子,此時的主網頁是.get()方法打開的網頁,之前打印browser.title也是指向的該網頁,現在我們利用browser.switch_to.window(handle)方法,將主網頁轉到最近打開的網頁中,並打印當前主網頁的標題:
'''切換主網頁至最近打開的網頁''' browser.switch_to.window(browser.window_handles[-1]) '''打印當前主網頁的網頁標題''' print(browser.title)
可以看到,使用主網頁切換方法后,我們的主網頁轉到指定的網頁中,這在對特殊的網頁跳轉方式下新開的網頁內容的采集很受用;
3.3 頁面元素定位
在介紹selenium的精髓——模擬瀏覽器行為之前,我們需要知道如何對網頁內的元素進行定位,譬如說我們要想定位到網頁中的翻頁按鈕,就需要對翻頁按鈕所在的位置進行定位,這里的定位不是指在屏幕的平面坐標上進行定位,而是基於網頁自身的CSS結構,其實selenium中對網頁元素進行定位的方式非常多,但是通過我大量的實踐,其中很多方法效果並不盡如人意,唯有其中基於xpath的定位方法十分方便,定位非常准確方便,因此本文不會浪費你的時間介紹其他效果不太好的方法,直接介紹基於xpath的定位方法,我們先了解一下什么是xpath:
關於xpath:
xpath是一門在xml文檔中查找信息的語言,只是為了在selenium中定位網頁元素的話,我們只需要掌握xpath路徑表達式即可;
xpath使用路徑表達式來識別xml文檔中的節點或節點集,我們先從一個示例出發來對xpath路徑表達式有一個認識:
還是以馬蜂窩游記頁面為例:
from selenium import webdriver browser = webdriver.Chrome() '''訪問馬蜂窩重慶游記匯總頁''' browser.get('http://www.mafengwo.cn/search/s.php?q=%E9%87%8D%E5%BA%86&t=info&seid=71F18E8D-AA90-4870-9928-2BE01E53DDBD&mxid=&mid=&mname=&kt=1')
通過瀏覽器的開發者工具,我們找到“下一頁”按鈕元素在CSS結構中所在的位置:
先把該元素完整的xpath路徑表達式寫出來:
//div/div/a[@class='ti next _j_pageitem']
接着我們使用基於xpath的定位方法,定位按鈕的位置並模擬點擊:
'''定位翻頁按鈕的位置並保存在新變量中''' ChagePageElement = browser.find_element_by_xpath("//div/div/a[@class='ti next _j_pageitem']") '''對按鈕位置變量使用click方法進行模擬點擊''' ChagePageElement.click()
上述代碼運行之后,我們的瀏覽器執行了對翻頁按鈕的模擬點擊,實現了翻頁:
現在我們來介紹一下xpath路徑表達式中的一些基本知識:
nodename:標明一個結點的標簽名稱
/:父節點與子節點之間的分隔符
//:代表父節點與下屬某個節點之間若干個中間節點
[]:指定最末端結點的屬性
@:在[]中指定屬性名稱和對應的屬性值
在xpath路徑表達式中還有很多其他內容,但在selenium中進行基本的元素定位了解到上面這些規則就可以了,所以我們上面的例子中的規則,表示的就是定位
若干節點-<div>
... ...
<div>
... ...
<a class='ti next _j_pageitem'></a>
... ...
<div>
... ...
</div>
利用這樣的方式,基於browser.find_element_by_xpath()和browser.find_elements_by_xpath(),我們就可以找到頁面中單個獨特元素或多個同類型元素,再使用.click()方法即可完成對頁面內任意元素的模擬點擊;
3.4 基礎的瀏覽器動作模擬
除了上面一小節介紹的使用元素.click()控制點擊動作以外,selenium還支持豐富多樣的其他常見動作,因為本文是我介紹selenium的上篇,下面只介紹兩個常用的動作,更復雜的組合動作放在之后的文章中介紹:
模擬網頁下滑:
很多時候我們會遇到這樣的動態加載的網頁,如光點壁紙的各個壁紙板塊,這里以風景板塊為例http://pic.adesk.com/cate/landscape:
這個網頁的特點是,大多數情況下沒有翻頁按鈕,而是需要用戶將頁面滑到底部之后,才會自動加載下一頁的內容,並且這種機制持續固定幾次后,會參雜一個必須點擊才可以進行翻頁操作的按鈕,我們可以在selenium中使用browser.execute_script()方法來傳入JavaScript腳本來執行瀏覽器動作,進而實現下滑功能;
對應下滑到底的JavaScript腳本為'window.scrollTo(0, document.body.scrollHeight)',我們用下面這段代碼來實現持續下滑,並及時捕捉翻頁按鈕進行點擊(利用錯誤處理機制來實現):
from selenium import webdriver import time browser = webdriver.Chrome() '''訪問光點壁紙風景板塊頁面''' browser.get('http://pic.adesk.com/cate/landscape') '''這里嘗試的時候不要循環太多次,快速加載圖片比較吃網速和內存''' for i in range(1, 20): '''這里使用一個錯誤處理機制, 如果有定位到加載下一頁按鈕就進行 點擊下一頁動作,否則繼續每隔1秒,下滑到底''' try: '''定位加載下一頁按鈕''' LoadMoreElement = browser.find_element_by_xpath("//div/div[@class='loadmore']") LoadMoreElement.click() except Exception as e: '''瀏覽器執行下滑動作''' browser.execute_script('window.scrollTo(0, document.body.scrollHeight)') time.sleep(1)
模擬輸入:
有些時候,我們需要對界面中出現的輸入框,即標簽為<input></input>代表的對象進行模擬輸入操作,這時候我們只需要對輸入框對應的網頁對象進行定位,然后使用browser.send_keys(輸入內容)來往輸入框中添加文本信息即可,下面是一個簡單的例子,我們從百度首頁出發,模擬了點擊登陸-點擊注冊-在用戶名輸入框中輸入指定的文本內容,這樣一個簡單的過程:
from selenium import webdriver browser = webdriver.Chrome() '''訪問百度首頁''' browser.get('http://www.baidu.com') '''對頁面右上角的登陸超鏈接進行定位,這里因為同名超鏈接有兩個, 所以使用find_elements_by_xpath來捕獲一個元素列表,再對其中 我們指定的對象執行點擊操作''' LoginElement = browser.find_elements_by_xpath("//a[@name='tj_login']") '''對指定元素進行點擊操作''' LoginElement[1].click() '''這段while語句是為了防止信息塊沒加載完成導致出錯''' while True: try: '''捕獲彈出的信息塊中的注冊按鈕元素''' SignUpElement = browser.find_elements_by_xpath("//a[@class='pass-reglink pass-link']") '''點擊彈出的信息塊中的注冊超鏈接''' SignUpElement[0].click() break except Exception as e: pass '''將主網頁切換至新彈出的注冊頁面中以便對其頁面內元素進行定位''' browser.switch_to.window(browser.window_handles[-1]) while True: try: '''對用戶名稱輸入框對應元素進行定位''' InputElement = browser.find_element_by_xpath("//input[@name='userName']") '''模擬輸入指定的文本信息''' InputElement.send_keys('Keras') break except Exception as e: pass
以上就是關於selenium進行網絡數據采集的上篇內容,其余的內容我會擠出時間繼續整理介紹,敬請關注,如有筆誤,望指出。