pyppeteer基础教程|Python比selenium效率更高的web自动化爬虫工具

发表时间:2020-02-22

Pyppeteer 简介

提起 selenium 想必大家都不陌生,作为一款知名的 Web 自动化测试框架,selenium 支持多款主流浏览器,提供了功能丰富的API 接口,经常被我们用作爬虫工具来使用。但是 selenium 的缺点也很明显,比如速度太慢、对版本配置要求严苛,最麻烦是经常要更新对应的驱动还有些网页是可以检测到是否是使用了selenium 。并且selenium 所谓的保护机制不允许跨域 cookies 保存以及登录的时候必须先打开网页然后后加载 cookies 再刷新的方式很不友好。

今天就给大家介绍另一款 web 自动化测试工具 Pyppeteer,虽然支持的浏览器比较单一,但在安装配置的便利性和运行效率方面都要远胜 selenium。

介绍 Pyppeteer 之前先说一下 Puppeteer,Puppeteer 是 Google 基于 Node.js 开发的一个工具,主要是用来操纵 Chrome  浏览器的 API,通过 Javascript 代码来操纵 Chrome 浏览器的一些操作,用作网络爬虫完成数据爬取、Web 程序自动测试等任务。其 API 极其完善,功能非常强大。 而 Pyppeteer 又是什么呢?它实际上是 Puppeteer 的 Python 版本的实现,但他不是 Google 开发的,是一位来自于日本的工程师依据 Puppeteer 的一些功能开发出来的非官方版本。

Pyppeteer 其实是 Puppeteer 的 Python 版本。pyppeteer 模块看不懂就去看puppeteer文档,pyppeteer 只是在 puppeteer之上稍微包装了下而已 。

注意:本来 chrome 就问题多多,puppeteer 也是各种坑,加上 pyppeteer 是基于前者的改编 python 版本,也就是产生了只要前两个有一个有 bug,那么 pyppeteer 就会原封不动的继承下来,本来这没什么,但是现在遇到的问题就是 pyppeteer 这个项目从2018年9月份之后几乎没更新过,前两者都在不断的更新迭代,而 pyppeteer 一直不更新,导致很多 bug 根本没人修复。

 

下面简单介绍下 Pyppeteer 的两大特点:chromium 浏览器 和 asyncio框架:

1).chromium

Chromium 是一款独立的浏览器,是 Google 为发展自家的浏览器 Google Chrome 而开启的计划,相当于 Chrome的实验版,且 Chromium 是完全开源的。二者基于相同的源代码构建,Chrome 所有的新功能都会先在 Chromium 上实现,待验证稳定后才会移植,因此 Chromium 的版本更新频率更高,也会包含很多新的功能,但作为一款独立的浏览器,Chromium 的用户群体要小众得多。两款浏览器“同根同源”,它们有着同样的 Logo,但配色不同,Chrome 由蓝红绿黄四种颜色组成,而 Chromium 由不同深度的蓝色构成。

Snipaste_2020-02-22_12-05-40.png

Pyppeteer 的 web 自动化是基于 chromium 来实现的,由于 chromium 中某些特性的关系,Pyppeteer 的安装配置非常简单,关于这一点稍后我们会详细介绍。

2).asyncio

asyncio 是 Python 的一个异步协程库,自3.4版本引入的标准库,直接内置了对异步IO的支持,号称是Python最有野心的库,官网上有非常详细的介绍:https://docs.python.org/3/library/asyncio.html


安装与使用

由于 Pyppeteer 采用了 Python 的 async 机制,所以其运行要求的 Python 版本为 3.5 及以上

1).极简安装

使用 pip3 install pyppeteer 命令就能完成 pyppeteer 库的安装,至于 chromium 浏览器,只需要一条 pyppeteer-install 命令就会自动下载对应的最新版本 chromium 浏览器到 pyppeteer 的默认位置。

window 下 安装完 pyppeteer ,会在 python 安装目录下的 Scripts 目录下 有 pyppeteer-install.exe和 pyppeteer-install-script.py 两个文件,执行 任意一个都可以安装 chromium 浏览器到 pyppeteer 的默认位置。

Snipaste_2020-02-22_12-06-55.png

运行 pyppeteer-install.exe :

Snipaste_2020-02-22_12-07-19.png

