摘要:简单总结一下pyppeteer的使用方法,pyppeteer的强悍之处在于能过selenium不能过的,或许是越简单粗暴的越稳定?

介绍

Puppeteer是谷歌出品的基于Node.js开发的一款工具,pyppeteer是Puppeteer的python封装?Pyppeteer使用chromium浏览器(chrome的开源版)和asyncio框架

安装

python3使用国内源,然后直接使用pip3 install pyppeteer。
pyppeteer会自己下载一个chromium浏览器到C盘,但是我想自定义位置。(ps:下载chromium浏览器的功能是由pyppeteer.chromium_downloader完成的,自定义的方法来自于:网络爬虫之使用pyppeteer替代selenium完美绕过webdriver检测 - 奥辰 - 博客园)

在python3控制台界面运行如下代码:

import pyppeteer.chromium_downloader
print('默认版本是:{}'.format(pyppeteer.__chromium_revision__))
print('可执行文件默认路径:{}'.format(pyppeteer.chromium_downloader.chromiumExecutable.get('win64')))
print('win64平台下载链接为:{}'.format(pyppeteer.chromium_downloader.downloadURLs.get('win64')))

输入如下:

默认版本是:588429
可执行文件默认路径:C:\Users\Administrator\AppData\Local\pyppeteer\pyppeteer\local-chromium\588429\chrome-win32\chrome.exe
win64平台下载链接为:https://storage.googleapis.com/chromium-browser-snapshots/Win_x64/588429/chrome-win32.zip

在环境变量中设置PYPPETEER_HOME为C:\chrome-win
pyppeteer的使用总结1.png

然后在运行上的代码输出如下:

pyppeteer的使用总结2.png

可以看到可执行文件的默认路径变为:

C:\chrome-win\local-chromium\588429\chrome-win32\chrome.exe

chrome.exe的下载链接为

win64平台下载链接为:https://storage.googleapis.com/chromium-browser-snapshots/Win_x64/588429/chrome-win32.zip

然后在C盘下创建相应目录确保chrome.exe在可执行文件的默认路径就行了

pyppeteer的使用总结3.png

使用例子

使用超级简单,但是关于asyncio框架我也只会个皮毛....

pyppeteer.launcher.launch

打开浏览器是通过pyppeteer.launcher.launch(options: dict = None, **kwargs) 方法,运行该函数后,会得到一个pyppeteer.browser.Browser实例,也就是说浏览器对象实例。launch方法是必须使用的方法。

  • ignoreHTTPSErrors (bool): 是否HTTPS错误,某人情况下为False.
  • headless (bool): 是否以无头模式(无界面模式)执行,默认为True,为True时是不会弹出可视界面的,所以,上面代码运行时设置headless=False。注意,下面还有个devtools参数,表示是否出现打开调试窗口,如果devtools设置为True,headless就算设置为False也会弹出可视界面。
  • executablePath (str): Chromium或Chrome浏览器的可执行文件路径,如果设置,则使用设置的这个路径,不使用默认设置.
  • slowMo (int|float): 设置这个参数可以延迟pyppeteer的操作,单位是毫秒.
  • args (List[str]): 要传递给浏览器进程的一些其他参数.
  • ignoreDefaultArgs (bool): 如果有些参数你不想使用默认值,那么,通过这个参数设置,不过,孩子,最好别用,有危险(电脑会爆炸).
  • handleSIGINT (bool): 是否响应 SIGINT 信号,是否允许通过快捷键Ctrl+C来终止浏览器进程,默认值为True,也就是允许.
  • handleSIGTERM (bool): 是否响应 SIGTERM 信号,也就是说kill命令关闭浏览器,,默认值为True,也就是允许.
  • handleSIGHUP (bool): 是否响应 SIGHUP 信号,即挂起信号,默认值为True,也就是允许.
  • dumpio (bool): 是要将浏览器进程的输出传递给process.stdout 和 process.stderr 对象,默认为False不传递。设置True 可以解决浏览器多开卡死
  • userDataDir (str): 用户数据文件目录.
  • env (dict): 以字典的形式传递给浏览器环境变量.
  • devtools (bool): 是否打开调试窗口,上面介绍headless参数是说过,默认值为False不打开.
  • logLevel (int|str): 日志级别,默认和 root logger 对象的级别相同.
  • autoClose (bool): 当所有操作都执行完后,是否自动关闭浏览器,默认True,自动关闭.
  • loop (asyncio.AbstractEventLoop): 时间循环。
  • appMode (bool): Deprecated.

关于args的参数设置

属性描述
–disable-infobars关闭自动化提示框
–window-size=1920,1080设置浏览器的大小,1920是宽,1080是宽
–log-level=30日志保存等级
–start-maximized窗口最大化模式
–proxy-server=http://localhost:1080设置代理
userDataDir=D:\userData\用户文件保存地址,可用于免登陆

简单使用

#!/usr/bin/python
# -*- coding: UTF-8 -*-

import asyncio
from pyppeteer import launch


