Lightpanda 学习教程

Lightpanda 学习教程

一、环境准备

1.1 前置知识

学习 Lightpanda 前,建议掌握以下知识:

知识领域 要求程度 说明
命令行操作 基础 熟悉终端命令
JavaScript 基础 理解 DOM 和 JS 执行
CDP 概念 基础 了解 Chrome DevTools Protocol
Puppeteer/Playwright 基础 有使用经验更佳

1.2 系统要求

# 支持的平台
- Linux x86_64
- macOS aarch64 (Apple Silicon)
- Windows + WSL2

# 硬件要求
- 最低内存: 256MB
- 推荐内存: 1GB+
- 磁盘空间: ~50MB

1.3 安装方式对比

方式 优点 缺点 推荐场景
二进制下载 最简单 需手动更新 快速体验
Docker 隔离环境 需 Docker 生产部署
源码构建 最新功能 需 Zig 环境 开发贡献

二、快速开始

2.1 安装

方式 1: 下载预构建二进制

Linux x86_64:

curl -L -o lightpanda https://github.com/lightpanda-io/browser/releases/download/nightly/lightpanda-x86_64-linux && \
chmod a+x ./lightpanda

macOS aarch64 (Apple Silicon):

curl -L -o lightpanda https://github.com/lightpanda-io/browser/releases/download/nightly/lightpanda-aarch64-macos && \
chmod a+x ./lightpanda

Windows (WSL2):

# 在 WSL 终端中执行 Linux 命令
curl -L -o lightpanda https://github.com/lightpanda-io/browser/releases/download/nightly/lightpanda-x86_64-linux && \
chmod a+x ./lightpanda

方式 2: Docker 安装

# 拉取并运行 (暴露 CDP 端口 9222)
docker run -d --name lightpanda -p 9222:9222 lightpanda/browser:nightly

# 验证运行
docker ps | grep lightpanda

# 查看日志
docker logs lightpanda

方式 3: 从源码构建

# 1. 安装 Zig (0.13+)
# macOS
brew install zig

# Linux
snap install zig --beta --classic

# 2. 克隆仓库
git clone https://github.com/lightpanda-io/browser.git
cd browser

# 3. 构建
zig build

# 构建产物位于 zig-out/bin/

2.2 验证安装

# 检查版本
./lightpanda --version

# 查看帮助
./lightpanda --help

2.3 Hello World: 第一个示例

# 抓取网页并输出内容
./lightpanda fetch --obey_robots --log_format pretty --log_level info https://demo-browser.lightpanda.io/campfire-commerce/

预期输出:

INFO  telemetry : telemetry status . . . . . . . . . . . . .  [+0ms]
      disabled = false

INFO  page : navigate . . . . . . . . . . . . . . . . . . . . [+6ms]
      url = https://demo-browser.lightpanda.io/campfire-commerce/
      method = GET
      reason = address_bar

INFO  browser : executing script . . . . . . . . . . . . . .  [+118ms]
      src = https://demo-browser.lightpanda.io/campfire-commerce/script.js

三、核心概念

3.1 概念图谱

┌─────────────────────────────────────────────────────────────────┐
│                    Lightpanda 核心概念                           │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │                    无头浏览器 (Headless)                  │   │
│  │   无 GUI、无渲染、专为自动化设计                          │   │
│  └─────────────────────────────────────────────────────────┘   │
│                              │                                  │
│              ┌───────────────┼───────────────┐                  │
│              ▼               ▼               ▼                  │
│  ┌───────────────┐ ┌───────────────┐ ┌───────────────┐          │
│  │  Zig Runtime  │ │   V8 Engine   │ │     CDP       │          │
│  │   浏览器核心  │ │  JS 执行引擎  │ │  协议支持     │          │
│  └───────────────┘ └───────────────┘ └───────────────┘          │
│              │               │               │                  │
│              └───────────────┼───────────────┘                  │
│                              ▼                                  │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │                    DOM 实现                              │   │
│  │   Zig 原生 DOM 树 + 事件系统 + Custom Elements           │   │
│  └─────────────────────────────────────────────────────────┘   │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

