第 2 课

开 CDP 服务,接 Puppeteer、Playwright 和 Python

用 lightpanda serve 在 9222 端口开 CDP 服务并用 /json/version 检查,用 puppeteer.connect 和 chromium.connectOverCDP 连接抓取名言,用 /metrics 看连接与命令数,再用官方 Python 包和 extract 选择器表沿 Next 链接抓完 100 条名言并统计标签。

图文25 分钟Lightpanda 官方文档 ↗

课程目录第 2 / 3 课

学习位置仅保存在当前浏览器,有效期 180 天。

01 / 图文教材

图文教材

本课对照的官方页面:Puppeteer / Playwright 快速开始、Puppeteer 用法、Playwright 用法、serve 参数、Python 包。命令与输出在 Linux x86_64 云主机上实测(Lightpanda 1.0.2、Node.js 20.19.2、puppeteer-core 24.43.1、playwright-core 1.64.1、Python 3.13.5、lightpanda 包 1.0.2),输出为节选。

上一课的 fetch 一次只处理一个页面。要点击、输入、翻页,就需要脚本来驱动浏览器。这一课让 Lightpanda 开一个 CDP 服务,用 Puppeteer、Playwright 连上去,再用官方 Python 包抓 10 页数据。

第 1 步:开 CDP 服务

CDP 是 Chrome DevTools Protocol,Puppeteer 和 Playwright 都用它控制 Chrome。Lightpanda 实现了同一套协议,所以这些库不用改就能连上它。开一个终端运行:

export LIGHTPANDA_DISABLE_TELEMETRY=true
lightpanda serve --host 127.0.0.1 --port 9222

对照结果:

$scope=note $level=note $msg="server running" address=127.0.0.1:9222
max_connections=16208 max_browser_connections=16

这个终端保持运行。再开一个终端,确认服务正常:

curl -s http://127.0.0.1:9222/json/version

对照结果:

{"Browser": "Lightpanda/1.0", "Protocol-Version": "1.3",
 "User-Agent": "Lightpanda/1.0", "Lightpanda-Version": "1.0.2",
 "webSocketDebuggerUrl": "ws://127.0.0.1:9222/"}

webSocketDebuggerUrl 就是下面脚本要连的地址。默认最多同时接 16 个 CDP 连接,可用 --cdp-max-connections 调整。--host 默认是 127.0.0.1,只有本机能连。改成 0.0.0.0 会让同一网络里的任何人都能操控这个浏览器,不要在公网机器上这样开。

第 2 步:用 Puppeteer 连接

新建项目目录,装 puppeteer-core。它和 puppeteer 的区别是不会下载 Chromium:

mkdir lp-quickstart && cd lp-quickstart
npm init -y
npm pkg set type=module
npm install --save puppeteer-core playwright-core

新建 pptr.mjs。关键是用 puppeteer.connect 连已有的浏览器,而不是用 launch 启动 Chrome:

import puppeteer from 'puppeteer-core';

const browser = await puppeteer.connect({
  browserWSEndpoint: 'ws://127.0.0.1:9222',
});
const context = await browser.createBrowserContext();
const page = await context.newPage();

await page.goto('https://quotes.toscrape.com/js/', {
  waitUntil: 'networkidle0',
});
const quotes = await page.$$eval('.quote', (els) =>
  els.slice(0, 3).map((el) => ({
    text: el.querySelector('.text').textContent,
    author: el.querySelector('.author').textContent,
  })),
);
console.log(await page.title());
console.log(quotes);

await page.close();
await context.close();
await browser.disconnect();

运行:

node pptr.mjs

对照结果(名言文字有删节):

Quotes to Scrape
[
  { text: '“The world as we have created it is a process of our ...”',
    author: 'Albert Einstein' },
  { text: '“It is our choices, Harry, that show what we truly are, ...”',
    author: 'J.K. Rowling' },
  { text: '“There are only two ways to live your life. ...”',
    author: 'Albert Einstein' }
]

测试机上整个脚本约 2 秒跑完。注意最后用的是 browser.disconnect() 而不是 close():浏览器是 serve 启动的,脚本只负责断开连接。

第 3 步:用 Playwright 连接

Playwright 用 chromium.connectOverCDP 连接。在同一目录新建 pw.mjs:

import { chromium } from 'playwright-core';

