返回博客
指南
Mihnea-Octavian ManolacheLast updated on May 12, 20263 min read

如何使用 Pyppeteer 构建网络抓取器(2026 年指南)

如何使用 Pyppeteer 构建网络抓取器(2026 年指南)
简而言之:Pyppeteer 是 Puppeteer 的非官方 Python 移植版,目前仍可用于驱动真正的 Chromium 浏览器 asyncio。在本指南中,您将学习如何安装它,并使用 Pyppeteer 编写一个现代化的网络爬虫,同时使用 asyncio.runtry/finally,处理等待、表单、截图、无限滚动、Cookie 和代理,并了解何时应迁移至 Playwright、Selenium 或托管式爬虫 API。

如果您已经觉得 requests 加上 BeautifulSoup 已无法满足需求——因为所需数据仅在 JavaScript 执行后才会显示——那么你很可能已经考虑过使用 Pyppeteer 构建网页爬虫。Pyppeteer 是 Puppeteer 的 Python 移植版,它允许你通过 async 。这足以处理单页应用、无限滚动信息流、搜索界面,以及任何隐藏在 fetch

本指南面向 2026 年的中级 Python 开发者。我们将涵盖对该项目现状的客观评估、与 Selenium、Playwright 和 Node Puppeteer 的对比、现代异步模式asyncio.run, try/finally、结构化等待),以及一个完整的端到端示例——该示例将在一个由 JavaScript 驱动的搜索界面上循环遍历多个关键词。读完本文后,您将获得一个可运行的 Pyppeteer 爬虫模板,并掌握一套清晰的决策框架,用于判断何时应选用 Pyppeteer,何时不应选用。

2026年的 Pyppeteer:适用场景与演变

从本质上讲,Pyppeteer 是一个 Python 封装库,它复现了 Puppeteer 的 APIlaunch 浏览器,打开 page,调用 waitForSelector、运行 evaluate,循环执行。这种思维模型与 GitHub 上原始的 Puppeteer 项目完全对应,如果你曾阅读过 Node 教程并希望继续使用 Python,这将非常有用。

2026 年需要坦诚提醒的是,Pyppeteer 的维护力度较弱。维护者在项目 README 中声明其仅进行最低限度的维护,且 Puppeteer 的若干新功能从未被移植过来。这并不意味着你的爬虫程序明天就会失效,但这确实意味着,在为长期运行的生产系统选择 Pyppeteer 之前,你应权衡 Playwright 以及托管式爬虫 API 作为替代方案。 我们将在文末再次探讨这一决策。

Pyppeteer 与 Selenium、Playwright 及 Puppeteer 的对比

在做出决定前,将 Pyppeteer 与其最接近的替代方案进行对比会有所帮助。下表是一份简明指南,可帮助您根据自身技术栈选择合适的工具,而非默认采用 Google 搜索结果中排名靠前的选项。

工具

语言

异步模型

支持的浏览器

隐身选项

维护

Pyppeteer

Python

原生 asyncio

Chromium

手动,无原生插件

维护力度较低

Playwright (Python)

Python

同步 + asyncio

Chromium、Firefox、WebKit

内置支持隐身模式的默认设置

由 Microsoft 积极开发

Selenium

Python(及其他语言)

同步(通过封装器异步)

Chromium、Firefox、Edge、Safari

selenium-stealth、未检测到的驱动程序

积极维护,成熟

Puppeteer(Node)

JavaScript / TypeScript

原生 Promise

Chromium、Firefox(实验性)

puppeteer-extra-plugin-stealth

由 Chrome 团队积极开发

实用建议:若需最新功能,请在 Node 环境中选用 Puppeteer;若新 Python 项目需要稳定的跨浏览器抓取功能,请选用 Playwright;若必须支持 Safari 或旧版 IE 风格的流程,请选用 Selenium;若需编写小型 Python 脚本或现有代码库已采用 Python 语言,请选用 Pyppeteer asyncio。如需更全面的对比,请参阅我们关于 Python 无头浏览器库及 Puppeteer 替代方案的汇总文章。