如果不运行 pyppeteer-install 命令,在第一次使用 pyppeteer 的时候也会自动下载并安装 chromium 浏览器,效果是一样的。总的来说,pyppeteer 比起 selenium 省去了 driver 配置的环节。

当然,出于某种原因(需要梯子,或者科学上网),也可能会出现chromium自动安装无法顺利完成的情况,这时可以考虑手动安装:首先,从下列网址中找到自己系统的对应版本,下载chromium压缩包:

'linux': 'https://storage.googleapis.com/chromium-browser-snapshots/Linux_x64/575458/chrome-linux.zip'
'mac': 'https://storage.googleapis.com/chromium-browser-snapshots/Mac/575458/chrome-mac.zip'
'win32': 'https://storage.googleapis.com/chromium-browser-snapshots/Win/575458/chrome-win32.zip'
'win64': 'https://storage.googleapis.com/chromium-browser-snapshots/Win_x64/575458/chrome-win32.zip'

然后,将压缩包放到pyppeteer的指定目录下解压缩,windows系统的默认目录。

其他系统下的默认目录可以参照下面:

Windows: C:\Users\<username>\AppData\Local\pyppeteer
OS X: /Users/<username>/Library/Application Support/pyppeteer
Linux: /home/<username>/.local/share/pyppeteeror in $XDG_DATA_HOME/pyppeteer if $XDG_DATA_HOME is defined.

Details see appdirs’s user_data_dir.

好了,安装完成之后我们命令行下测试下:
>>> import pyppeteer
如果没有报错,那么就证明安装成功了。

2).使用

Pyppeteer 是一款非常高效的 web 自动化测试工具,由于 Pyppeteer 是基于 asyncio 构建的它的所有 属性 和方法 几乎都是 coroutine (协程) 对象因此在构建异步程序的时候非常方便,天生就支持异步运行。

程序构建的基本思路是新建 一个 browser 浏览器 和 一个 页面 page

看下面这段代码,在 main 函数中,先是建立一个浏览器对象,然后打开新的标签页,访问百度主页,对当前页面截图并保存为“example.png”,最后关闭浏览器。前文也提到过,pyppeteer 是基于 asyncio 构建的,所以在使用的时候需要用到 async/await 结构。

import asyncio
from pyppeteer import launch
 
 
async def main():
    browser = await launch()
    page = await browser.newPage()
    await page.goto('http://baidu.com')
    await page.screenshot({'path': 'example.png'})
    await browser.close()
 
 
asyncio.get_event_loop().run_until_complete(main())

运行上面这段代码会发现并没有浏览器弹出运行,这是因为 Pyppeteer 默认使用的是无头浏览器,如果想要浏览器显示,需要在launch 函数中设置参数 “headless =False”,程序运行结束后在同一目录下会出现截取到的网页图片:

Snipaste_2020-02-22_12-26-28.png


遇到的错误

  • 1)pyppeteer.errors.NetworkError: Protocol error Network.getCookies: Target close

控制访问指定 url 之后 await page.goto(url),会遇到上面的错误,如果这时候使用了 sleep 之类的延时也会出现这个错误或者类似的 time out。 
这个问题是 puppeteer 的 bug,但是对方已经修复了,而 pyppeteer 迟迟没更新,就只能靠自己了。

async def scroll_page(page):
    cur_dist = 0
    height = await page.evaluate("() => document.body.scrollHeight")
    while True:
        if cur_dist < height:
            await page.evaluate("window.scrollBy(0, 500);")
            await asyncio.sleep(0.1)
            cur_dist += 500
        else:
            break

可以把 python 第三方库 websockets 版本 7.0 改为 6.0 就可以了,亲测可用。

pip uninstall websockets #卸载websockets
 
pip install websockets==6.0 
或者
pip install websockets==6.0 --force-reinstall #指定安装6.0版本

  • 2)chromium浏览器多开页面卡死问题。

解决这个问题的方法就是浏览器初始化的时候添加'dumpio':True。

# 启动 pyppeteer 属于内存中实现交互的模拟器
browser = await launch({'headless': False, 'args': ['--no-sandbox'], 'dumpio': True})
  • 3)浏览器窗口很大,内容显示很小。

需要设置浏览器显示大小,默认就是无法正常显示。可以看到页面左侧右侧都是空白,网站内容并没有完整铺满chrome.

