pyppeteer进阶技巧
记录一下在使用pyppeteer过程中慢慢发现的一些稍微高级一点的用法。
一、拦截器简单用法
拦截器作用于单个Page,即浏览器中的一个标签页。每初始化一个Page都要添加一下拦截器。拦截器实际上是
通过给各种事件添加回调函数来实现的。
事件列表可参见:pyppeteer.page.Page.Events
常用拦截器:
- request:发出网络请求时触发
- response:收到网络响应时触发
- dialog:页面有弹窗时触发
使用request拦截器修改请求:
# coding:utf8
import asyncio
from pyppeteer import launch from pyppeteer.network_manager import Request launch_args = {
"headless": False,
"args": [
"--start-maximized",
"--no-sandbox",
"--disable-infobars",
"--ignore-certificate-errors",
"--log-level=3",
"--enable-extensions",
"--window-size=1920,1080",
"--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.97 Safari/537.36",
],
} async def modify_url(request: Request):
if request.url == "https://www.baidu.com/":
await request.continue_({"url": "https://www.baidu.com/s?wd=ip&ie=utf-8"})
else:
await request.continue_() async def interception_test():
# 启动浏览器
browser = await launch(**launch_args)
# 新建标签页
page = await browser.newPage()
# 设置页面打开超时时间
page.setDefaultNavigationTimeout(10 * 1000)
# 设置窗口大小
await page.setViewport({"width": 1920, "height": 1040}) # 启用拦截器
await page.setRequestInterception(True) # 设置拦截器
# 1. 修改请求的url
if 1:
page.on("request", modify_url)
await page.goto("https://www.baidu.com") await asyncio.sleep(10) # 关闭浏览器
await page.close()
await browser.close()
return if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(interception_test())
使用response拦截器获取某个请求的响应:
async def get_content(response: Response):
"""
# 注意这里不需要设置 page.setRequestInterception(True)
page.on("response", get_content)
:param response:
:return:
"""
if response.url == "https://www.baidu.com/":
content = await response.text()
title = re.search(b"<title>(.*?)</title>", content)
print(title.group(1))
干掉页面所有弹窗:
async def handle_dialog(dialog: Dialog):
"""
page.on("dialog", get_content)
:param dialog:
:return:
"""
await dialog.dismiss()
二、拦截器实现切换代理
一般情况下浏览器添加代理的方法为设置启动参数:
--proxy-server=http://user:password@ip:port
例如:
launch_args = {
"headless": False,
"args": [
"--proxy-server=http://localhost:1080",
"--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.97 Safari/537.36",
],
}
但此种方式的缺点很明显,只能在浏览器启动时设置。当需要切换代理时,只能重启浏览器,这个代价
就太高了,所以我们可以想想其他办法。
思路很简单:
- request拦截器可以修改请求属性并且返回自定义响应内容
- 使用第三方库来发送网络请求,并设置代理。然后封装响应内容返回给浏览器
上代码:
import aiohttp aiohttp_session = aiohttp.ClientSession(loop=asyncio.get_event_loop()) proxy = "http://127.0.0.1:1080"
async def use_proxy_base(request: Request):
"""
# 启用拦截器
await page.setRequestInterception(True)
page.on("request", use_proxy_base)
:param request:
:return:
"""
# 构造请求并添加代理
req = {
"headers": request.headers,
"data": request.postData,
"proxy": proxy, # 使用全局变量 则可随意切换
"timeout": 5,
"ssl": False,
}
try:
# 使用第三方库获取响应
async with aiohttp_session.request(
method=request.method, url=request.url, **req
) as response:
body = await response.read()
except Exception as e:
await request.abort()
return # 数据返回给浏览器
resp = {"body": body, "headers": response.headers, "status": response.status}
await request.respond(resp)
return
或者再增加一些缓存来节约一下带宽:
# 静态资源缓存
static_cache = {}
async def use_proxy_and_cache(request: Request):
"""
# 启用拦截器
await page.setRequestInterception(True)
page.on("request", use_proxy_base)
:param request:
:return:
"""
global static_cache
if request.url not in static_cache:
# 构造请求并添加代理
req = {
"headers": request.headers,
"data": request.postData,
"proxy": proxy, # 使用全局变量 则可随意切换
"timeout": 5,
"ssl": False,
}
try:
# 使用第三方库获取响应
async with aiohttp_session.request(
method=request.method, url=request.url, **req
) as response:
body = await response.read()
except Exception as e:
await request.abort()
return # 数据返回给浏览器
resp = {"body": body, "headers": response.headers, "status": response.status}
# 判断数据类型 如果是静态文件则缓存起来
content_type = response.headers.get("Content-Type")
if content_type and ("javascript" in content_type or "/css" in content_type):
static_cache[request.url] = resp
else:
resp = static_cache[request.url] await request.respond(resp)
return
三、反反爬虫
使用pyppeteer来模拟浏览器进行爬虫行动,我们的本意是伪装自己,让目标网站认为我是一个真实的人,然而
总有一些很蛋疼的东西会暴露自己。比如当你使用我上面的配置去模拟淘宝登录的时候,会发现怎么都登录不上。因
为浏览器的navigator.webdriver属性暴露了你的身份。在正常浏览器中,这个属性是没有的。但是当你使用pyppeteer
或者selenium时,默认情况下这个参数就会设置为true。
去除这个属性有两种方式。
先说简单的,pyppeteer的启动参数中,默认会增加一个:--enable-automation
去掉方式如下: 在导入launch之前先把默认参数改了
from pyppeteer import launcher
# hook 禁用 防止监测webdriver
launcher.AUTOMATION_ARGS.remove("--enable-automation")
from pyppeteer import launch
还有个稍微复杂点的方式,就是利用拦截器来实现注入JS代码。
JS代码参见:
https://github.com/dytttf/little_spider/blob/master/pyppeteer/pass_webdriver.js
拦截器代码:
async def pass_webdriver(request: Request):
"""
# 启用拦截器
await page.setRequestInterception(True)
page.on("request", use_proxy_base)
:param request:
:return:
"""
# 构造请求并添加代理
req = {
"headers": request.headers,
"data": request.postData,
"proxy": proxy, # 使用全局变量 则可随意切换
"timeout": 5,
"ssl": False,
}
try:
# 使用第三方库获取响应
async with aiohttp_session.request(
method=request.method, url=request.url, **req
) as response:
body = await response.read()
except Exception as e:
await request.abort()
return if request.url == "https://www.baidu.com/":
with open("pass_webdriver.js") as f:
js = f.read()
# 在html源码头部添加js代码 修改navigator属性
body = body.replace(b"<title>", b"<script>%s</script><title>" % js.encode()) # 数据返回给浏览器
resp = {"body": body, "headers": response.headers, "status": response.status}
await request.respond(resp)
return
这个功能pyppeteer是有专门的函数来做这件事情的:
pyppeteer.page.Page.evaluateOnNewDocument
BUT,这个函数实现的有问题,总是不起作用 。而与之对比,如果你用的是nodejs的puppeteer的话,这个函数
是生效的。
四、使用Xvfb配合实现headless效果
之所以用pyppeteer,很大程度上是为了使用chromium的无头headless模式。无头更省资源,限制也少。然而现
实很残酷,特别是对爬虫。
类似于navigator.webdriver这样的东西可以用来检测是否是机器人。还有更多的手段可以来检测是否是headless。
比如:headless模式下没有window.chrome属性。具体我就不列了,反正好多。可以参见文后链接。关于如何伪装
headless模式,使其不被探测到,网上资料也有很多,也很有用。但是,这个东西细节太多了。。。。。。还得看目
标网站工程师的心情和实力。如果对方有大把时间去检测各种边边角角的东西,不断提升代码的混淆程度,死磕到底
的话,就有点得不偿失了。
于是,我在死磕了携程酒店三天后,幡然醒悟。(有兴趣的可以尝试一下,看如何在无头模式下爬取携程酒店数据)
既然无头这么难搞,就不搞了。直接使用Xvfb来实现虚拟显示器,这样就变成有头的了:)。
问题解决。
文内Python代码见:
https://github.com/dytttf/little_spider/blob/master/pyppeteer/use_case.py
参考文献:
无头浏览器相关
MAKING CHROME HEADLESS UNDETECTABLE
Detecting Chrome headless, new techniques
Xvfb
https://www.x.org/releases/X11R7.6/doc/man/man1/Xvfb.1.xhtml
https://blog.csdn.net/Nobody_Wang/article/details/60887659
pyppeteer进阶技巧的更多相关文章
- 《前端之路》之 JavaScript 进阶技巧之高阶函数(下)
目录 第二章 - 03: 前端 进阶技巧之高阶函数 一.防篡改对象 1-1:Configurable 和 Writable 1-2:Enumerable 1-3:get .set 2-1:不可扩展对象 ...
- 【原创】分布式之数据库和缓存双写一致性方案解析(三) 前端面试送命题(二)-callback,promise,generator,async-await JS的进阶技巧 前端面试送命题(一)-JS三座大山 Nodejs的运行原理-科普篇 优化设计提高sql类数据库的性能 简单理解token机制
[原创]分布式之数据库和缓存双写一致性方案解析(三) 正文 博主本来觉得,<分布式之数据库和缓存双写一致性方案解析>,一文已经十分清晰.然而这一两天,有人在微信上私聊我,觉得应该要采用 ...
- SQL优化之SQL 进阶技巧(下)
上文( SQL优化之SQL 进阶技巧(上) )我们简述了 SQL 的一些进阶技巧,一些朋友觉得不过瘾,我们继续来下篇,再送你 10 个技巧 一. 使用延迟查询优化 limit [offset], [r ...
- SQL优化之SQL 进阶技巧(上)
由于工作需要,最近做了很多 BI 取数的工作,需要用到一些比较高级的 SQL 技巧,总结了一下工作中用到的一些比较骚的进阶技巧,特此记录一下,以方便自己查阅,主要目录如下: SQL 的书写规范 SQL ...
- WPF进阶技巧和实战03-控件(3-文本控件及列表控件)
系列文章链接 WPF进阶技巧和实战01-小技巧 WPF进阶技巧和实战02-布局 WPF进阶技巧和实战03-控件(1-控件及内容控件) WPF进阶技巧和实战03-控件(2-特殊容器) WPF进阶技巧和实 ...
- WPF进阶技巧和实战03-控件(4-基于范围的控件及日期控件)
系列文章链接 WPF进阶技巧和实战01-小技巧 WPF进阶技巧和实战02-布局 WPF进阶技巧和实战03-控件(1-控件及内容控件) WPF进阶技巧和实战03-控件(2-特殊容器) WPF进阶技巧和实 ...
- 25个 Git 进阶技巧
[ 原文] http://www.open-open.com/lib/view/open1431331496857.html 我已经使用git差不多18个月了,觉得自己对它应该已经非常了解.然后来自G ...
- Ansible 进阶技巧
原文 http://www.ibm.com/developerworks/cn/linux/1608_lih_ansible/index.html?ca=drs- 简介 Ansible 是一个系 ...
- WPF进阶技巧和实战09-事件(2-多点触控)
多点触控输入 多点触控输入和传统的基于比的输入的区别是多点触控识别手势,用户可以移动多根手指以执行常见的操作,放大,旋转,拖动等. 多点触控的输入层次 WPF允许使用键盘和鼠标的高层次输入(例如单击和 ...
随机推荐
- python总结三
1.线性表若采用链式存储结构的时候,要求内存中可用存储单位的地址是:连续或者不连续都可以 链式存储去找后继节点或者前驱节点是使用指针来实现的,不需要连续的内存,当然,也可以是连续的内存地址 2. 线性 ...
- scala eval
package com.jason case class JJ(d: Double*) object Ss { def main(args: Array[String]): Unit = { impo ...
- 重置mysql root密码
首先结束mysql进程,利用ps aux | grep mysql命令得到进程ID号,kill ID号,结束进程其次,运行mysqld --skip-grant-tables 或者mysqld_saf ...
- oracle 之 using 使用
oracle 中 using关键字使用规则: 1.查询必须是等值连接.2.等值连接中的列必须具有相同的名称和数据类型. 使用using关键字简化连接时,需要注意以下几点:1.使用 table1表和 ...
- java ---- 认识类和对象
---- 开发方法 结构化开发方法特点: 1.面向功能划分软件结构 2.自顶而下 3.最小的子系统是方法 4.制约了软件的可维护性和扩展性 面向对象开发方法特点: 1.把软件系统看成各种对象和集合 ...
- 解决ajax跨域请求问题
自己做网站的时候,经常遇到跨域问题,下面是平时多次实践总结出的解决方法,大家有什么更好的思路,可以相互交流下~ XMLHttpRequest cannot load http://www.imooc. ...
- SPA单页面应用和MPA多页面应用(转)
原文:https://www.jianshu.com/p/a02eb15d2d70 单页面应用 第一次进入页面时会请求一个html文件,刷新清除一下,切换到其他组件,此时路径也相应变化,但是并没有新的 ...
- go中&^(按位置零)符号的含义
go中有一个 &^ 的运算符,它代表的是按位置零 首先来看下几个输出例子: i := 1 &^ 0 fmt.Println("1 &^ 0 -- ",i) ...
- 独立使用 ecj
ECJ 是 Eclipse Compiler for Java 的缩写,是 JavaTM 认可的 Java 编译工具(类似 javac).可以单独下载使用. 下载地址: http://mirrors. ...
- 部署CentOS虚拟机集群
1.在虚拟机中安装CentOS (1)使用CentOS-6.5-i386-minimal.iso.(2)创建虚拟机:打开Virtual Box,点击“新建”按钮,点击“下一步”,输入虚拟机名称为esh ...