const browser = await chromium.connectOverCDP('ws://127.0.0.1:9222');
const context = await browser.newContext();
const page = await context.newPage();

await page.goto('https://quotes.toscrape.com/js/');
await page.waitForSelector('.quote');
const n = await page.locator('.quote').count();
const first = await page.locator('.quote .author').first().textContent();
console.log({ count: n, firstAuthor: first });

await page.close();
await context.close();
await browser.close();

运行:

node pw.mjs

对照结果:

{ count: 10, firstAuthor: 'Albert Einstein' }

Python 版 Playwright 写法相同,官方安装命令是 pip install playwright,连接时用 p.chromium.connect_over_cdp("ws://127.0.0.1:9222")。官方步骤里只有这一条安装命令,没有用 playwright install 下载浏览器(本课没有实跑 Python 版 Playwright,这段未实测)。

两个脚本都跑完后,可以看看服务端的统计。serve 默认提供 Prometheus 格式的 /metrics 接口:

curl -s http://127.0.0.1:9222/metrics |
  grep -E '^serve_(connections|commands)_total\{driver="cdp"\}'

对照结果:

serve_connections_total{driver="cdp"} 2
serve_commands_total{driver="cdp"} 138

两个脚本一共发了 138 条 CDP 命令。不需要这个接口时,启动时加 --disable-metrics 关掉。练习完回到第一个终端按 Ctrl+C 停掉服务。

第 4 步:用官方 Python 包抓 10 页数据

Lightpanda 还有官方 Python 包 lightpanda。它自带浏览器二进制文件,不用单独开 serve,也不用装 Playwright。要求 Python 3.10 及以上:

python3 -m venv .venv
. .venv/bin/activate
pip install lightpanda

新建 scrape.py,沿着页面上的 Next 链接把 10 页名言全部抓下来,再统计标签。代码来自官方指南:

from collections import Counter

from lightpanda import Browser

SCHEMA = {
    "quotes": [
        {
            "selector": ".quote",
            "fields": {
                "text": ".text",
                "author": ".author",
                "tags": [".tag"],
            },
        }
    ],
    "next": {"selector": "li.next a", "attr": "href"},
}

with Browser() as browser, browser.new_session() as page:
    quotes = []
    url = "https://quotes.toscrape.com/js/"
    while url:
        page.goto(url=url)
        data = page.extract(schema=SCHEMA)
        quotes.extend(data["quotes"])
        url = data["next"]

    print(f"{len(quotes)} quotes")
    tags = Counter(tag for q in quotes for tag in q["tags"])
    print("Top tags:", tags.most_common(5))

运行:

python scrape.py

对照结果(与官方文档给出的结果一致):

100 quotes
Top tags: [('love', 14), ('inspirational', 13), ('life', 13),
('humor', 12), ('books', 11)]

(第二行是排版折行。)

测试机上 10 页约 6 秒跑完。extract 用的选择器表(schema)规则如下:

  • 字符串 ".text":取第一个匹配元素的文字
  • {"selector": ..., "attr": "href"}:取第一个匹配元素的属性,链接会补全成完整网址
  • [".tag"]:取所有匹配元素的文字,得到列表
  • [{"selector": ".quote", "fields": {...}}]:每个匹配元素生成一条记录,fields 里的选择器在这个元素内部查找

最后一页没有 Next 链接,data["next"] 是 None,循环自然结束。

第 5 步:了解其他连接方式(未实测)

本课没有实跑下面两种方式,按官方文档列出,需要时再查:

Lightpanda 没有图形渲染,官方文档说明它的截图只是按文字排版画出来的,没有图片、字体和 CSS 颜色,需要像素级截图的任务仍要用 Chrome。官方云服务支持用 browser=chrome 参数换成 Chrome 实例。

小结

  • lightpanda serve 默认在 127.0.0.1:9222 开 CDP 服务,/json/version 可用来检查服务是否正常
  • Puppeteer 用 puppeteer.connect({ browserWSEndpoint }),Playwright 用 chromium.connectOverCDP,装 -core 版本就够了
  • 脚本结束时 Puppeteer 用 disconnect(),浏览器进程交给 serve 管理
  • /metrics 能看到连接数和命令数,--disable-metrics 可以关闭
  • 官方 Python 包自带浏览器,page.extract 加选择器表就能抓结构化数据

下一课把 Lightpanda 作为 MCP 服务器交给 AI 应用,让大模型自己操作浏览器。