3.2 核心概念详解

无头浏览器 (Headless Browser)

定义: 没有图形用户界面的浏览器,通过程序控制执行操作

传统浏览器:
┌─────────────────────────────────────┐
│  GUI (按钮、地址栏、标签页)          │
│  ┌─────────────────────────────┐    │
│  │  渲染引擎 (CSS/布局/绘制)    │    │
│  │  ┌─────────────────────┐    │    │
│  │  │  DOM + JavaScript   │    │    │
│  │  └─────────────────────┘    │    │
│  └─────────────────────────────┘    │
└─────────────────────────────────────┘

Lightpanda:
┌─────────────────────────────────────┐
│  ┌─────────────────────────────┐    │
│  │  DOM + JavaScript (V8)      │    │
│  │  (无渲染层)                  │    │
│  └─────────────────────────────┘    │
└─────────────────────────────────────┘

Lightpanda 设计取舍:

组件 状态 原因
CSS 引擎 ❌ 无 自动化不需要样式计算
布局引擎 ❌ 无 不需要视觉布局
图像解码 ❌ 无 不渲染图片
GPU 合成 ❌ 无 无视觉输出
JavaScript ✅ 有 自动化核心需求
DOM ✅ 有 页面操作必需
网络 ✅ 有 加载页面必需

CDP (Chrome DevTools Protocol)

定义: Chrome 开发工具协议,用于程序化控制浏览器

┌─────────────────────────────────────────────────────────────┐
│                    CDP 协议结构                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  客户端 (Puppeteer/Playwright)                              │
│         │                                                   │
│         │ WebSocket                                         │
│         ▼                                                   │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                 CDP 消息                             │   │
│  │  {                                                  │   │
│  │    "id": 1,                                        │   │
│  │    "method": "Page.navigate",                      │   │
│  │    "params": {"url": "https://example.com"}        │   │
│  │  }                                                  │   │
│  └─────────────────────────────────────────────────────┘   │
│         │                                                   │
│         ▼                                                   │
│  Lightpanda Browser                                        │
│                                                             │
└─────────────────────────────────────────────────────────────┘

CDP 主要域 (Domains):

功能 示例方法
Page 页面操作 navigate, reload
Runtime JS 执行 evaluate, callFunctionOn
DOM DOM 操作 getDocument, querySelector
Network 网络监控 requestWillBeSent, responseReceived
Emulation 设备模拟 setDeviceMetricsOverride

Zig 语言特性

为什么选择 Zig:

// Zig 示例: 简洁的内存管理
const std = @import("std");

pub fn main() void {
    // 编译时已知大小
    var buffer: [100]u8 = undefined;

    // 显式内存管理
    const allocator = std.heap.page_allocator;

    // 无隐藏控制流
    const result = doSomething() catch |err| {
        handle(err);
        return;
    };
}

Zig 优势:

特性 说明
无隐藏控制流 所有控制流显式可见
Comptime 编译时执行代码
手动内存管理 精确控制内存使用
与 C 互操作 无缝调用 C 代码
零成本抽象 高级特性无运行时开销

3.3 术语表

术语 英文 定义
Headless Headless 无图形界面的浏览器模式
CDP Chrome DevTools Protocol Chrome 开发工具协议
DOM Document Object Model 文档对象模型
V8 V8 JavaScript Engine Google 的 JS 引擎
Zig Zig 系统级编程语言
Comptime Compile-time 编译时执行

四、CLI 命令详解

4.1 fetch 命令

抓取网页内容:

# 基本用法
./lightpanda fetch https://example.com

# 遵守 robots.txt
./lightpanda fetch --obey_robots https://example.com

# 详细日志
./lightpanda fetch --log_format pretty --log_level debug https://example.com

# 输出到文件
./lightpanda fetch https://example.com > output.html

参数说明:

参数 说明 默认值
--obey_robots 遵守 robots.txt false
--log_format 日志格式 (pretty/json) json
--log_level 日志级别 (debug/info/warn/error) info
--timeout 超时时间 (秒) 30