# Pyppeteer 支持字典 和 关键字传参,Puppeteer 只支持字典传参。
# 这里使用字典传参
browser = await launch(
    {
        'headless': False, 
        'dumpio': True, 
        'autoClose': False, 
        'args': [
            '--no-sandbox', 
            '--window-size=1366,850'
        ]
    }
)
await page.setViewport({'width': 1366, 'height': 768})

通过上面设置Windows-size和Viewport大小来实现网页完整显示。

但是对于那种向下无限加载的长网页这种情况如果浏览器是可见状态会显示不全,针对这种情况的解决方法就是复制当前网页新开一个标签页粘贴进去就正常了

Pyppeteer 和 Puppeteer 的 不同点

  • Pyppeteer支持字典和关键字传参,Puppeteer只支持字典传参
# Puppeteer只支持字典传参
browser = await launch({'headless': True})
# Pyppeteer支持字典和关键字传参
browser = await launch({'headless': True})
browser = await launch(headless=True)
  • 元素选择器方法名 $变为querySelector
# Puppeteer使用$符
Page.$()/Page.$$()/Page.$x()
# Pyppeteer使用Python风格的函数名
Page.querySelector()/Page.querySelectorAll()/Page.xpath()
# 简写方式为:
Page.J(), Page.JJ(), and Page.Jx()
  • Page.evaluate() 和 Page.querySelectorEval()的参数

Puppeteer的evaluate()方法使用JavaScript原生函数或JavaScript表达式字符串。Pyppeteer的evaluate()方法只使用JavaScript字符串,该字符串可以是函数也可以是表达式,Pyppeteer会进行自动判断。但有时会判断错误,如果字符串被判断成了函数,并且报错,可以添加选项force_expr=True,强制Pyppeteer作为表达式处理。

获取页面内容:

content = await page.evaluate('document.body.textContent', force_expr=True)
element = await page.querySelector('h1')
title = await page.evaluate('(element) => element.textContent', element)

基础用法

 

抓取内容  可以使用 xpath 表达式
"""
# Pyppeteer 三种解析方式
    Page.querySelector()      # 选择器
    Page.querySelectorAll()
    Page.xpath()                   # xpath  表达式
# 简写方式为:
    Page.J(), Page.JJ(), and Page.Jx()
"""

示例 1 :

import asyncio
from pyppeteer import launch
 
 
async def main():
    # headless参数设为False,则变成有头模式
    # Pyppeteer支持字典和关键字传参,Puppeteer只支持字典传参
    
    # 指定引擎路径
    # exepath = r'C:\Users\Administrator\AppData\Local\pyppeteer\pyppeteer\local-chromium\575458\chrome-win32/chrome.exe'
    # browser = await launch({'executablePath': exepath, 'headless': False, 'slowMo': 30})
    
    browser = await launch(
        # headless=False,
        {'headless': False}
    )
 
    page = await browser.newPage()
 
    # 设置页面视图大小
    await page.setViewport(viewport={'width': 1280, 'height': 800})
 
    # 是否启用JS,enabled设为False,则无渲染效果
    await page.setJavaScriptEnabled(enabled=True)
    # 超时间见 1000 毫秒
    res = await page.goto('https://www.toutiao.com/', options={'timeout': 1000})
    resp_headers = res.headers  # 响应头
    resp_status = res.status  # 响应状态
    
    # 等待
    await asyncio.sleep(2)
    # 第二种方法,在while循环里强行查询某元素进行等待
    while not await page.querySelector('.t'):
        pass
    # 滚动到页面底部
    await page.evaluate('window.scrollBy(0, document.body.scrollHeight)')
 
    await asyncio.sleep(2)
    # 截图 保存图片
    await page.screenshot({'path': 'toutiao.png'})
 
    # 打印页面cookies
    print(await page.cookies())
 
    """  打印页面文本 """
    # 获取所有 html 内容
    print(await page.content())
 
    # 在网页上执行js 脚本
    dimensions = await page.evaluate(pageFunction='''() => {
            return {
                width: document.documentElement.clientWidth,  // 页面宽度
                height: document.documentElement.clientHeight,  // 页面高度
                deviceScaleFactor: window.devicePixelRatio,  // 像素比 1.0000000149011612
            }
        }''', force_expr=False)  # force_expr=False  执行的是函数
    print(dimensions)
 
    #  只获取文本  执行 js 脚本  force_expr  为 True 则执行的是表达式
    content = await page.evaluate(pageFunction='document.body.textContent', force_expr=True)
    print(content)
 
    # 打印当前页标题
    print(await page.title())
 
    # 抓取新闻内容  可以使用 xpath 表达式
    """
    # Pyppeteer 三种解析方式
    Page.querySelector()  # 选择器
    Page.querySelectorAll()
    Page.xpath()  # xpath  表达式
    # 简写方式为:
    Page.J(), Page.JJ(), and Page.Jx()
    """
    element = await page.querySelector(".feed-infinite-wrapper > ul>li")  # 纸抓取一个
    print(element)
    # 获取所有文本内容  执行 js
    content = await page.evaluate('(element) => element.textContent', element)
    print(content)
 
    # elements = await page.xpath('//div[@class="title-box"]/a')
    elements = await page.querySelectorAll(".title-box a")
    for item in elements:
        print(await item.getProperty('textContent'))
        # <pyppeteer.execution_context.JSHandle object at 0x000002220E7FE518>
 
        # 获取文本
        title_str = await (await item.getProperty('textContent')).jsonValue()
 
        # 获取链接
        title_link = await (await item.getProperty('href')).jsonValue()
        print(title_str)
        print(title_link)
 
    # 关闭浏览器
    await browser.close()
 
 