配置 Pyppeteer(Python、Chromium 及 M1/M2 修复)

请使用 Python 3.10 或更高版本,并创建虚拟环境。通过 `uv`,安装只需一行命令:

uv init pyppeteer-demo && cd pyppeteer-demo
uv add pyppeteer
uv run pyppeteer-install   # downloads bundled Chromium

若您更倾向于使用纯 pip,请替换为 python -m venv .venv && pip install pyppeteer && pyppeteer-install。首次运行时,Pyppeteer 可能会拉取一个打包的 Chromium 构建(截至撰写本文时约为 150MB,因此在部署前请重新检查最新的发布说明)。若要跳过该下载并使用系统自带的 Chromium,请设置 PYPPETEER_SKIP_CHROMIUM_DOWNLOAD=1 并传入 executablePathlaunch:

# macOS:  /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
# Linux:  /usr/bin/google-chrome
# Windows: C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe
await launch(executablePath='/usr/bin/google-chrome', headless=True)

M1/M2 Mac 注意事项:Pyppeteer 在 arm64。如果 Chromium 拒绝启动或立即崩溃,请在 Rosetta 环境下重新运行终端,通常安装就能顺利完成。

使用 Pyppeteer 构建一个简易的网页抓取工具:一个现代模板

这是一个可复用的 Pyppeteer 网络爬虫入门模板,它使用 asyncio.run,将浏览器封装在 try/finally,并将渲染后的 HTML 传递给 BeautifulSoup。我们将抓取 quotes.toscrape.com/js/,这是一个通过 JavaScript 渲染引语的沙盒页面,因此普通 HTTP 客户端看到的是一张空白的 <body>.

import asyncio
from bs4 import BeautifulSoup
from pyppeteer import launch

URL = 'https://quotes.toscrape.com/js/'

async def scrape() -> list[dict]:
    browser = await launch(headless=True, args=['--no-sandbox'])
    try:
        page = await browser.newPage()
        await page.goto(URL, {'waitUntil': 'networkidle2'})
        await page.waitForSelector('.quote')
        html = await page.content()
        soup = BeautifulSoup(html, 'html.parser')
        return [
            {
                'text': q.select_one('.text').get_text(strip=True),
                'author': q.select_one('.author').get_text(strip=True),
            }
            for q in soup.select('.quote')
        ]
    finally:
        await browser.close()

if __name__ == '__main__':
    for row in asyncio.run(scrape()):
        print(row)

这里有三个关键点。 asyncio.run 取代了旧教程中仍展示的 get_event_loop().run_until_complete 模式,而旧教程中仍常展示该模式。 try/finally 确保即使代码抛出异常,Chromium 也能被关闭。而 waitForSelector 是显式的同步点,而非固定的 sleep ,后者会在加载快的页面上浪费时间,而在加载慢的页面上超时。

以正确的方式等待元素

Pyppeteer 提供了多种等待方式,选择哪种至关重要。 waitFor() 是模糊且容易出错的,因为它等待“某件事”发生,而 waitForSelector() 则明确且仅在目标节点存在于 DOM 中时才完成。请在 waitForNavigation ,并在页面触发 waitUntil='networkidle2' 当页面发出后台 fetch 调用时使用。作为备选方案,你可以调用 page.waitFor(5000) 来暂停五秒,但请将任何固定时间的等待视为最后手段,因为这是导致抓取程序不稳定的最大单一原因。

点击、输入和提交表单

为了实现更丰富的交互,请结合 page.click, page.type (配合一个微小的 delay 以更像真人),以及 page.keyboard.press。表单提交后,应与点击操作并行等待页面跳转,以免遗漏 URL 变化:

await page.type('input[name="q"]', 'pyppeteer', {'delay': 80})
await asyncio.gather(
    page.waitForNavigation({'waitUntil': 'networkidle2'}),
    page.keyboard.press('Enter'),
)

该模式适用于登录表单、搜索栏以及任何由POST请求触发重定向的UI。

截图和 PDF 导出