4.2 serve 命令

启动 CDP 服务器:

# 启动服务器 (默认端口 9222)
./lightpanda serve

# 指定端口
./lightpanda serve --port 8080

# 绑定地址
./lightpanda serve --host 0.0.0.0 --port 9222

# 详细日志
./lightpanda serve --log_level debug

4.3 其他命令

# 查看版本
./lightpanda --version

# 查看帮助
./lightpanda --help
./lightpanda fetch --help
./lightpanda serve --help

五、与 Puppeteer 集成

5.1 基本连接

const puppeteer = require('puppeteer-core');

async function main() {
    // 连接到 Lightpanda
    const browser = await puppeteer.connect({
        browserURL: 'http://localhost:9222'
    });

    // 创建页面
    const page = await browser.newPage();

    // 导航
    await page.goto('https://example.com');

    // 获取标题
    const title = await page.title();
    console.log('Title:', title);

    // 执行 JavaScript
    const result = await page.evaluate(() => {
        return document.body.innerText;
    });
    console.log('Content:', result.substring(0, 200));

    // 关闭
    await browser.close();
}

main().catch(console.error);

5.2 批量抓取示例

const puppeteer = require('puppeteer-core');

const urls = [
    'https://example.com/page1',
    'https://example.com/page2',
    'https://example.com/page3',
    // ... 更多 URL
];

async function scrape(url, browser) {
    const page = await browser.newPage();
    try {
        await page.goto(url, { waitUntil: 'networkidle0' });

        const data = await page.evaluate(() => ({
            title: document.title,
            content: document.body.innerText,
            links: Array.from(document.querySelectorAll('a')).map(a => a.href)
        }));

        return { url, success: true, data };
    } catch (error) {
        return { url, success: false, error: error.message };
    } finally {
        await page.close();
    }
}

async function main() {
    const browser = await puppeteer.connect({
        browserURL: 'http://localhost:9222'
    });

    // 并行抓取 (Lightpanda 支持高并发)
    const results = await Promise.all(
        urls.map(url => scrape(url, browser))
    );

    console.log(JSON.stringify(results, null, 2));
    await browser.close();
}

main().catch(console.error);

5.3 与传统 Puppeteer 对比

传统 Puppeteer (Chrome):

const puppeteer = require('puppeteer');

async function main() {
    // 启动完整 Chrome
    const browser = await puppeteer.launch({
        headless: 'new'
    });
    // 内存占用: ~200MB/页面
    // ...
}

使用 Lightpanda:

const puppeteer = require('puppeteer-core');

async function main() {
    // 连接到 Lightpanda
    const browser = await puppeteer.connect({
        browserURL: 'http://localhost:9222'
    });
    // 内存占用: ~24MB/100页面
    // ...
}

六、与 Playwright 集成

6.1 基本连接

const { chromium } = require('playwright');

async function main() {
    // 连接到 Lightpanda
    const browser = await chromium.connectOverCDP('http://localhost:9222');

    const page = await browser.newPage();
    await page.goto('https://example.com');

    // 截图功能不可用 (Lightpanda 无渲染引擎)
    // const screenshot = await page.screenshot(); // ❌ 会失败

    // 但可以获取 DOM 内容
    const content = await page.content();
    console.log(content);

    await browser.close();
}

main().catch(console.error);

6.2 API 测试示例

const { chromium } = require('playwright');

async function testAPI() {
    const browser = await chromium.connectOverCDP('http://localhost:9222');
    const page = await browser.newPage();

    // 监听网络请求
    page.on('response', async response => {
        if (response.url().includes('/api/')) {
            console.log('API Response:', response.url());
            try {
                const data = await response.json();
                console.log('Data:', data);
            } catch (e) {
                // 非 JSON 响应
            }
        }
    });

    // 导航到页面
    await page.goto('https://example.com');

    // 等待 API 请求完成
    await page.waitForTimeout(2000);

    await browser.close();
}

testAPI().catch(console.error);

七、Go 客户端 (chromedp)

7.1 安装

go get -u github.com/chromedp/chromedp

