记录一下在使用pyppeteer过程中慢慢发现的一些稍微高级一点的用法。

一、拦截器简单用法

拦截器作用于单个Page,即浏览器中的一个标签页。每初始化一个Page都要添加一下拦截器。拦截器实际上是

通过给各种事件添加回调函数来实现的。

事件列表可参见:pyppeteer.page.Page.Events

常用拦截器:

    • request:发出网络请求时触发
    • response:收到网络响应时触发
    • dialog:页面有弹窗时触发

使用request拦截器修改请求:

  1. # coding:utf8
  2. import asyncio
  3. from pyppeteer import launch
  4.  
  5. from pyppeteer.network_manager import Request
  6.  
  7. launch_args = {
  8. "headless": False,
  9. "args": [
  10. "--start-maximized",
  11. "--no-sandbox",
  12. "--disable-infobars",
  13. "--ignore-certificate-errors",
  14. "--log-level=3",
  15. "--enable-extensions",
  16. "--window-size=1920,1080",
  17. "--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",
  18. ],
  19. }
  20.  
  21. async def modify_url(request: Request):
  22. if request.url == "https://www.baidu.com/":
  23. await request.continue_({"url": "https://www.baidu.com/s?wd=ip&ie=utf-8"})
  24. else:
  25. await request.continue_()
  26.  
  27. async def interception_test():
  28. # 启动浏览器
  29. browser = await launch(**launch_args)
  30. # 新建标签页
  31. page = await browser.newPage()
  32. # 设置页面打开超时时间
  33. page.setDefaultNavigationTimeout(10 * 1000)
  34. # 设置窗口大小
  35. await page.setViewport({"width": 1920, "height": 1040})
  36.  
  37. # 启用拦截器
  38. await page.setRequestInterception(True)
  39.  
  40. # 设置拦截器
  41. # 1. 修改请求的url
  42. if 1:
  43. page.on("request", modify_url)
  44. await page.goto("https://www.baidu.com")
  45.  
  46. await asyncio.sleep(10)
  47.  
  48. # 关闭浏览器
  49. await page.close()
  50. await browser.close()
  51. return
  52.  
  53. if __name__ == "__main__":
  54. loop = asyncio.get_event_loop()
  55. loop.run_until_complete(interception_test())

使用response拦截器获取某个请求的响应:

  1. async def get_content(response: Response):
  2. """
  3. # 注意这里不需要设置 page.setRequestInterception(True)
  4. page.on("response", get_content)
  5. :param response:
  6. :return:
  7. """
  8. if response.url == "https://www.baidu.com/":
  9. content = await response.text()
  10. title = re.search(b"<title>(.*?)</title>", content)
  11. print(title.group(1))

干掉页面所有弹窗:

  1. async def handle_dialog(dialog: Dialog):
  2. """
  3. page.on("dialog", get_content)
  4. :param dialog:
  5. :return:
  6. """
  7. await dialog.dismiss()

二、拦截器实现切换代理

一般情况下浏览器添加代理的方法为设置启动参数:

--proxy-server=http://user:password@ip:port

例如:

  1. launch_args = {
  2. "headless": False,
  3. "args": [
    "--proxy-server=http://localhost:1080",
  4. "--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",
  5. ],
  6. }

但此种方式的缺点很明显,只能在浏览器启动时设置。当需要切换代理时,只能重启浏览器,这个代价

就太高了,所以我们可以想想其他办法。

思路很简单:

  1. request拦截器可以修改请求属性并且返回自定义响应内容
  2. 使用第三方库来发送网络请求,并设置代理。然后封装响应内容返回给浏览器

上代码:

  1. import aiohttp
  2.  
  3. aiohttp_session = aiohttp.ClientSession(loop=asyncio.get_event_loop())
  4.  
  5. proxy = "http://127.0.0.1:1080"
  6. async def use_proxy_base(request: Request):
  7. """
  8. # 启用拦截器
  9. await page.setRequestInterception(True)
  10. page.on("request", use_proxy_base)
  11. :param request:
  12. :return:
  13. """
  14. # 构造请求并添加代理
  15. req = {
  16. "headers": request.headers,
  17. "data": request.postData,
  18. "proxy": proxy, # 使用全局变量 则可随意切换
  19. "timeout": 5,
  20. "ssl": False,
  21. }
  22. try:
  23. # 使用第三方库获取响应
  24. async with aiohttp_session.request(
  25. method=request.method, url=request.url, **req
  26. ) as response:
  27. body = await response.read()
  28. except Exception as e:
  29. await request.abort()
  30. return
  31.  
  32. # 数据返回给浏览器
  33. resp = {"body": body, "headers": response.headers, "status": response.status}
  34. await request.respond(resp)
  35. return