async def main():
    # 浏览器 启动参数
    start_parm = {
        # 启动chrome的路径
        "executablePath": r"E:\tmp\chrome-win\chrome.exe",
        # 关闭无头浏览器 默认是无头启动的
        "headless": False,
    }
    # 创建浏览器对象,可以传入字典形式参数
    browser = await launch(**start_parm)

    # 创建一个页面对象, 页面操作在该对象上执行
    page = await browser.newPage()
    await page.goto('http://www.nows.fun/')  # 页面跳转
    page_text = await page.content()  # 页面内容
    print(page_text)
    await browser.close()  # 关闭浏览器对象

asyncio.get_event_loop().run_until_complete(main())   # 创建异步池并执行main函数。

设置窗口大小

ps:设置窗口大小有的时候能够躲避检测
关于window-size和viewport,一个是设置浏览器的大小,一个是设置网页的大小

import asyncio
from pyppeteer import launch

width, height = 1920, 1080 # 尺寸配置

async def main():
    browser = await launch(headless=False,
                           args=[f'--window-size={width},{height}'])  # 设置全屏正常浏览器大小
    page = await browser.newPage()
    await page.setViewport({'width': width, 'height': height})
    await page.goto('https://www.taobao.com')
    await asyncio.sleep(100)

asyncio.get_event_loop().run_until_complete(main())

下面一个代码动态的根据显示屏屏幕调整:

import asyncio
from pyppeteer import launch
 
def screen_size():
    """使用tkinter获取屏幕大小"""
    import tkinter
    tk = tkinter.Tk()
    width = tk.winfo_screenwidth()
    height = tk.winfo_screenheight()
    tk.quit()
    return width, height
 
async def main():
    browser = await launch(headless=False)
    page = await browser.newPage()
    width, height = screen_size()
    await page.setViewport({ # 最大化窗口
        "width": width,
        "height": height
    })
await page.goto('http://www.baidu.com/')
await asyncio.sleep(100)
await browser.close()
 
asyncio.get_event_loop().run_until_complete(main())    

或者下面这个

# !/usr/bin/python
# -*- coding: UTF-8 -*-

import asyncio
import tkinter

from pyppeteer import launcher

# 注意 在导入launch之前先把默认参数改了
# 去除自动化 启动参数
launcher.AUTOMATION_ARGS.remove("--enable-automation")
from pyppeteer import launch


async def main():
    # 浏览器 启动参数
    start_parm = {
        # 启动chrome的路径
        "executablePath": r"E:\tmp\chrome-win\chrome.exe",
        # 关闭无头浏览器
        "headless": False,
        "args": [
            '--disable-infobars',  # 关闭自动化提示框
            '--no-sandbox',  # 关闭沙盒模式
            '--start-maximized',  # 窗口最大化模式
        ],
    }
    browser = await launch(**start_parm)
    page = await browser.newPage()

    # 查看当前 桌面视图大小
    tk = tkinter.Tk()
    width = tk.winfo_screenwidth()
    height = tk.winfo_screenheight()
    tk.quit()
    print(f'设置窗口为:width:{width} height:{height}')

    # 设置网页 视图大小
    await page.setViewport(viewport={'width': width, 'height': height})
    await page.goto('http://www.nows.fun/')
    page_text = await page.content()
    print(page_text)
    await browser.close()


asyncio.get_event_loop().run_until_complete(main())

设置UA头

ps:设置UA头有的时候能够躲避检测

import asyncio
from pyppeteer import launch
 
async def main():
browser = await launch(headless=False)
page = await browser.newPage()
# 设置请求头userAgent
await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3542.0 Safari/537.36')
await page.goto('http://www.baidu.com/')
await asyncio.sleep(100)
await browser.close()
 
asyncio.get_event_loop().run_until_complete(main())

关闭受控制提示

browser = await launch(headless=False,  userDataDir='./userdata', args=['--disable-infobars'])  # 关闭提示: Chrome 正受到自动测试软件的控制

设置用户目录

比如淘宝登陆一次后,设置目录后下次打开淘宝会处于登陆状态,很方便。用的时候先手工在pyppeteer打开的浏览器中登陆一次。

browser = await launch(headless=False, userDataDir='./userdata', args=['--disable-infobars'])  # 设置缓存目录./userdata

执行js脚本

import asyncio
from pyppeteer import launch

async def main():
    browser = await launch()  # 新建一个Browser对象
    page = await browser.newPage()  # 新建一个选项卡,page对象
    await page.goto('http://quotes.toscrape.com/js/')  # 访问指定页面
    await page.screenshot(path='example.png')  # 截图
    await page.pdf(path='测试.pdf')  # 保存为pdf
    dimensions = await page.evaluate('''() => {
        return {
            width: document.documentElement.clientWidth,
            height: document.documentElement.clientHeight,
            deviceScaleFactor: window.devicePixelRatio,
        }
    }''')  # 执行js代码
    print(dimensions)  # 返回js执行结果
    await browser.close()

asyncio.get_event_loop().run_until_complete(main())  # 异步启动

page.evaluateOnNewDocument和page.evaluate的区别

page.evaluateOnNewDocument: 永久注入js, 每次打开新页面注入。

await page.evaluateOnNewDocument('''() => {
       Object.defineProperty(navigator, 'webdriver', {
           get: () => false,
       });
   }''');

