使用selenium模擬瀏覽器進(jìn)行數(shù)據(jù)抓取無疑是當(dāng)下最通用的數(shù)據(jù)采集方案厦凤,它通吃各種數(shù)據(jù)加載方式鼻吮,能夠繞過客戶JS加密,繞過爬蟲檢測较鼓,繞過簽名機(jī)制椎木。它的應(yīng)用,使得許多網(wǎng)站的反采集策略形同虛設(shè)博烂。由于selenium不會在HTTP請求數(shù)據(jù)中留下指紋香椎,因此無法被網(wǎng)站直接識別和攔截。
這是不是就意味著selenium真的就無法被網(wǎng)站屏蔽了呢禽篱?非也士鸥。selenium在運(yùn)行的時(shí)候會暴露出一些預(yù)定義的Javascript變量(特征字符串),例如"window.navigator.webdriver"谆级,在非selenium環(huán)境下其值為undefined烤礁,而在selenium環(huán)境下,其值為true(如下圖所示為selenium驅(qū)動下Chrome控制臺打印出的值)肥照。
除此之外脚仔,還有一些其它的標(biāo)志性字符串(不同的瀏覽器可能會有所不同),常見的特征串如下所示:
webdriver
__driver_evaluate
__webdriver_evaluate
__selenium_evaluate
__fxdriver_evaluate
__driver_unwrapped
__webdriver_unwrapped
__selenium_unwrapped
__fxdriver_unwrapped
_Selenium_IDE_Recorder
_selenium
calledSelenium
_WEBDRIVER_ELEM_CACHE
ChromeDriverw
driver-evaluate
webdriver-evaluate
selenium-evaluate
webdriverCommand
webdriver-evaluate-response
__webdriverFunc
__webdriver_script_fn
__$webdriverAsyncExecutor
__lastWatirAlert
__lastWatirConfirm
__lastWatirPrompt
$chrome_asyncScriptInfo
$cdc_asdjflasutopfhvcZLmcfl_
了解了這個(gè)特點(diǎn)之后舆绎,就可以在瀏覽器客戶端JS中通過檢測這些特征串來判斷當(dāng)前是否使用了selenium鲤脏,并將檢測結(jié)果附加到后續(xù)請求之中,這樣服務(wù)端就能識別并攔截后續(xù)的請求吕朵。
下面講一個(gè)具體的例子猎醇。
鯤之鵬的技術(shù)人員近期就發(fā)現(xiàn)了一個(gè)能夠有效檢測并屏蔽selenium的網(wǎng)站應(yīng)用:驗(yàn)證碼表單頁,如果是正常的瀏覽器操作努溃,能夠有效的通過驗(yàn)證硫嘶,但如果是使用selenium就會被識別,即便驗(yàn)證碼輸入正確梧税,也會被提示“請求異常,拒絕操作”沦疾,無法通過驗(yàn)證(如下圖所示)称近。可以看到它檢測了"webdriver", "__driver_evaluate", "__webdriver_evaluate"等等這些selenium的特征串。提交驗(yàn)證碼的時(shí)候抓包可以看到一個(gè)_token參數(shù)(很長)哮塞,selenium檢測結(jié)果應(yīng)該就包含在該參數(shù)里刨秆,服務(wù)端借以判斷“請求異常,拒絕操作”。
現(xiàn)在才進(jìn)入正題忆畅,如何突破網(wǎng)站的這種屏蔽呢衡未?
我們已經(jīng)知道了屏蔽的原理,只要我們能夠隱藏這些特征串就可以了家凯。但是還不能直接刪除這些屬性眠屎,因?yàn)檫@樣可能會導(dǎo)致selenium不能正常工作了。我們采用曲線救國的方法肆饶,使用中間人代理,比如fidder, proxy2.py或者mitmproxy岖常,將JS文件(本例是yoda.*.js這個(gè)文件)中的特征字符串給過濾掉(或者替換掉驯镊,比如替換成根本不存在的特征串),讓它無法正常工作竭鞍,從而達(dá)到讓客戶端腳本檢測不到selenium的效果板惑。
下面我們驗(yàn)證下這個(gè)思路。這里我們使用mitmproxy實(shí)現(xiàn)中間人代理)偎快,對JS文件(本例是yoda.*.js這個(gè)文件)內(nèi)容進(jìn)行過濾冯乘。啟動mitmproxy代理并加載response處理腳本:
mitmdump.exe -S modify_response.py
其中modify_response.py腳本如下所示:
# coding: utf-8
# modify_response.py
import re
from mitmproxy import ctx
def response(flow):
"""修改應(yīng)答數(shù)據(jù)
"""
if '/js/yoda.' in flow.request.url:
# 屏蔽selenium檢測
for webdriver_key in ['webdriver', '__driver_evaluate', '__webdriver_evaluate', '__selenium_evaluate', '__fxdriver_evaluate', '__driver_unwrapped', '__webdriver_unwrapped', '__selenium_unwrapped', '__fxdriver_unwrapped', '_Selenium_IDE_Recorder', '_selenium', 'calledSelenium', '_WEBDRIVER_ELEM_CACHE', 'ChromeDriverw', 'driver-evaluate', 'webdriver-evaluate', 'selenium-evaluate', 'webdriverCommand', 'webdriver-evaluate-response', '__webdriverFunc', '__webdriver_script_fn', '__$webdriverAsyncExecutor', '__lastWatirAlert', '__lastWatirConfirm', '__lastWatirPrompt', '$chrome_asyncScriptInfo', '$cdc_asdjflasutopfhvcZLmcfl_']:
ctx.log.info('Remove "{}" from {}.'.format(webdriver_key, flow.request.url))
flow.response.text = flow.response.text.replace('"{}"'.format(webdriver_key), '"NO-SUCH-ATTR"')
flow.response.text = flow.response.text.replace('t.webdriver', 'false')
flow.response.text = flow.response.text.replace('ChromeDriver', '')
在selnium中使用該代理(mitmproxy默認(rèn)監(jiān)聽127.0.0.1:8080)訪問目標(biāo)網(wǎng)站,mitmproxy將過濾JS中的特征符串晒夹,如下圖所示:
經(jīng)多次測試裆馒,該方法可以有效的繞過selenium檢測,成功提交驗(yàn)證碼表單丐怯。
抄自:http://www.site-digger.com/html/articles/20180821/653.html
成功
附上mitmproxy簡單介紹(格式渣喷好,可以直接訪問原文)
抄自 https://blog.wolfogre.com/posts/usage-of-mitmproxy/
本文是一個(gè)較為完整的 mitmproxy 教程,側(cè)重于介紹如何開發(fā)攔截腳本读跷,幫助讀者能夠快速得到一個(gè)自定義的代理工具梗搅。
本文假設(shè)讀者有基本的 python 知識,且已經(jīng)安裝好了一個(gè) python 3 開發(fā)環(huán)境效览。如果你對 nodejs 的熟悉程度大于對 python无切,可移步到 anyproxy,anyproxy 的功能與 mitmproxy 基本一致丐枉,但使用 js 編寫定制腳本哆键。除此之外我就不知道有什么其他類似的工具了,如果你知道瘦锹,歡迎評論告訴我洼哎。
本文基于 mitmproxy v4烫映,當(dāng)前版本號為 [v4.0.1]
(https://blog.wolfogre.com/redirect/v3/Ax7R98RpJpVlv3sgL6mHzyQSAwM8Cv46xcU7LxImWv3FLS8tPHP6U8UtLy08c_pTxSFXLjbFyDvFbf40bv4wbv4xMRIDAzwK_jrFxVoWBjtuQQYW3Dsh_cU8Bk0KxaQE-cwFzC0vLTxz-lPF)。
顧名思義噩峦,mitmproxy 就是用于 MITM 的 proxy锭沟,MITM 即中間人攻擊(Man-in-the-middle attack)。用于中間人攻擊的代理首先會向正常的代理一樣轉(zhuǎn)發(fā)請求识补,保障服務(wù)端與客戶端的通信族淮,其次,會適時(shí)的查凭涂、記錄其截獲的數(shù)據(jù)祝辣,或篡改數(shù)據(jù),引發(fā)服務(wù)端或客戶端特定的行為切油。
不同于 fiddler 或 wireshark 等抓包工具蝙斜,mitmproxy 不僅可以截獲請求幫助開發(fā)者查看、分析澎胡,更可以通過自定義腳本進(jìn)行二次開發(fā)孕荠。舉例來說,利用 fiddler 可以過濾出瀏覽器對某個(gè)特定 url 的請求攻谁,并查看稚伍、分析其數(shù)據(jù),但實(shí)現(xiàn)不了高度定制化的需求戚宦,類似于:“截獲對瀏覽器對該 url 的請求个曙,將返回內(nèi)容置空,并將真實(shí)的返回內(nèi)容存到某個(gè)數(shù)據(jù)庫受楼,出現(xiàn)異常時(shí)發(fā)出郵件通知”垦搬。而對于 mitmproxy,這樣的需求可以通過載入自定義 python 腳本輕松實(shí)現(xiàn)艳汽。
但 mitmproxy 并不會真的對無辜的人發(fā)起中間人攻擊悼沿,由于 mitmproxy 工作在 HTTP 層,而當(dāng)前 HTTPS 的普及讓客戶端擁有了檢測并規(guī)避中間人攻擊的能力骚灸,所以要讓 mitmproxy 能夠正常工作糟趾,必須要讓客戶端(APP 或?yàn)g覽器)主動信任 mitmproxy 的 SSL 證書,或忽略證書異常甚牲,這也就意味著 APP 或?yàn)g覽器是屬于開發(fā)者本人的——顯而易見义郑,這不是在做黑產(chǎn),而是在做開發(fā)或測試丈钙。
那這樣的工具有什么實(shí)際意義呢非驮?據(jù)我所知目前比較廣泛的應(yīng)用是做仿真爬蟲,即利用手機(jī)模擬器雏赦、無頭瀏覽器來爬取 APP 或網(wǎng)站的數(shù)據(jù)劫笙,mitmproxy 作為代理可以攔截芙扎、存儲爬蟲獲取到的數(shù)據(jù),或修改數(shù)據(jù)調(diào)整爬蟲的行為填大。
事實(shí)上戒洼,以上說的僅是 mitmproxy 以正向代理模式工作的情況,通過調(diào)整配置允华,mitmproxy 還可以作為透明代理圈浇、反向代理、上游代理靴寂、SOCKS 代理等磷蜀,但這些工作模式針對 mitmproxy 來說似乎不大常用,故本文僅討論正向代理模式百炬。
安裝
“安裝 mitmproxy”這句話是有歧義的褐隆,既可以指“安裝 mitmproxy 工具”,也可以指“安裝 python 的 mitmproxy 包”剖踊,注意后者是包含前者的庶弃。
pip install mitmproxy (windows)
運(yùn)行
要啟動 mitmproxy 用 mitmproxy、mitmdump蜜宪、mitmweb 這三個(gè)命令中的任意一個(gè)即可,這三個(gè)命令功能一致祥山,且都可以加載自定義腳本圃验,唯一的區(qū)別是交互界面的不同。(我一般使用 mitmdump -s 腳本.py 命令運(yùn)行)
腳本
腳本的編寫需要遵循 mitmproxy 規(guī)定的套路缝呕,這樣的套路有兩個(gè)澳窑,使用時(shí)選其中一個(gè)套路即可。
第一個(gè)套路是供常,編寫一個(gè) py 文件供 mitmproxy 加載摊聋,文件中定義了若干函數(shù),這些函數(shù)實(shí)現(xiàn)了某些 mitmproxy 提供的事件栈暇,mitmproxy 會在某個(gè)事件發(fā)生時(shí)調(diào)用對應(yīng)的函數(shù)麻裁,形如:
import mitmproxy.http
from mitmproxy import ctx
num = 0
def request(flow: mitmproxy.http.HTTPFlow):
global num
num = num + 1
ctx.log.info("We've seen %d flows" % num)
第二個(gè)套路(我沒成功)是,編寫一個(gè) py 文件供 mitmproxy 加載源祈,文件定義了變量 addons煎源,addons 是個(gè)數(shù)組,每個(gè)元素是一個(gè)類實(shí)例香缺,這些類有若干方法手销,這些方法實(shí)現(xiàn)了某些 mitmproxy 提供的事件,mitmproxy 會在某個(gè)事件發(fā)生時(shí)調(diào)用對應(yīng)的方法图张。這些類锋拖,稱為一個(gè)個(gè) addon诈悍,比如一個(gè)叫 Counter 的 addon:
import mitmproxy.http
from mitmproxy import ctx
class Counter:
def __init__(self):
self.num = 0
def request(self, flow: mitmproxy.http.HTTPFlow):
self.num = self.num + 1
ctx.log.info("We've seen %d flows" % self.num)
addons = [
Counter()
]
事件
上述的腳本估計(jì)不用我解釋相信大家也看明白了,就是當(dāng) request 發(fā)生時(shí)兽埃,計(jì)數(shù)器加一侥钳,并打印日志。這里對應(yīng)的是 request 事件讲仰,那攏共有哪些事件呢冕房?不多耙册,也不少,這里詳細(xì)介紹一下蔓同。
事件針對不同生命周期分為 5 類∑浚“生命周期”這里指在哪一個(gè)層面看待事件则北,舉例來說尚揣,同樣是一次 web 請求娜庇,我可以理解為“HTTP 請求 -> HTTP 響應(yīng)”的過程,也可以理解為“TCP 連接 -> TCP 通信 -> TCP 斷開”的過程泰偿。那么,如果我想拒絕來個(gè)某個(gè) IP 的客戶端請求蜈垮,應(yīng)當(dāng)注冊函數(shù)到針對 TCP 生命周期 的 tcp_start
事件晋南,又或者姜凄,我想阻斷對某個(gè)特定域名的請求時(shí)态秧,則應(yīng)當(dāng)注冊函數(shù)到針對 HTTP 聲明周期的 http_connect
事件愤诱。其他情況同理匣砖。
下面一段估計(jì)會又臭又長脆粥,如果你沒有耐心看完,那至少看掉針對 HTTP 生命周期的事件鲜棠,然后跳到示例盒音。
1,針對 HTTP 生命周期
def http_connect(self, flow: mitmproxy.http.HTTPFlow):
(Called when) 收到了來自客戶端的 HTTP CONNECT 請求。在 flow 上設(shè)置非 2xx 響應(yīng)將返回該響應(yīng)并斷開連接譬圣。CONNECT 不是常用的 HTTP 請求方法瓮恭,目的是與服務(wù)器建立代理連接,僅是 client 與 proxy 的之間的交流厘熟,所以 CONNECT 請求不會觸發(fā) request屯蹦、response 等其他常規(guī)的 HTTP 事件。
def requestheaders(self, flow: mitmproxy.http.HTTPFlow):
(Called when) 來自客戶端的 HTTP 請求的頭部被成功讀取绳姨。此時(shí) flow 中的 request 的 body 是空的登澜。
def request(self, flow: mitmproxy.http.HTTPFlow):
(Called when) 來自客戶端的 HTTP 請求被成功完整讀取。
def responseheaders(self, flow: mitmproxy.http.HTTPFlow):
(Called when) 來自服務(wù)端的 HTTP 響應(yīng)的頭部被成功讀取就缆。此時(shí) flow 中的 response 的 body 是空的帖渠。
def response(self, flow: mitmproxy.http.HTTPFlow):
(Called when) 來自服務(wù)端端的 HTTP 響應(yīng)被成功完整讀取。
def error(self, flow: mitmproxy.http.HTTPFlow):
(Called when) 發(fā)生了一個(gè) HTTP 錯誤竭宰。比如無效的服務(wù)端響應(yīng)空郊、連接斷開等。注意與“有效的 HTTP 錯誤返回”不是一回事切揭,后者是一個(gè)正確的服務(wù)端響應(yīng)狞甚,只是 HTTP code 表示錯誤而已。
- 針對 TCP 生命周期
(懶得抄) - 針對 Websocket 生命周期
(懶得抄) - 針對網(wǎng)絡(luò)連接生命周期
(懶得抄) - 通用生命周期
(懶得抄)
事實(shí)上考慮到 mitmproxy 的實(shí)際使用場景廓旬,大多數(shù)情況下我們只會用到針對 HTTP 生命周期的幾個(gè)事件哼审。再精簡一點(diǎn),甚至只需要用到 http_connect孕豹、request涩盾、response 三個(gè)事件就能完成大多數(shù)需求了