JavaScript動態(tài)渲染頁面爬取
JavaScript動態(tài)渲染得頁面不止Ajax一種。例如,有些頁面的分頁部分由JavaScript生成,而非原始HTML代碼,這其中并不包含Ajax請求。還有類似淘寶這種頁面,即使是Ajax獲取的數(shù)據(jù),其Ajax接口中也含很多加密參數(shù),使我們難以直接找出規(guī)律,也很難直接通過分析Ajax爬取數(shù)據(jù)。
為解決這些問題,可以直接模擬瀏覽器運行,然后爬取數(shù)據(jù),這樣就可以實現(xiàn)再瀏覽器中看到的內(nèi)容是什么樣,爬取的源碼就是什么樣——所見即所爬。無須去管網(wǎng)頁內(nèi)部的JavaScript使用什么算法渲染頁面,也不用管網(wǎng)頁后臺的Ajax接口到底含有哪些參數(shù)。
Python提供了許多模擬瀏覽器運行的庫,例如Selenium、Splash、Pyppetter、Playwright等,
可以實現(xiàn)所見即所爬,輕松爬取動態(tài)渲染頁面。
Selenium的使用
在很多情況下,Ajax請求的接口含有加密參數(shù),例如token、sign等,示例網(wǎng)址https://spa2.scrape.center/的Ajax接口就包含一個token的參數(shù),如圖所示:
由于請求Ajax接口時必須加上token參數(shù),因此如果不深入分析并找到token參數(shù)的構(gòu)造邏輯,是難以直接模擬Ajax請求的。
方法通常有兩種:一種是深挖其中的邏輯,把token參數(shù)的構(gòu)造邏輯完全找出來,再用Python代碼復(fù)現(xiàn),構(gòu)造Ajax請求;另一種是直接模擬瀏覽器的運行,繞過這個過程,因為在瀏覽器里是可以看到這個數(shù)據(jù)的,所以如果能把看到的數(shù)據(jù)直接爬取下來,當(dāng)然就能獲取對應(yīng)的信息了。
這里采用第二種方法,模擬瀏覽器的運行,爬取數(shù)據(jù)。由于使用的工具是Selenium,因此先了解一下它的基本使用方法。
Selenium是一個自動化測試工具,利用它可以驅(qū)動瀏覽器完成特定的操作,例如點擊、下拉等,還可以獲取瀏覽器當(dāng)前呈現(xiàn)的頁面的源代碼,做到所見即所爬,對于一些JavaScript動態(tài)渲染的頁面來說,這種爬取方式非常有效。
- 準(zhǔn)備工作
以Chrome瀏覽器為例講解Selenium的用法。在開始之前,請確保已經(jīng)正確安裝了Chrome瀏覽器,并配置好了ChromeDriver。另外,還需要正確安裝好Python的Selenium庫。
- 基本用法
首先大體看一下Selenum的功能,示例代碼如下:
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
# 創(chuàng)建一個Service對象,傳入ChromeDriver的路徑
service = Service(executable_path='./chromedriver')
# 創(chuàng)建一個WebDriver對象,傳入Service對象
browser = webdriver.Chrome(service=service)
try:
browser.get('https://www.baidu.com')
input = browser.find_element(By.ID, 'kw')
input.send_keys('Python')
input.send_keys(Keys.ENTER)
wait = WebDriverWait(browser, 10)
wait.until(EC.presence_of_element_located((By.ID, 'content_left')))
print(browser.current_url)
print(browser.get_cookies())
print(browser.page_source)
finally:
browser.close()
運行代碼后,會自動彈出一個Chrome瀏覽器。瀏覽器會跳轉(zhuǎn)到百度頁面,然后在搜索框中輸入Python,就會跳轉(zhuǎn)到搜索結(jié)果頁,如圖所示:
此時控制臺的輸出結(jié)果如下, 因為頁面源代碼過長,所以此處省略其內(nèi)容:
可以看到,我們得到當(dāng)前URL、Cookie內(nèi)容和頁面源代碼都是瀏覽器中的真實內(nèi)容。所以說,用Selenium驅(qū)動瀏覽器加載網(wǎng)頁,可以直接拿到JavaScript渲染的結(jié)果,無須關(guān)心使用的是什么加密系統(tǒng)。
- 初始化瀏覽器對象
Selenium支持的瀏覽器非常多,既有Chrome、Firefox、Edge、Safari等電腦端端瀏覽器,也有Android、BlackBerry等手機端端瀏覽器。我們可以用如下方式初始化瀏覽器對象:
from selenium import webdriver
browser = webdriver.Chrome()
browser = webdriver.Firefox()
browser = webdriver.Edge()
browser = webdriver.Safari()
這就完成了瀏覽器對象的初始化,并將其賦值給了browser。接下來,我們要做的就是調(diào)用browser,執(zhí)行其各個方法以模擬瀏覽器的操作。
- 訪問頁面
我們可以使用get方法請求網(wǎng)頁,向其參數(shù)傳入要請求網(wǎng)頁的URL即可。例如,使用get方法訪問淘寶,并打印出淘寶頁面的源代碼,代碼如下:
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
options = webdriver.ChromeOptions()
chrome_service = Service(executable_path='./chromedriver')
browser = webdriver.Chrome(options=options, service=chrome_service)
browser.get('https://www.taobao.com')
print(browser.page_source)
browser.close()
運行這段代碼后,彈出了Chrome瀏覽器并且自動訪問了淘寶,然后控制臺輸出了淘寶頁面的源代碼,隨后瀏覽器關(guān)閉。通過上面幾行代碼,就可以驅(qū)動瀏覽器并獲取網(wǎng)頁源碼,非常便捷。
如下圖所示:
/usr/bin/python3 /Users/bruce_liu/PycharmProjects/崔慶才--爬蟲/第7章JavaScript動態(tài)渲染頁面爬取/Selenium/訪問頁面/visit_page.py
<html lang="zh-CN" class="ks-webkit537 ks-webkit ks-chrome97 ks-chrome"><head><script charset="utf-8" src="https://g.alicdn.com/tbhome/??taobao-2021/0.0.37/common/head/item-bar.jst.html-min.js,taobao-2021/0.0.37/common/head/item.jst.html-min.js" async=""></script><script charset="utf-8" src="https://g.alicdn.com/tbhome/??taobao-2021/0.0.37/common/head/index-min.js,taobao-2021/0.0.37/c/shop/item.jst.html-min.js,taobao-2021/0.0.37/c/live/item.jst.html-min.js,taobao-2021/0.0.37/c/hotsale/item.jst.html-min.js,taobao-2021/0.0.37/c/hotsale/config-min.js,taobao-2021/0.0.37/c/helper/item.jst.html-min.js,taobao-2021/0.0.37/c/goods/item.jst.html-min.js,taobao-2021/0.0.37/c/qiang/item.jst.html-min.js,taobao-2021/0.0.37/common/pipe/index-min.js,taobao-2021/0.0.37/c/fashion/item.jst.html-min.js" async=""></script><script charset="utf-8" src="https://g.alicdn.com/tbhome/??taobao-2021/0.0.37/common/inject-min.js,taobao-2021/0.0.37/c/shop/index-min.js,taobao-2021/0.0.37/c/sale/index-min.js,taobao-2021/0.0.37/c/live/index-min.js,taobao-2021/0.0.37/c/hotsale/index-min.js,taobao-2021/0.0.37/c/helper/index-min.js,taobao-2021/0.0.37/c/goods/index-min.js,taobao-2021/0.0.37/c/qiang/index-min.js,taobao-2021/0.0.37/c/fashion/index-min.js" async=""></script><script charset="utf-8" src="https://g.alicdn.com/tbhome/??taobao-2021/0.0.37/lib/lazy-min.js" async=""></script><script src="https://textlink.simba.taobao.com/?name=tbhs&cna=NR6JHrCS9Q0CAXqPOLap4j2O&nn=&count=13&pid=430266_1006&_ksTS=1711419448007_70&callback=jsonp71" async=""></script>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=10,chrome=1">
<meta name="renderer" content="webkit">
<title>淘寶</title>
<meta name="spm-id" content="a21bo">
<meta name="description" content="淘寶網(wǎng) - 亞洲較大的網(wǎng)上交易平臺,提供各類服飾、美容、家居、數(shù)碼、話費/點卡充值… 數(shù)億優(yōu)質(zhì)商品,同時提供擔(dān)保交易(先收貨后付款)等安全交易保障服務(wù),并由商家提供退貨承諾、破損補寄等消費者保障服務(wù),讓你安心享受網(wǎng)上購物樂趣!">
<meta name="aplus-xplug" content="NONE">
<meta name="keyword" content="淘寶,掏寶,網(wǎng)上購物,C2C,在線交易,交易市場,網(wǎng)上交易,交易市場,網(wǎng)上買,網(wǎng)上賣,購物網(wǎng)站,團購,網(wǎng)上貿(mào)易,安全購物,電子商務(wù),放心買,供應(yīng),買賣信息,網(wǎng)店,一口價,拍賣,網(wǎng)上開店,網(wǎng)絡(luò)購物,打折,免費開店,網(wǎng)購,頻道,店鋪">
<meta name="msvalidate.01" content="6E0390D0C5FFD883392E1B1E070FE901">
<link rel="dns-prefetch" href="http://g.alicdn.com">
<link rel="dns-prefetch" href="http://img.alicdn.com">
<link rel="dns-prefetch" href="http://tce.alicdn.com">
<link rel="dns-prefetch" href="http://gm.mmstat.com">
<link ref="dns-prefetch" href="http://tce.taobao.com">
......
......
</div>
<script src="https://o.alicdn.com/tbhome/tbnav/index.js" crossorigin="anonymous"></script>
<script src="http://g.alicdn.com/jstracker/sdk-assests/5.5.7/index.js" crossorigin="anonymous"></script>
<script>
if (window.JSTracker2 && typeof window.JSTracker2.setJsErrorFilters === 'function') {
window.JSTracker2.setJsErrorFilters([
}
</script>
<div id="J_SiteFooter" style="min-height: 150px;"></div><script src="http://g.alicdn.com/dinamic/barrier-free/0.0.14/aria.js?appid=7e39dd4d92f393f9450d8fc1f6bafdf9" charset="utf-8" crossorigin="anonymous" id="ariascripts"></script><script src="https://mmstl-tianhe.tanx.com/imp?e=EI6Tcu1I57dhn5ny0MYV17itTH4elfHClgvvQMRvyKotVZ%2BMZXb1IKSGvYSTQULLnDGsL7%2F80wbNWHiDfQA%2FF6fPYeq%2BUVIUt4oS3Ry9Yvxg23JJPjcFGglyetaJTM3r%2BGu5AupyvbGqDwsvMj7DN0EuvZPul854QXIjWeWEI%2FstYFYNXrxPJfuP%2FYrCGX7dR%2FB7WOTSniHP9Ecdp4KYYYS5XFdX0LlQGwm%2Bxfh7VoYv4RRBLeVMrvuephet%2B1E4mcspDTYQS0UwhUQ2NLksNSP7O853W5Uf5jF5RF7FO%2Bt%2FvA2pFnpI57UFDS2I8FjMSJeNjyoZM49rHrSwCbjeYy8IR7EpTOapG9uyrT1CX02CV64WRid2L9sBdt3yP653pVMJdrB71p8vB0pm6TsM1QWUA7kM6OE7FxK524yezLCbguSRsYxRUzFUmr2wHpjlBVm8Dv1nqV3cHy1nwc85oL0mR7g%2B5FCLwdfeNb%2BUbTEoPcAHYnhtUmuZHtCovyEs&k=513"></script><iframe id="CrossStorageClient-dbab0f34-94de-47e8-afc6-63999a0409f8" src="https://www.taobao.com/wow/z/tbhome/default/kissy-search-suggest-iframe" style="display: none; position: absolute; top: -999px; left: -999px;"></iframe></body></html>
- 查找節(jié)點
Selenium可以驅(qū)動瀏覽器完成各種操作,比如填充表單、模擬點擊等。例如,想要往某個輸入框中輸入文字,總得知道這個輸入框在哪吧?對此,Selenium為我們提供了一系列用了查找節(jié)點的方法,我們可以使用這些方法獲取想要的節(jié)點,以便執(zhí)行下一步的操作或者提取信息。
- 單個節(jié)點
例如,想從淘寶頁面中提取搜索框這個節(jié)點,首先就要觀察這個頁面的源代碼,如圖所示:
可以發(fā)現(xiàn),淘寶頁面的id屬性值search-box,可以用多種方式獲取它們。例如,find_element(By.ID, ‘search-box’), find_element(By.CSS_SELECTOR, ‘#search-box’),實現(xiàn)代碼如下:
<selenium.webdriver.remote.webelement.WebElement (session="62cc2bc2e196c3aa7015201b8324b1bc",
element="9e8c29ae-072f-485f-8e2d-b6199566f37a")>
<selenium.webdriver.remote.webelement.WebElement (session="62cc2bc2e196c3aa7015201b8324b1bc",
element="9e8c29ae-072f-485f-8e2d-b6199566f37a")>
可以看到兩種返回方式完全一致。
- 多個節(jié)點
如果查找的目標(biāo)節(jié)點在網(wǎng)頁中只有一個,那么用find_element方法就完全可以實現(xiàn)。但如果目標(biāo)節(jié)點有多個,再用find_element方法查找,就只能得到第一個節(jié)點了,此時需要用find_elements方法才能找到所有滿足條件的節(jié)點。注意,這個方法名稱中的element后面多一個s,注意區(qū)分。
例如,要查找淘寶頁面左側(cè)導(dǎo)航條的所有條目,可以這樣實現(xiàn):
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
options = webdriver.ChromeOptions()
services = Service(executable_path='./chromedriver')
browser = webdriver.Chrome(options=options, service=services)
browser.get('https://www.taobao.com')
lis = browser.find_elements(By.CSS_SELECTOR, '.service-bd li')
print(lis)
browser.close()
運行結(jié)果如下:
[<selenium.webdriver.remote.webelement.WebElement (session="d7c2af118c2c03214b1067aa0ab0bfab", element="369fdde2-69b4-474a-952f-a8645883cec8")>,
<selenium.webdriver.remote.webelement.WebElement (session="d7c2af118c2c03214b1067aa0ab0bfab", element="b07127c3-5ec0-44f8-92d4-caddac7c17b3")>,
<selenium.webdriver.remote.webelement.WebElement (session="d7c2af118c2c03214b1067aa0ab0bfab", element="0d25b16d-7ef3-47c9-a9b5-0996afff6ad5")>,
<selenium.webdriver.remote.webelement.WebElement (session="d7c2af118c2c03214b1067aa0ab0bfab", element="06cecb64-bd8f-45e2-9f77-65ab95221e51")>,
<selenium.webdriver.remote.webelement.WebElement (session="d7c2af118c2c03214b1067aa0ab0bfab", element="b60120cc-9692-4615-b44a-55f4fb482819")>,
<selenium.webdriver.remote.webelement.WebElement (session="d7c2af118c2c03214b1067aa0ab0bfab", element="ca538bd2-2ab6-4f88-be48-b3e56b67269d")>,
<selenium.webdriver.remote.webelement.WebElement (session="d7c2af118c2c03214b1067aa0ab0bfab", element="e394e356-827b-426c-be39-6523298cabd6")>,
<selenium.webdriver.remote.webelement.WebElement (session="d7c2af118c2c03214b1067aa0ab0bfab", element="072c5d22-5d9d-4ff8-ac71-0a33d956f815")>,
<selenium.webdriver.remote.webelement.WebElement (session="d7c2af118c2c03214b1067aa0ab0bfab", element="d124ebef-1274-45c1-bae5-ffa0da3ee6b8")>]
可以看到,得到的內(nèi)容變成了列表類型,列表中的每個節(jié)點都屬于WebElement類型。總結(jié)發(fā)現(xiàn),如果使用find_element方法,只能得到匹配成功的第一個節(jié)點,這個節(jié)點是WebElement類型的。如果使用find_elements方法,那么結(jié)果是列表類型的,列表中的每個節(jié)點都屬于WebElement類型。
獲取多個節(jié)點可以使用find_elements(By.ID)、find_elements(By.XPATH)、find_elements(By.LINK_TEXT)、find_elements(By.TAG_NAME)、find_elements(By.CLASS_NAME)、find_elements(By.CSS_SELECTOR)。同理,我們可以直接使用find_elements(By.CSS_SELECTOR, ‘.service-bd li’)得到的結(jié)果是完全一致的。
- 節(jié)點交互
Selenium可以驅(qū)動瀏覽器執(zhí)行一些操作。比較常見的用法有:用send_keys方法輸入文字,用clear方法清空文字,用click方法點擊按鈕。示例如下:
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
import time
options = webdriver.ChromeOptions()
services = Service(executable_path='./chromedriver')
browser = webdriver.Chrome(options=options, service=services)
browser.get('https://www.taobao.com')
input = browser.find_element(By.ID, 'q')
input.send_keys('iPhone')
time.sleep(3)
input.clear()
input.send_keys('iPad')
button = browser.find_element(By.CLASS_NAME, 'btn-search')
button.click()
這里首先驅(qū)動瀏覽器打開淘寶,然后使用find_element(By.ID)方法獲取輸入框,再使用send_keys方法輸入文字iPhone,等待3秒后用clear方法清空輸入框,再次調(diào)用send_keys方法輸入文字iPad,之后使用find_element(By.CLASS_NAME)方法獲取搜索按鈕,最后調(diào)用click方法實現(xiàn)搜索。
- 動作鏈
在上面的實例中,交互操作都是針對某個節(jié)點執(zhí)行。例如,對于輸入框,調(diào)用它的輸入文字方法send_keys和清空文字方法clear;對于搜索按鈕,調(diào)用了它的點擊方法click。其實還有一些操作,它們沒有特定的執(zhí)行對象,比如鼠標(biāo)拖拽、鍵盤按鍵等,這些操作需要用另一種方式執(zhí)行,那就是動作鏈。
例如,可以這樣實現(xiàn)拖拽節(jié)點的操作,將某個節(jié)點從一處拖拽至另一處:
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.service import Service
from selenium.webdriver import ActionChains
options = webdriver.ChromeOptions()
service = Service(executable_path='./chromedriver')
browser = webdriver.Chrome(options=options,service=service)
url = 'http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable'
browser.get(url)
browser.switch_to.frame('iframeResult')
source = browser.find_element(By.CSS_SELECTOR, '#draggable')
target = browser.find_element(By.CSS_SELECTOR, '#droppable')
actions = ActionChains(browser)
actions.drag_and_drop(source, target)
actions.perform()
這里首先打開網(wǎng)頁中的一個拖拽實例,然后依次選中要拖拽的節(jié)點和拖拽至的目標(biāo)節(jié)點,接著聲明一個ActionChains對象并賦值給actions變量,再后調(diào)用actions變量的drag_and_drop方法聲明拖拽對象和拖拽目標(biāo),最后調(diào)用perform方法執(zhí)行動作,就完成了拖拽操作,拖拽前和拖拽后的頁面如圖所示:
拖拽后的圖片如下:
- 運行JavaScript
還有一些操作,Selenium沒有提供API,例如下拉進度條,面對這種情況可以模擬運行JavaScript,此時使用execute_script方法即可實現(xiàn),代碼如下:
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
import time
options = webdriver.ChromeOptions()
services = Service(executable_path='../chromedriver')
browser = webdriver.Chrome(service=services, options=options)
browser.get('https://www.zhihu.com/explore')
browser.execute_script('window.scrollTo(0, document.body.scrollHeight)')
browser.execute_script('alert("To Bottom")')
time.sleep(12)
browser.close()
這里利用execute_script方法將進度條下拉到最底部,然后就彈出了警告提示框。所以說有了execute_script方法,那些沒有被提供API的功能幾乎都可以運行JavaScript的方式實現(xiàn)。
- 獲取節(jié)點信息
前面已經(jīng)通過page_source屬性獲取了網(wǎng)頁的源代碼,下面就可以用解析庫(如正則表達式、Beautiful Soup、pyquery等)從中提取信息了。
既然Selenium已經(jīng)提供了選擇節(jié)點的方法,返回的結(jié)果是WebElement類型,那么它肯定也有相關(guān)的方法和屬性用來直接提取節(jié)點信息,例如屬性、文本值等。這樣我們就不需要通過解析源代碼提取信息了。一起體驗一下吧。
- 獲取屬性
可以使用get_attribute方法獲取節(jié)點屬性,但其前提是得先選中這個節(jié)點,示例如下:
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
options = webdriver.ChromeOptions()
services = Service(executable_path='../chromedriver')
browser = webdriver.Chrome(options=options, service=services)
url = 'https://spa2.scrape.center/'
browser.get(url)
logo = browser.find_element(By.CLASS_NAME, 'logo-image')
print(logo)
print(logo.get_attribute('src'))
運行代碼,它會驅(qū)動瀏覽器打開示例頁面,然后獲取其中class名稱為logo-image的節(jié)點,最后打印出這個節(jié)點的src屬性??刂婆_的輸出結(jié)果如下:
<selenium.webdriver.remote.webelement.WebElement (session="c8bd8a8807d19a97572ebbbac014f8d8",
element="6d73a938-a051-4ba0-9b3b-3bb7057ae1d1")>
https://spa2.scrape.center/img/logo.a508a8f0.png
向get_attribute方法的參數(shù)傳入想要獲取的屬性名,就可以得到該屬性的值了。
- 獲取文本值
每個WebElement節(jié)點都有text屬性,直接調(diào)用這個屬性就可以得到節(jié)點內(nèi)部的文本信息,相當(dāng)于pyquery中的text方法,示例如下:
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
options = webdriver.ChromeOptions()
services = Service(executable_path='../chromedriver')
browser = webdriver.Chrome(service=services, options=options)
url = 'https://spa2.scrape.center/'
browser.get(url)
input = browser.find_element(By.CLASS_NAME, 'logo-title')
print(input.text)
控制臺輸出結(jié)果如下:
Scrape
- 獲取ID、位置、標(biāo)簽名和大小
除了屬性和文本值,WebElement節(jié)點還有一些其他屬性,例如id屬性用于獲取節(jié)點ID、location屬性用于獲取節(jié)點在頁面中的相對位置,tag_name屬性用于獲取標(biāo)簽的名稱,size屬性用于獲取節(jié)點的大小、也就是寬高,這些屬性有時候還是很有用的。示例如下:
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
options = webdriver.ChromeOptions()
services = Service(executable_path='../chromedriver')
browser = webdriver.Chrome(service=services, options=options)
url = 'https://spa2.scrape.center/'
browser.get(url)
input = browser.find_element(By.CLASS_NAME, 'logo-title')
print('ID屬性:', input.id)
print('location屬性:', input.location)
print('tag_name屬性:', input.tag_name)
print('size屬性:', input.size)
運行結(jié)果如下:
ID屬性: a9ae0c3c-677c-4c26-897d-526bf206e02b
location屬性: {'x': 205, 'y': 13}
tag_name屬性: span
size屬性: {'height': 40, 'width': 73}
這里首先獲取class名稱為logo-title的節(jié)點,然后分別調(diào)用該節(jié)點的id、location、tag_name、size屬性獲取了對應(yīng)的屬性值。
- 切換Frame
網(wǎng)頁中有一種節(jié)點叫做iframe,也就是子Frame,相當(dāng)于頁面的子頁面,它的結(jié)構(gòu)和外部網(wǎng)頁的結(jié)構(gòu)完全一致。Selenium打開一個頁面后,默認是父Frame里操作,此時這個頁面如果還有子Frame,它是不能獲取子Frame里的節(jié)點,這時就需要使用switch_to.frame方法切換Frame。示例如下:
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.service import Service
options = webdriver.ChromeOptions()
services = Service(executable_path='../chromedriver')
browser = webdriver.Chrome(service=services, options=options)
url = 'http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable'
browser.get(url)
browser.switch_to.frame('iframeResult')
try:
logo = browser.find_element(By.CLASS_NAME, 'logo')
except:
print('NO LOGO')
browser.switch_to.parent_frame()
logo = browser.find_element(By.CLASS_NAME, 'logo')
print(logo)
print(logo.text)
還是以演示動作鏈操作時的網(wǎng)頁為例,首先通過switch_to.frame方法切換到子Frame里,然后嘗試獲取其中的logo節(jié)點(子Frame里并沒有l(wèi)ogo節(jié)點),如果找不到,就會拋出異常,異常被捕捉后,會輸出NO LOGO。接著,切換回父Frame,重新獲取logo節(jié)點,發(fā)現(xiàn)此時可以成功獲取了。
控制臺輸出結(jié)果如下:
NO LOGO
<selenium.webdriver.remote.webelement.WebElement (session="a34f120f7337feed37fcca8aa3af86a7",
element="dea6705a-d39f-477a-b9ac-a7c042d274e6")>
所以,當(dāng)頁面中包含子Frame時,如果想獲取子Frame中的節(jié)點,需要先調(diào)用switch_to.frame方法切換到對應(yīng)的Frame,再進行操作。
- 延時等待
在Selenium中,get方法在網(wǎng)頁框架中加載結(jié)束后才會結(jié)束執(zhí)行,如果我們嘗試在get方法執(zhí)行完畢時獲取網(wǎng)頁源代碼,其結(jié)果可能并不是瀏覽器完全加載完成的頁面,因為某些頁面有額外的Ajax請求,頁面還會經(jīng)由JavaScript渲染。所以,在必要的時候,我們需要設(shè)置瀏覽器延時等待一定的時間,確保節(jié)點已經(jīng)加載出來。
等待方式有兩種:一種是隱式等待,一種是顯式等待。
- 隱式等待
使用隱式等待執(zhí)行測試時,如果Selenium沒有在DOM中找到節(jié)點,將繼續(xù)等待,在超出設(shè)定時間后,拋出找不到節(jié)點的異常。換句話說,在查找節(jié)點而節(jié)點沒有立即出現(xiàn)時,隱式等待會先等待一段時間再查找DOM,默認的等待時間是0。示例如下:
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
options = webdriver.ChromeOptions()
services = Service(executable_path='../chromedriver')
browser = webdriver.Chrome(service=services, options=options)
browser.implicitly_wait(10)
browser.get('https://spa2.scrape.center/')
input = browser.find_element(By.CLASS_NAME, 'logo-image')
print(input)
運行結(jié)果如下:
<selenium.webdriver.remote.webelement.WebElement (session="278b608e46fa3c124a3fa7867b93a34e",
element="8b49c4ec-73bc-4176-948f-3a735fd84e55")>
這里我們用implicitly_wait方法實現(xiàn)了隱式等待。
- 顯示等待
隱式等待效果其實不好,因為只規(guī)定了一個固定時間,而頁面的加載時間會受到網(wǎng)絡(luò)條件的影響。還有一種更適合的等待方式——顯式等待,這種方式會指定要查找的節(jié)點和最長等待時間。如果在規(guī)定時間內(nèi)加載出了要查找的節(jié)點,就返回這個節(jié)點;如果到了規(guī)定時間依然沒有加載出點,就拋出超時異常。示例如下:
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
options = webdriver.ChromeOptions()
services = Service(executable_path='../chromedriver')
browser = webdriver.Chrome(service=services, options=options)
browser.get('https://www.taobao.com')
wait = WebDriverWait(browser, 10)
input = wait.until(EC.presence_of_element_located((By.ID, 'q')))
button = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '.btn-search')))
print(input, button)
這里首先引入WebDriverWait對象,指定最長等待時間為10,并賦值給wait變量,然后調(diào)用wait的until方法,傳入等待條件。
這里先傳入了presence_of_element_located這個條件,代表節(jié)點出現(xiàn),其參數(shù)是節(jié)點的定位元組(By.ID, ‘q’),表示節(jié)點ID為q的節(jié)點(即搜索框)。這樣做達到的效果是如果節(jié)點ID為q的節(jié)點在10秒內(nèi)成功加載出來了,就返回該節(jié)點;如果超過10秒還沒加載出來,就拋出異常。
然后傳入的等待條件是element_to_be_clickable,代表按鈕可點擊,所以查找按鈕時要查找CSS選擇器為.btn-search的按鈕,如果10秒內(nèi)它是可點擊的,就是按鈕節(jié)點成功加載出來了,就返回該節(jié)點;如果超過10秒還是不可點擊,也就是按鈕節(jié)點沒有加載出來,就拋出異常。
運行代碼,在網(wǎng)速較佳的情況下是可以成功加載節(jié)點的??刂婆_輸出結(jié)果如下:
<selenium.webdriver.remote.webelement.WebElement (session="84cc41714d2e631dd447da556a3585d0",
element="90a9a753-b7dc-45c5-b827-556a4f7a8fda")>
<selenium.webdriver.remote.webelement.WebElement (session="84cc41714d2e631dd447da556a3585d0",
element="16b85e70-d0cb-4217-ba93-77ea65a62474")>
可以看到,成功輸出了兩個節(jié)點,都是WebElement類型的。如果網(wǎng)絡(luò)有問題,10秒到了還是沒有成功加載,就拋出TimeoutException異常。
- 前進和后退
平常使用瀏覽器時,都是前進和后退功能,Selenium也可以完成這個操作,它使用forward方法實現(xiàn)前進,使用back方法實現(xiàn)后退。示例如下:
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
import time
options = webdriver.ChromeOptions()
services = Service('../chromedriver')
browser = webdriver.Chrome(service=services, options=options)
browser.get('https://www.baidu.com/')
browser.get('https://www.taobao.com/')
browser.get('https://www.python.org/')
browser.back()
time.sleep(1)
browser.forward()
browser.close()
這里我們先連續(xù)訪問了3個頁面,然后調(diào)用back方法回到第2個頁面,接著調(diào)用forward方法又前進到第3個頁面。
- Cookie
使用Selenium,還可以方便地對Cookie進行操作,例如獲取、添加、刪除等。示例如下:
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
options = webdriver.ChromeOptions()
services = Service('../chromedriver')
browser = webdriver.Chrome(service=services, options=options)
browser.get('https://www.zhihu.com/explore')
browser.add_cookie({'name':'name', 'domain':'www.zhihu.com', 'value':'germey'})
print(browser.get_cookies())
browser.delete_all_cookies()
print(browser.get_cookies())
這里我們先訪問了知乎。知乎頁面加載完成后,瀏覽器其實已經(jīng)生成Cookie了。然后,調(diào)用瀏覽器對象的get_cookes方法獲取所有的Cookie。接著,添加一個Cookie,這里傳入一個字典,包含name、domain和value等鍵值。之后,再次獲取所有的Cookie,會發(fā)現(xiàn)結(jié)果中多了一項,就是我們新加的Cookie。最后,調(diào)用delete_all_cookies方法刪除所有的Cookie并再次獲取,會發(fā)現(xiàn)此事結(jié)果就空了。
控制臺輸出結(jié)果如下:
[{'domain': '.www.zhihu.com', 'httpOnly': False, 'name': 'name', 'path': '/',
'secure': True, 'value': 'germey'}, {'domain': 'www.zhihu.com',
'httpOnly': False, 'name': 'KLBRSID', 'path': '/', 'secure': False,
'value': 'ed2ad9934af8a1f80db52dcb08d13344|1711448448|1711448445'},
{'domain': '.zhihu.com', 'expiry': 1742984447, 'httpOnly': False,
'name': 'Hm_lvt_98beee57fd2ef70ccdd5ca52b9740c49', 'path': '/',
'secure': False, 'value': '1711448448'},
{'domain': '.zhihu.com', 'httpOnly': False,
'name': 'Hm_lpvt_98beee57fd2ef70ccdd5ca52b9740c49', 'path': '/',
'secure': False, 'value': '1711448448'},
{'domain': '.zhihu.com', 'expiry': 1806056445,
'httpOnly': False, 'name': 'd_c0', 'path': '/',
'secure': False, 'value': 'ABBY2F-lXhiPTq3E1glsHl-yDs3b6u15x4w=|1711448445'},
{'domain': '.zhihu.com', 'httpOnly': False, 'name': '_xsrf', 'path': '/',
'secure': False, 'value': '34131e3a-3c44-4ff2-857a-563bd80c2f3f'},
{'domain': '.zhihu.com', 'expiry': 1774520445, 'httpOnly': False,
'name': '_zap', 'path': '/', 'secure': False,
'value': '62da9fb2-33b9-4234-b23e-a192b6f507e4'}]
[]
通過以上方法操作Cookie還是非常方便的。
- 選項卡管理
訪問網(wǎng)頁的時候,會開啟一個選項卡。在Selenium中,我們也可以對選項卡做操作。示例如下:
import time
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
services = Service(executable_path='../chromedriver')
options = webdriver.ChromeOptions()
browser = webdriver.Chrome(service=services, options=options)
browser.get('https://www.baidu.com')
browser.execute_script('window.open()')
print(browser.window_handles)
browser.switch_to.window(browser.window_handles[1])
browser.get('https://www.taobao.com')
time.sleep(1)
browser.switch_to.window(browser.window_handles[0])
browser.get('https://python.org')
這里首先訪問百度,然后調(diào)用execute_script方法,向其參數(shù)傳入window.open()這個JavaScript語句,表示新開啟一個選項卡。接著,我們想切換到這個新開的選項卡。window_handles屬性用于獲取當(dāng)前開啟的所有選項卡,返回值是選項卡的代號列表。要想切換選項卡,只需要調(diào)用switch_to.windows方法即可,其中參數(shù)是目的選項卡的代號。這里我們將新開選項卡的代號傳入,就切換到了第2個選項卡,然后在這個選項卡下打開一個新頁面,再重新調(diào)用switch_to.window方法切換回到第1個選項卡。控制臺的輸出結(jié)果如下:
['CDwindow-D97D968F0BD8741543EE3C6FB42692CF', 'CDwindow-E218A8DBC3864BBE41F29F61A2E1ED78']
- 異常處理
在使用Selenium的過程中,難免會遇到一些異常,例如超時、節(jié)點未找到等,一旦出現(xiàn)此類異常,程序便不會繼續(xù)運行了。此時我們可以使用try except語句捕獲各種異常。
首先,演示一下節(jié)點未找到的異常,示例如下:
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
options = webdriver.ChromeOptions()
services = Service(executable_path='../chromedriver')
browser = webdriver.Chrome(service=services, options=options)
browser.get('https://www.baidu.com')
browser.find_element(By.ID, 'hello')
這里首先打開百度頁面,然后嘗試選擇一個并不存在的節(jié)點,就會遇到節(jié)點未找到的異常。控制臺的輸出結(jié)果如下:
selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to locate element:
{"method":"css selector","selector":"[id="hello"]"}
(Session info: chrome=97.0.4692.71);
For documentation on this error,
please visit: https://www.selenium.dev/documentation/webdriver/troubleshooting/errors#no-such-element-exception
可以看到,這里拋出了NoSuchElementException異常,這通常表示節(jié)點未找到。為了防止程序遇到異常而中斷運行,我們需要捕獲這些異常,示例如下:
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.common.exceptions import TimeoutException, NoSuchElementException
options = webdriver.ChromeOptions()
services = Service(executable_path='../chromedriver')
browser = webdriver.Chrome(service=services, options=options)
try:
browser.get('https://www.baidu.com')
except TimeoutException:
print('Time Out')
try:
browser.find_element(By.ID, 'hello')
except NoSuchElementException:
print('No Element')
finally:
browser.close()
這里我們使用try except語句捕獲各類異常。例如,對查找節(jié)點的方法find_element_by_id捕獲NoSuchElementException異常,這樣一旦出現(xiàn)這樣的錯誤,就會進行異常處理,程序也不會中斷。控制臺的輸出結(jié)果如下:
No Element
- 反屏蔽
現(xiàn)在有很多網(wǎng)站增加了對Selenium的檢測,防止一些爬蟲的惡意爬取,如果檢測到有人使用Selenium打開瀏覽器,就直接屏蔽。
在大多數(shù)情況下,檢測到基本原理是檢測瀏覽器窗口下的window.navigator對象中是否包含webdriver屬性。因為在正常使用瀏覽器時,這個屬性應(yīng)該是undefined,一旦使用了Selenium,它就會給window.navigator對象設(shè)置webdriver屬性。很多網(wǎng)站通過JavaScript語句判斷是否存在webdriver屬性,如果存在就直接屏蔽。
一個典型的案例網(wǎng)站https://antispider1.scrape.center/就是使用上述原理,檢測是否存在webdriver屬性,如果我們使用Selenium直接爬取該網(wǎng)站的數(shù)據(jù),網(wǎng)站就會返回如圖所示的頁面:
在Selenium中,可以用CDP(即Chrome Devtools Protocol,Chrome開發(fā)工具協(xié)議)解決這個問題,利用它可以實現(xiàn)在每個頁面剛加載的時候就執(zhí)行JavaScript語句,將webdriver屬性設(shè)置空,這里執(zhí)行的CDP方法叫做Page.addScriptToEvaluateOnNewDocument,將上面的JavaScript語句傳入其中即可。另外,還可以加入幾個選項來隱藏WebDriver提示條和自動化擴展信息,代碼實現(xiàn)如下:
from selenium import webdriver
from selenium.webdriver import ChromeOptions
from selenium.webdriver.chrome.service import Service
import time
option = ChromeOptions()
service = Service(executable_path='../chromedriver')
option.add_experimental_option('excludeSwitches', ['enable-automation'])
option.add_experimental_option('useAutomationExtension', False)
browser = webdriver.Chrome(service=service, options=option)
browser.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument',
{'source': 'Object.defineProperty(navigator, "webdriver",{get:() => undefined})'})
browser.get('https://antispider1.scrape.center/')
time.sleep(16)
這樣就能加載出整個頁面了,如圖所示:
在多數(shù)時候,以上方法可以實現(xiàn)Selenium的反屏蔽。但也存在一些特殊網(wǎng)站會對WebDriver屬性設(shè)置更多的特征檢測,這種情況下可能需要具體排查。
- 無頭模式
Chrome瀏覽器從60版起,已經(jīng)開啟了對無頭模式的支持,即Headless。無頭模式下,網(wǎng)站運行的時候不會彈出窗口,從而減少了干擾,同時還減少了一些資源(如圖片)的加載,所以無頭模式也在一定程度上節(jié)省了資源加載的時間和網(wǎng)絡(luò)帶寬。
我們可以借助ChromeOptions對象開啟Chrome瀏覽器的無頭模式,代碼實現(xiàn)如下:
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
option = webdriver.ChromeOptions()
option.add_argument('--headless')
services = Service(executable_path='../chromedriver')
browser = webdriver.Chrome(options=option, service=services)
browser.set_window_size(1366, 768)
browser.get('https://www.taonan.gov.cn')
browser.get_screenshot_as_file('preview.png')
這里利用ChromeOptions對象的add_argument方法添加了一個參數(shù)—headless,從而開啟了無頭模式。在無頭模式下,最好設(shè)置一下窗口的大小,因此這里調(diào)用了set_window_size方法。之后打開頁面,并調(diào)用get_screenshot_as_file方法輸出了頁面截圖。
運行這段代碼后,會發(fā)現(xiàn)窗口不會再彈出來了,代碼依然正常運行,最后輸出的頁面截圖如下圖所示:
這樣我們就在無頭模式下完成了頁面的爬取和截圖操作。文章來源:http://www.zghlxwxcb.cn/news/detail-847864.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-847864.html
到了這里,關(guān)于JavaScript動態(tài)渲染頁面爬取——Selenium的使用的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!