page.screenshot() 默认会捕获可见视口。传递 fullPage=True 用于从上到下的截图,若需特定分辨率,请先调用 setViewport 以获取自上而下的截图,若需特定分辨率请先调用 page.pdf() ,并在打印样式简洁的页面上效果最佳:

await page.setViewport({'width': 1440, 'height': 900})
await page.screenshot({'path': 'page.png', 'fullPage': True})
await page.pdf({
    'path': 'page.pdf',
    'format': 'A4',
    'printBackground': True,
    'margin': {'top': '20mm', 'bottom': '20mm', 'left': '15mm', 'right': '15mm'},
})

处理无限滚动和懒加载

无限滚动页面仅在您将视口向下推至底部时才渲染下一批内容。使用 page.evaluate 来运行一个小型 JS 循环,监听 document.body.scrollHeight ,并在数据流停止增长时停止:

await page.evaluate('''async () => {
  await new Promise(resolve => {
    let last = 0;
    const timer = setInterval(() => {
      window.scrollBy(0, 800);
      const h = document.body.scrollHeight;
      if (h === last) { clearInterval(timer); resolve(); }
      last = h;
    }, 400);
  });
}''')

如果内容源确实无穷无尽,请为循环设置最大迭代次数上限。

对于需要登录的页面,请先登录一次,保存 Cookie,并在下次运行时重新加载,以免触发重新登录的验证:

cookies = await page.cookies()          # save somewhere safe
await page.setCookie(*saved_cookies)    # restore later
await page.setUserAgent('Mozilla/5.0 ... Chrome/124 Safari/537.36')
await page.setViewport({'width': 1366, 'height': 768})

配对 setUserAgent 与匹配的 setViewport ,以确保设备指纹在内部保持一致。一个具有 320 像素视口的桌面用户代理是机器人检测中的典型破绽。

使用 Pyppeteer 实现端到端网页抓取:抓取基于 JavaScript 的搜索界面

让我们将所有内容整合起来。下面的脚本会循环遍历多个关键词,将每个关键词输入到客户端渲染结果的搜索栏中,等待结果卡片出现,使用 querySelectorAllEval,并在处理下一个关键词前清空输入框。请根据您的实际目标替换 URL 和选择器。

import asyncio
from pyppeteer import launch

KEYWORDS = ['python', 'pyppeteer', 'asyncio']
SEARCH_URL = 'https://example.com/search'   # JS-rendered UI

async def search_one(page, keyword: str) -> list[str]:
    await page.click('input[name="q"]', {'clickCount': 3})
    await page.keyboard.press('Backspace')
    await page.type('input[name="q"]', keyword, {'delay': 60})
    await page.keyboard.press('Enter')
    await page.waitForSelector('.result-card', {'timeout': 10000})
    return await page.querySelectorAllEval(
        '.result-card h3',
        '(nodes) => nodes.map(n => n.innerText.trim())',
    )

async def main():
    browser = await launch(headless=True, args=['--no-sandbox'])
    try:
        page = await browser.newPage()
        await page.goto(SEARCH_URL, {'waitUntil': 'networkidle2'})
        results = {}
        for kw in KEYWORDS:
            results[kw] = await search_one(page, kw)
            await asyncio.sleep(2)   # be polite
        return results
    finally:
        await browser.close()

if __name__ == '__main__':
    print(asyncio.run(main()))

这种模式带来了两项改进。首先,所有关键词共用一个浏览器和一个页面,从而降低了运行成本。其次,显式的 waitForSelector 使爬虫能够抵御网络抖动,因此当请求耗时 600 毫秒而非 200 毫秒时,任务不会立即崩溃。在此基础上,通过 asyncio.gather ,是顺理成章的下一步。

在 Pyppeteer 中使用代理和轮询

Pyppeteer 虽能出色地处理浏览器自动化,但本身不管理代理,因此需在启动时进行配置。 --proxy-server Chromium 标志接受单个端点,而 page.authenticate 并在首次请求前添加凭据:

import random
from pyppeteer import launch

PROXIES = [
    'http://user:pass@proxy-a.example.com:8000',
    'http://user:pass@proxy-b.example.com:8000',
    'http://user:pass@proxy-c.example.com:8000',
]

