fetch-page

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Trang web → nội dung chính

网页 → 主要内容

Chạy thế nào

运行方式

URL ──► Chrome headless ──CDP──► tab đã chạy JS xong ──► defuddle chạy trong tab ──► md | html | json
Script mở Chrome với cổng debug, nối websocket vào một tab và ra lệnh CDP: đổi User-Agent, đặt khung màn hình desktop,
Page.navigate
, chờ trang ổn định. Sau đó nó inject bundle defuddle vào trang và gọi
parseAsync()
trên DOM thật. Defuddle luôn lọc trang trước;
--format
chỉ quyết định kết quả lọc được in ra dưới dạng nào.
Defuddle chạy trong Chrome chứ không qua JSDOM: CSS thật, không phải parse lại HTML 1–2 MB, nên phần defuddle chỉ mất dưới 1 giây.
URL ──► Chrome headless ──CDP──► 已执行完JS的标签页 ──► 在标签页内运行defuddle ──► md | html | json
脚本以调试端口启动 Chrome,通过 websocket 连接到一个标签页并下达 CDP 指令:修改 User-Agent,设置桌面屏幕尺寸,
Page.navigate
,等待页面稳定。然后它将 defuddle bundle 注入页面并对真实 DOM 调用
parseAsync()
。Defuddle 始终先过滤页面;
--format
只决定过滤结果以哪种形式输出。
Defuddle 在 Chrome 内部运行,而不是通过 JSDOM:使用真实 CSS,不需要重新解析 1–2 MB 的 HTML,因此 defuddle 部分只需不到 1 秒。

Dùng

使用

Thư mục skill:
${CLAUDE_SKILL_DIR}
. Lần đầu (hoặc khi chưa có
node_modules/
trong thư mục skill):
bash
npm install --prefix ${CLAUDE_SKILL_DIR}
Mỗi lần lấy trang:
bash
node ${CLAUDE_SKILL_DIR}/fetch-page.mjs <url> -o /tmp/page.md
FlagTác dụng
--format md|html|json
Dạng đầu ra, mặc định
md
(xem bảng dưới)
-o <file>
Ghi kết quả ra file. Không có thì in ra stdout
--raw-html <file>
Lưu thêm HTML thô của trang (sau khi JS chạy, trước khi defuddle lọc) để soi khi kết quả lạ
--format
Ra gìKhi nào dùng
md
Frontmatter YAML + markdown, giống
defuddle parse --markdown --frontmatter
Đọc, tóm tắt, lưu bài
html
HTML đã làm sạch, không kèm metadataCần giữ cấu trúc HTML (bảng phức tạp, render lại)
json
Cả object defuddle trả về:
content
là HTML,
contentMarkdown
là markdown, cùng
title
,
author
,
published
,
schemaOrgData
,
metaTags
…
Cần metadata để xử lý tiếp bằng code
Cần Node ≥ 22 (dùng
WebSocket
có sẵn) và Chrome. Chrome không nằm ở chỗ quen thuộc thì đặt
CHROME_PATH
.
stderr in
title
,
url
(sau redirect),
wordCount
và thời gian. Một bài báo thường mất 2–6 giây.
技能目录:
${CLAUDE_SKILL_DIR}
。首次(或技能目录中没有
node_modules/
时):
bash
npm install --prefix ${CLAUDE_SKILL_DIR}
每次抓取页面:
bash
node ${CLAUDE_SKILL_DIR}/fetch-page.mjs <url> -o /tmp/page.md
Flag作用
--format md|html|json
输出格式,默认
md
(见下表)
-o <file>
将结果写入文件。不指定则输出到 stdout
--raw-html <file>
额外保存页面原始 HTML(JS 执行后、defuddle 过滤前),用于结果异常时排查
--format
输出内容使用场景
md
YAML frontmatter + markdown,类似
defuddle parse --markdown --frontmatter
阅读、总结、保存文章
html
已清理的 HTML,不含 metadata需要保留 HTML 结构(复杂表格、重新渲染)
json
defuddle 返回的完整对象:
content
为 HTML,
contentMarkdown
为 markdown,以及
title
、
author
、
published
、
schemaOrgData
、
metaTags
等
需要 metadata 做后续代码处理
需要 Node ≥ 22(使用内置的
WebSocket
)和 Chrome。如果 Chrome 不在常规位置,请设置
CHROME_PATH
。
stderr 输出
title
、
url
(重定向后)、
wordCount
和时间。一篇文章通常需要 2–6 秒。

