selenium简介
当我们使用 requests 抓取页面的时候,得到的结果可能会和在浏览器中看到的不一样,正常显示的页面数据,使用 reuquests 却没有得到结果。这是因为 requests 获取的都是原始 HTML 文档,而浏览器中的页面则是经过 Javascript 数据处理后生成的结果,这些数据的来源有多种,可能是通过 AJax 加载的,也可能是经过 Javascript 和特定算法计算后生成的。
此时解决方法通常有两种:
- 深挖 Ajax 的逻辑,把接口地址和其加密参数构造逻辑完全找出来,再用 Python 复现,构造 Ajax请求
- 通过模拟浏览器的方式,绕过这个过程。
这里我们主要介绍下第二种方式,模拟浏览器爬取。
Selenium 是一个自动化测试工具,利用它可以驱动浏览器执行特定的操作。比如点击,下拉等操作,同时还可以获取浏览器当前呈现的页面源代码,做到 所见即所得。对于一些使用 Javascript 动态渲染的页面来说,此种抓取方式非常有效!
反爬虫
但是,使用 Selenium 调用 ChromeSriver 来打开网页,还是与正常打开网页有一定的区别的。现在很多网站都加上了对 Selenium 的检测,来防止一些爬虫的恶意爬取。
大多数情况下,检测的基本原理是检测当前浏览器窗口下的 window.navigator
对象是否包含 webdriver
这个属性。在正常使用浏览器的情况下,这个属性是 undefined
,然后一旦我们使用了 selenium,这个属性就被初始化为 true
,很多网站就通过 Javascript 判断这个属性实现简单的反 selenium爬虫。
这时候我们可能想到通过 Javascript 直接把这个 webdriver 属性置空,比如通过调用 execute_script
方法来执行如下代码:
Object.defineProperty(navigator, "webdriver", {
get: () => undefined})
这行 Javascript 的确可以把 webdriver 属性置空,但是 execute_script 调用这行 Javascript 语句实际上是在页面加载完毕之后才执行的,执行得太晚了,网站早在页面渲染之前就已经对 webdriver 属性进行了检测,所有用上述方法并不能达到效果。
反反爬虫
基于上边举例的反爬措施,我们主要可以使用如下方法解决:
配置 Selenium 选项
option.add_experimental_option("excludeSwitches", ['enable-automation'])
但是 ChromeDriver 79.0.3945.36
版本修改了非无头模式下排除 “启用自动化” 时 window.navigator.webdriver
是未定义的问题,要想正常使用,需要把 Chrome 回滚 79 之前的版本,并找到对应的 ChromeDriver 版本,这样才可以!
当然,大家也可以参考 CDP(Chrome Devtools-Protocol)
文档,使用 driver.execute_cdp_cmd
在 selenium 中调用 CDP
的命令。下述代码只需要执行一次,之后只要不关闭这个 driver 开启的窗口,无论打开多少网址,它都会在网站自带的所有 JS 之前执行这个语句,从而达到隐藏 webdriver 的目的。
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
options = Options()
# 隐藏 正在受到自动软件的控制 这几个字
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option('useAutomationExtension', False)
driver = webdriver.Chrome(executable_path=r"E:\chromedriver\chromedriver.exe", options=options)
# 修改 webdriver 值
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": "Object.defineProperty(navigator, 'webdriver', {get: () => undefined})"
})
driver.get('https://www.baidu.com')
另外如下配置也可以去除 webdriver 特征
options = Options()
options.add_argument("--disable-blink-features")
options.add_argument("--disable-blink-features=AutomationControlled")
控制已打开的浏览器
既然使用 selenium 打开的浏览器存在一些特定参数,那么我们可以另辟蹊径,直接手动打开一个真实的浏览器,然后再使用 selenium 控制不就可以了吗!
-
利用 Chrome DevTools 协议打开一个浏览器,它允许客户检查和调试 Chrome 浏览器
(1)关闭所有打开的 Chrome 窗口
(2)打开 CMD,在命令行中输入命令:
# 此处 Chrome 的路径需要修改为你本机的 Chrome 安装位置 # --remote-debugging-port 指定任何打开的端口 "C:\Program Files(x86)\Google\Chrome\Application\chrome.exe" --remote-debugging-port=9222
若路径正确,此时会打开一个新的 Chrome 窗口
-
使用 selenium 连接这个打开的 Chrome 窗口
from selenium import webdriver from selenium.webdriver.chrome.options import Options options = Options() # 此处端口需要与上一步中使用的端口保持一致 # 其它大多博客此处使用 127.0.0.1:9222, 经测试无法连接, 建议采用 localhost:9222 # 具体原因参见: https://www.codenong.com/6827310/ options.add_experimental_option("debuggerAddress", "localhost:9222") driver = webdriver.Chrome(executable_path=r"E:\chromedriver\chromedriver.exe", options=options) driver.get('https://www.baidu.com')
但是使用本方法也存在一些弊端:
浏览器一旦启动,selenium中对浏览器的配置就不在生效了,例如 –-proxy-server
等,当然你也可以一开始启动 Chrome 的时候就加上
mitmproxy中间人
mitmproxy
其实和 fiddler/charles
等抓包工具的原理有些类似,作为一个第三方,它会把自己伪装成你的浏览器向服务器发起请求,服务器返回的 response 会经由它传递给你的浏览器,你可以 通过编写脚本来更改这些数据的传递,从而实现对服务器的 “欺骗” 和对客户端的 “欺骗”
部分网站采用单独的 js 文件来识别 webdriver 的结果,我们可以通过 mitmproxy 拦截识别 webdriver 标识符的 js 文件,并伪造正确的结果。
参考:使用 mitmproxy + python 做拦截代理
待续…
其实,不只是 webdriver,selenium打开浏览器后,还会有这些特征码:
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
...
如果你不相信,我们可以来做一个实验,分别使用 正常浏览器, selenium+Chrome
,selenium+Chrome headless
打开这个网址:https://bot.sannysoft.com/
当然,这些例子并不是为了打击各位的自信,仅仅是希望大家不要学会了部分技术就开始沾沾自喜,时刻保持一颗赤子之心,怀着对技术的热情继续前进。爬虫与反爬虫这场没有硝烟的战争,还在继续 …