7.2 基本使用

package main

import (
    "context"
    "fmt"
    "log"
    "time"

    "github.com/chromedp/chromedp"
)

func main() {
    // 创建上下文,连接到 Lightpanda
    allocCtx, cancel := chromedp.NewRemoteAllocator(context.Background(), "ws://localhost:9222")
    defer cancel()

    ctx, cancel := chromedp.NewContext(allocCtx)
    defer cancel()

    // 设置超时
    ctx, cancel = context.WithTimeout(ctx, 30*time.Second)
    defer cancel()

    var result string

    // 执行任务
    err := chromedp.Run(ctx,
        chromedp.Navigate("https://example.com"),
        chromedp.WaitVisible("body"),
        chromedp.Text("body", &result),
    )

    if err != nil {
        log.Fatal(err)
    }

    fmt.Println("Content:", result[:200])
}

7.3 并发抓取

package main

import (
    "context"
    "fmt"
    "log"
    "sync"
    "time"

    "github.com/chromedp/chromedp"
)

func scrape(url string, wg *sync.WaitGroup) {
    defer wg.Done()

    allocCtx, cancel := chromedp.NewRemoteAllocator(context.Background(), "ws://localhost:9222")
    defer cancel()

    ctx, cancel := chromedp.NewContext(allocCtx)
    defer cancel()

    ctx, cancel = context.WithTimeout(ctx, 30*time.Second)
    defer cancel()

    var title string
    var content string

    err := chromedp.Run(ctx,
        chromedp.Navigate(url),
        chromedp.Title(&title),
        chromedp.Text("body", &content),
    )

    if err != nil {
        log.Printf("Error scraping %s: %v", url, err)
        return
    }

    fmt.Printf("URL: %s\nTitle: %s\nContent: %s...\n\n",
        url, title, content[:min(100, len(content))])
}

func main() {
    urls := []string{
        "https://example.com/page1",
        "https://example.com/page2",
        "https://example.com/page3",
    }

    var wg sync.WaitGroup

    for _, url := range urls {
        wg.Add(1)
        go scrape(url, &wg)
    }

    wg.Wait()
}

八、实战案例

8.1 案例 1: 大规模网页抓取

// scrape-large-scale.js
const puppeteer = require('puppeteer-core');
const fs = require('fs');

const CONCURRENCY = 50; // Lightpanda 支持高并发
const BATCH_SIZE = 100;

async function scrapeBatch(urls, browser) {
    const results = [];

    for (let i = 0; i < urls.length; i += CONCURRENCY) {
        const batch = urls.slice(i, i + CONCURRENCY);
        const batchResults = await Promise.all(
            batch.map(url => scrapeUrl(url, browser))
        );
        results.push(...batchResults);
        console.log(`Progress: ${results.length}/${urls.length}`);
    }

    return results;
}

async function scrapeUrl(url, browser) {
    const page = await browser.newPage();
    try {
        await page.goto(url, { timeout: 10000, waitUntil: 'domcontentloaded' });

        const data = await page.evaluate(() => ({
            url: window.location.href,
            title: document.title,
            meta: {
                description: document.querySelector('meta[name="description"]')?.content,
                keywords: document.querySelector('meta[name="keywords"]')?.content,
            },
            headings: Array.from(document.querySelectorAll('h1, h2')).map(h => h.textContent),
            links: Array.from(document.querySelectorAll('a[href]')).map(a => ({
                text: a.textContent,
                href: a.href
            })),
            content: document.body.innerText.substring(0, 5000)
        }));

        return { url, success: true, data };
    } catch (error) {
        return { url, success: false, error: error.message };
    } finally {
        await page.close();
    }
}

async function main() {
    // 读取 URL 列表
    const urls = fs.readFileSync('urls.txt', 'utf-8')
        .split('\n')
        .filter(url => url.trim());

    console.log(`Total URLs: ${urls.length}`);

    const browser = await puppeteer.connect({
        browserURL: 'http://localhost:9222'
    });

    const results = await scrapeBatch(urls, browser);

    // 保存结果
    fs.writeFileSync('results.json', JSON.stringify(results, null, 2));

    console.log(`Completed: ${results.filter(r => r.success).length}/${results.length}`);

    await browser.close();
}

