sametcn99/catchapage
GitHub: sametcn99/catchapage
基于 Bun 和 Playwright 构建的自动化网页捕获工具包,支持多设备模式下批量渲染页面并保存 HTML 快照与全页截图。
Stars: 0 | Forks: 0
# catchapage
catchapage 是一个自动化网页捕获工具包,它可以抓取一个精心整理的 URL 列表,在多种设备配置文件下渲染每个页面,并为每种变体保存渲染后的 HTML 和全页截图。它基于 Bun 和 Playwright 构建,提供快速、可脚本化的捕获功能,非常适合用于视觉审查、归档工作流和回归跟踪。
## 亮点
- 多种设备模式(桌面设备、平板设备、移动设备),支持自定义视口、区域设置和用户代理(user agents)。
- 具有弹性的导航机制,通过回退策略进行重试,并在捕获前等待有意义的内容加载完成。
- 每个 URL 提供完整的产物包:HTML 快照、PNG 截图以及同步的控制台日志。
- 采用带有时间戳名称的隔离运行文件夹,便于比较不同的捕获会话。
- 单一配置模块(`src/config.ts`),可调整超时时间、DNS 规则和设备描述符。
- 可扩展的面向对象设计:`PageCaptureRunner`、`DeviceContextFactory` 和 `FileLogger` 为自定义行为提供了清晰的扩展点。
- 支持并行执行。
## 典型用例
- 为需要按需获取多设备截图的 QA 团队提供视觉回归基线。
- 竞品研究或内容归档,在保留图像的同时保留 HTML 副本。
- 监控营销或落地页,以确保部署的页面随时间推移能一致渲染。
- 受益于可重现、高保真网页捕获的内部文档,从而无需手动进行浏览器操作。
## 项目结构
- `index.ts`:入口点,负责连接日志记录、从 `links.txt` 加载 URL 并编排整个捕获运行过程。
- `src/pageCaptureRunner.ts`:核心运行器,负责准备运行目录、驱动 Playwright 并捕获每个设备变体。
- `src/deviceContextFactory.ts`:为桌面设备、平板设备和移动设备模式构建浏览器上下文选项,并在缺少描述符时进行回退处理。
- `src/loadUrlsFromFile.ts`:对从 `links.txt` 获取的 URL 进行规范化和验证。
- `src/fileLogger.ts`:在运行文件夹准备就绪后,将控制台输出流式传输到 `output//console.log`。
- `src/captureOutcome.ts`:表示捕获成功或失败的轻量级值对象。
- `src/joinPath.ts`:规范化路径片段,以确保一致的文件系统输出。
- `links.txt`:输入的 URL 列表(每行一个),即将被捕获的目标。
- `output/`:按带有时间戳的运行文件夹分组的生成产物。
## 架构概述
- **编排器 (Orchestrator)** — `index.ts` 负责引导会话、配置日志钩子,并将任务委托给运行器。
- **运行器 (Runner)** — `PageCaptureRunner` 协调文件夹准备、导航重试以及针对特定设备的捕获。
- **设备提供者 (Device providers)** — `DeviceContextFactory` 提供浏览器上下文选项,并妥善处理描述符回退。
- **I/O 工具类** — `loadUrlsFromFile`、`joinPath` 和 `FileLogger` 隔离了文件系统、路径和日志记录的职责。
- **结果通道** — `CaptureOutcome` 封装了用于后续处理的成功或失败元数据。
该设计遵循 OOP 原则:每个类都具有单一职责,通过显式接口进行协作,并且横切关注点(如日志记录、观察者)是通过依赖注入而非硬编码实现的。
## 前置条件
- [Bun](https://bun.sh/) 1.1 或更高版本。
- Playwright 浏览器二进制文件。使用 `bunx playwright install` 进行一次性安装。
## 安装
```
bun install
```
## 用法
1. **准备 URL**
编辑 `links.txt`,每行放置一个 URL。仅提供域名也是可以接受的;它们会被自动规范化为 `https://`。
2. **运行捕获**
bun start
或直接执行入口点:
bun run index.ts
3. **检查结果**
- 产物将被写入 `output//`。
- 每个 URL 会生成一个带有 slug 的子文件夹,包含:
- `page.desktop.html`, `page.tablet.html`, `page.mobile.html`
- `page.desktop.png`, `page.tablet.png`, `page.mobile.png`
- `console.log` 存储了本次运行期间合并的 stdout/stderr 输出。
运行文件夹在每次执行时创建一次,并使用 `en-GB` 日期格式(`DD-MM-YYYY-HH-MM-SS`)进行标记。
### 输出布局
生成的运行目录类似于以下结构:
```
output/
└── 21-02-2025-09-30-12/
├── console.log
├── example-com/
│ ├── page.desktop.html
│ ├── page.desktop.png
│ ├── page.tablet.html
│ ├── page.tablet.png
│ ├── page.mobile.html
│ └── page.mobile.png
└── sametcc-me/
├── page.desktop.html
├── page.desktop.png
├── page.tablet.html
├── page.tablet.png
├── page.mobile.html
└── page.mobile.png
```
`console.log` 包含运行期间发出的每一条控制台消息(包括 Bun 端的警告和错误),并格式化带有时间戳和严重性标签。
## 配置说明
`src/config.ts` 集中了所有可配置项。它构建了一个 `zod` schema,从 `Bun.env` 读取数据,验证类型,并在首次导入时实例化一个冻结的 `RuntimeConfiguration` 实例。每个值都有默认值,因此应用程序无需 `.env` 文件即可启动;同时,如果输入格式不正确,会通过明确的错误信息快速失败。
### 设置环境变量
1. 将 `.env.example` 复制为 `.env`。
2. 覆盖您需要的任何变量。Bun 会自动为 `bun start`、`bun run` 和 `bunx` 脚本加载 `.env`,因此不需要额外的标志。
3. 重新运行 `bun start`。如果某个值无法解析,配置加载器会解释哪个变量无效以及原因。
或者,您也可以在 shell 或 CI 任务中导出变量——`RuntimeConfiguration` 始终从 `Bun.env` 读取。
### 验证规则
- 数值字段必须包含有效的数字;否则 `zod` 会抛出原始字符串错误。
- 布尔字段只接受 `"true"` 或 `"false"`。
- 逗号分隔的列表(例如 `CHROMIUM_HOST_RESOLVER_RULES`)会被拆分、修剪并过滤掉空项。
- `*_COLOR_SCHEME` 接受 `dark`、`light`、`no-preference`、`null` 或 `undefined`。
- 设备描述符名称会被修剪。当缺少描述符时,`DeviceContextFactory` 会记录一次性警告,并回退到手动设置的视口和屏幕尺寸。
### 常见调整
- **设备配置文件**:替换 Playwright 描述符名称(例如 `DESKTOP_DEVICE_DESCRIPTOR`)或自定义视口、缩放比例和用户代理。
- **区域设置和时区**:调整 locale/timezone 常量以模拟特定区域的行为。
- **超时**:修改导航、空闲等待和内容就绪的阈值,以适应较慢的网站。
- **网络**:在隔离环境中运行捕获时,提供主机解析规则或自定义 DNS 服务器。
### 特定环境的调整
- **气隙环境**:配置 `CHROMIUM_DNS_SERVERS` 或 `CHROMIUM_HOST_RESOLVER_RULES`,以便 Chromium 通过内部基础设施解析域名。
- **高延迟站点**:增加 `PRIMARY_NAVIGATION_TIMEOUT_MS`、`FALLBACK_NAVIGATION_TIMEOUT_MS` 或 `CONTENT_READY_TIMEOUT_MS`。
- **最小化等待**:当页面轻量且具有确定性时,减少 `CAPTURE_STABILIZATION_DELAY_MS`。
- **亮色模式捕获**:将 `*_COLOR_SCHEME` 切换为 `"light"` 以模拟替代主题。
### 配置参考
下表显示了环境变量如何映射到导出的配置,以及未提供覆盖值时应用的默认值。有关完整的字符串(例如,user-agent 标头),请参阅 `.env.example`。
#### 核心路径与开关
| 环境变量 | 配置属性 | 默认值 | 备注 |
|-----------------------------|--------------------------------------|-------------|-----------------------------------------|
| `DEFAULT_OUTPUT_DIR` | `config.DEFAULT_OUTPUT_DIR` | `output` | 用于带时间戳运行记录的根目录。 |
| `LINKS_FILE` | `config.LINKS_FILE` | `links.txt` | 包含要捕获的 URL 的文件。 |
| `PARALLEL_CAPTURE_ENABLED` | `config.PARALLEL_CAPTURE_ENABLED` | `true` | 启用多页面并发。 |
#### 导航与时间
| 环境变量 | 配置属性 | 默认值 | 备注 |
|-------------------------------------|------------------------------------------------|---------|------------------------------------------------------------|
| `PRIMARY_NAVIGATION_TIMEOUT_MS` | `config.PRIMARY_NAVIGATION_TIMEOUT_MS` | `45000` | 等待 `networkidle` 的超时时间。 |
| `FALLBACK_NAVIGATION_TIMEOUT_MS` | `config.FALLBACK_NAVIGATION_TIMEOUT_MS` | `60000` | `domcontentloaded` 回退策略的超时时间。 |
| `POST_NAVIGATION_IDLE_MS` | `config.POST_NAVIGATION_IDLE_MS` | `1000` | 运行稳定性检查前的延迟。 |
| `CAPTURE_STABILIZATION_DELAY_MS` | `config.CAPTURE_STABILIZATION_DELAY_MS` | `2000` | 截图前的额外等待时间。 |
| `CONTENT_READY_TIMEOUT_MS` | `config.CONTENT_READY_TIMEOUT_MS` | `10000` | 等待有效 DOM 内容的最长时间。 |
#### 桌面设备配置
| 环境变量 | 配置属性 | 默认值 | 备注 |
|---------------------------------|--------------------------------------------------|--------------------------|---------------------------------------------|
| `DESKTOP_DEVICE_DESCRIPTOR` | `config.DESKTOP_DEVICE_DESCRIPTOR` | `Desktop Chrome` | Playwright 预设名称。 |
| `DESKTOP_VIEWPORT_WIDTH` | `config.DESKTOP_VIEWPORT.width` | `1280` | 像素。 |
| `DESKTOP_VIEWPORT_HEIGHT` | `config.DESKTOP_VIEWPORT.height` | `720` | 像素。 |
| `DESKTOP_SCREEN_WIDTH` | `config.DESKTOP_SCREEN.width` | `1920` | 报告的 `window.screen.width`。 |
| `DESKTOP_SCREEN_HEIGHT` | `config.DESKTOP_SCREEN.height` | `1080` | 报告的 `window.screen.height`。 |
| `DESKTOP_DEVICE_SCALE_FACTOR` | `config.DESKTOP_DEVICE_SCALE_FACTOR` | `1.5` | 每个设备独立像素的像素数。 |
| `DESKTOP_LOCALE` | `config.DESKTOP_LOCALE` | `en-US` | 通过上下文选项传递的区域设置。 |
| `DESKTOP_TIMEZONE_ID` | `config.DESKTOP_TIMEZONE_ID` | `Europe/Istanbul` | IANA 时区标识符。 |
| `DESKTOP_COLOR_SCHEME` | `config.DESKTOP_COLOR_SCHEME` | `dark` | 针对 `prefers-color-scheme` 的媒体模拟。 |
| `DESKTOP_USER_AGENT` | `config.DESKTOP_USER_AGENT` | Windows 10 上的 Chrome UA | `.env.example` 中的完整字符串。 |
#### 移动设备配置
| 环境变量 | 配置属性 | 默认值 | 备注 |
|---------------------------------|--------------------------------------------------|--------------------------|---------------------------------------------|
| `MOBILE_DEVICE_DESCRIPTOR` | `config.MOBILE_DEVICE_DESCRIPTOR` | `Pixel 5` | Playwright 预设名称。 |
| `MOBILE_VIEWPORT_WIDTH` | `config.MOBILE_VIEWPORT.width` | `390` | 像素。 |
| `MOBILE_VIEWPORT_HEIGHT` | `config.MOBILE_VIEWPORT.height` | `844` | 像素。 |
| `MOBILE_SCREEN_WIDTH` | `config.MOBILE_SCREEN.width` | `1080` | 报告的 `window.screen.width`。 |
| `MOBILE_SCREEN_HEIGHT` | `config.MOBILE_SCREEN.height` | `2340` | 报告的 `window.screen.height`。 |
| `MOBILE_DEVICE_SCALE_FACTOR` | `config.MOBILE_DEVICE_SCALE_FACTOR` | `3` | 每个设备独立像素的像素数。 |
| `MOBILE_LOCALE` | `config.MOBILE_LOCALE` | `en-US` | 通过上下文选项传递的区域设置。 |
| `MOBILE_TIMEZONE_ID` | `config.MOBILE_TIMEZONE_ID` | `America/Los_Angeles` | IANA 时区标识符。 |
| `MOBILE_COLOR_SCHEME` | `config.MOBILE_COLOR_SCHEME` | `dark` | 针对 `prefers-color-scheme` 的媒体模拟。 |
| `DEFAULT_MOBILE_USER_AGENT` | `config.DEFAULT_MOBILE_AGENT` | Android 上的 Chrome UA | `.env.example` 中的完整字符串。 |
#### 平板设备配置
| 环境变量 | 配置属性 | 默认值 | 备注 |
|---------------------------------|--------------------------------------------------|--------------------------|---------------------------------------------|
| `TABLET_DEVICE_DESCRIPTOR` | `config.TABLET_DEVICE_DESCRIPTOR` | `iPad (gen 7)` | Playwright 预设名称。 |
| `TABLET_VIEWPORT_WIDTH` | `config.TABLET_VIEWPORT.width` | `1024` | 像素。 |
| `TABLET_VIEWPORT_HEIGHT` | `config.TABLET_VIEWPORT.height` | `1366` | 像素。 |
| `TABLET_SCREEN_WIDTH` | `config.TABLET_SCREEN.width` | `1620` | 报告的 `window.screen.width`。 |
| `TABLET_SCREEN_HEIGHT` | `config.TABLET_SCREEN.height` | `2160` | 报告的 `window.screen.height`。 |
| `TABLET_DEVICE_SCALE_FACTOR` | `config.TABLET_DEVICE_SCALE_FACTOR` | `2` | 每个设备独立像素的像素数。 |
| `TABLET_LOCALE` | `config.TABLET_LOCALE` | `en-US` | 通过上下文选项传递的区域设置。 |
| `TABLET_TIMEZONE_ID` | `config.TABLET_TIMEZONE_ID` | `America/Los_Angeles` | IANA 时区标识符。 |
| `TABLET_COLOR_SCHEME` | `config.TABLET_COLOR_SCHEME` | `dark` | 针对 `prefers-color-scheme` 的媒体模拟。 |
| `DEFAULT_TABLET_USER_AGENT` | `config.DEFAULT_TABLET_USER_AGENT` | iPad 上的 Safari UA | `.env.example` 中的完整字符串。 |
#### Chromium 网络设置
| 环境变量 | 配置属性 | 默认值 | 备注 |
|-----------------------------------|-----------------------------------------|------------------------------|------------------------------------------------------------|
| `CHROMIUM_HOST_RESOLVER_RULES` | `config.CHROMIUM_HOST_RESOLVER_RULES` | *(空列表)* | 逗号分隔的主机解析规则。 |
| `CHROMIUM_USE_CUSTOM_DNS` | `config.CHROMIUM_USE_CUSTOM_DNS` | `false` | 设置为 `true` 时启用自定义 DNS 路由。 |
| `CHROMIUM_DNS_SERVERS` | `config.CHROMIUM_DNS_SERVERS` | `["94.140.14.14","94.140.14.15"]` | 启用时应用的逗号分隔的 DNS 服务器。 |
## 工作原理
1. `index.ts` 加载并验证 URL,设置 `FileLogger`,然后将控制权交给 `PageCaptureRunner`。
2. `PageCaptureRunner` 准备一个运行文件夹,然后针对每个链接:
- 创建一个唯一的目录名称。
- 通过 `captureVariant` 捕获桌面设备、平板设备和移动设备变体。
- 使用 `navigateWithFallback` 重试导航(`networkidle` → `domcontentloaded`),并在保存 HTML 和 PNG 产物之前等待 DOM 稳定。
3. 处理完所有 URL 后,会在控制台打印一份摘要报告;如果有任何捕获失败,进程将以非零状态退出。
### 执行生命周期
- **引导**:控制台方法被包装,以便在运行文件夹可用之前将日志缓冲。
- **准备**:创建带时间戳的输出文件夹,附加观察者,并为每个 URL 配置目录。
- **导航**:主要策略和回退策略尝试可靠地加载内容,同时验证 HTTP 状态码。
- **捕获**:为桌面设备、平板设备和移动设备上下文生成 HTML 内容和全页截图。
- **报告**:`CaptureOutcome` 汇总状态,运行器打印摘要,并在发生失败时设置 `process.exitCode`。
## 开发脚本
- `bun start` — 运行捕获 pipeline。
- `bun lint` — 使用 Biome 对项目进行 Lint。
- `bun format` — 就地格式化源代码。
## 扩展 Pipeline
- **自定义产物**:继承 `PageCaptureRunner` 或包装 `captureVariant` 以添加 PDF 导出或额外的设备配置文件。`withContext` 助手提供了一个隔离的 Playwright 上下文,可用于进一步的操作(如网络追踪、可访问性快照等)。
- **增强日志**:扩展 `FileLogger` 以将日志流式传输到外部系统(例如 S3、HTTP endpoint)或使用运行元数据丰富条目。
- **动态 URL 源**:将 `loadUrlsFromFile` 替换为一个可以直接替换的函数,在调用运行器之前从 API、数据库或 CI 产物中获取 URL。
- **CI 集成**:与 cron 或 CI pipeline 结合使用,以安排捕获并将 `output` 目录作为构建产物归档。
## 日志与可观测性
- **控制台拦截**:`FileLogger` 接管 `console.log`、`console.warn` 和 `console.error`,向 stdout 和磁盘上的 `console.log` 发出相同的行。
- **缓冲写入**:在运行文件夹建立之前,消息会被缓冲,确保在引导阶段不会丢失任何输出。
- **结构化格式**:每个条目都包含时间戳、严重性和序列化的 payload,以便日志处理器可以轻松解析它们。
- **观察者模式**:`RunFolderLoggingObserver` 演示了如何在不修改 `PageCaptureRunner` 的情况下附加其他观察者(如指标、通知)。
## 常见问题
- **我可以只捕获部分设备吗?**
可以。移除 `capturePage` 中不必要的 `captureVariant` 调用,或者使用 feature flags 将它们限制起来。
- **如何注入身份验证标头或 cookie?**
扩展 `captureVariant` 以设置 `page.context().addCookies(...)` 或在调用 `navigateWithFallback` 之前配置额外的 HTTP 标头。
- **在代理后面运行需要什么条件?**
更新 `buildChromiumLaunchOptions` 以追加 `--proxy-server=`,如果需要,配置 `context.setExtraHTTPHeaders` 进行身份验证。
- **并行执行安全吗?**
安全。每次运行都会写入其独立的时间戳目录,并打开隔离的 Playwright 上下文,因此并发进程不会发生冲突。
祝您捕获愉快!
标签:BeEF, Bun, Playwright, QA, 回归测试, 爬虫, 特征检测, 网页截图, 自动化攻击