或者再增加一些缓存来节约一下带宽:

  1. # 静态资源缓存
  2. static_cache = {}
  3. async def use_proxy_and_cache(request: Request):
  4. """
  5. # 启用拦截器
  6. await page.setRequestInterception(True)
  7. page.on("request", use_proxy_base)
  8. :param request:
  9. :return:
  10. """
  11. global static_cache
  12. if request.url not in static_cache:
  13. # 构造请求并添加代理
  14. req = {
  15. "headers": request.headers,
  16. "data": request.postData,
  17. "proxy": proxy, # 使用全局变量 则可随意切换
  18. "timeout": 5,
  19. "ssl": False,
  20. }
  21. try:
  22. # 使用第三方库获取响应
  23. async with aiohttp_session.request(
  24. method=request.method, url=request.url, **req
  25. ) as response:
  26. body = await response.read()
  27. except Exception as e:
  28. await request.abort()
  29. return
  30.  
  31. # 数据返回给浏览器
  32. resp = {"body": body, "headers": response.headers, "status": response.status}
  33. # 判断数据类型 如果是静态文件则缓存起来
  34. content_type = response.headers.get("Content-Type")
  35. if content_type and ("javascript" in content_type or "/css" in content_type):
  36. static_cache[request.url] = resp
  37. else:
  38. resp = static_cache[request.url]
  39.  
  40. await request.respond(resp)
  41. return

三、反反爬虫

使用pyppeteer来模拟浏览器进行爬虫行动,我们的本意是伪装自己,让目标网站认为我是一个真实的人,然而

总有一些很蛋疼的东西会暴露自己。比如当你使用我上面的配置去模拟淘宝登录的时候,会发现怎么都登录不上。因

为浏览器的navigator.webdriver属性暴露了你的身份。在正常浏览器中,这个属性是没有的。但是当你使用pyppeteer

或者selenium时,默认情况下这个参数就会设置为true。

去除这个属性有两种方式。

先说简单的,pyppeteer的启动参数中,默认会增加一个:--enable-automation

去掉方式如下: 在导入launch之前先把默认参数改了

  1. from pyppeteer import launcher
  2. # hook 禁用 防止监测webdriver
  3. launcher.AUTOMATION_ARGS.remove("--enable-automation")
  4. from pyppeteer import launch

还有个稍微复杂点的方式,就是利用拦截器来实现注入JS代码。

JS代码参见:

  https://github.com/dytttf/little_spider/blob/master/pyppeteer/pass_webdriver.js

拦截器代码:

  1. async def pass_webdriver(request: Request):
  2. """
  3. # 启用拦截器
  4. await page.setRequestInterception(True)
  5. page.on("request", use_proxy_base)
  6. :param request:
  7. :return:
  8. """
  9. # 构造请求并添加代理
  10. req = {
  11. "headers": request.headers,
  12. "data": request.postData,
  13. "proxy": proxy, # 使用全局变量 则可随意切换
  14. "timeout": 5,
  15. "ssl": False,
  16. }
  17. try:
  18. # 使用第三方库获取响应
  19. async with aiohttp_session.request(
  20. method=request.method, url=request.url, **req
  21. ) as response:
  22. body = await response.read()
  23. except Exception as e:
  24. await request.abort()
  25. return
  26.  
  27. if request.url == "https://www.baidu.com/":
  28. with open("pass_webdriver.js") as f:
  29. js = f.read()
  30. # 在html源码头部添加js代码 修改navigator属性
  31. body = body.replace(b"<title>", b"<script>%s</script><title>" % js.encode())
  32.  
  33. # 数据返回给浏览器
  34. resp = {"body": body, "headers": response.headers, "status": response.status}
  35. await request.respond(resp)
  36. 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

https://stackoverflow.com/questions/57298901/unable-to-hide-chrome-is-being-controlled-by-automated-software-infobar-within

 
 
 
 
 
 
 
 
 