main().catch(console.error);

8.2 案例 2: AI Agent 网页操作

# ai_agent_browser.py
import asyncio
from playwright.async_api import async_playwright

class AIWebAgent:
    """基于 Lightpanda 的 AI Web Agent"""

    def __init__(self, cdp_url="http://localhost:9222"):
        self.cdp_url = cdp_url
        self.browser = None
        self.page = None

    async def connect(self):
        """连接到 Lightpanda"""
        self.playwright = await async_playwright().start()
        self.browser = await self.playwright.chromium.connect_over_cdp(self.cdp_url)
        self.page = await self.browser.new_page()

    async def close(self):
        """关闭连接"""
        if self.browser:
            await self.browser.close()
        if self.playwright:
            await self.playwright.stop()

    async def navigate(self, url):
        """导航到 URL"""
        await self.page.goto(url)
        return await self.page.title()

    async def extract_text(self, selector="body"):
        """提取文本内容"""
        return await self.page.locator(selector).inner_text()

    async def click_and_wait(self, selector, wait_for=None):
        """点击并等待"""
        await self.page.click(selector)
        if wait_for:
            await self.page.wait_for_selector(wait_for)

    async def extract_links(self):
        """提取所有链接"""
        links = await self.page.evaluate("""
            () => Array.from(document.querySelectorAll('a[href]'))
                .map(a => ({ text: a.textContent, href: a.href }))
        """)
        return links

    async def extract_structured_data(self, schema):
        """根据 schema 提取结构化数据"""
        return await self.page.evaluate(f"""
            (schema) => {{
                const result = {{}};
                for (const [key, selector] of Object.entries(schema)) {{
                    const el = document.querySelector(selector);
                    result[key] = el ? el.textContent : null;
                }}
                return result;
            }}
        """, schema)

async def main():
    agent = AIWebAgent()
    await agent.connect()

    try:
        # 导航
        title = await agent.navigate("https://example.com")
        print(f"Page: {title}")

        # 提取文本
        text = await agent.extract_text()
        print(f"Content preview: {text[:200]}...")

        # 提取链接
        links = await agent.extract_links()
        print(f"Found {len(links)} links")

        # 结构化提取
        schema = {
            "title": "h1",
            "description": "meta[name=description]",
            "main_content": "main"
        }
        data = await agent.extract_structured_data(schema)
        print("Structured data:", data)

    finally:
        await agent.close()

asyncio.run(main())

8.3 案例 3: 自动化测试

// automated-testing.js
const { chromium } = require('playwright');

async function runTests() {
    const browser = await chromium.connectOverCDP('http://localhost:9222');

    const tests = [
        testHomePage,
        testLoginPage,
        testAPIEndpoints,
    ];

    const results = [];

    for (const test of tests) {
        const context = await browser.newContext();
        const page = await context.newPage();

        try {
            await test(page);
            results.push({ name: test.name, status: 'PASS' });
        } catch (error) {
            results.push({ name: test.name, status: 'FAIL', error: error.message });
        } finally {
            await context.close();
        }
    }

    await browser.close();

    console.log('\n=== Test Results ===');
    results.forEach(r => {
        const icon = r.status === 'PASS' ? '✅' : '❌';
        console.log(`${icon} ${r.name}: ${r.status}`);
        if (r.error) console.log(`   Error: ${r.error}`);
    });
}

async function testHomePage(page) {
    await page.goto('https://example.com');

    // 验证标题存在
    const title = await page.title();
    if (!title) throw new Error('Title is empty');

    // 验证主要内容
    const body = await page.locator('body').innerHTML();
    if (body.length < 100) throw new Error('Content too short');

    // 验证没有 JS 错误
    page.on('pageerror', error => {
        throw new Error(`JS Error: ${error.message}`);
    });
}

