Pyppeteer的使用

在 7.1 节,我们学习了 Selenium 的基本用法,其功能的确非常强大,但很多时候会发现它也有一些不太方便的地方,例如配置环境时,需要先安装好相关测览器,例如 Chrome、Firefox 等,然后到官方网站下载对应的驱动。最重要的是,需要安装对应的 Python Selenium 库,而且得看版本是否对应,这确实不太方便。另外,如果要大规模部署 Selenium,一些环境配置问题也是很头疼的。

本节,我们介绍 Selenium 的另一个替代品: Pyppeteer。

是 Pyppeteer,不是 Puppeteer,Puppeteer 是基于 Nodejs 的,Pyppeteer 是基于 Python 的。

Pyppeteer 介绍

Puppeteer 是 Google 基于 Node.js 开发的一个工具,有了它,我们可以利用 JavaScript 控制 Chrome 浏览器的一些操作。当然,Puppeteer 也可以应用于网络爬虫上,其 API 极其完善,功能非常强大。

Pyppeteer 又是什么呢?它其实是 Puppeteer 的 Python 版实现,但不是 Google 开发的,是由一位来自日本的工程师依据 Puppeteer 的一些功能开发出来的非官方版本。

Pyppeteer 的背后实际上有一个类似于 Chrome 的浏览器一一Chromium,它执行一些动作,从而进行网页煊染。首先,介绍一下 Chromium 浏览器和 Chrome 浏览器的渊源。

Chromium 是 Google 为了研发 Chrome 启动的项目,是完全开源的。二者基于相同的源代码而构建,Chrome 的所有新功能都会先在 Chromium 上实现,待验证稳定后才移植到 Chrome 上,因此 Chromium 的版本更新频率更高,同时包含很多新功能。但作为一款独立的浏览器,Chromium 的用户群体要小众得多。两款浏览器 “同根同源”,有着同样的 logo,只是配色不同,Chromium logo 的颜色是不同深度的蓝色,Chrome logo 的颜色是蓝色、红色、黄色和绿色这 4 种颜色,如图7-19所示

图7-19 Chromium浏览器和Chrome 浏览器的logo

总的来说,两款浏览器的内核一样,实现方式也一样,可以看作开发版和正式版,功能上没有太大区别。

Pyppeteer 就是依赖 Chromium 浏览器运行的。如果第一次运行 Pyppeteer 的时候,没有安装 Chromium 浏览器,程序就会帮我们自动安装和配置好,免去了烦琐的环境配置等工作。另外,Pyppeteer 是基于 Python 的新特性 async 实现的,所以它的一些操作执行也支持异步方式,和 Selenium 相比效率也提高了。

下面我们就一起了解一下 Pyppeteer 的相关用法。

安装

首先要解决的便是安装问题。由于 Pyppeteer 采用了 Python 的 async 机制,所以要求 Python 版本为 3.5 及以上。

使用 pip3 工具安装 Pyppeteer 即可:

pip3 install pyppeteer

具体的安装过程可以参考 https://setup.scrape.center/pyppetter

安装完成之后,便可以开始接下来的学习。

快速上手

我们测试一下基本的页面染操作,这里用网址 https://spa2.scrape.center/ 做测试,如图 7-20 所示。

图7-20 测试网站

这个网站在 7.1 节已经分析过了,整个页面是用 JavaScript 道染出来的,一些 Ajax 接口还带有加密参数,所以没法直接使用 requests 爬取看到的数据,同时也不太好直接模拟 Ajax 来获取数据。

在 7.1 节介绍的使用 Selenium 爬取这个网站中数据的方式,原理就是模拟浏览器的操作,直接用浏览器把页面煊染出来,然后直接获取煊染后的结果。基于同样的原理,Pyppeteer 也可以做到。

下面我们用 Pyppeteer 试试,代码可以写为如下这样:

import asyncio
from pyppeteer import launch
from pyquery import PyQuery as pq