asyncio.get_event_loop().run_until_complete(main())

示例 2 :

import asyncio
import pyppeteer
from collections import namedtuple
 
headers = {
    'date': 'Sun, 28 Apr 2019 06:50:20 GMT',
    'server': 'Cmcc',
    'x-frame-options': 'SAMEORIGIN\nSAMEORIGIN',
    'last-modified': 'Fri, 26 Apr 2019 09:58:09 GMT',
    'accept-ranges': 'bytes',
    'cache-control': 'max-age=43200',
    'expires': 'Sun, 28 Apr 2019 18:50:20 GMT',
    'vary': 'Accept-Encoding,User-Agent',
    'content-encoding': 'gzip',
    'content-length': '19823',
    'content-type': 'text/html',
    'connection': 'Keep-alive',
    'via': '1.1 ID-0314217270751344 uproxy-17'
}
 
Response = namedtuple("rs", "title url html cookies headers history status")
 
 
async def get_html(url):
    browser = await pyppeteer.launch(headless=True, args=['--no-sandbox'])
    page = await browser.newPage()
    res = await page.goto(url, options={'timeout': 10000})
    data = await page.content()
    title = await page.title()
    resp_cookies = await page.cookies()  # cookie
    resp_headers = res.headers  # 响应头
    resp_status = res.status  # 响应状态
    print(data)
    print(title)
    print(resp_headers)
    print(resp_status)
    return title
 
 
if __name__ == '__main__':
    url_list = [
        "https://www.toutiao.com",
        "http://jandan.net/ooxx/page-8#comments",
        "https://www.12306.cn/index"
    ]
    task = [get_html(url) for url in url_list]
 
    loop = asyncio.get_event_loop()
    results = loop.run_until_complete(asyncio.gather(*task))
    for res in results:
        print(res)

模拟输入

模拟输入文本:

# 模拟输入 账号密码  {'delay': rand_int()} 为输入时间
await page.type('#TPL_username_1', "sadfasdfasdf")
await page.type('#TPL_password_1', "123456789", )
 
await page.waitFor(1000)
await page.click("#J_SubmitStatic")


使用 tkinter 获取页面高度 宽度

def screen_size():
    """使用tkinter获取屏幕大小"""
    import tkinter
    tk = tkinter.Tk()
    width = tk.winfo_screenwidth()
    height = tk.winfo_screenheight()
    tk.quit()
    return width, height


pyppeteer github 地址:https://github.com/miyakogi/pyppeteer

pyppeteer  英文文档地址:https://miyakogi.github.io/pyppeteer/
pyppeteer 官方文档 API Reference :https://miyakogi.github.io/pyppeteer/reference.html

微配音

文章来源互联网,尊重作者原创,如有侵权,请联系管理员删除。邮箱:417803890@qq.com / QQ:417803890


Python Free

邮箱:417803890@qq.com
QQ:417803890

皖ICP备19001818号
© 2019 copyright www.pythonf.cn - All rights reserved

微信扫一扫关注公众号:

联系方式

Python Free