js代码识别Selenium+Webdriver及其应对方案

今天学到的新知识,开发爬虫的过程中使用Selenium + Chromedriver 也能很轻松的被js识别。

例:

使用下面这一段代码启动Chrome窗口:

from selenium import webdriver
import time

driver = webdriver.Chrome()
time.sleep(300)
driver.quit()

在这个窗口中打开开发者工具,并定位到Console选项卡,现在,在这个窗口输入如下的js代码:

window.navigator.webdriver

js代码识别Selenium+Webdriver及其应对方案
开发者工具返回了 true
但是,如果打开一个普通的Chrome窗口,执行相同的命令,可以发现返回值为undefined,如下图所示。
js代码识别Selenium+Webdriver及其应对方案
所以,如果网站通过js代码获取这个参数,返回值为undefined说明是正常的浏览器,返回 true 说明用的是Selenium模拟浏览器。
这里给出一个检测Selenium的js代码例子:

webdriver = window.navigator.webdriver;
if(webdriver){
    console.log('你以为使用Selenium模拟浏览器就没事了?')
} else {
    console.log('正常浏览器')
}

网站只要在页面加载的时候运行这个js代码,就可以识别访问者是不是用的Selenium模拟浏览器。如果是,就禁止访问或者触发其他反爬虫的机制。

那么对于这种情况,在爬虫开发的过程中如何防止这个参数告诉网站你在模拟浏览器呢?

最简单的想法是通过覆盖这个参数从而隐藏自己,但实际上这个值是不能被覆盖的:
js代码识别Selenium+Webdriver及其应对方案
精通js的朋友可能会使用下面这一段代码来实现:

Object.defineProperties(navigator, {webdriver:{get:()=>undefined}});

js代码识别Selenium+Webdriver及其应对方案
确实修改成功了。但是,如果此时在模拟浏览器中通过点击链接、输入网址进入另一个页面,或者开启新的窗口会发现,window.navigator.webdriver 又变成了 true 。如下图所示。
js代码识别Selenium+Webdriver及其应对方案

那么可以在每一个页面都打开以后,再次通过webdriver 执行上面的js代码,从而实现在每个页面都把 window.navigator.webdriver 设置为undefined 呢?

答案是不行。因为当执行:driver.get(网址)的时候,浏览器会打开网站,加载页面并运行网站自带的js代码。所以在你重设window.navigator.webdriver之前,实际上网站早就已经知道你是模拟浏览器了。

解决方法,一:可以通过编写Chrome插件来解决这个问题,让插件里面的js代码在网站自带的所有js代码之前执行。

二: 更简单的办法是设置Chromedriver的启动参数。

在启动Chromedriver之前,为Chrome开启实验性功能参数 excludeSwitches,它的值为[‘enable-automation’],完整代码如下:

from selenium.webdriver import Chrome
from selenium.webdriver import ChromeOptions

option = ChromeOptions()
option.add_experimental_option('excludeSwitches', ['enable-automation'])
driver = Chrome(options=option)

此时启动的Chrome窗口,在右上角会弹出一个提示(是否禁用开发者模式),不要点击停用按钮
再次在开发者工具的 Console 选项卡中查询window.navigator.webdriver,可以发现这个值已经自动变成undefined了。并且无论你打开新的网页,开启新的窗口还是点击链接进入其他页面,都不会让它变成true。运行效果如下图所示。
js代码识别Selenium+Webdriver及其应对方案
登陆知乎实验:
爬取知乎数据其实不需要登录,但是这里我们为了测试就验证一下上文方法
截至2019年05月20日,本文所讲的方法可以用来登录知乎。
如果使用 Selenium 直接登录知乎,会弹出验证码;

from selenium.webdriver import Chrome
from selenium.webdriver import ChromeOptions
driver = Chrome()
driver.get("https://www.zhihu.com/signin?next=%2F")
driver.find_element_by_name("username").clear()
driver.find_element_by_name("username").send_keys("YOUR_USERNAME")
driver.find_element_by_name("password").click()
driver.find_element_by_name("password").clear()
driver.find_element_by_name("password").send_keys("YOUR_PASSWORD")
driver.find_element_by_xpath(
    u"(.//*[normalize-space(text()) and normalize-space(.)='忘记密码?'])[1]/following::button[1]").click()

cookies = driver.get_cookies()
driver.quit()

弹出验证码框
js代码识别Selenium+Webdriver及其应对方案
使用本文的方法再登录知乎,才能够成功伪装成真实的浏览器,不会弹出验证码。

from selenium.webdriver import Chrome
from selenium.webdriver import ChromeOptions

# 仅仅多了以下两行,进入开发者模式
option = ChromeOptions()
option.add_experimental_option('excludeSwitches', ['enable-automation'])
driver = Chrome(options=option)

driver.get("https://www.zhihu.com/signin?next=%2F")
driver.find_element_by_name("username").clear()
driver.find_element_by_name("username").send_keys("YOUR_USERNAME")
driver.find_element_by_name("password").click()
driver.find_element_by_name("password").clear()
driver.find_element_by_name("password").send_keys("YOUR_PASSWORD")
driver.find_element_by_xpath(
    u"(.//*[normalize-space(text()) and normalize-space(.)='忘记密码?'])[1]/following::button[1]").click()

cookies = driver.get_cookies()
driver.quit()

成功登录
js代码识别Selenium+Webdriver及其应对方案