Pyppeteer 究竟是什么?该如何使用它?
如果你正在阅读这篇文章,那么你很可能已经对网页脚本编程有了大致的了解。根据你偏好的编程语言不同,你可能已经听说过 Puppeteer 或 Selenium。但 Pyppeteer 在网页抓取领域确实是一个新面孔。简而言之,Pyppeteer 与 Puppeteer 的相似度远高于与 Selenium。
Puppeteer 是一个 Node.js 库,用于通过 DevTools 协议控制无头版 Chrome。Pyppeteer 是 Puppeteer 的 Python 移植版本。 与原版 Puppeteer 一样,Pyppeteer 是一个用 Python 编写的库,其核心功能是实现浏览器的自动化操作。换句话说,Pyppeteer 是 Puppeteer API 的 Python 实现,它允许您在 Python 环境中使用 Puppeteer 的各项功能。两者之间的主要区别在于所使用的编程语言。
您应该了解的 Pyppeteer 术语
在继续之前,我认为我们应该先讨论一下 Pyppeteer 相关语境中常用的几个术语:
- 无头模式:指在不显示图形用户界面(GUI)的情况下启动浏览器。换句话说,它是在“后台”运行的,屏幕上无法看到它。这种模式通常用于在抓取过程中减少资源占用。
- 带界面的浏览器:相反,所谓“带界面的”浏览器是指运行时带有图形用户界面的浏览器。这与无头浏览器相对,通常用于测试、调试或手动与网页交互。
- 浏览器上下文:这是浏览器中所有页面共用的状态。它通常用于设置全局浏览器设置,例如 Cookie、HTTP 头和地理位置。
- DOM:文档对象模型(DOM)是用于 HTML 和 XML 文档的编程接口。它以树状结构呈现网页的结构,其中的节点代表各个元素。Pyppeteer 允许您通过操作 DOM 来与页面中的元素进行交互。
- 元素:网页的基本构成单元。它们通过标签、属性和值来定义。
当然,这其中还有更多内容,你会在学习过程中逐渐掌握。但我希望你能先对这些概念有个大致的了解,这样我们才能有一个扎实的开端。我相信,掌握这些术语将有助于你更好地理解本文的核心内容。
为什么要在网页抓取项目中使用 Pyppeteer?
我认为这个问题有两个方面。首先,为什么Pyppeteer总体上是网络爬虫的理想选择。其次,为什么选择Pyppeteer而非Selenium。总体而言,Pyppeteer的一些优势包括:
- 评估 JavaScript:Pyppeteer 提供了一个 `page.evaluate()` 函数。它允许您在页面上下文中执行 JavaScript 代码。
- 网络控制:Pyppeteer 提供了 `page.on()` 方法。该方法允许您监听页面上发生的网络事件,例如请求和响应。
- 跟踪与日志记录:Pyppeteer 允许您跟踪浏览器的活动,并记录页面中的浏览器消息。这使得调试、追踪和理解网站的运行情况变得轻而易举。
与 Selenium 相比,两者非常相似,因为它们都用于自动化控制网页浏览器。不过,Pyppeteer 相比 Selenium 仍存在一些关键差异和优势:
- 简洁性:与 Selenium 相比,Pyppeteer 的 API 更加简洁且一致,这使得初学者更容易上手。Pyppeteer API 基于 DevTools 协议构建,该协议与浏览器紧密集成,因此易于学习和使用。
- 性能:由于 Pyppeteer 基于 DevTools 协议构建,因此其运行速度可能快于 Selenium。该协议专为调试网页而设计,其运行速度远快于 Selenium WebDriver。
- 更强大的网络控制:Pyppeteer 允许对浏览器的网络设置进行更精细的控制,例如请求拦截以及请求/响应阻断。这使得测试和诊断网络相关问题变得更加容易。
当然,这还涉及一个选择问题。以我为例。平时我主要用 JavaScript 编写代码,对 Puppeteer 也相当熟悉。但另一方面,我最喜欢的编程语言是 Python。因此,如果我要用自己偏好的语言开发一个基于已知技术的爬虫工具,我可能会选择 Pyppeteer。
话虽如此,我认为本文的“理论”部分已经讲完了。现在是时候开始实际编码了。
如何使用 Pyppeteer 创建网页抓取工具
在开始编写代码之前,让我先向大家介绍一下Pyppeteer 的官方文档。 我一直提倡,每当遇到困难时,首先查阅官方文档。这比直接在社区(比如 Stackoverflow)提问要好。我通常发现,只要先阅读文档,大多数问题的答案都能找到。所以,请把这当作我的一番善意建议。无论何时遇到困难,请先查阅文档,然后搜索答案,只有在万不得已的情况下才提问。
#1:配置环境
首先,作为一名 Python 开发者,你可能对虚拟环境已经很熟悉了。因此,我们需要做的第一件事就是为项目创建一个虚拟环境。以下是我通常使用的命令序列:
# 创建一个新目录并进入该目录
~ » mkdir py_project && cd py_project
# 创建虚拟环境
~ » python3 -m venv env
# 激活虚拟环境
~ » source env/bin/activate
关于虚拟环境,现在已经全部准备就绪。接下来,是时候安装 Pyppeteer 了。既然终端已经打开,只需输入:
# 使用 pip 安装该包
~ » python3 -m pip install pyppeteer
# 在您的 IDE 中打开该项目
~ » code .#2:创建一个简单的 Pyppeteer 爬虫
上一条命令会打开 Visual Studio Code 或您常用的 IDE。既然现在已经进入“开发环境”,我们就来创建一个新的 `.py` 文件来存放代码。我将文件命名为 `scraper.py`。请注意,Pyppeteer 原生支持异步执行。因此,让我们在文件中导入 `asyncio` 和 `pyppeteer`:
import asyncio
from pyppeteer import launch
完成这一步后,我们可以继续学习更复杂的代码。总体而言,我并不是函数式编程的坚定拥护者。不过,我认为将代码拆分成小块有助于更好地学习。那么,让我们把代码封装到一个函数中:
async def scrape(url):
browser = await launch()
page = await browser.newPage()
await page.goto(url)
content = await page.content()
await browser.close()
return content