page.evaluate:当前页面注入, 页面跳转后js失效。

await page.evaluate('''() => {
       Object.defineProperty(navigator, 'webdriver', {
           get: () => false,
       });
   }''');

模拟操作

pyppeteer提供了Keyboard和Mouse两个类来实现模拟操作,前者是用来实现键盘模拟,后者实现鼠标模拟(还有其他触屏之类的就不说了)。

主要来说说输入和点击:

import os
os.environ['PYPPETEER_HOME'] = 'D:\Program Files'
import asyncio
from pyppeteer import launch
 
async def main():
browser = await launch(headless=False, args=['--disable-infobars'])
page = await browser.newPage()
await page.goto('https://h5.ele.me/login/')
await page.type('form section input', '12345678999') # 模拟键盘输入手机号
await page.click('form section button') # 模拟鼠标点击获取验证码
await asyncio.sleep(200)
await browser.close()
 
asyncio.get_event_loop().run_until_complete(main())

上面的模拟操作中,无论是模拟键盘输入还是鼠标点击定位都是通过css选择器,似乎pyppeteer的type和click直接模拟操作定位都只能通过css选择器(或者是我在官方文档中没找到方法),当然,要间接通过xpath先定位,然后再模拟操作也是可以的。下一小节中模拟登陆外卖平台就是用这种方法,不过,这种方法要麻烦一些,不推荐。

某电商平台模拟登陆

这是我摘抄的, page.evaluate我认为是没用的。因为网页加载完成后,在运行js,感觉还是使用page.evaluateOnNewDocument最好。重点在于,看看作者对于xpath的用法就行。
我曾经用selenium + chrome 实现了模拟登陆这个电商平台,但是实在是有些麻烦,绕过对webdriver的检测不难,但是,通过webdriver对浏览器的每一步操作都会留下特殊的痕迹,会被平台识别,这个必须通过重新编译chrome的webdriver才能实现,麻烦得让人想哭。不说了,都是泪,下面直接上用pyppeteer实现的代码:

import os
os.environ['PYPPETEER_HOME'] = 'D:\Program Files'
import asyncio
from pyppeteer import launch
 
def screen_size():
    """使用tkinter获取屏幕大小"""
    import tkinter
    tk = tkinter.Tk()
    width = tk.winfo_screenwidth()
    height = tk.winfo_screenheight()
    tk.quit()
    return width, height
 
 
async def main():
    js1 = '''() =>{
 
        Object.defineProperties(navigator,{
        webdriver:{
            get: () => false
            }
        })
    }'''
 
    js2 = '''() => {
        alert (
            window.navigator.webdriver
        )
    }'''
    browser = await launch({'headless':False, 'args':['--no-sandbox'],})
 
    page = await browser.newPage()
    width, height = screen_size()
    await page.setViewport({ # 最大化窗口
        "width": width,
        "height": height
    })
    await page.goto('https://h5.ele.me/login/')
    await page.evaluate(js1)
    await page.evaluate(js2)
    input_sjh = await page.xpath('//form/section[1]/input[1]')
    click_yzm = await page.xpath('//form/section[1]/button[1]')
    input_yzm = await page.xpath('//form/section[2]/input[1]')
    but = await page.xpath('//form/section[2]/input[1]')
    print(input_sjh)
    await input_sjh[0].type('*****手机号********')
    await click_yzm[0].click()
    ya = input('请输入验证码:')
    await input_yzm[0].type(str(ya))
    await but[0].click()
    await asyncio.sleep(3)
    await page.goto('https://www.ele.me/home/')
    await asyncio.sleep(100)
    await browser.close()
 
asyncio.get_event_loop().run_until_complete(main())

登录时,由于等待时间过长(我猜的)导致出现以下错误:

pyppeteer.errors.NetworkError: Protocol Error (Runtime.callFunctionOn): Session closed. Most likely the page has been closed.

在github上找到了解决方法,似乎只能改源码,找到pyppeteer包下的connection.py模块,在其43行和44行改为下面这样:

self._ws = websockets.client.connect(
# self._url, max_size=None, loop=self._loop)
self._url, max_size=None, loop=self._loop, ping_interval=None, ping_timeout=None)

再次运行就没问题了。可以成功绕过官方对webdriver的检测,登录成功,诸位可以自己尝试一下。

总结

当使用selenium+webdriver写爬虫被检测到时,pyppeteer是你得不二选择,几乎所有能在人工操作浏览器进行的操作通过pyppeteer都能实现,且能完美避开官方对webdriver的检测。pyppeteer涉及的使用方法还很多,本文只介绍了常用方法的很小很小一部分,需要一说的是,pyppeteer的中文资料真的很少,多看看官方文档吧。

参考

网络爬虫之使用pyppeteer替代selenium完美绕过webdriver检测 - 奥辰 - 博客园
Google推出的爬虫新神器:Pyppeteer,神挡杀神,佛挡杀佛!_个人文章 - SegmentFault 思否
Pyppeteer简介与案例
pyppeteer的环境搭建,常见参数及2个案例_崔永华的博客-CSDN博客

文章目录