logo
0
0
WeChat Login

Web Reader

一个给 AI 用的 Web 阅读器,输入网页 URL,返回 Markdown。

项目基于 Node.jsExpressPlaywright 实现:服务接收目标网页 URL,使用浏览器加载页面内容,结合 Readability 提取正文,再转换为 Markdown 输出。项目支持通过 config/sites.json 为特定站点注入 Cookie 和请求头,并通过 config/cleaning.json 配置全局或站点级清洗规则,适合处理需要登录态、自定义访问头或定制正文清洗的网站。

功能特性

  • 通过浏览器渲染页面后抓取内容
  • 使用 Readability 提取正文区域
  • 将正文转换为 Markdown 返回
  • 支持按域名或 域名:端口 匹配站点配置
  • 支持为目标站点注入 Cookie
  • 支持额外请求头,并单独处理 Host
  • 支持全局清洗规则与站点专属清洗规则
  • 监听 config/sites.jsonconfig/cleaning.json 变更并自动热加载

项目结构

. ├── config/ │ ├── sites.json # 站点请求配置(cookies / headers) │ └── cleaning.json # 正文清洗配置(global / sites) ├── index.mjs # 服务入口 ├── Dockerfile.prod # 生产镜像构建文件 ├── push-docker.sh # 构建并推送 Docker 镜像脚本 ├── package.json └── package-lock.json

NPM 包引用说明

当前项目的主要依赖与用途如下:

包名用途
express提供 HTTP 服务,对外暴露 /{targetUrl} 形式的读取接口
playwright启动 Chromium,加载并渲染目标网页内容
turndown将提取出的 HTML 正文转换为 Markdown
web-to-markdown间接提供 @mozilla/readabilitylinkedom 等正文提取相关能力
@playwright/browser-chromium作为开发依赖,配合 Playwright 浏览器能力使用

代码中的实际引用

index.mjs 中,核心 npm 包的职责如下:

  • express:创建 Web 服务并处理请求路由
  • playwright:打开页面、设置请求头、注入 Cookie、抓取最终 HTML
  • @mozilla/readability:从页面中提取正文内容
  • linkedom:将 HTML 转换为可供 Readability 分析的 DOM 结构
  • turndown:把 HTML 正文输出为 Markdown

说明:当前代码直接引用了 @mozilla/readabilitylinkedom。这两个包目前可通过 web-to-markdown 的依赖链获得;如果后续需要更稳定、显式的依赖管理,建议将它们加入 package.json 的直接依赖中。

运行环境

  • Node.js 20+
  • npm
  • Linux/macOS/Windows
  • 首次本地运行需要安装 Playwright Chromium

本地启动

1. 安装依赖

npm install

2. 安装 Chromium

npm run setup

3. 启动服务

npm start

启动后默认监听:

http://127.0.0.1:9998

使用方式

将目标 URL 直接拼接在服务地址后面:

http://127.0.0.1:9998/http://example.com

curl 示例

curl "http://127.0.0.1:9998/https://example.com"

返回内容类型为:

text/markdown; charset=utf-8

错误示例

如果 URL 不合法,会返回 400

URL 格式错误,示例: http://127.0.0.1:9998/http://baidu.com

如果抓取失败,会返回 502

配置说明

项目配置统一放在 config/ 目录下:

  • config/sites.json:站点请求配置,用于注入 cookiesheaders
  • config/cleaning.json:正文清洗配置,用于定义全局规则和站点专属规则

服务启动后会监听这两个配置文件,修改后自动热加载,无需重启。

config/sites.json

config/sites.json 用于给特定站点配置 cookiesheaders

支持以下两种 key:

  • hostname
  • hostname:port

示例:

{ "example.com": { "cookies": "session=abc123; theme=dark", "headers": { "X-Requested-With": "WebReader" } }, "192.168.100.133:6060": { "cookies": ".AspNetCore.Antiforgery.xxx=token", "headers": { "Host": "192.168.100.133:6060" } } }

