接上篇,繼續(xù)梳理 Python 爬蟲入門的知識點。這里將重點說明,如何識別網站反爬蟲機制及應對策略,使用 Selenium 模擬瀏覽器操作等內容,干貨滿滿,一起學習和成長吧。
1、識別反爬蟲機制及應對策略
1.1 測試網站是否開啟了反爬蟲
隨著互聯(lián)網技術的日益革新,大多數的網站都會使用反爬蟲機制。我們在爬取目標頁面之前,第一步就是要識別需不需要應對網站的反爬蟲,常見的測試方式有:
<1>、使用?requests 模塊提供的 API
# 以get方式發(fā)送請求,暫時不加入請求頭
response = requests.get(url)
if response.status_code != 200:
print(f"網站開啟了發(fā)爬蟲機制,響應狀態(tài)為{status_code}")
<2>、使用爬蟲框架提供的測試命令
以 Scrapy 框架為例,需先下載該依賴(pip install Scrapy),接著在 CMD 命令行 或 其他終端鍵入測試命令:?
Scrapy shell 目標url
如果上述的測試返回響應碼為200,說明網站不存在反爬蟲機制,如果是其他的響應碼類型,需要具體分析對應哪種反爬蟲機制。
響應狀態(tài)碼
這里,順帶普及下響應狀態(tài)碼 Status Code 的內容,它是用 3 位十進制數表示,以代碼的形式表示服務器對請求的處理結果?,F(xiàn)行的 RFC 標準把狀態(tài)碼分成了五類,用數字的第一位表示分類,范圍在100~599 之間。這五類的具體含義,如下:
1××:提示信息,表示目前是協(xié)議處理的中間狀態(tài),還需要后續(xù)的操作;
2××:成功,報文已經收到并被正確處理;
3××:重定向,資源位置發(fā)生變動,需要客戶端重新發(fā)送請求;
4××:客戶端錯誤,請求報文有誤,服務器無法處理;
5××:服務器錯誤,服務器在處理請求時內部發(fā)生了錯誤。
最常用的狀態(tài)碼有,200(成功),301(永久移動),302(臨時移動),400(錯誤請求),401(未授權),403(請求被禁止),404(未找到),500(服務器內部錯誤)。使用爬蟲過程中,遇到最多的則是 200 和?403 了。
當然,目前 RFC 標準里總共有 41 個狀態(tài)碼,但狀態(tài)碼定義是開放的,允許自行擴展。因此像 Apache、Nginx 等 Web 服務器都定義了一些專有的狀態(tài)碼。如果自己開發(fā) Web 應用的話,也可以在不沖突的前提下定義項目專有的狀態(tài)碼。
1.2 User-Agent檢查
在上篇已經介紹過,檢查?User-Agent 是一種最簡單且最常見的反爬蟲策略,網站會檢查發(fā)送的請求中是否存在請求頭信息,如果檢查不存在的話,網站服務器會拒絕請求!
應對策略也很簡單,發(fā)送請求的時候加上請求頭信息即可,如下:
import requests
from fake_useragent import UserAgent
# 請求頭
headers = UserAgent(path="D:\\XXX\\reptile\\fake_useragent.json").google
# 發(fā)送請求
response = requests.get(url, headers)
# 獲取整個html文檔
html = response.text
print(html)
1.3 訪問頻率檢查
通常情況下,人工去點擊頁面,不會在1s內訪問幾十甚至上百個網頁,也不會有類似 5s/次 訪問頁面的精確操作,一旦識別到這類情況,網站則會懷疑你是機器人,就會觸發(fā)反爬蟲機制,拒絕這種高頻次或精準性的請求操作。
應對這種情況,根據這種機制特征,我們需要設置隨機的訪問時間間隔,降低請求網站的頻率(最好考慮目標網站的訪問承受能力,不要把人家搞崩了~)。當然,也可以使用?Selenium 模擬瀏覽器操作,該模塊的使用在后面再詳細的說明,這里不展開。
1.4 Session訪問限制
我們知道 HTTP 是一種無狀態(tài)的協(xié)議,為了維持客戶端與服務器之間的通信狀態(tài),使用 Cookie 技術來保持雙方的通信狀態(tài)。有些網站的網頁是需要登錄才允許爬取數據的,而登錄的原理就是瀏覽器首次通過用戶名密碼登錄之后,服務器給客戶端發(fā)送一個隨機的 Cookie,下次瀏覽器請求其它頁面時,就把剛才的 Cookie 隨著請求一起發(fā)送給服務器,這樣服務器就知道該用戶已經是登錄用戶。
應對這種情況,需要調整下爬蟲代碼,使用帶 Session 會話的請求登陸,然后再訪問目標網頁,如下:
import requests
# 構建Session會話
session = requests.Session()
# 通過post請求登陸
session.post(login_url, data={username, pwd})
# 訪問目標網頁
r = session.get(home_url)
# 釋放會話資源
session.close()
當構建一個 Session 會話之后,客戶端第一次發(fā)起請求登錄賬戶,服務器自動把 Cookie 信息保存在 Session?對象中,發(fā)起第二次請求時 requests 自動把 Session 中的 Cookie 信息發(fā)送給服務器,使之保持通信狀態(tài)。
1.5 IP訪問限制
有些網站為了不讓你爬取,可能會精準識別你的 IP 操作。比如,如果發(fā)現(xiàn)短時間內該 IP多次訪問同一個頁面,則會判定你在使用爬蟲,觸發(fā)網站的反爬蟲,則會封殺你的 IP(至于永久封殺還是一定時間內不能再次訪問,看具體網站的策略了~)。這種情況不好識別,因為當你意識到這種情況時,你當前 IP 可能無法繼續(xù)訪問了。
當然,應對這種情況,通過代理 IP 可以迎刃而解。我們可以爬取網上公開且免費的代理 IP,有了大量代理 IP 后可以每請求幾次更換一個 IP,這樣就能繼續(xù)之前的爬蟲操作了。
import requests
import re
def get_ip_list(url, headers, pattern):
ip_list = []
response = requests.get(url, headers)
if response.status_code == 200:
html = response.text
# 按正則先匹配到提取的范圍
match_all_data = re.findall(pattern, html)
for i in range(len(match_all_data)):
# 對每一條url進行精準匹配,可根據具體的情況進行分析
url = re.findall(pattern, match_all_data[i])
ip_list.append(url)
return ip_list
這里,為了能最有效率的利用這些代理 IP,爬取完成后可以考慮放到數據庫保存,每次使用時先從數據庫里拿。另外,當數據庫里可用的代理 IP 數量低于某個閾值時,可以考慮再爬取一批代理 IP,用于更新或補充自己的 IP 池,這樣 IP 池里就會保持處于可用狀態(tài)。
1.6 驗證碼校驗
生活中,我們看到的驗證碼可謂五花八門,主要類型有:隨機數字圖片驗證碼、滑動驗證碼、手機驗證碼、郵箱/手機號組合驗證等。網站通過常規(guī)驗證碼和滑動驗證碼的校驗,可以區(qū)分人機操作,并防止惡意注冊、暴力破解、刷票、論壇灌水、黑客攻擊等行為;而通過手機號碼、郵箱手機號組合等校驗,主要是驗證用戶信息,保護用戶信息安全。對于爬蟲而言,自動爬取后兩種的網頁數據就無能為力了,一般是對前兩種驗證碼策略的應對。
隨機數字圖片驗證碼,又可以細分很多小類型,需要根據不同的類型采取不同的策略。比如,字母/數字組合類型(Z5f0ty)、計算題類型(1+5=?),這種簡單的驗證碼可通過機器學習識別。又比如,圖片驗證類型,這種復雜的驗證碼可通過專門的付費打碼平臺人工打碼。
滑動驗證碼,采用的是高精度人機行為識別技術,用戶只需向右滑動拼圖,補齊缺塊兒,即可完成驗證,這種可以使用?Selenium 模擬瀏覽器執(zhí)行滑動操作。
1.7 動態(tài)網頁
有時候,我們使用?requests 模塊獲取網頁內容會很少,并且也不包含我們采集的目標數據,這是因為頁面展示的實時信息是動態(tài)渲染的,比如股票行情實時的數據、證券交易公開的數據等。面對這種動態(tài)的網頁,使用 requests 模塊就不再適合了。
我們使用瀏覽器的【開發(fā)者工具】定位看到的數據,有可能是動態(tài)渲染過的,也有可能不是。那么,該如何識別網頁上的數據是不是動態(tài)渲染的呢?方法很簡單,通過右鍵點擊網頁空白處,選擇【查看網頁源代碼】,將頁面上的數據復制后去源代碼里搜索,如果搜索不到則可以判定是動態(tài)渲染的!應對這種反爬策略,我們仍可以選擇使用?Selenium 模擬瀏覽器操作獲取數據。
我們隨便搜索一個財經網站測試下,通過【開發(fā)者工具】能看到 88.28 指數是存在的,如下:
去【查看網頁源代碼】搜索?88.28 ,顯示這個指數不存在:?
說明當前網頁是動態(tài)渲染的,可以考慮使用?Selenium 模塊去爬取。
1.8 數據API加密
爬蟲和反爬蟲本質上是一種攻防關系,如果網站真心不讓你爬取自己的頁面數據,則會使用難度更大,破解更復雜的反爬策略。
比如,前端 JS 加密。網站花了一番心思的加密操作,相對于爬取方來說,也是需要一定時間和分析能力才能進行解密,極大的增加了爬取的難度。
又比如,網站使用多個不同的字體文件加密,只有使用約定的字體文件方式才能解密。網站服務端根據字體映射文件,先將客戶端查詢的數據進行轉換再傳給前端,前端根據字體文件進行逆向解密,映射方式可以自由選擇,比如數字亂序顯示等。這樣爬蟲可以正常爬取,但得到的數據卻是錯誤的!
所謂的道高一尺,魔高一丈,恐怕不過如此吧,哈哈哈~
2、Selenium模塊
2.1 選擇 Requests 還是?Selenium ?
從上面介紹的反爬應對策略不難發(fā)現(xiàn),當 Requests 模塊解決不了的爬取問題,使用 Selenium 模塊基本可以解決。到這里可能會產生疑問:這兩個模塊都是爬蟲的核心庫,為什么不直接使用?Selenium 替代?Requests 做所有的爬蟲操作呢?
這是因為 Requests 模塊是直接訪問網頁,爬取速度非???,而 Selenium 模塊要先打開模擬瀏覽器再訪問網頁,爬取速度相對較慢。實際使用中,肯定會優(yōu)先考慮使用?Requests 模塊,而 Requests 模塊解決不了的話再使用 Selenium 模塊。
如果說 Requests 模塊可以爬取50%的網站,那么 Selenium 模塊可以爬取95%的網站,大部分爬取難度較高的網站都可以用它爬取。
2.2?Selenium 該怎么用?
Selenium 是一個用于 Web 應用程序測試的工具,它能夠驅動瀏覽器模擬用戶的操作,比如鼠標點擊、鍵盤輸入等行為,支持的瀏覽器包括 IE(7, 8, 9, 10, 11),Mozilla Firefox,Safari,Google Chrome,Opera,Edge 等。通過 Selenium 模塊能比較容易地獲取網頁源代碼,還能自動下載網絡資源。
首先,需要下載?Selenium 模塊,如下:
pip install selenium
接著,下載和安裝瀏覽器驅動程序,不同的瀏覽器驅動下載地址,參考如下(按需選擇):
- Edge:Microsoft Edge WebDriver - Microsoft Edge Developer
- Chrome:https://chromedriver.storage.googleapis.com/index.html
- Firefox:Releases · mozilla/geckodriver · GitHub
- Safari:WebDriver Support in Safari 10 | WebKit
下載的瀏覽器驅動需要與當前使用的瀏覽器版本保持一致,我用的是 Google 瀏覽器,通過【設置】--【關于 Chrome】,可以查看版本號:
找到自己需要的驅動路徑,下載:
下載完成后,解壓 zip 包會得到一個 chromedriver.exe,將這個可執(zhí)行文件放到 Python 的?Scripts 目錄下就完成了安裝。驗證是否成功安裝驅動,可以打開 CMD,輸入命令,驗證如下:
到此,萬事俱備,就可以小試牛刀了。
打開 Google 瀏覽器,并訪問百度的首頁,在獲取網頁源代碼后,等待30s,最后關閉瀏覽器。以這個操作流程為例,測試代碼如下:
from selenium import webdriver
def test_open_browser(url):
# 打開 Google 瀏覽器
browser = webdriver.Chrome()
# 訪問目標網址
browser.get(url)
# 獲取網頁源代碼
data = browser.page_source
# 休眠30s(智能等待)
browser.implicitly_wait(30)
# 關閉瀏覽器
browser.quit()
這樣就能輕松獲取網頁源代碼數據了。如果不需要彈出模擬瀏覽器窗口,就能獲取網頁源代碼,可啟用無界面瀏覽器模式(Chrome Headless),在打開 Google 瀏覽器的代碼之前,新增和修改如下代碼:
co = webdriver.ChromeOptions()
co.add_argument('--headless')
browser = webdriver.Chrome(options=co)
browser 還有更多 API 可以自行嘗試,接下來會進行一些用法總結。
2.3?Selenium 操作模擬瀏覽器小結
除了訪問網頁,Selenium 模塊還可以進行其他行為操作。該模塊 API,常用的屬性有:
- browser.window_handles:獲取瀏覽器所有窗口的句柄
- browser.current_window_handle:獲取當前瀏覽器窗口的句柄
- browser.name:獲取當前瀏覽器驅動的名稱
- browser.page_source:獲取網頁源代碼
- browser.title:獲取當前網頁的標題
- browser.current_url:獲取當前網頁的網址
常用的操作方法有:
- browser.maximize_window():窗口最大化
- browser.minimize_window():窗口最小化
- browser.set_window_size(width, height):設置窗口尺寸
- browser.forword():前進
- browser.back():后退
- browser.
switch_to_frame("f1"):切換到內嵌框架(用法即將廢棄) - browser.switch_to.frame("f1"):切換到內嵌框架
- browser.
switch_to_window("w1"):切換到內嵌窗口(用法即將廢棄) - browser.switch_to.window("w1"):切換到內嵌窗口(一般與browser.window_handles搭配使用)
- browser.get_cookies():獲取當前網頁用到的cookies
- browser.refresh():刷新當前網頁
- browser.close():關閉瀏覽器
- browser.find_element_by_xxx("").send_keys():模擬鍵盤輸入
- browser.find_element_by_xxx("").click():點擊對象
- browser.find_element_by_xxx("").submit():提交對象的內容
- browser.find_element_by_xxx("").text:獲取文本信息
- browser.find_element_by_xxx("").clear():清除對象的內容
find_element_by_xxx() 用于定位網頁元素,有很多使用方法,如下:
注意:
如果xxx是xpath的話,格式為:
????????browser.find_element_by_xpath('XPath表達式')
如果xxx是css_selector的話,格式為:
????????browser.find_element_by_css_selector('CSS選擇器')
實際使用中,二者都常用來定位網頁元素,根據需要進行選擇。
XPath 如何使用?
XPath 可理解為網頁元素的名字或 ID。find_element_by_xpath() 函數可根據 XPath 表達式定位網頁元素,利用開發(fā)者工具,可獲取網頁元素的XPath表達式,如下:
?鍵盤事件:包括鍵盤tab、回車、ctrl鍵等
from selenium.webdriver.common.keys import Keys
# Tab 切換到密碼框
browser.find_element_by_class_name("username").send_keys(Keys.TAB)
# ENTER 回車登陸
browser.find_element_by_class_name("password").send_keys(Keys.ENTER)
# 全選、復制、剪切等
browser.find_element_by_name("input").send_keys(Keys.CONTROL, 'a')
browser.find_element_by_name("input").send_keys(Keys.CONTROL, 'c')
browser.find_element_by_name("input").send_keys(Keys.CONTROL, 'x')
鼠標事件:包括鼠標右鍵、雙擊、拖動、移動鼠標到某個元素上等
from selenium.webdriver.common.action_chains import ActionChains
elem = browser.find_element_by_xpath("")
# 鼠標點擊行為
ActionChains(browser).click(elem).perform() # 單擊,默認左擊
ActionChains(browser).double_click(elem).perform() # 雙擊
ActionChains(browser).context_click(elem).perform() # 右擊
ActionChains(browser).click_and_hold(elem).perform() # 單擊并停留
# 鼠標拖動
to_elem = browser.find_element_by_xpath("")
ActionChains(browser).drag_and_drop(elem, to_elem).perform()
ActionChains(browser).move_to_element(elem).perform() # 鼠標懸停
ActionChains(browser).move_by_offset(x, y).perform() # 鼠標移動,需要指定x,y軸的偏移量
Expected Conditions:期望的條件,可用來判斷瀏覽器的動態(tài)頁面元素出現(xiàn)和消失情況,經常與 WebDriverWait 搭配使用,通過以下方式引入:
from selenium.webdriver.support import expected_conditions as EC
常用 API 有:文章來源:http://www.zghlxwxcb.cn/news/detail-797373.html
title_is
:判斷當前頁面的 title 是否精確等于預期;title_contains
:判斷當前頁面的 title 是否包含預期字符串;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 是否包含了預期的字符串;text_to_be_present_in_element_value
:判斷某個元素中的 value 屬性是否包含了預期的字符串;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)是否符合預期;element_located_selection_state_to_be
:跟上面的方法作用一樣,只是上面的方法傳入定位到的 element,而這個方法傳入 locator;alert_is_present
:判斷頁面上是否存在 alert。
最后
至此,爬蟲入門的知識點也介紹完了,有了這些基礎知識后,便可以進一步的進行爬蟲實戰(zhàn)了。后面將通過爬蟲實戰(zhàn),演示如何靈活的爬取不同網站并解析出目標數據,并記錄下爬取和解析過程中遇到的一些問題,一起期待吧~文章來源地址http://www.zghlxwxcb.cn/news/detail-797373.html
到了這里,關于【爬蟲系列】Python 爬蟲入門(2)的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!