async function testLoginPage(page) {
    await page.goto('https://example.com/login');

    // 填写表单
    await page.fill('input[name="email"]', 'test@example.com');
    await page.fill('input[name="password"]', 'password123');

    // 提交
    await page.click('button[type="submit"]');

    // 等待响应
    await page.waitForURL('**/dashboard', { timeout: 5000 });
}

async function testAPIEndpoints(page) {
    // 监听 API 响应
    const apiResponses = [];

    page.on('response', async response => {
        if (response.url().includes('/api/')) {
            apiResponses.push({
                url: response.url(),
                status: response.status()
            });
        }
    });

    await page.goto('https://example.com');
    await page.waitForTimeout(2000);

    // 验证 API 调用
    if (apiResponses.length === 0) {
        throw new Error('No API calls detected');
    }

    // 验证状态码
    const errors = apiResponses.filter(r => r.status >= 400);
    if (errors.length > 0) {
        throw new Error(`API errors: ${JSON.stringify(errors)}`);
    }
}

runTests().catch(console.error);

九、常见问题

Q1: 为什么截图失败?

A: Lightpanda 没有渲染引擎,不支持截图:

// ❌ 这会失败
const screenshot = await page.screenshot();

// ✅ 使用 DOM 内容代替
const html = await page.content();

Q2: 如何调试?

A: 使用详细日志:

# 启动时启用调试日志
./lightpanda serve --log_level debug --log_format pretty

Q3: 内存不够用怎么办?

A: Lightpanda 已经非常节省内存,但仍可优化:

// 减少并行数
const CONCURRENCY = 20; // 降低并发

// 及时关闭页面
await page.close();

// 复用浏览器实例
// 不要频繁创建新的浏览器连接

Q4: Web API 不支持怎么办?

A: 检查支持状态,使用替代方案:

// 检查 API 是否可用
const supported = await page.evaluate(() => {
    return typeof window.fetch === 'function';
});

// 使用 CDP 方法代替
if (!supported) {
    // 通过 CDP 直接执行网络请求
}

Q5: 与 Chrome 行为不一致?

A: Lightpanda 是独立实现,可能有差异:

// 添加兼容性检查
const isLightpanda = await page.evaluate(() => {
    return navigator.userAgent.includes('Lightpanda');
});

if (isLightpanda) {
    // 使用 Lightpanda 特定的处理逻辑
}

十、参考资料

10.1 官方资源

10.2 相关工具

10.3 学习路径

┌─────────────────────────────────────────────────────────────────┐
│                    Lightpanda 学习路径                           │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  Day 1: 入门                                                   │
│  ├── 安装 Lightpanda                                          │
│  ├── 运行第一个 fetch 命令                                     │
│  └── 理解无头浏览器概念                                        │
│                                                                 │
│  Day 2-3: 集成                                                 │
│  ├── 连接 Puppeteer                                           │
│  ├── 连接 Playwright                                          │
│  └── 编写第一个自动化脚本                                      │
│                                                                 │
│  Day 4-5: 实践                                                 │
│  ├── 批量网页抓取                                              │
│  ├── API 测试                                                  │
│  └── 性能优化                                                  │
│                                                                 │
│  Day 6-7: 进阶                                                 │
│  ├── 高并发场景                                                │
│  ├── 错误处理                                                  │
│  └── 生产部署                                                  │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

十一、总结

通过本教程,你学会了:

  1. 核心概念: 无头浏览器、CDP 协议、Zig 语言特性
  2. 安装方式: 二进制下载、Docker、源码构建
  3. CLI 使用: fetch、serve 命令
  4. 工具集成: Puppeteer、Playwright、chromedp
  5. 实战案例: 网页抓取、AI Agent、自动化测试
  6. 问题排查: 常见问题和解决方案

Lightpanda 核心价值: - 🚀 11x 更快的执行速度 - 💾 9x 更低的内存占用 - 🔌 即时启动 - 🔗 CDP 协议兼容

下一步行动: - [ ] 安装并运行 Lightpanda - [ ] 尝试连接 Puppeteer/Playwright - [ ] 迁移现有自动化脚本 - [ ] 体验性能提升

Happy Automating! 🚀