Đọc kết quả trước khi dùng

使用前先检查结果

wordCount
là chỗ nhìn đầu tiên. Bài báo mà chỉ vài chục từ, hay markdown dừng giữa chừng, thì defuddle đã bỏ mất một phần. Soi bằng cách chạy lại với
--raw-html
, rồi so các đoạn
<p>
trong thân bài của HTML với markdown.
Lỗi thì script thoát với mã 1 và in lý do ra stderr: domain sai (
net::ERR_NAME_NOT_RESOLVED
), vẫn kẹt ở trang chặn bot, hoặc trang bắt giải captcha. Gặp captcha thì đừng chạy lại, kết quả sẽ y hệt.
先看
wordCount
。如果文章只有几十个词,或 markdown 中途停止,说明 defuddle 丢失了部分内容。可用
--raw-html
重新运行,然后对比 HTML 正文中的
<p>
片段与 markdown。
出错时脚本以退出码 1 结束并在 stderr 打印原因:域名错误(
net::ERR_NAME_NOT_RESOLVED
)、仍卡在反爬页面、或页面要求通过验证码。遇到验证码不要重试,结果会一模一样。

Vì sao script đặt những thứ này

为什么脚本要设置这些

Mỗi dòng dưới đây là một lỗi đã gặp thật. Bỏ cái nào thì lỗi đó quay lại.
Trong scriptNếu không có
Network.setUserAgentOverride
bỏ chữ
HeadlessChrome
Cloudflare trả trang "Just a moment..." thay cho bài (machinelearningmastery.com)
Emulation.setDeviceMetricsOverride
rộng 1440
Khung headless mặc định ~756px, AP News coi là mobile, gập nửa sau bài bằng
display: none
, defuddle xoá phần bị ẩn → mất nửa bài
Không chờ
Page.loadEventFired
, chỉ chờ DOMContentLoaded rồi chờ chữ trên trang thôi đổi (tối đa 10s)
Trang báo nhiều quảng cáo không bao giờ bắn
load
, script treo hoặc chờ không 15s
FIX_CODE_HIGHLIGHTERS
thay khối Urvanov/Crayon bằng
<pre><code>
Plugin WordPress này vẽ code thành bảng số dòng + code, defuddle ra bảng markdown với code dồn một dòng
CAPTCHA_PAGE
nhận ra trang captcha AWS WAF (
window.gokuProps
, script
captcha.awswaf.com
)
Script thoát mã 0 và in trang "Let's confirm you are human" ra như một bài 29 từ (arstechnica.com)
Page.setBypassCSP
Trang có CSP chặt chặn việc inject bundle defuddle
Chờ Chrome thoát hẳn rồi mới xoá profile tạm
rmSync
gặp
ENOTEMPTY
vì Chrome còn đang ghi file
下面每一行都是实际遇到过的错误。去掉哪一行,对应错误就会复发。
脚本中的设置如果缺少
Network.setUserAgentOverride
去掉
HeadlessChrome
字样
Cloudflare 会返回 "Just a moment..." 页面而不是文章(machinelearningmastery.com)
Emulation.setDeviceMetricsOverride
宽度 1440
默认 headless 视口约 756px,AP News 视其为移动端,用
display: none
折叠文章后半部分,defuddle 会删除被隐藏的部分 → 丢失半篇文章
不等待
Page.loadEventFired
,只等 DOMContentLoaded 后等待页面文字变化(最多 10s)
广告很多的新闻页面永远不会触发
load
,脚本挂起或白等 15 秒
FIX_CODE_HIGHLIGHTERS
将 Urvanov/Crayon 代码块替换为
<pre><code>
这个 WordPress 插件把代码渲染成“行号表格 + 代码”,defuddle 会输出代码被挤成一行的 markdown 表格
CAPTCHA_PAGE
识别 AWS WAF 验证码页面(
window.gokuProps
、
captcha.awswaf.com
脚本)
脚本以退出码 0 结束,并将 "Let's confirm you are human" 页面当作一篇 29 词的文章输出(arstechnica.com)
Page.setBypassCSP
严格 CSP 会阻止注入 defuddle bundle
等待 Chrome 完全退出后才删除临时 profile
rmSync
会因 Chrome 仍在写文件而报
ENOTEMPTY