async def main():
    browser = await launch()
page = await browser.newPage()
await page.goto('https://spa2.scrape.center/')
await page.waitForSelector('.item .name')
doc = pq(await page.content())
names = [item.text() for item in doc('.item .name').items()]
print('Names:', names)
await browser.close()

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

运行结果如下:

Names: ['霸王别姬- Farewell My Concubine', '这个杀手不太冷- Léon', '肖申克的救赎- The Shawshank Redemption',
'泰坦尼克号- Titanic', '罗马假日- Roman Holiday', '滚烫的爱恋- Flirting Scholar', '乱世佳人- Gone with
the Wind', '喜剧之王- The King of Comedy', '楚门的世界- The Truman Show', '狮子王- The Lion King']

先粗略看下一下代码,大体意思是访问了测试网站,然后等待 .item.name 节点加载出来,随后通过 pyquery 从页面源码中提取电影的名称并输出,最后关闭 Pyppeteer。运行结果和之前用 Selenium 实现的结果一样,我们成功模拟了页面的加载行为,然后提取了页面上所有电影的名称。

那么,其中具体发生了什么?我们来逐行解析一下。

  • 调用 launch 方法新建了一个 Browser 对象,并赋值给 browser 变量。这一步相当于启动了浏览器。

  • 调用 newPage 方法,新建了一个 Page 对象,并赋值给 page 变量。相当于在浏览器中新建了一个选项卡,这时候虽然启动了一个新的选项卡,但是还未访问任何页面,浏览器窗然是空白的。

  • 调用 pagegoto 方法,相当于在浏览器中输入 goto 方法的参数中的 URL,之后浏览器加载对应的页面。

  • 调用 pagewaitForSelector 方法,传入选择器,页面就会等待选择器对应的节点信息加载出来,加载出来后,就立即返回,否则持续等待直到超时。如果顺利的话,页面会成功加载出来。

  • 页面加载完成后,调用 content 方法,可以获取当前浏览器页面的源代码,这就是 JavaScript 渲染后的结果。

  • 进一步,用 pyquery 解析并提取页面上的电影名称,就得到了最终结果了。

另外,其他一些方法(例如调用 asyncioget_event_loop 等方法)的相关操作属于 Python 异步编程 async 相关的内容,大家如果不熟悉,可以查看第 6 章的知识。

通过上面的代码,我们同样可以爬取 JavaScript 染的页面。怎么样?相比 Selenium,这个代码是不是更简洁易读,环境配置也更加方便。在这个过程中,我们没有配置 Chrome 浏览器,没有配置浏览器驱动,免去了一些烦琐的步骤,却达到了和 Selenium 一样的效果,还实现了异步爬取。

接下来,我们看另外一个例子:

import asyncio
from pyppeteer import launch

width, height = 1366, 768

async def main():
    browser = await launch()
    page = await browser.newPage()
    await page.setViewport({'width': width, 'height': height})
    await page.goto('https://spa2.scrape.center/')
    await page.waitForSelector('.item .name')
    await asyncio.sleep(2)
    await page.screenshot(path='example.png')
    dimensions = await page.evaluate('''() => {
        return {
            width: document.documentElement.clientWidth,
            height: document.documentElement.clientHeight,
            deviceScaleFactor: window.devicePixelRatio,
        }
    }''')
    print(dimensions)
    await browser.close()

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

这里我们用到了几个新的方法,设置了页面窗口的大小、保存了页面截图、执行 JavaScript 语句并返回了对应的数据。其中,在 screenshot 方法里,通过 path. 参数用于传入页面截图的保存路径,另外还可以指定截图的保存格式 type、清晰度 quality、是否全屏 fullPage 和裁切 clip 等参数。页面截图的样例如图 7-21 所示。

图7-21 截图样例

可以看到,返回结果是 JavaScript 煊染后的页面,和我们在浏览器中看到的结果一模一样。

我们还调用 evaluate 方法执行了一些 JavaScript 语句。这里给 JavaScript 传入了一个函数,使用 return 方法返回了页面的宽、高、像素大小比率这三个值,最后得到的是一个 JSON 格式的对象,内容如下:

{'width': 1366, 'height': 768, 'deviceScaleFactor': 1 }

实例就先感受到这里,有太多功能还没提及。

总之,利用 Pyppeteer 可以控制浏览器执行几乎所有想实现的操作和功能,利用它自由地控制爬虫当然也不在话下。

了解了基本的实例后,再来梳理 Pyppeteer 的一些基本和常用操作。Pyppeteer 几乎所有的功能都能在其官方文档的 API Reference 里找到,文档链接是 https://pyppeteer.github.io/pyppeteer/reference.html ,使用哪个方法就来这里查询即可,参数不必死记硬背,即用即查就好。

launch方法

使用 Pyppeteer 的第一步便是启动浏览器。启动浏览器相当于点击桌面上的浏览器图标,用 Pyppeteer 实现时,调用 launch 方法即可。

先来看下 launch 方法的 API,链接为: https://pyppeteer.github.io/pyppeteer/reference.html#launcher ,该方法的定义如下:

pyppeteer.launcher.launch(options:dict=None,**kwargs)→pyppeteer.browser.Browser

可以看到,launch 方法处于 launcher 模块中,在声明中没有特别指定参数,返回值是 browser 模块中的 Browser 对象。另外,观察源码可以发现,这是一个 async 修饰的方法,所以在调用的时候需要使用 await

接下来,看看 launch 方法的参数。

  • ignoreHTTPSErrors(bool):是否忽略 HTTPS 的错误,默认是 False。

  • headless(bool):是否启用无头模式,即无界面模式。如果 devtools 参数是 True,该参数就会被设置为 False,否则为 True,即默认开启无界面模式。

  • executablePath(str):可执行文件的路径。指定该参数之后就不需要使用默认的 Chromium 浏览器了,可以指定为已有的 Chrome 或 Chromium。

  • slowMo(int/float):通过传入指定的时间,可以减缓 Pyppeteer 的一些模拟操作。

  • args(List[str]):在执行过程中可以传入的额外参数。

  • ignoreDefaultArgs(bool):是否忽略 Pyppeteer 的默认参数。如果使用这个参数,那么最好通过 args 设置一些参数,否则可能会出现一些意想不到的问题。这个参数相对比较危险,慎用。

  • handleSIGINT(bool):是否响应 SIGINT 信号,也就是是否可以使用 Ctrl+C 终止浏览器程序,默认是 True。

  • handleSIGTERM(bool):是否响应 SIGTERM 信号(一般是 kill 命令),默认是 True。

  • handleSIGHUP(bool):是否响应 SIGHUP 信号,即挂起信号,例如终端退出操作,默认是 True。

  • dumpio(bool):是否将 Pyppeteer 的输出内容传给 process.stdout 对象和 process.stderr 对象,默认是 False。

  • userDataDir(str):用户数据文件夹,可以保留一些个性化配置和操作记录。

  • env(dict):环境变量,可以传入字典形式的数据。

  • devtools(bool):是否自动为每一个页面开启调试工具,默认是 False。如果将这个参数设置为 True,那么 headless 参数就会无效,会被强制设置为 False。

  • logLevel(int/str):日志级别,默认和 root logger 对象的级别相同。

  • autoclose(bool):当一些命令执行完之后,是否自动关闭浏览器,默认是 True。

  • loop(asyncio.AbstractEventLoop):事件循环对象。好了,了解了这些参数之后,小试牛刀一下吧。

好了,了解了这些参数之后,小试牛刀一下吧。

无头模式

首先,试用一下最常用的参数一一 headless。如果将它设置为 True 或者默认不设置,那么在启动的时候是看不到任何界面的。如果把它设置为 False,那么在启动的时候就可以看到界面了。我们一般会在调试的时候把它设置为 False,在生产环境中设置为 True。下面先尝试一下关闭无头模式:

import asyncio
from pyppeteer import launch

async def main():
    browser = await launch(headless=False)
    await asyncio.sleep(100)

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

运行这段代码之后在控制台看不到任何输出,但是会出现一个空白的 Chromium 界面,如图 7-22 所示。

图7-22 空白的 Chromium 界面

这就是一个光秃秃的浏览器而已,看一下相关信息,如图 7-23 所示。

图7-23 相关信息

上面有 Chromium 浏览器的 logo,开发者内部版本号,将其当作开发版的 Chrome 浏览器就好。

调试模式

本节开启调试模式。例如,在写爬虫的时候经常需要分析网页结构和网络请求,所以开启调试 工具还是很有必要的。可以将 devtools 参数设置为 True,这样每开启一个界面,就会弹出一个调试窗口,非常方便,示例如下:

import asyncio
from pyppeteer import launch

async def main():
    browser = await launch(devtools=True)
    page = await browser.newPage()
    await page.goto('https://www.baidu.com')
    await asyncio.sleep(100)

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

刚才说过,如果 devtools 参数设置为 True,无头模式就会关闭,界面始终会显示出来。这里我们新建了一个页面,打开了百度,界面运行效果如图 7-24 所示。

图7-24 界面运行效果

禁用提示条

可以看到图 7-24 的上面有一条提示 “Chrome 正在受到自动测试软件的控制”,这个提示条有点烦人,怎么关闭呢?这时候就需要用到 args 参数了,禁用操作如下:

browser = await launch(headless=False, args=['--disable-infobars'])

这里不再写完整代码了,就是给 launch 方法中的 args 参数传入 list 形式的数据,这里使用的是 --disable-infobars

防止检测

有人会说,刚刚只是把提示关闭了,有些网站还是能检测到 Webdriver 属性。不妨拿之前的案例网站 https://antispiderl.scrape.center/ 验证一下:

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://antispiderl.scrape.center/')
    await asyncio.sleep(100)

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

果然被检测到了,如图 7-25 所示。

图7-25 检测结果

这说明 Pyppeteer 开启 Chromium 后,照样能被检测到 Webdriver 属性的存在。

那么如何规避此问题呢?Pyppeteer 的 Page 对象有一个叫作 evaluateOnNewDocument 的方法,意思是在每次加载网页的时候执行某条语句,这里可以利用它执行隐藏 Webdriver 属性的命令,代码改写如下:

import asyncio
from pyppeteer import launch

async def main():
    browser = await launch(headless=False, args=['--disable-infobars'])
    page = await browser.newPage()
    await page.evaluateOnNewDocument('Object.defineProperty(navigator, "webdriver", {get: () => undefined})')
    await page.goto('https://antispiderl.scrape.center/')
    await asyncio.sleep(100)

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

可以看到,整个页面成功加载出来了,绕过了对 Webdriver 属性的检测,如图 7-26 所示。

图7-26 加载成功的页面

页面大小设置

在图 7-28 中,还可以发现页面的显示 bug,整个浏览器的窗口要比显示内容的窗口大,这个情况并非每个页面都会出现。

这时可以设置窗口大小,调用 Page 对象的 setViewport 方法即可,代码如下:

import asyncio
from pyppeteer import launch

width, height = 1366, 768

async def main():
    browser = await launch(headless=False, args=['--disable-infobars', f'--window-size={width},{height}'])
    page = await browser.newPage()
    await page.setViewport({'width': width, 'height': height})
    await page.evaluateOnNewDocument('Object.defineProperty(navigator, "webdriver", {get: () => undefined})')
    await page.goto('https://antispider1.scrape.center/')
    await asyncio.sleep(100)

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

这里我们同时设置了浏览器窗口的宽高以及显示区域的宽高,让二者一致,最后发现页面显示正常了,如图 7-27 所示。

图7-27 正常显示的页面

用户数据持久化

刚才我们看到,每次打开 Pyppeteer 的时候,都是一个新的空白的浏览器。如果网页需要登录,那么即使这次登录成功,下一次打开时也还是空白的,又得登录一次,这的确是一个问题。

以淘宝为例,很多时候在关闭浏览器并再次打开时,它依然处于登录状态。这是因为淘宝的一些关键 Cookie 已经保存到本地了,再次登录的时候可以直接读取并保持登录状态。

那么,这些信息保存在哪里呢?答案是用户目录下。其中不仅包含浏览器的基本配置信息,还包含一些 Cache、Cookie 等信息,如果我们能在浏览器启动的时候读取这些信息,就可以恢复一些历史记录甚至登录状态信息了。

这也解决了一个问题:很多朋友每次在启动 Selenium 或 Pyppeteer 的时候总是一个全新的浏览器。究其原因就是没有设置用户目录,如果设置了,每次打开时就不会是一个全新的浏览器了,它可以恢复之前的历史记录,和很多网站的登录信息。

那么,怎么设置用户目录呢?很简单,在启动浏览器的时候设置 userDataDir 属性就好了。示例如下:

import asyncio
from pyppeteer import launch

async def main():
    browser = await launch(headless=False, userDataDir='./userdata', args=['--disable-infobars'])
    page = await browser.newPage()
    await page.goto('https://www.taobao.com')
    await asyncio.sleep(100)

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

这里将 userDataDir 属性的值设置为了 ./userdata,即当前目录的 userdata 文件夹。首先运行一下这段代码,然后登录一次淘宝,这时候可以观察到在当前运行的目录下又多了一个 userdata 文件夹,其结构如图 7-28 所示。

图7-28 userdata 文件夹的结构

关于这个文件夹的具体介绍可以看官方的一些说明,例如 https:/chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md

再次运行上面的代码,可以发现淘宝已经处于登录状态,不需要再次登录了,这样就成功跳过了登录的流程。当然,也可能由于时间太久,Cookie 都过期了,还是需要登录。

以上便是 launch 方法及其对应参数的配置。

Browser

我们了解了 launch 方法,它的返回值是一个 Browser 对象,即浏览器对象,我们通常会将其赋值给 browser 变量(其实就是 Browser 类的一个实例)。

下面来看看 Browser 类的定义:

class pyppeteer.browser.Browser(connection: pyppeteer.connection.Connection, contextIds: list[str], ignoreHTTPSErrors: bool, setDefaultViewport: bool, process: Optional[subprocess.Popen] = None, closeCallback: Callable[[None], Awaitable[None]] = None, **kwargs)

从这里可以看到,Browser 类的构造方法有很多参数,大多数情况下直接使用 launch 方法或 connect 方法构建浏览器对象即可。

browser 作为 Browser 类的实例,自然有很多用于操作浏览器的方法,下面我们选取一些比较有用的方法介绍一下。

开启无痕模式

我们知道 Chrome 浏览器有无痕模式,其好处就是环境比较干净,不与其他浏览器示例共享 Cache、Cookie 等内容,可以通过 createIncognitoBrowserContext 方法开启无痕模式。示例如下:

import asyncio
from pyppeteer import launch

width, height = 1200, 768

async def main():
    browser = await launch(headless=False,
                           args=['--disable-infobars', f'--window-size={width},{height}'])

    context = await browser.createIncognitoBrowserContext()
    page = await context.newPage()
    await page.setViewport({'width': width, 'height': height})
    await page.goto('https://www.baidu.com')
    await asyncio.sleep(100)

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

这里调用的是 browsercreateIncognitoBrowserContext 方法,返回值是一个 context 对象。利用 context 对象可以新建选项卡。

运行这段代码后,我们发现浏览器进入了无痕模式,界面如图 7-29 所示。

图7-29 开启浏览器的无痕模式

关闭

怎样关闭浏览器就不多说了,使用的是 close 方法。很多时候会因为忘记关闭浏览器而产生额外开销,因此一定要记得在浏览器使用完毕之后调用 close 方法。示例如下:

import asyncio
from pyppeteer import launch
from pyquery import PyQuery as pq

async def main():
    browser = await launch()
    page = await browser.newPage()
    await page.goto('https://spa2.scrape.center/')
    await browser.close()

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

Page

Page 即页面,对应一个网页、一个选项卡。

在前面的很多示例中,其实已经出现了 Page 对象的身影,这里再详细看一下它的一些常见用法。

选择器

Page 对象内置了很多用于选取节点的选择器方法,例如 J 方法,给它传入一个选择器,就能返回匹配到的第一个节点,等价于 querySelector 方法;又如 JJ 方法,给它传入选择器,会返回符合选择器的所有节点组成的列表,等价于 querySelectorAll 方法。

下面我们分别调用了 J 方法、querySelector 方法、JJ 方法和 querySelectorAll 方法,代码如下:

import asyncio
from pyppeteer import launch
from pyquery import PyQuery as pq

async def main():
    browser = await launch()
    page = await browser.newPage()
    await page.goto('https://spa2.scrape.center/')
    await page.waitForSelector('.item .name')
    j_result1 = await page.J('.item .name')
    j_result2 = await page.querySelector('.item .name')
    jj_result1 = await page.JJ('.item .name')
    jj_result2 = await page.querySelectorAll('.item .name')
    print('J Result1:', j_result1)
    print('J Result2:', j_result2)
    print('JJ Result1:', jj_result1)
    print('JJ Result2:', jj_result2)
    await browser.close()

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

运行结果如下:

可以看到,J 方法和 querySelector 方法的返回结果都是和传入的选择器相匹配的单个节点,返回值为 ElementHandle 对象。JJ 方法和 querySelectorAll 方法则都是返回了和选择器相匹配的节点组成的列表,列表中的内容是 ElementHandle 对象。

选项卡操作

前面我们已经多次演练了新建选项卡的操作,使用的是 newPage 方法。那么新建选项卡之后,怎样获取和切换呢?先调用 pages 方法获取所有打开的页面,然后选择一个页面调用其 bringToFront 方法即可。下面来看一个例子:

import asyncio
from pyppeteer import launch

async def main():
    browser = await launch(headless=False)
    page = await browser.newPage()
    await page.goto('https://www.baidu.com')
    page = await browser.newPage()
    await page.goto('https://www.bing.com')
    pages = await browser.pages()
    print('Pages:', pages)
    page1 = pages[1]
    await page1.bringToFront()
    await asyncio.sleep(100)

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

这里启动了 Pyppeteer,然后调用 newPage 方法新建了两个选项卡,并访问了两个网站。

页面操作

一定要有对应的方法来控制一个页面的加载、前进、后退、关闭和保存等行为,示例如下:

import asyncio
from pyppeteer import launch
from pyquery import PyQuery as pq

async def main():
    browser = await launch(headless=False)
    page = await browser.newPage()
    await page.goto('https://dynamic1.scrape.cuiqingcai.com/')
    await page.goto('https://spa2.scrape.center/')
    # 后退
    await page.goBack()
    # 前进
    await page.goForward()
    # 刷新
    await page.reload()
    # 保存 PDF
    await page.pdf()
    # 截图
    await page.screenshot()
    # 设置页面 HTML
    await page.setContent('<h2>Hello World</h2>')
    # 设置 User-Agent
    await page.setUserAgent('Python')
    # 设置 Headers
    await page.setExtraHTTPHeaders(headers={})
    # 关闭
    await page.close()
    await browser.close()

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

这里我们介绍了一些常用的控制页面的方法,除此以外,还设置了 User-Agent、Headers。

点击

Pyppeteer 同样可以模拟点击,调用其 click 方法即可。以 https://spa2.scrape.center/ 为例,等其所有节点都加载出来后,模拟点击:

import asyncio
from pyppeteer import launch
from pyquery import PyQuery as pq

async def main():
    browser = await launch(headless=False)
    page = await browser.newPage()
    await page.goto('https://spa2.scrape.center/')
    await page.waitForSelector('.item .name')
    await page.click('.item .name', options={
        'button': 'right',
        'clickCount': 1, # 1 或 2
        'delay': 3000, # 毫秒
    })
    await browser.close()

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

这里 click 方法中的第一个参数就是选择器,即在哪里操作。第二个参数是几项配置,具体有如下内容。

  • button: 鼠标按钮,取值有 left、middle、right。

  • clickCount: 点击次数,取值有 1 和 2,表示单击和双击。

  • delay: 延迟点击。

输入文本

Pyppeteer 也可以输入文本,使用 type 方法即可,示例如下:

import asyncio
from pyppeteer import launch
from pyquery import PyQuery as pq

async def main():
    browser = await launch(headless=False)
    page = await browser.newPage()
    await page.goto('https://www.taobao.com')
    # 后退
    await page.type('#q', 'iPad')
    # 关闭
    await asyncio.sleep(10)
    await browser.close()

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

这里我们打开淘宝,给 type 方法的第一个参数传入选择器,第二个参数传入要输入的文本内容,Pyppeteer 就可以帮我们完成输入了。

获取信息

Page 对象需要调用 content 方法获取源码,Cookies 对象调用 cookies 方法获取,示例如下:

import asyncio
from pyppeteer import launch
from pyquery import PyQuery as pq

async def main():
    browser = await launch(headless=False)
    page = await browser.newPage()
    await page.goto('https://spa2.scrape.center/')
    print('HTML:', await page.content())
    print('Cookies:', await page.cookies())
    await browser.close()

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

执行

Pyppeteer 可以支持执行 JavaScript 语句,使用 evaluate 方法即可。看之前的例子:

import asyncio
from pyppeteer import launch

width, height = 1366, 768

async def main():
    browser = await launch()
    page = await browser.newPage()
    await page.setViewport({'width': width, 'height': height})
    await page.goto('https://spa2.scrape.center/')
    await page.waitForSelector('.item .name')
    await asyncio.sleep(2)
    await page.screenshot(path='example.png')
    dimensions = await page.evaluate('''() => {
        return {
            width: document.documentElement.clientWidth,
            height: document.documentElement.clientHeight,
            deviceScaleFactor: window.devicePixelRatio,
        }
    }''')
    print(dimensions)
    await browser.close()

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

这里我们调用 evaluate 方法执行了 JavaScript 语句,并获取了对应的结果。另外,Pyppeteer 还有 exposeFunctionevaluateOnNewDocumentevaluateHandle 方法,可以了解一下。

延时等待

在本节最开头的地方,我们演示了 waitForSelector 的用法,它可以让页面等待某些符合条件的节点加载出来再返回结果。这里我们给 waitForSelector 传入一个 CSS 选择器,如果找到符合条件的节点,就立马返回结果,否则等待直到超时。

除了 waitForSelector 外,还有很多其他的等待方法,具体如下。

  • waitForFunction:等待某个 JavaScript 方法执行完毕或返回结果。

  • waitForNavigation:等待页面跳转,如果没加载出来,就报错。

  • waitForRequest:等待某个特定的请求发出。

  • waitForResponse:等待某个特定请求对应的响应。

  • waitFor:通用的等待方法。

  • waitForXPath:等待符合 XPath 的节点加载出来。

通过各种等待方法,就可以控制页面的加载情况了。

总结

Pyppeteer 还有其他很多功能,例如键盘事件、鼠标事件、对话框事件等,这里就不再一一赘述了。

更多内容可以参考官方文档 https://miyakogi.github.io/pyppeteer/reference.html 的案例说明。

本节我们凭借一些小案例介绍了 Pyppeteer 的基本用法,7.6 节将使用 Pyppeteer 完成一个爬取实例。