该函数以 URL 作为输入,并使用 Pyppeteer 启动一个无头浏览器。随后,它会导航至指定的 URL,获取页面内容,并关闭浏览器。其返回值即为从页面中收集到的 HTML 内容。您可以使用此函数抓取几乎任何网站。要使用该函数,您需要在 `asyncio` 事件循环中调用它,如下所示:
async def main():
content = await scrape('https://www.example.com')
print(content)
loop = asyncio.get_event_loop()
loop.run_until_complete(main())#3:增加更多功能
到目前为止,我们已经拥有了一个可以运行的爬虫。但这基本上就是全部了。如果你想使用 Pyppeteer 构建一个更高级的网页爬虫,就必须为 id 添加更多功能。剧透预警:我们将深入探索面向对象编程的世界。但首先,让我们明确我们的目标。我们希望这个爬虫能够实现什么功能?
- 使用一些自定义值初始化浏览器
- 浏览网页并提取内容
- 在输入框中输入文本
- 提取单个元素的值
- 从多个元素中提取值
3.1. 自定义选项
那么,我们先创建一个新的 `Scraper` 类,稍后再添加它的方法:
class Scraper:
def __init__(self, launch_options: dict) -> None:
self.options = launch_options['options']
self.viewPort = launch_options['viewPort'] if 'viewPort' in launch_options else None
pass
我们为 Scraper 传递的唯一参数是一个 `launch_options` 字典。如您所见,该字典包含两个键。其中一个键用于定义 Pyppeteer的启动选项,另一个键要么是 `None`,要么是一个包含 `viewPort` 的 `width` 和 `height` 的字典。本方法使用的是后者。
3.2. 导航至某页面
如果你查看我们之前使用的函数,就会发现它既涵盖了导航功能,也包含了从特定 URL 中提取原始数据的功能。我们唯一需要做的,就是对该函数进行微调,将其转换为 Scraper 的方法:
async def goto(self, url: str) -> None:
self.browser = await launch(options=self.options)
self.page = await self.browser.newPage()
await self.page.setViewport(self.viewPort) if self.viewPort != None else print('[i] 使用默认视口')
await self.page.goto(url)
这个方法非常简单。首先,它会启动一个新的浏览器,并应用我们之前设置的自定义选项。然后,它会创建一个新页面;如果我们的 `launch_options` 字典中包含 `viewPort`,就会设置该页面的视口。否则,它会记录一条简单消息。最后,它会将我们引导至目标页面。
3.3. 从页面中提取原始数据
同样,该方法位于我们最初的 `scraper` 函数中。我们只需等待 `page.content()` 加载完毕,并返回其值:
async def get_full_content(self) -> str:
content = await self.page.content()
return content
3.4. 在输入框中输入文本
要使用 Pyppeteer 在输入框中输入内容,你需要完成两件事。首先,定位该元素;其次,向其中添加一些值。幸运的是,Pyppeteer 提供了实现这两项操作的方法:
async def type_value(self, selector: str, value: str) -> None:
element = await self.page.querySelector(selector)
await element.type(value)
3.5. 从页面中提取值
请记住,我们既需要能够从单个元素中提取值,也需要能够从多个元素中提取值。虽然可以用同一个方法来处理这两种情况,但我通常更喜欢将它们分开。因此,目前我将再添加两个方法:
async def extract_one(self, selector) -> str:
element = await self.page.querySelector(selector)
text = await element.getProperty("textContent")
return await text.jsonValue()
在此,我们使用 `querySelector` 方法定位元素。随后,我们获取 `textContent` 并返回其 `jsonValue()`。另一方面,当需要选择多个元素时,我们将使用 `querySelector`:
async def extract_many(self, selector) -> list:
result = []
elements = await self.page.querySelectorAll(selector)
for element in elements:
text = await element.getProperty("textContent")
result.append(await text.jsonValue())
return result
该方法的工作原理与 `extract_one` 类似。唯一的区别在于其返回值。这次我们返回的是选定元素内所有文本组成的列表。我想,至此,我们已经实现了所有目标。
#4:做到隐蔽
在网页抓取中,隐蔽性可以理解为不被察觉的能力。当然,构建一个完全无法被察觉的抓取工具需要付出大量努力。例如,Web Scraping API 的“隐身模式”由一支专门的团队负责维护。正是得益于这些努力,我们的抓取工具在每次请求时都能生成独特的指纹。
但本教程的总体目标是引导大家走上正确的道路。对于一个使用 Pyppeteer 构建的完整网页爬虫而言,正确的道路意味着需要为其添加一些隐身功能。 幸运的是,正如 Node.js 中有 `puppeteer-extra-plugin-stealth` 一样,Python 也有相应的包。它的名字也很直观,叫做 `pyppeteer-stealth`。要将其添加到你的项目中,首先使用 pip 安装它:
~ » python3 -m pip install pyppeteer_stealth
然后将其导入到你的项目中,并只需添加一行代码:
async def goto(self, url: str) -> None:
self.browser = await launch(options=self.options)
self.page = await self.browser.newPage()
# 设置为隐身模式
await stealth(self.page)
await self.page.setViewport(self.viewPort) if self.viewPort != None else print('[i] 使用默认视口')
await self.page.goto(url)
下面是运行爬虫的方法。我在代码中添加了一些注释,以说明每个步骤的作用:
async def main():
# Define the launch options dictionary
launch_options = {
'options': {
'headless': False,
'autoClose': True
},
'viewPort': {
'width': 1600,
'height': 900
}
}
# Initialize a new scraper
scraper = Scraper(launch_options)
# Navigae to your target
await scraper.goto('https://russmaxdesign.github.io/accessible-forms/accessible-name-input01.html')
# Type `This is me` inside the input field
await scraper.type_value(
'#fish',
'This is me')
# Scrape the entire page
content = await scraper.get_full_content()
print(content)
# Scrape one single element
el = await scraper.extract_one('body > div:nth-child(14) > ul')
print(el)
# Scrape multiple elements
els = await scraper.extract_many('p')
print(els)
loop = asyncio.get_event_loop()
loop.run_until_complete(main())结论
Pyppeteer 是一款出色的网络爬虫工具。它将 Puppeteer 的完整 API 移植到了 Python 平台,使 Python 开发者无需学习 JavaScript 即可使用这项技术。此外,虽然我不认为它能完全取代 Selenium,但它无疑是一个不错的替代方案。
希望今天的文章能为你的学习之路增添价值。既然我喜欢挑战大家的极限,那就请你在此基础上更进一步吧。我们共同构建的爬虫是一个非常好的起点,它引出了编程中的一个关键概念:面向对象编程(OOP)。因此,我挑战你为 `Scraper` 添加更多方法,让它变得真正出色。




