Selenium 系列篇(六):反反爬篇
1. 反爬
有时候,我们利用 Selenium 自动化爬取某些网站时,极有可能会遭遇反爬。
实际上,我们使用默认的方式初始化 WebDriver 打开一个网站,下面这段 JS 代码永远为 true,而手动打开目标网站的话,则为:undefined
# 通过这段 JS 脚本区分是爬虫还是人工操作
window.navigator.webdriver
稍微有一点反爬经验的工程师利用上面的差别,很容易判断访问对象是否为一个爬虫,然后对其做反爬处理,返回一堆脏数据或各种验证码。
如果要实现后面的自动化操作,首先要解决的就是这个反爬的问题。
常见的反反爬方案包含:设置参数 excludeSwitches、mitmproxy 拦截过滤、cdp 命令,下面分别来说说。
2.设置参数 excludeSwitches
Chrome79 之前可以通过配置 ChromeOptions 驱动参数,来达到反反爬的目的。
只需要将参数打开,设置 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)
driver.implicitly_wait(10)
driver.get("http://www.google.com")
这个参数是实验性参数,所以右上角会提示:请停用开发者模式运行的扩展程序,不能点击停用。
这样,设置这个参数后:
window.navigator.webdriver 的值就变成 undefined 了。
3. mitproxy 拦截
众所周知,mitproxy 可以拦截到网络请求,做其他处理,这里只需要进行 JS 代码注入即可。
# 待执行的 JS 代码,修改 window.navigator.webdriver 的值
js_exec = 'Object.defineProperties(navigator,{webdriver:{get:() => false}});'
# 重写 response,截获网络请求,js注入
def response(slef,flow: mitmproxy.http.HTTPFlow):
if 'google' in flow.request.url:
flow.response.text = js_exec + flow.response.text
然后启动 mitmdump
# 启动mitmproxy
mitmdump -p 8888 -s 111.py
最后,配置 ChromeOptions 指向 mitmdump代码即可。
# 配置ChromeOptions
option.add_argument("--proxy-server=http://127.0.0.1:8888")
4. cdp 命令
cdp 全称是:Chrome Devtools-Protocol
通过 addScriptToEvaluateOnNewDocument() 方法可以在页面还未加载之前,运行一段脚本。
如此,我们只需要提前设置:
window.navigator.webdriver 的值为 undefined 即可。
from selenium.webdriver import Chrome
from selenium.webdriver import ChromeOptions
option = ChromeOptions()
# 打开参数
# option.add_argument("--proxy-server=http://127.0.0.1:8888")
# driver = Chrome(options=option)
driver = Chrome()
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": """
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
})
"""
})
driver.implicitly_wait(10)
driver.get("http://www.google.com")
5. 其他
通过上面的 3 种方法可以很好的解决 Selenium 自动化被反爬的问题。
- Edgware.RC1中ZuulFallbackProvider的改进
- JPA的多表复杂查询:详细篇
- 尝试使用Memcached遇到的狗血问题
- Enumerable#Zip 实现一下
- 更新自己,不要影响其他人
- 【译】Spring官方教程:Spring Boot整合消息中间件RabbitMQ
- [实录]解决Migrator.Net 小bug
- Jenkins Pipeline插件十大最佳实践!
- Spring Cloud Hystrix的请求合并
- JQuery JCshare 0.1 分享插件
- Java中的即时编译(Just-in-time compilation)
- 无尽的忙碌换来幸福的日子
- 消费者驱动的微服务契约测试套件:Spring Cloud Contract
- 自己做的一个小程序 可采集、导出、模板、配置
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 【014期】JavaSE面试题(十四):基本IO流
- 微信小程序开发实战(24):选择图像
- 反 996 有理:催程序员交代码,写不出好软件
- 一千个不用 Null 的理由!
- WebAssembly 是 Deno 的好搭档
- Chrome开发者工具的11个高级使用技巧
- 怒爬某破Hub站资源,简单4步撸个鉴黄平台!
- 审阅“史上”最烂的代码
- BeanUtils 是用 Spring 的还是 Apache 的好?
- 一看就会的mysql索引优化(真实案例)
- 【015期】JavaSE面试题(十五):网络IO流
- 算法篇:二分查找基础篇
- 算法篇:双指针之接雨水
- 因用了Insert into select语句,美女同事被开除了!
- 【原创】Java并发编程系列33 | 深入理解线程池(上)