pyppeteer进阶技巧的更多相关文章

  1. 《前端之路》之 JavaScript 进阶技巧之高阶函数(下)

    目录 第二章 - 03: 前端 进阶技巧之高阶函数 一.防篡改对象 1-1:Configurable 和 Writable 1-2:Enumerable 1-3:get .set 2-1:不可扩展对象 ...

  2. 【原创】分布式之数据库和缓存双写一致性方案解析(三) 前端面试送命题(二)-callback,promise,generator,async-await JS的进阶技巧 前端面试送命题(一)-JS三座大山 Nodejs的运行原理-科普篇 优化设计提高sql类数据库的性能 简单理解token机制

    [原创]分布式之数据库和缓存双写一致性方案解析(三)   正文 博主本来觉得,<分布式之数据库和缓存双写一致性方案解析>,一文已经十分清晰.然而这一两天,有人在微信上私聊我,觉得应该要采用 ...

  3. SQL优化之SQL 进阶技巧(下)

    上文( SQL优化之SQL 进阶技巧(上) )我们简述了 SQL 的一些进阶技巧,一些朋友觉得不过瘾,我们继续来下篇,再送你 10 个技巧 一. 使用延迟查询优化 limit [offset], [r ...

  4. SQL优化之SQL 进阶技巧(上)

    由于工作需要,最近做了很多 BI 取数的工作,需要用到一些比较高级的 SQL 技巧,总结了一下工作中用到的一些比较骚的进阶技巧,特此记录一下,以方便自己查阅,主要目录如下: SQL 的书写规范 SQL ...

  5. WPF进阶技巧和实战03-控件(3-文本控件及列表控件)

    系列文章链接 WPF进阶技巧和实战01-小技巧 WPF进阶技巧和实战02-布局 WPF进阶技巧和实战03-控件(1-控件及内容控件) WPF进阶技巧和实战03-控件(2-特殊容器) WPF进阶技巧和实 ...

  6. WPF进阶技巧和实战03-控件(4-基于范围的控件及日期控件)

    系列文章链接 WPF进阶技巧和实战01-小技巧 WPF进阶技巧和实战02-布局 WPF进阶技巧和实战03-控件(1-控件及内容控件) WPF进阶技巧和实战03-控件(2-特殊容器) WPF进阶技巧和实 ...

  7. 25个 Git 进阶技巧

    [ 原文] http://www.open-open.com/lib/view/open1431331496857.html 我已经使用git差不多18个月了,觉得自己对它应该已经非常了解.然后来自G ...

  8. Ansible 进阶技巧

    原文  http://www.ibm.com/developerworks/cn/linux/1608_lih_ansible/index.html?ca=drs-   简介 Ansible 是一个系 ...

  9. WPF进阶技巧和实战09-事件(2-多点触控)

    多点触控输入 多点触控输入和传统的基于比的输入的区别是多点触控识别手势,用户可以移动多根手指以执行常见的操作,放大,旋转,拖动等. 多点触控的输入层次 WPF允许使用键盘和鼠标的高层次输入(例如单击和 ...

随机推荐

  1. vue : 无法加载文件 C:\Users\xxx\AppData\Roaming\npm\vue.ps1

    最近因为电脑太卡,小颖把电脑重装了,重装后再执行 npm install -g @vue/cli 时可能是网络问题,一直不能成功,小颖就把npm指向了淘宝镜像: npm install -g cnpm ...

  2. Apache的安装部署 2(加密认证 ,网页重写 ,搭建论坛)

    一.http和https的基本理论知识1. 关于https: HTTPS(全称:Hypertext Transfer Protocol Secure,超文本传输安全协议),是以安全为目标的HTTP通道 ...

  3. RPKM FPKM TPM RSEM

    RPKM:Reads Per Kilobases Per Million Reads指的是每1百万个reads中比对到每1kb碱基外显子上的reads数 FPKM:Fragments Per Kilo ...

  4. Centos目录及其常用处理命令

    1.Centos之常见目录作用介绍[1] 我们先切换到系统根目录 / 看看根目录下有哪些目录 [root@localhost ~]# cd / [root@localhost /]# ls bin   ...

  5. 20165313-bof进阶

    实践基础知识 1.ALSR 1.定义: ASLR,全称为 Address Space Layout Randomization,地址空间布局随机化,它将进程的某些内存空间地址进行随机化来增大入侵者预测 ...

  6. Linux 系统开机时间及当前时间

    最近一次系统开机时间:date -d "$(awk -F. '{print $1}' /proc/uptime) second ago" +"%Y-%m-%d %H:%M ...

  7. Visual Studio报错/plugin.vs.js,行:1074,错误:缺少标识符、字符串或数字

    C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\IDE\PrivateAssemblies/plugin. ...

  8. linux lnmp环境下 安装apache教程

    linux lnmp环境下 安装apache教程 源码安装 apr ,apr-util 安装apache要用<pre>wget http://mirrors.cnnic.cn/apache ...

  9. losetup命令使用

    1.losetup命令 Linux系统losetup命令用来设置循坏设备,循坏设备可以把文件虚拟成块设备,借此来模拟整个文件系统,让用户得以将其视为硬盘驱动器,光驱等设备,并挂入当作目录来使用. (1 ...

  10. 前端与算法 leetcode 36. 有效的数独

    目录 # 前端与算法 leetcode 36. 有效的数独 题目描述 概要 提示 解析 算法 传入[['5', '3', '.', '.', '7', '.', '.', '.', '.'],['6' ...