async def launch_with_proxy():
    proxy = random.choice(PROXIES)   # naive rotation
    host = proxy.split('@')[-1]
    browser = await launch(args=[f'--proxy-server=http://{host}'])
    page = await browser.newPage()
    await page.authenticate({'username': 'user', 'password': 'pass'})
    return browser, page

即使使用干净的代理,最终也会遇到速率限制,因此建议按会话或按关键词轮换代理。如需更深入的实现方案,请参阅我们的 Python 代理轮换指南。若自行管理代理池令您感到繁琐,可选用托管型住宅代理产品,或 WebScrapingAPI Scraper API 等基于请求的 API 来分担这项工作。

隐身与指纹管理检查清单

Pyppeteer 默认不包含原生隐身插件,因此您需要自行强化浏览器。最低可行检查清单:

  • 设置一个真实且最新的桌面用户代理,并配合 page.setUserAgent.
  • 通过 page.setViewport (1366x768 或 1440x900 是安全的默认值)。
  • 修补 navigator.webdriver flag evaluateOnNewDocument 钩子中修补flag,使其返回 undefined 而非 true.
  • 保持 Cookie 卫生:在会话之间清除 Cookie,或者在轮换 IP 时轮换会话。
  • 对于任何具有严密机器人防御的目标,请通过住宅或移动代理轮换 IP
  • 使用 delay 启用 type 并保持 asyncio.sleep 操作间隔,以模拟自然行为。

2026年生产级最佳实践

若想让基于 Pyppeteer 的网络爬虫在实际运行中经受住考验,请遵循以下规则:

  • 运行入口点时使用 asyncio.run(main())。忘掉 get_event_loop()loop.run_until_complete();现代函数更简洁且更少出错。
  • 将每个浏览器封装在 try/finally 中,这样即使你的代码抛出异常,Chromium进程也会被终止。浏览器资源泄漏是导致CI运行器崩溃的首要原因。
  • 优先使用 waitForSelector (显式) 而非 waitFor (含糊的)方式。仅将固定时长睡眠保留给已记录的反机器人延迟。
  • 请礼貌地进行流量控制。尊重 robots.txt,将范围限定在公开数据上,并添加抖动,确保 100 次请求不会在 100 毫秒内全部到达。
  • 添加结构化日志(每页一行 JSON),并记录 URL、状态、响应时间以及选择器匹配次数。当目标网站首次更改 HTML 时,你会庆幸自己做了这些准备。

当 Pyppeteer 并非合适工具时(以及替代方案)

Pyppeteer 非常适合一次性脚本、内部自动化以及已使用 asyncio。但一旦需要跨浏览器兼容性、最新的 CDP 功能、官方隐身模式或大规模并发处理,它便会显露出局限性。请参考以下粗略的决策准则:

  • 对于原型开发、周末快手项目以及日处理量在几百页以内的脚本,请继续使用 Pyppeteer
  • 当需要支持 Firefox 或 WebKit、强大的自动等待功能,或一流的追踪能力时,请转用 Playwright (Python)
  • 若必须支持 Safari 或接入现有测试网格,请转用 Selenium
  • 若您在代理轮换、验证码处理和无头基础设施上的投入时间超过了实际数据采集,请使用托管式爬取 API

关键要点

  • Pyppeteer 是 Puppeteer 的 Python 移植版本,维护力度较轻;在 2026 年,它仍适用于 asyncio,但对于没有备用方案的长期运行生产系统而言并非理想选择。
  • 请使用 asyncio.run, try/finally,并 waitForSelector ,而非过时教程中展示的旧版事件循环和 waitFor 模式。
  • 一个完整的 Pyppeteer 网络爬虫应涵盖等待、表单输入、截图、PDF 处理、无限滚动、Cookie 复用和代理等功能,而不仅仅是 goto.
  • 由于没有原生的隐身插件,因此用户代理、视口、 navigator.webdriverCookie 管理以及 IP 轮换都需由您自行负责。
  • 一旦您的抓取程序超出了单台机器、单个浏览器或单个代理的范围,请选择 Playwright、Selenium 或托管式抓取 API。