Khi gặp site mới lỗi

遇到新站点出错时

  • Khối code ra thành bảng ⇒ site dùng một plugin highlight khác. Tìm trong HTML thô (
    --raw-html
    ) chỗ chứa code thô (thường là
    textarea
    ẩn hoặc thuộc tính
    data-*
    ), thêm selector vào
    FIX_CODE_HIGHLIGHTERS
    .
  • Mất một đoạn giữa bài ⇒ tìm trong HTML xem đoạn đó có bị ẩn (
    display: none
    , class
    hidden
    ) không. Thường là JS của site gập bài lại; tìm điều kiện khiến nó gập (khung màn hình, cookie) thay vì tắt
    removeHiddenElements
    — tắt đi thì menu mobile, popup ẩn cũng lọt vào markdown.
  • 代码块变成表格 ⇒ 站点使用了其他高亮插件。在原始 HTML(
    --raw-html
    )中找到包含原始代码的位置(通常是隐藏的
    textarea
    或
    data-*
    属性),将选择器添加到
    FIX_CODE_HIGHLIGHTERS
    。
  • 文章中间缺失一段 ⇒ 在 HTML 中检查该段落是否被隐藏(
    display: none
    、
    hidden
    类)。通常是站点 JS 折叠了文章;找出触发折叠的条件(视口尺寸、cookie),而不是关闭
    removeHiddenElements
    ——关闭后移动端菜单、隐藏弹窗也会混入 markdown。

Giới hạn

限制

  • Chỉ qua được kiểu chặn bot nhìn User-Agent. Captcha, kiểm tra dấu vân tay trình duyệt, paywall thì không qua. Captcha chỉ nhận ra được loại của AWS WAF; loại khác (reCAPTCHA, hCaptcha toàn trang…) vẫn lọt qua thành kết quả, nên
    wordCount
    thấp bất thường thì mở markdown ra xem.
  • Ngôn ngữ trong fence code là defuddle đoán; HTML không ghi thì có thể đoán sai (Python ra
    ```js
    ).
  • buildFrontmatter
    được import theo đường dẫn file trong
    node_modules/defuddle/dist/
    vì package không export nó. Version defuddle ghim ở
    0.19.4
    trong
    package.json
    ; nâng version thì kiểm tra lại đường dẫn này.
  • 只能绕过基于 User-Agent 的反爬。验证码、浏览器指纹检测、付费墙无法绕过。验证码只能识别 AWS WAF 类型;其他类型(reCAPTCHA、整页 hCaptcha 等)仍会混入结果,所以
    wordCount
    异常低时请打开 markdown 查看。
  • 代码块围栏中的语言是 defuddle 猜测的;HTML 未记录时可能猜错(Python 被标成
    ```js
    )。
  • buildFrontmatter
    是通过
    node_modules/defuddle/dist/
    中的文件路径导入的,因为包未导出它。defuddle 版本固定在
    package.json
    的
    0.19.4
    ;升级版本时请重新检查此路径。