
總結(jié):
- 顯示等待: 滿足條件后 ,才會處理網(wǎng)頁;
隱示等待: 設(shè)置一個等待時間;- 注意: 搜索框 是元組
element = WebDriverWait(driver, 10).until(
EC.presence_of_element_located( # 元素加載到DOM,但是不保證他是可見的;
(By.ID, "inp-query") # 找搜索框 是 元組
)- 顯式等待比隱式等待更節(jié)約執(zhí)行時間,因此更推薦使用顯式等待方式判斷頁面元素是否存在
動態(tài)網(wǎng)頁處理
很多網(wǎng)站都采用AJAX技術(shù)、SPA技術(shù),部分內(nèi)容都是異步動態(tài)加載的。可以提高用戶體驗,減少不必要的流量,方便CDN加速等。(大部分的網(wǎng)頁,尤其是手機)
但是,對于爬蟲程序爬取到的HTML頁面相當(dāng)于頁面模板了,動態(tài)內(nèi)容不在其中。
解決辦法之一,如果能構(gòu)造一個包含JS引擎的瀏覽器,讓它加載網(wǎng)頁并和網(wǎng)站交互,我們編程從這個瀏覽器獲取內(nèi)容包括動態(tài)內(nèi)容。這個瀏覽器不需要和用戶交互的界面,只要能支持HTTP、HTTPS協(xié)議和服務(wù)器端交互,能解析HTML、CSS、JS就行了。
1. PhantomJS
它是一個headless無頭瀏覽器,支持Javascript。可以運行在Windows、Linux、Mac OS等。
所謂無頭瀏覽器,就是包含Js引擎、瀏覽器排版引擎等核心組件,但是沒有和用戶交互的界面的瀏覽器。
官網(wǎng) http://phantomjs.org/
官方文檔 http://phantomjs.org/
下載 http://phantomjs.org/
下載對應(yīng)操作系統(tǒng)的Phantomjs,解壓縮就可以使用 ;
測試:
確定是否安裝成功;
// test.js
"use strict";
console.log('hello world');
phantom.exit();
$ phantomjs-2.1.1-windows\bin\phantomjs.exe test.js
hello world # 成功運行;
# 自帶exmple試運行;
$ phantomjs-2.1.1-windows\bin\phantomjs.exe phantomjs-2.1.1-windows\examples\hello.js
Hello, world!
$ phantomjs-2.1.1-windows\bin\phantomjs.exe phantomjs-2.1.1-windows\examples\version.js
using PhantomJS version 2.1.1
2. Selenium
它是一個WEB自動化測試工具。它可以直接運行在瀏覽器中,支持主流的瀏覽器,包括PhantomJS(無界面瀏覽器)。 動作測試:
官網(wǎng) https://www.seleniumhq.org/
安裝
$ pip install selenium
WebDriver 可以看一下

chrom瀏覽器的web driver(chromedriver.exe),可以在下面網(wǎng)址訪問:
http://npm.taobao.org/mirrors/chromedriver/
firefox(火狐瀏覽器)的web driver (geckodriver.exe)在這里訪問:
https://github.com/mozilla/geckodriver/releases
其他瀏覽器驅(qū)動可以見下面列表:
Edge:https://developer.microsoft.com/en-us/micrsosft-edage/tools/webdriver
Safari:https://webkit.org/blog/6900/webdriver-support-in-safari-10/
部分Chromedriver支持的Chrome版本對照表:
chromedriver版本 支持的Chrome版本
v2.41 v67-69
v2.40 v66-68
v2.39 v66-68
v2.38 v65-67
v2.37 v64-66
v2.36 v63-65
v2.35 v62-64
v2.34 v61-63
v2.33 v60-62
v2.32 v59-61
v2.31 v58-60
v2.30 v58-60
v2.29 v56-58
v2.28 v55-57
v2.27 v54-56
v2.26 v53-55
v2.25 v53-55
v2.24 v52-54
v2.23 v51-53
v2.22 v49-52
v2.21 v46-50
3. 開發(fā)實戰(zhàn)
不同瀏覽器都會提供操作的接口,Selenium就是使用這些接口來操作瀏覽器
Selenium最核心的對象就是webdriver,通過它就可以操作瀏覽器、截圖、HTTP訪問、解析HTML等。
from selenium.webdriver import PhantomJS
driver = PhantomJS('F:/BaiduNetdiskDownload/2019 Python網(wǎng)絡(luò)期班配套資料/slides/chapter16爬蟲/install/phantomjs-2.1.1-windows/bin/phantomjs.exe')
driver.set_window_size(1280,1080)
driver.get('https://www.baidu.com/?tn=78000241_5_hao_pg')
driver.save_screenshot('F:/test/screen-baidu.png')
#----------------------------------------------------------
# 一般還是使用無頭瀏覽器,忽略警告;
UserWarning: Selenium support for PhantomJS has been deprecated, please use headless versions of Chrome or Firefox instead
warnings.warn('Selenium support for PhantomJS has been deprecated, please use headless '
1. 處理異步請求
bing的查詢結(jié)果是通過異步請求返回結(jié)果,所以,直接訪問頁面不能直接獲取到搜索結(jié)果。

. 定位頁面中的內(nèi)容是否返回成功
from urllib import parse
from selenium.webdriver import PhantomJS
import datetime, time, random
# #如果沒有在環(huán)境變量指定PhantomJS位置
driver = PhantomJS('F:/BaiduNetdiskDownload/2019 Python網(wǎng)絡(luò)期班配套資料/slides/chapter16爬蟲/install/phantomjs-2.1.1-windows/bin/phantomjs.exe')
driver.set_window_size(1280,1080)
# 保存圖片;
def savepic():
basepath = 'F:/test/'
filename = "{:%Y%m%d%H%M%S}-{:03}.png".format(datetime.datetime.now(), random.randint(1,100))
driver.save_screenshot(basepath+filename)
# 生成頁面快照并保存
# 模擬瀏覽器輸入網(wǎng)址;
url = 'https://cn.bing.com/search?q=%E9%A9%AC%E5%93%A5%E6%95%99%E8%82%B2' #自動轉(zhuǎn)的;
# url = 'https://cn.bing.com/search?' + parse.urlencode({'q':'馬哥教育'}) # parse.urlencode轉(zhuǎn)字符;
driver.get(url) # get方法會一直等到頁面加載,然后才會繼續(xù)執(zhí)行程序,通常測試會在這里選擇time.sleep(2)
MAXRETRIES = 5
for i in range(MAXRETRIES):
try:
ele = driver.find_element_by_id('b_results')
print(i, ele.is_displayed()) # 元素看不看得見;
if not ele.is_displayed():
time.sleep(1)
continue
savepic()
# break # 正常加break;
except Exception as e:
print(type(e), e)
print('--------------------')
driver.quit()
# ----------------------------------------
0 False
1 True # 頁面內(nèi)容返回成功;
2 True
3 True
4 True
可能結(jié)果未必能看到,說明數(shù)據(jù)回來了,而且組織好了,但是沒有顯示出來。
可以增加判斷元素是否顯示的代碼,直到等待的數(shù)據(jù)呈現(xiàn)在頁面上;
2. 下拉框處理
Selenium專門提供了Select類來處理網(wǎng)頁中的下拉框(下拉一次,整個頁面重新刷新一次,效率低下)
不過下拉框用的頁面越來越少了,本次使用 https://www.oschina.net/search?scope=project&q=python
select 下拉框的處理

from disk cache或者from memory cache又是在什么情況下會出現(xiàn)呢?
從字面意思不難理解,這都是瀏覽器的一種緩存機制。disk 是從硬盤中讀取資源,而 memory 則是從內(nèi)存中獲取資源,兩者的區(qū)別就是內(nèi)存和硬盤的區(qū)別:memory 中的資源是臨時的,當(dāng)關(guān)閉或者刷新頁面后就會丟失;而 disk 是存在硬盤上的,可以從文件夾中找到。
那是不是 memory 中的資源等下載加載頁面的時候又要從服務(wù)器獲取呢?其實不然,memory 中的資源其實也同時會存在 disk 中,所以下一次加載,瀏覽器會優(yōu)先從 disk 中檢索

重新刷新請求,說明 disk cache 是在服務(wù)器中重新下載的;

from urllib import parse
from selenium.webdriver import PhantomJS
from selenium.webdriver.support.ui import Select # 在 webdriver.support.ui 下找 Select;
import datetime, time, random
# #如果沒有在環(huán)境變量指定PhantomJS位置
driver = PhantomJS('F:/BaiduNetdiskDownload/2019 Python網(wǎng)絡(luò)期班配套資料/slides/chapter16爬蟲/install/phantomjs-2.1.1-windows/bin/phantomjs.exe')
driver.set_window_size(1280,1080)
# 保存圖片;
def savepic():
basepath = 'F:/test/'
filename = "{:%Y%m%d%H%M%S}-{:03}.png".format(datetime.datetime.now(), random.randint(1,100))
driver.save_screenshot(basepath+filename)
# 生成頁面快照并保存
# 模擬瀏覽器輸入網(wǎng)址;
url = 'https://www.oschina.net/search?scope=project'
# url = 'https://cn.bing.com/search?' + parse.urlencode({'q':'馬哥教育'}) # parse.urlencode轉(zhuǎn)字符;
driver.get(url) # get方法會一直等到頁面加載,然后才會繼續(xù)執(zhí)行程序,通常測試會在這里選擇time.sleep(2)
print(1, driver.current_url)
savepic()
# 模擬select 框 怎么使用;
sel = driver.find_element_by_name('tag1')
ele = Select(sel)
ele.select_by_value('309')
# ele.select_by_index(1)
print(2, driver.current_url)
savepic()
driver.quit()
3. 模擬鍵盤操作(模擬登陸)
web driver提供了一些列find方法, 用戶獲取一個網(wǎng)頁中的元素。元素對象可以使用send_keys模擬鍵盤輸入。
oschina的登錄頁, 登錄成功后會跳轉(zhuǎn)到首頁, 首頁右上角會顯示會員信息, 如果未登錄, 無此信息。

在瀏覽器中 模擬登錄過程:
分別 有: 打開登錄頁截圖;輸入賬戶截圖;登陸成功 截圖,拿到cookies ,可以隨時登錄;
from urllib import parse
from selenium.webdriver import PhantomJS
from selenium.webdriver.support.ui import Select
from selenium.webdriver.common.keys import Keys
import datetime, time, random
# #如果沒有在環(huán)境變量指定PhantomJS位置
driver = PhantomJS('F:/BaiduNetdiskDownload/2019 Python網(wǎng)絡(luò)期班配套資料/slides/chapter16爬蟲/install/phantomjs-2.1.1-windows/bin/phantomjs.exe')
driver.set_window_size(1280,1080)
# 保存圖片;
def savepic():
basepath = 'F:/test/'
filename = "{:%Y%m%d%H%M%S}-{:03}.png".format(datetime.datetime.now(), random.randint(1,100))
driver.save_screenshot(basepath+filename)
# 生成頁面快照并保存
# 模擬瀏覽器登錄網(wǎng)址;
url = 'https://www.oschina.net/home/login'
# url = 'https://cn.bing.com/search?' + parse.urlencode({'q':'馬哥教育'}) # parse.urlencode轉(zhuǎn)字符;
driver.get(url) # get方法會一直等到頁面加載,然后才會繼續(xù)執(zhí)行程序,通常測試會在這里選擇time.sleep(2)
print(1, driver.current_url)
savepic() # 打開登錄頁截圖;
# 模擬select 框 怎么使用;
username = driver.find_element_by_name('username')
username.send_keys('')
username = driver.find_element_by_name('password')
username.send_keys('')
# driver.find_element_by_class_name()
savepic() # 輸入賬戶截圖
password.send_keys(Keys.ENTER)
time.sleep(1)
savepic() # 登陸成功 截圖 或者 加上一個驗證是否有 退出按鈕的 驗證;
print(2, driver.current_url)
cookies = driver.get_cookies() #
print(cookies,type(cookies)) # 會有key : oscpid
driver.quit()
#----------------------------------
登錄成功 會有 oscpid;
獲得cookies 登錄后 爬取頁面
# 獲得cookies 登錄后 爬取頁面
import requests
from requests.cookies import RequestsCookieJar
from fake_useragent import UserAgent
headers = {'User-agent':UserAgent().random}
# no cookies
response = requests.request('GET', url, headers=headers)
text = response.text
print(response.url) # url中有 login 是未登錄狀態(tài);
print('==========================================')
# cookies # 湊cookies 字典;
jar = RequestsCookieJar()
for cookies in cookies:
jar.set(cookies.get('name'), cookies.get=('value')) # 湊cookies 字典;
response = requests.request('GET', url, headers=headers, cookies=jar)
text = response.text
print(response.url) # url中沒有 login登陸成功
# 保存登錄后的html
with open('./cnblog-html.html','w' , encoding='UTF-8') as f:
f.write(text)
驗證碼的問題 不是重點;要看AI模型的成功率;
4. 頁面等待技術(shù)
越來越多的頁面使用Ajax這樣的異步加載技術(shù),這就會導(dǎo)致代碼中要訪問的頁面元素,還沒有被加載就被訪問了(轉(zhuǎn)圈),拋出異常。
方法1 線程休眠
使用time.sleep(n) 來等待數(shù)據(jù)加載。
配合循環(huán)一直等到數(shù)據(jù)被加載完成,可以解決很多頁面動態(tài)加載或加載慢的問題。當(dāng)然可以設(shè)置一個最大重試次數(shù),以免一直循環(huán)下去。參看本文"處理異步請求”
方法2 Selenium等待
Selenium的等待分為:顯示等待 和 隱式等待
隱式等待,等待特定的時間
顯式等待,指定一個條件,一直等到這個條件成立后繼續(xù)執(zhí)行,也可以設(shè)置超時時間,超時會拋異常
參考:https://www.selenium.dev/documentation/en/
# 官網(wǎng)案例:
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support.expected_conditions import presence_of_element_located
#This example requires Selenium WebDriver 3.13 or newer
with webdriver.Firefox() as driver:
wait = WebDriverWait(driver, 10)
driver.get("https://google.com/ncr")
driver.find_element(By.NAME, "q").send_keys("cheese" + Keys.RETURN)
first_result = wait.until(presence_of_element_located(By.CSS_SELECTOR, "h3>div"))
print(first_result.get_attribute("textContent"))
#-------------------------------------------------------------------------------------------
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support.expected_conditions import EC
import datetime, time, random
# #如果沒有在環(huán)境變量指定PhantomJS位置
driver = PhantomJS('F:/BaiduNetdiskDownload/2019 Python網(wǎng)絡(luò)期班配套資料/slides/chapter16爬蟲/install/phantomjs-2.1.1-windows/bin/phantomjs.exe')
driver.set_window_size(1280,1080)
# 保存圖片;
def savepic():
basepath = 'F:/test/'
filename = "{:%Y%m%d%H%M%S}-{:03}.png".format(datetime.datetime.now(), random.randint(1,100))
driver.save_screenshot(basepath+filename)
# 生成頁面快照并保存
url = ""
driver.get(url)
driver.find_element(By.ID, 'inp-qury')
try:
element = WebDriverWait(driver, 10) .until(
EC.presence_of_element_locate( # 元素加載到DOM,但是不保證他是可見的;
(By.ID, "myDynamicElement")
)
)
finally:
driver.quit()
表單
| expected_conditionsn內(nèi)置條件 | 說明 |
|---|---|
| title_is | 判斷當(dāng)前頁面的title是否精確等于預(yù)期 |
| title_contains | 判斷當(dāng)前頁面的title是否包含預(yù)期字符串 |
| presence_of_element_located | 判斷某個元素是否被加到了dom樹里,并不代表該元素一定可見 |
| visibility_of_element_located | 判斷某個元素是否可見??梢姶碓胤请[藏,并且元素的寬和高都不等于0 |
| visibility_of | 跟上面的方法做一樣的事情,只是上面的方法要傳入locator,這個方法直接傳定位到的element就好了 |
| presence_of_all_elements_located | 判斷是否至少有1個元素存在于dom樹中。舉個例子,如果頁面上有n個元素的class都是'column-md-3',那么只要有1個元素存在,這個方法就返回True |
| text_to_be_present_in_element | 判斷某個元素中的text是否包含了預(yù)期的字符串 |
| text_to_be_present_in_element_value | 判斷某個元素中的value屬性是否包含了預(yù)期的字符串 |
| frame_to_be_available_and_switch_to_it | 判斷該frame是否可以switch進去, 如果可以的話, 返回True 且switch進去, 否則返回False |
| invisibility_of_element_located | 判斷某個元素中是否不存在于dom樹或不可見 |
| element_to_be_clickable | 判斷某個元素中是否可見并且是enable的, 這樣的話才叫clickable |
| staleness_of | 等某個元素從dom樹中移除, 注意, 這個方法也是返回True或 False |
| element_to_be_selected | 判斷某個元素是否被選中了,一般用在下拉列表 |
| element_selection_state_to_be | 判斷某個元素的選中狀態(tài)是否符合預(yù)期 |
| element_located_selection_state_to_be | 跟上面的方法作用一樣,只是上面的方法傳入定位到的element, 而這個方法傳入locator |
| alert_is_present | 判斷頁面上是否存在alert |
顯示等待
結(jié)合WebDriverWait和expected_conditions判斷元素是否存在,
每間隔1秒判斷一次,10s超時,存在返回True,不存返回False
WebDriverWait類提供方法:
(1)until(method, message='')
在規(guī)定時間內(nèi),每隔一段時間調(diào)用一下method方法,至到期返回值不為False,如果超時拋出帶有message的TimeoutException異常信息(1)until_not(method, message='')
與until()方法相反,表示在規(guī)定時間內(nèi),每隔一段時間調(diào)用一下method方法,至到期返回值為False,如果超時拋出帶有message的TimeoutException異常信息
:param locator: locator為元組類型,如("id", "kw")
定位搜索框,搜索電影;


from selenium import webdriver
from selenium.webdriver import PhantomJS # 使用無頭瀏覽器
from selenium.webdriver.common.by import By # 鍵盤操作
from selenium.webdriver.common.keys import Keys # 負(fù)責(zé)操作界面;
from selenium.webdriver.support.ui import WebDriverWait # 負(fù)責(zé)循環(huán)等待
from selenium.webdriver.support import expected_conditions as EC # 負(fù)責(zé)條件觸發(fā);
import datetime, time, random
# #如果沒有在環(huán)境變量指定PhantomJS位置
driver = PhantomJS('F:/BaiduNetdiskDownload/2019 Python網(wǎng)絡(luò)期班配套資料/slides/chapter16爬蟲/install/phantomjs-2.1.1-windows/bin/phantomjs.exe')
driver.set_window_size(1280,1080)
# 保存圖片;
def savepic():
basepath = 'F:/test/'
filename = "{:%Y%m%d%H%M%S}-{:03}.png".format(datetime.datetime.now(), random.randint(1,100))
driver.save_screenshot(basepath+filename)
# 生成頁面快照并保存
url = "https://movie.douban.com/"
driver.get(url)
# element = driver.find_element(By.ID, 'inp-qury') # 找到搜索框 1;
# 在10s內(nèi), 定位元素inp-query , 元素加載到DOM,但是不保證他是可見的;
try:
element = WebDriverWait(driver, 10).until(
EC.presence_of_element_located( # 元素加載到DOM,但是不保證他是可見的;
(By.ID, "inp-query") # 找搜索框 是 元組
)
)
element.send_keys('TRON')
element.send_keys(Keys.ENTER)
print(driver.current_url)
savepic()
except Exception as e:
print(type(e), e)
finally:
driver.quit()
#----------------------------------------------------
C:\ProgramData\Miniconda3\envs\blog\lib\site-packages\selenium\webdriver\phantomjs\webdriver.py:49: UserWarning: Selenium support for PhantomJS has been deprecated, please use headless versions of Chrome or Firefox instead
warnings.warn('Selenium support for PhantomJS has been deprecated, please use headless '
https://search.douban.com/movie/subject_search?search_text=TRON&cat=1002 #搜索網(wǎng)頁;
隱式等待
找不到這個元素,繼續(xù)等10s,還是找不到元素,結(jié)束程序;
from selenium import webdriver
from selenium.webdriver import PhantomJS # 使用無頭瀏覽器
from selenium.webdriver.common.by import By # 鍵盤操作
from selenium.webdriver.common.keys import Keys # 負(fù)責(zé)操作界面;
from selenium.webdriver.support.ui import WebDriverWait # 負(fù)責(zé)循環(huán)等待
from selenium.webdriver.support import expected_conditions as EC # 負(fù)責(zé)條件觸發(fā);
import datetime, time, random
# #如果沒有在環(huán)境變量指定PhantomJS位置
driver = PhantomJS('F:/BaiduNetdiskDownload/2019 Python網(wǎng)絡(luò)期班配套資料/slides/chapter16爬蟲/install/phantomjs-2.1.1-windows/bin/phantomjs.exe')
driver.implicitly_wait(10) # 拿到driver等10s
driver.set_window_size(1280,1080)
# 保存圖片;
def savepic():
basepath = 'F:/test/'
filename = "{:%Y%m%d%H%M%S}-{:03}.png".format(datetime.datetime.now(), random.randint(1,100))
driver.save_screenshot(basepath+filename)
# 生成頁面快照并保存
url = "https://movie.douban.com/"
driver.get(url)
# 等 10s, 元素加載到DOM樹,找id=inp-query
try:
element = driver.find_element(By.ID, 'inp-query') # 找到搜索框 1;
# element = WebDriverWait(driver, 1).until(
# EC.presence_of_element_located( # 元素加載到DOM,但是不保證他是可見的;
# (By.ID, "inp-query") # 找搜索框 是 元組
# )
# )
element.send_keys('TRON')
element.send_keys(Keys.ENTER)
print(driver.current_url) # 新的url;
savepic()
except Exception as e:
print(type(e), e)
finally:
driver.quit()
#----------------------------------
https://search.douban.com/movie/subject_search?search_text=TRON&cat=1002
<class 'selenium.common.exceptions.NoSuchElementException'> Message: {"errorMessage":"Unable to find element with id 'inp-qu1ery'","request":{"headers":{"Accept":"application/json","Accept-Encoding":"identity","Content-Length":"91","Content-Type":"application/json;charset=UTF-8","Host":"127.0.0.1:4483","User-Agent":"selenium/3.141.0 (python windows)"},"httpVersion":"1.1","method":"POST","post":"{\"using\": \"id\", \"value\": \"inp-qu1ery\", \"sessionId\": \"bb714600-ed9e-11ea-bb6d-6b286a00b9a6\"}","url":"/element","urlParsed":{"anchor":"","query":"","file":"element","directory":"/","path":"/element","relative":"/element","port":"","host":"","password":"","user":"","userInfo":"","authority":"","protocol":"","source":"/element","queryKey":{},"chunks":["element"]},"urlOriginal":"/session/bb714600-ed9e-11ea-bb6d-6b286a00b9a6/element"}}
Screenshot: available via screen
總結(jié)
Selenium的Web Driver是其核心, 從Selenium 2開始就是最重要的編程核心對象, 在Selenium 3中更是如此。
和瀏覽器交互全靠它,它可以:
- 打開URL, 可以跟蹤跳轉(zhuǎn), 可以返回當(dāng)前頁面的實際URL
- 獲取頁面的title
- 處理cookie
- 控制瀏覽器的操作,例如前進、后退、刷新、關(guān)閉,最大化等
- 執(zhí)行JS腳本
- 在DOM中搜索頁面元素Web Element, 指定的或一批, find系方法
- 操作網(wǎng)頁元素
- 模擬下拉框操作Select(element)
- 在元素上模擬鼠標(biāo)操作click()
- 在元素上模擬鍵盤輸入send_keys()
- 獲取元素文字text
- 獲取元素的屬性get_attribute()
Selenium通過Web Driver來驅(qū)動瀏覽器工作, 而瀏覽器是一個個獨立的瀏覽器進程。