常见问题

Pyppeteer 在 2026 年是否仍在维护且安全?

其实不然。维护者在项目的 GitHub README 中明确指出,Pyppeteer 仅进行最低限度的维护,且很少移植 Puppeteer 的新功能。它虽然仍能运行并进行抓取,但对于长期运行的生产系统,在投入使用前,您应评估 Playwright(Python)或托管式抓取 API 作为更积极开发的替代方案。

Pyppeteer 和 Puppeteer 有什么区别?

Puppeteer 是 Chrome 团队推出的用于自动化 Chromium 的官方 Node.js 库。Pyppeteer 是一个非官方的 Python 移植版本,它复现了 Puppeteer 大部分 API,但使用 asyncio 而非 Promises。Pyppeteer 在新功能方面通常落后于 Puppeteer,且部分 Puppeteer API 完全缺失,因此两者的生态系统在形态上相似,但在覆盖范围上存在差异。

对于新的 Python 爬虫项目,我应该选择 Pyppeteer、Playwright 还是 Selenium?

对于 2026 年的新项目,建议默认选用 Python 版的 Playwright。它处于积极开发中,支持 Chromium、Firefox 和 WebKit,并内置自动等待功能,能有效消除许多不稳定因素。若需支持 Safari 或现有测试集群,请选择 Selenium。仅当您需要扩展现已使用 Pyppeteer 的旧脚本时,才应选择 Pyppeteer。

Pyppeteer 能否自行绕过 Cloudflare、机器人检测或 CAPTCHA?

不能。Pyppeteer 默认不包含隐身插件,也没有内置的 CAPTCHA 破解功能。你可以通过设置逼真的用户代理、修补 navigator.webdriver并轮换住宅IP,但要可靠地突破现代Cloudflare或hCaptcha的验证,通常需要使用经过强化处理的框架,或采用能自动处理解锁机制的请求级抓取API。

为什么我的 Pyppeteer 脚本在 M1 或 M2 Mac 上会崩溃?

捆绑的 Chromium 在 Apple Silicon 上较为敏感。最常见的解决方法是在 Rosetta 环境下重新启动终端,并重新运行 pyppeteer-install,这将使 x86_64 版本的 Chromium 能正常安装并启动。另一种方法是设置 PYPPETEER_SKIP_CHROMIUM_DOWNLOAD=1 并指向 executablePath 指向 arm64已安装的原生版 Google Chrome。

总结

在 2026 年,若您需要一个小巧且支持异步操作的 Python 脚本来驱动真正的 Chromium 浏览器,使用 Pyppeteer 构建网络爬虫仍是一个合理的选择。您已拥有可运行的入门模板、用于等待、表单、截图、无限滚动、Cookie 和代理的模式,此外还有一份隐身检查清单,并清楚了解何时应升级到 Playwright、Selenium 或托管型替代方案。

老实说:Pyppeteer 维护力度较弱,因此你应将其视为战术工具而非长期平台。建议将浏览器封装在 try/finally,优先 waitForSelector ,并预留预算以备迁移——当目标网站升级反爬虫防御的速度超过 Pyppeteer 移植下一个 CDP 功能的速度时,您便需要迁移。

如果代理轮换、验证码或 Chromium 升级所耗费的时间开始超过抓取本身,请将请求层交由 WebScrapingAPI 的 Scraper API 处理,让您的 Pyppeteer 代码专注于解析您真正关心的数据。

关于作者
Mihnea-Octavian Manolache, 全栈开发工程师 @ WebScrapingAPI
Mihnea-Octavian Manolache全栈开发工程师

Mihnea-Octavian Manolache 是 WebScrapingAPI 的全栈及 DevOps 工程师,负责开发产品功能并维护确保平台平稳运行的基础设施。

开始构建

准备好扩展您的数据收集规模了吗?

加入2,000多家企业,使用WebScrapingAPI在无需任何基础设施开销的情况下,以企业级规模提取网络数据。