字段说明

  • cookies 使用标准的 name=value; name2=value2 格式
  • headers 中可配置任意额外请求头
  • Host 头不会通过 extraHTTPHeaders 注入,而是通过请求拦截单独设置

config/cleaning.json

config/cleaning.json 用于定义正文清洗规则,包含两个层级:

  • global:全局默认规则,对所有站点生效
  • sites:站点专属规则,按域名或 域名:端口 匹配,并叠加到 global

示例:

{ "global": { "removeSelectors": ["script", "style", ".copy-btn"], "anchorSelectors": ["a.anchor"], "codeBlockSelectors": ["pre"], "githubCardSelectors": ["a[id$='-card']", "a[repo]"], "imageSourceAttributes": ["data-src", "src"], "imageSrcsetAttributes": ["data-srcset", "srcset"], "imageAltAttributes": ["alt", "title"], "meaninglessImageAltPatterns": ["^(图片|图|image|img|photo|插图)$"], "placeholderImagePatterns": ["^data:image/svg\\+xml", "placeholder", "spacer", "blank"], "placeholderImageMaxWidth": 4, "placeholderImageMaxHeight": 4, "markdownReplacements": [ { "pattern": "\\n{3,}", "flags": "g", "replacement": "\\n\\n" } ] }, "sites": { "www.16c.top": { "removeSelectors": [".code-block > button.copy-button"], "anchorSelectors": ["article h1 .anchor", "article h2 .anchor"], "githubCardSelectors": [".github-card", ".repo-card", "a[href*='github.com/']"] } } }

常用字段说明

  • removeSelectors:直接删除命中的节点,如脚本、样式、复制按钮等
  • anchorSelectors:删除标题锚点等无意义辅助链接
  • codeBlockSelectors:将命中的代码块重建为标准 pre > code 文本结构
  • githubCardSelectors:将 GitHub 卡片替换为普通链接,减少卡片噪音
  • imageSourceAttributes / imageSrcsetAttributes:按顺序挑选真实图片地址
  • imageAltAttributes:按顺序挑选图片说明文字
  • meaninglessImageAltPatterns:匹配“图片 / image”这类无意义 alt 文案
  • placeholderImagePatternsplaceholderImageMaxWidthplaceholderImageMaxHeight:用于识别占位图
  • markdownReplacements:在 HTML 转 Markdown 后执行正则替换,适合修复重复编号、锚点残留、多余空行等问题

Docker 运行

本地构建镜像

docker build -f Dockerfile.prod -t web-reader:local .

启动容器

docker run -d \ --name web-reader \ -p 9998:9998 \ -v $(pwd)/config:/app/config:ro \ web-reader:local

访问示例

http://127.0.0.1:9998/http://example.com

推送 Docker 镜像

项目提供了 push-docker.sh 用于构建并推送镜像。

依赖环境变量

  • CNB_DOCKER_REGISTRY
  • CNB_REPO_SLUG_LOWERCASE

示例

export CNB_DOCKER_REGISTRY=registry.example.com export CNB_REPO_SLUG_LOWERCASE=my-team/web-reader bash ./push-docker.sh

脚本会生成两个标签:

  • latest
  • 时间戳标签,例如 20260407153000

实现说明

处理流程如下:

  1. 接收请求中的目标 URL
  2. 根据目标地址匹配 config/sites.jsonconfig/cleaning.json
  3. 启动或复用 Chromium
  4. 注入请求头和 Cookie
  5. 加载页面 HTML
  6. 使用 Readability 提取正文
  7. 按清洗规则处理 HTML 与 Markdown
  8. 转换并返回最终 Markdown

注意事项

  • 服务端口当前固定为 9998
  • 目标 URL 必须以 http://https:// 开头
  • 某些站点可能依赖更复杂的登录态、脚本执行时机或反爬策略,抓取结果会受目标站点限制
  • Docker 容器中建议通过挂载整个 config/ 目录提供外部配置,方便动态更新站点请求规则与清洗规则