一个给 AI 用的 Web 阅读器,输入网页 URL,返回 Markdown。
项目基于 Node.js、Express 和 Playwright 实现:服务接收目标网页 URL,使用浏览器加载页面内容,结合 Readability 提取正文,再转换为 Markdown 输出。项目支持通过 config/sites.json 为特定站点注入 Cookie 和请求头,并通过 config/cleaning.json 配置全局或站点级清洗规则,适合处理需要登录态、自定义访问头或定制正文清洗的网站。
Readability 提取正文区域域名:端口 匹配站点配置CookieHost 头config/sites.json 与 config/cleaning.json 变更并自动热加载. ├── config/ │ ├── sites.json # 站点请求配置(cookies / headers) │ └── cleaning.json # 正文清洗配置(global / sites) ├── index.mjs # 服务入口 ├── Dockerfile.prod # 生产镜像构建文件 ├── push-docker.sh # 构建并推送 Docker 镜像脚本 ├── package.json └── package-lock.json
当前项目的主要依赖与用途如下:
| 包名 | 用途 |
|---|---|
express | 提供 HTTP 服务,对外暴露 /{targetUrl} 形式的读取接口 |
playwright | 启动 Chromium,加载并渲染目标网页内容 |
turndown | 将提取出的 HTML 正文转换为 Markdown |
web-to-markdown | 间接提供 @mozilla/readability 与 linkedom 等正文提取相关能力 |
@playwright/browser-chromium | 作为开发依赖,配合 Playwright 浏览器能力使用 |
在 index.mjs 中,核心 npm 包的职责如下:
express:创建 Web 服务并处理请求路由playwright:打开页面、设置请求头、注入 Cookie、抓取最终 HTML@mozilla/readability:从页面中提取正文内容linkedom:将 HTML 转换为可供 Readability 分析的 DOM 结构turndown:把 HTML 正文输出为 Markdown说明:当前代码直接引用了 @mozilla/readability 和 linkedom。这两个包目前可通过 web-to-markdown 的依赖链获得;如果后续需要更稳定、显式的依赖管理,建议将它们加入 package.json 的直接依赖中。
Node.js 20+npmPlaywright Chromiumnpm install
npm run setup
npm start
启动后默认监听:
http://127.0.0.1:9998
将目标 URL 直接拼接在服务地址后面:
http://127.0.0.1:9998/http://example.com
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:站点请求配置,用于注入 cookies 和 headersconfig/cleaning.json:正文清洗配置,用于定义全局规则和站点专属规则服务启动后会监听这两个配置文件,修改后自动热加载,无需重启。
config/sites.json 用于给特定站点配置 cookies 和 headers。
支持以下两种 key:
hostnamehostname: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 用于定义正文清洗规则,包含两个层级:
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 文案placeholderImagePatterns、placeholderImageMaxWidth、placeholderImageMaxHeight:用于识别占位图markdownReplacements:在 HTML 转 Markdown 后执行正则替换,适合修复重复编号、锚点残留、多余空行等问题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
项目提供了 push-docker.sh 用于构建并推送镜像。
CNB_DOCKER_REGISTRYCNB_REPO_SLUG_LOWERCASEexport CNB_DOCKER_REGISTRY=registry.example.com
export CNB_REPO_SLUG_LOWERCASE=my-team/web-reader
bash ./push-docker.sh
脚本会生成两个标签:
latest20260407153000处理流程如下:
config/sites.json 与 config/cleaning.jsonChromiumReadability 提取正文9998http:// 或 https:// 开头config/ 目录提供外部配置,方便动态更新站点请求规则与清洗规则