SkillHub

article-archiver

v1.0.0

Automatically archive web articles and Twitter Articles to Feishu documents. ALWAYS trigger when user shares URLs from x.com (Twitter), mp.weixin.qq.com (WeChat articles), or says "保存/记录/收藏 [链接]". Handles long articles (600+ lines), multiple images (20+ images), preserves formatting (bold, code bloc...

Sourced from ClawHub, Authored by yemoo

Installation

Please help me install the skill `article-archiver` from SkillHub official store. npx skills add yemoo/article-archiver

Article Archiver

自动抓取网页文章和 Twitter Article,完整归档到飞书文档。

⚠️ 关键实现要点(必读)

1. 必须读取配置文件获取归档位置

source ~/.openclaw/workspace/skills/article-archiver/config/feishu-locations.sh
# 使用 $DEFAULT_SPACE_ID 和 $DEFAULT_PARENT_NODE

2. 图片处理流程: - 使用 agent-browser 打开页面 - 滚动加载所有图片(懒加载) - 用 img.preview 或类似选择器过滤内容图片(排除头像、图标) - 分析每张图片在文章中的位置(找到前面最近的段落文本) - 下载图片到 /tmp/ - 使用 feishu_doc upload_image 并指定 after_block_id 插入到正确位置 - 关键:upload_image 支持 after_block_id 参数,可以精确控制图片位置

3. 样式保留: - 引用块:使用 > 开头 - 粗体:使用 **text** - 列表:使用 -1. - 代码块:使用 ```

4. 内容传递方式: - 使用 OpenClaw 的 read 工具读取文件 - 不能在 feishu_doc 参数中使用 $(cat file) - 大文件分批处理(每批 100 行)

5. 去重检查: - 归档前先搜索同名文档 - 如果存在,返回已有文档链接 - 不要重复创建

6. 防止乱码和格式问题: - UTF-8 编码: - 使用 html-to-markdown-fixed.js(而不是 html-to-markdown-final.js) - 该脚本使用 Buffer 确保 UTF-8 编码正确,避免中文字符被截断 - 自动移除替换字符 ufffd��� - 标题格式修复: - 脚本会自动修复 # nn内容 格式为 # 内容 - 避免飞书渲染时标题为空、内容在下一行的问题 - 验证方法: - 写入飞书前,检查 JSON 输出中是否有 ufffd��� - 检查标题格式是否正确(标题标记和内容在同一行) - 如果仍有问题: - 不要手动重写原文内容,必须从原始来源精确提取 - 长文本分段写入时,不要在多字节字符中间截断

7. 避免重复标题: - 飞书文档已经有页面标题(Page title),不要在正文开头再写一次标题 - 从原文提取内容时,跳过第一个 H1 标题(通常就是文章标题) - 如果 web_fetch 或 agent-browser 提取的内容以标题开头,删除第一行 - 正文应该从引言、摘要或第一段正文开始 - 元数据后直接接正文,不要插入标题

8. 元数据格式(重要): - 必须使用引用块格式(灰色背景,有样式,好看) - 标准格式(参考 https://qingzhao.feishu.cn/wiki/ZVCFwN7bci1uyhknLpucA18FnSe): ```markdown

原始链接: https://example.com/article 归档时间: 2026-03-12 22:16:00 来源: example.com 作者: 张三 (@username)


`` - **关键点**: - 每行以>开头(引用块) - 字段名用粗体+ 冒号 - 字段值用普通文本 - 元数据和正文之间用分隔线---` 分开 - ❌ 不要使用: - Markdown 表格(会被渲染成飞书表格,不美观) - 普通文本(没有样式,不好看) - Emoji(📄、📅、✍️)在引用块中显示不好

9. 图片位置精确控制: - 图片必须插入到正确的段落之后,不能随意放置 - 使用 feishu_doc upload_image 时,必须指定 after_block_id - 步骤: 1. 先写入全部正文内容 2. 使用 feishu_doc list_blocks 获取所有 block_id 3. 根据图片前面的文本内容,找到对应的 block_id 4. 用 after_block_id 参数将图片插入到正确位置 - 不要在写入正文时就尝试插入图片(会导致位置错乱)

核心功能

  • ✅ 支持长文章(600+ 行)
  • ✅ 支持多图片(20+ 张),图片插入到正确位置
  • ✅ 保留完整格式(粗体、代码块、列表)
  • ✅ 按月份自动组织
  • ✅ 自动去重(检测重复 URL)

使用方法

触发条件

自动触发(无需用户明确说"归档"): - 用户发送 Twitter/X 链接:https://x.com/...https://twitter.com/... - 用户发送微信公众号链接:https://mp.weixin.qq.com/... - 用户说"保存 [链接]"、"记录 [链接]"、"收藏 [链接]"

手动触发: - 用户明确说"归档这篇文章"、"保存到飞书" - 用户发送其他网页链接并询问是否归档

触发后的行为: 1. 立即开始归档,不要询问确认 2. 归档过程中可以简短提示"正在归档..." 3. 归档成功后发送通知消息(包含文档链接) 4. 归档失败时说明原因,提供解决建议

归档流程

0. 去重检查(必须先执行)

在开始归档前,必须先检查文档是否已存在

# 1. 提取文章标题(使用修复版脚本)
TITLE=$(node scripts/html-to-markdown-fixed.js "$URL" "$(cat config/twitter-cookies.txt)" | jq -r '.title')

# 2. 在知识库中搜索同名文档
feishu_wiki nodes --space-id 7527734827164909572 --parent-node NqZvwBqMTiTEtkkMsRoc76rznce | 
  jq -r '.nodes[] | select(.title == "'"$TITLE"'") | .node_token'

# 3. 如果找到同名文档,跳过归档
if [ -n "$EXISTING_NODE" ]; then
  echo "⚠️ 文档已存在,跳过归档"
  echo "已存在的文档:https://qingzhao.feishu.cn/wiki/$EXISTING_NODE"
  exit 0
fi

去重规则: - 标题完全一样 → 视为已存在 - 跳过归档,返回已存在文档的链接 - 不要重复创建相同标题的文档

实现方式: 1. 先提取文章标题(不需要完整内容) 2. 在目标位置搜索同名文档 3. 如果存在,直接返回链接并跳过 4. 如果不存在,继续正常归档流程

1. 识别文章类型

if (url.includes('x.com') && url.includes('/status/')) {
  // Twitter Article - 需要特殊处理
  return 'twitter-article';
} else {
  // 普通网页
  return 'web-page';
}

2. 抓取内容

Twitter Article(长文章 + 多图片):

# 使用修复版脚本(解决乱码和格式问题)
cd ~/.openclaw/workspace/skills/article-archiver
node scripts/html-to-markdown-fixed.js "$URL" "$(cat config/twitter-cookies.txt)" > /tmp/article.json

普通网页

# 使用 web_fetch
web_fetch --url <url> --extract-mode markdown

3. 分段写入(核心方法)

⚠️ 重要:正确的文件内容传递方式

在 OpenClaw 会话中,不能使用 bash 命令替换 $(cat file) 直接传给 feishu_doc,因为会被当成字面字符串。

正确方法

方法 1:使用 OpenClaw 的 read 工具(推荐)

// 1. 先用 read 工具读取文件
const content = await read({ path: '/tmp/content.txt' });

// 2. 然后传给 feishu_doc
await feishu_doc({
  action: 'append',
  doc_token: DOC_TOKEN,
  content: content
});

方法 2:分批读取和上传(适合大文件)

// 读取文件总行数
const totalLines = parseInt(await exec({ 
  command: 'wc -l < /tmp/article-body.txt' 
}));

const BATCH_SIZE = 100;
let currentLine = 1;

while (currentLine <= totalLines) {
  const endLine = Math.min(currentLine + BATCH_SIZE - 1, totalLines);

  // 读取这批内容
  const content = await read({
    path: '/tmp/article-body.txt',
    offset: currentLine,
    limit: BATCH_SIZE
  });

  // 上传到飞书
  await feishu_doc({
    action: 'append',
    doc_token: DOC_TOKEN,
    content: content
  });

  currentLine = endLine + 1;

  // 延迟,避免 API 限流
  await new Promise(resolve => setTimeout(resolve, 300));
}

图片处理(关键步骤):

# 1. 使用 agent-browser 打开页面并提取图片
agent-browser open "https://example.com/article"
sleep 3

# 2. 滚动页面加载所有图片(懒加载)
agent-browser scroll down 5000
sleep 2
agent-browser scroll down 5000
sleep 2

# 3. 提取图片 URL(过滤头像和 SVG)
agent-browser eval 'JSON.stringify(
  Array.from(document.querySelectorAll("img"))
    .filter(img => !img.src.startsWith("data:") && 
                   !img.src.includes("avatar") && 
                   img.naturalWidth > 300)
    .map(img => img.src)
)' > /tmp/image-urls.json

# 4. 下载并上传每张图片
cat /tmp/image-urls.json | jq -r '.[]' | while read url; do
  curl -s -o /tmp/img.jpg "$url"
  # 使用 feishu_doc upload_image 上传
  # 注意:必须用 file_path 参数,不是 path
done

# 5. 关闭浏览器
agent-browser close

图片上传注意事项: - 使用 feishu_docupload_image action - 参数名是 file_path,不是 path - 每次上传后延迟 300ms 避免限流 - 图片会自动插入到文档末尾

样式保留: - 使用 Markdown 格式保留样式 - 引用块:使用 > 开头 - 代码块:使用 ``` 包裹 - 列表:使用 -1. 开头 - 粗体:使用 **text**

关键原则: - 必须先读取文件内容到变量 - 然后把变量传给 feishu_doc - 不能在 feishu_doc 参数中使用 bash 命令替换 - 大文件分批处理(每批 100 ���) - 分批之间延迟 0.3 秒 - 图片单独上传,不要嵌入 Markdown

4. 添加元数据头部

标准格式(引用块 + 粗体字段名,参考 https://qingzhao.feishu.cn/wiki/ZVCFwN7bci1uyhknLpucA18FnSe):

// 提取元数据
const metadata = JSON.parse(await exec({ 
  command: 'cat /tmp/article.json' 
}));

const title = metadata.title;
const author = metadata.author;
const username = metadata.username;
const timestamp = new Date().toISOString().slice(0, 19).replace('T', ' ');

// 判断来源
let source, authorText;
if (url.includes('x.com') || url.includes('twitter.com')) {
  source = 'x.com (Twitter Article)';
  authorText = `${author} (@${username})`;
} else if (url.includes('mp.weixin.qq.com')) {
  source = '微信公众号';
  authorText = author;
} else {
  source = url.match(/https?://([^/]+)/)[1];
  authorText = author;
}

// 写入元数据头部(引用块格式)
await feishu_doc({
  action: 'append',
  doc_token: DOC_TOKEN,
  content: `> **原始链接:** ${url}
> **归档时间:** ${timestamp}
> **来源:** ${source}
> **作者:** ${authorText}

---`
});

关键点: - 每行以 > 开头(引用块) - 字段名用 **粗体** + 冒号 - 字段值用普通文本 - 不要用 Emoji(📄、📅、✍️) - 元数据和正文之间用分隔线 --- 分开

5. 写入正文内容

使用 OpenClaw read 工具读取并写入

// 读取正文内容
const content = await read({ path: '/tmp/article.json' });
const articleContent = JSON.parse(content).content;

// 写入正文
await feishu_doc({
  action: 'append',
  doc_token: DOC_TOKEN,
  content: articleContent
});

飞书会自动处理: - Markdown 图片语法 !alt → 自动下载并转换为内部图片 - 代码块 ```language ... ``` → 渲染为代码块 - 粗体、列表、标题等 → 正确渲染

6. 验证完整性

# 读取文档,检查块数
feishu_doc read --doc_token $DOC_TOKEN

# 验证:
# - 文字段落数 = 预期段落数
# - 图片数 = 预期图片数
# - 总块数 = 文字段落数 + 图片数

7. 发送成功通知

成功消息格式

✅ 文章已归档到飞书

📄 标题:[文章标题]
🔗 原文:[原始链接]
📁 位置:学习资料抓取 → 待阅读
🔗 飞书:[飞书文档链接]

📊 统计:
- 内容:[行数] 行
- 图片:[数量] 张
- 格式:完整保留

失败消息格式

❌ 归档失败

原因:[失败原因]
建议:[解决建议]

原文链接:[原始链接]

使用 message 工具发送

// 成功通知
message({
  action: "send",
  target: "ou_a39e6975059f975a7ffd25892243b64a",  // 威哥的 user_id
  message: "✅ 文章已归档到飞书nn📄 标题:...n🔗 飞书:..."
});

// 失败通知
message({
  action: "send",
  target: "ou_a39e6975059f975a7ffd25892243b64a",
  message: "❌ 归档失败nn原因:...n建议:..."
});

配置

飞书归档位置

配置文件config/feishu-locations.sh

# 默认归档位置
DEFAULT_SPACE_ID="7527734827164909572"
DEFAULT_PARENT_NODE="YziUwLVlBi9BX7kVtkJcf7nQns2"  # 学习资料抓取 → 待阅读

# 可选的归档位置
LOCATION_TO_READ="7527734827164909572:YziUwLVlBi9BX7kVtkJcf7nQns2"  # 待阅读
LOCATION_ROOT="7527734827164909572:Lfj1d3Pcmo0o2IxrHrOcMsffnZb"      # 根目录

使用方法

  1. 修改默认位置: ```bash # 编辑配置文件 vim ~/.openclaw/workspace/skills/article-archiver/config/feishu-locations.sh

# 修改 DEFAULT_PARENT_NODE 为目标 node_token DEFAULT_PARENT_NODE="YziUwLVlBi9BX7kVtkJcf7nQns2" ```

  1. 临时指定位置(调用时传参): bash # 归档到指定节点 feishu_wiki create --parent-node <node_token> --title "文章标题"

  2. 添加新位置bash # 在配置文件中添加 LOCATION_ARCHIVE="7527734827164909572:XXX" # 归档目录 LOCATION_DRAFT="7527734827164909572:YYY" # 草稿目录

获取 node_token

# 方法 1:从飞书 URL 中提取
# https://qingzhao.feishu.cn/wiki/YziUwLVlBi9BX7kVtkJcf7nQns2
# node_token = YziUwLVlBi9BX7kVtkJcf7nQns2

# 方法 2:使用 feishu_wiki 工具查询
feishu_wiki nodes --space-id 7527734827164909572

当前配置的位置: - 待阅读(默认):YziUwLVlBi9BX7kVtkJcf7nQns2 - 路径:学习资料抓取 → 待阅读 - 用途:新归档的文章默认放这里 - 根目录Lfj1d3Pcmo0o2IxrHrOcMsffnZb - 路径:学习资料抓取 - 用途:直接归档到根目录

Twitter Article 需要登录 Cookie:

# Cookie 文件位置
~/.openclaw/workspace/skills/article-archiver/config/twitter-cookies.txt

# 格式
auth_token=xxx; ct0=yyy; twid=zzz

# 更新 Cookie
echo "auth_token=xxx; ct0=yyy; twid=zzz" > config/twitter-cookies.txt

文档格式

# 学习资料抓取

# 2026-03

## 文章标题

> **原始链接**:https://example.com/article
> 
> **归档时间**:2026-03-11 20:00:00
> 
> **来源**:example.com
> 
> **作者**:作者名 (@username)

文章内容...

!图片1

更多内容...

!图片2

---

支持的平台

Twitter / X

URL 格式: - https://x.com/username/status/123456789 - https://twitter.com/username/status/123456789

特点: - 支持长文章(Twitter Article) - 支持多图片(20+ 张) - 需要 Cookie 认证

微信公众号

URL 格式: - https://mp.weixin.qq.com/s/...

特点: - 支持富文本格式 - 支持图片 - 无需认证

普通网页

支持的网站: - 技术博客(Medium、Dev.to、掘金等) - 新闻网站 - 个人博客

特点: - 使用 web_fetch 抓取 - 自动提取正文 - 保留基本格式

使用示例

示例 1:Twitter 链接(自动触发)

用户:https://x.com/mkdir700/status/2020652753190887566

系统行为: 1. 检测到 Twitter 链接,自动触发归档 2. 检查去重:在知识库中搜索同名文档 3. 如果不存在,继续归档流程 4. 提示:"正在归档..." 5. 抓取完整内容(672 行 + 24 张图片) 6. 归档到飞书(学习资料抓取 → 待阅读) 7. 发送成功通知: ``` ✅ 文章已归档到飞书

📄 标题:月成本不到 100 元,如何实现 Token 自由 🔗 原文:https://x.com/mkdir700/status/2020652753190887566 📁 位置:学习资料抓取 → 待阅读 🔗 飞书:https://qingzhao.feishu.cn/wiki/XXX

📊 统计: - 内容:672 行 - 图片:24 张 - 格式:完整保留 ```

示例 2:重复文档(自动跳过)

用户:https://x.com/mkdir700/status/2020652753190887566

系统行为: 1. 检测到 Twitter 链接,自动触发归档 2. 检查去重:发现同名文档已存在 3. 跳过归档,返回已存在文档的链接 4. 发送通知: ``` ⚠️ 文档已存在,跳过归档

📄 标题:月成本不到 100 元,如何实现 Token 自由 🔗 已存在的文档:https://qingzhao.feishu.cn/wiki/XXX ```

示例 2:微信公众号(自动触发)

用户:https://mp.weixin.qq.com/s/abc123

系统行为: 1. 检测到微信公众号链接,自动触发归档 2. 抓取文章内容 3. 归档到飞书 4. 发送成功通知

示例 3:手动触发

用户:保存 https://example.com/article

系统行为: 1. 检测到"保存"关键词 + 链接 2. 自动触发归档 3. 归档成功后发送通知

示例 4:归档失败

用户:https://x.com/someuser/status/123(Cookie 过期)

系统行为: 1. 尝试抓取,发现 Cookie 过期 2. 发送失败通知: ``` ❌ 归档失败

原因:Twitter Cookie 已过期 建议: 1. 浏览器登录 x.com 2. 复制 Cookie(auth_token, ct0, twid) 3. 更新配置文件:config/twitter-cookies.txt

原文链接:https://x.com/someuser/status/123 ```

脚本工具

html-to-markdown-fixed.js(推荐使用)

从 Twitter Article HTML 转换为格式化 Markdown,修复了 UTF-8 乱码和标题格式问题:

node scripts/html-to-markdown-fixed.js <article_url> <cookie_string>

功能: - ✅ 修复 UTF-8 编码问题(使用 Buffer 确保中文字符不被截断) - ✅ 修复标题格式问题(# nn内容# 内容) - ✅ 自动移除替换字符 ufffd��� - ✅ 保留粗体(**text**) - ✅ 保留代码块(```shell ... ```) - ✅ 保留列表(- item) - ✅ 提取图片 URL

与旧版本的区别: - html-to-markdown-final.js:旧版本,存在乱码和格式问题 - html-to-markdown-fixed.js:新版本,已修复所有已知问题

archive-long-article.sh

处理长文章(600+ 行)+ 多图片(20+ 张):

cd ~/.openclaw/workspace/skills/article-archiver/scripts
./archive-long-article.sh <article_url> <doc_token>

功能: 1. 抓取完整内容(使用 html-to-markdown-fixed.js) 2. 提取图片 URL 3. 按图片位置分段 4. 生成段落清单(manifest.json) 5. 输出 feishu_doc 命令(需要在 OpenClaw 环境执行)

常见问题

Q1: 中文出现乱码(���)?

原因:UTF-8 多字节字符在传输或序列化时被截断。

解决: - 使用 html-to-markdown-fixed.js 而不是 html-to-markdown-final.js - 该脚本使用 Buffer 确保 UTF-8 编码正确 - 自动移除替换字符

Q2: 标题为空,内容在下一行?

原因:Markdown 格式 # nn内容 导致飞书渲染时标题为空。

解决: - 使用 html-to-markdown-fixed.js - 脚本会自动修复标题格式为 # 内容

Q3: 图片位置不对,都堆在文档末尾?

原因:先写完所有文字,再统一上传图片。

解决:按图片位置分段,交替写入文本和图片。

Q4: 内容不完整,只有开头部分?

原因:长文章一次性写入失败。

解决:使用 archive-long-article.sh 脚本,分段处理。

Q5: 格式丢失(粗体、代码块)?

原因:使用了简单的文本提取,没有保留 HTML 格式。

解决:使用 html-to-markdown.js 脚本,正确转换格式。

Q4: 执行慢、消耗大量 token?

原因:手动逐个调用 feishu_doc 工具。

解决:用 bash 脚本批量处理,脚本执行不消耗 token。

症状:抓取 Twitter Article 失败,返回登录页面。

解决: 1. 浏览器登录 x.com 2. 打开开发者工具 → Application → Cookies 3. 复制 auth_token, ct0, twid 4. 更新 config/twitter-cookies.txt

成功案例

案例 1:mkdir700 长文章

  • URL:https://x.com/mkdir700/status/2020652753190887566
  • 标题:月成本不到 100 元,如何实现 Token 自由
  • 内容:672 行
  • 图片:24 张
  • 结果:✅ 完整归档,图片位置正确,格式完整
  • 文档:https://qingzhao.feishu.cn/docx/NZHpd5xHxoTjYPxlVfpcaKtOnvh

案例 2:暂星的文章

  • URL:https://x.com/lumoswhy/status/2030807300257300613
  • 内容:451 行
  • 图片:5 张
  • 结果:✅ 完整归档,格式正确

注意事项

  1. 版权:仅用于个人学习和归档
  2. 去重:同一 URL 不重复归档
  3. 错误处理:抓取失败时提示用户,记录日志
  4. API 限流:每次操作间隔 0.3 秒
  5. Cookie 管理:定期检查 Twitter Cookie 是否过期

改进历史

  • 2026-03-10:初始版本,支持基本归档
  • 2026-03-11:重大改进
  • 支持长文章(600+ 行)
  • 支持多图片(20+ 张)
  • 图片位置正确(分段写入 + 交替插入)
  • 格式完整保留(粗体、代码块、列表)
  • 添加 archive-long-article.sh 脚本
  • 添加完整性验证
  • 添加飞书归档位置配置(支持灵活修改目标位置)
  • 添加自动触发机制(检测 Twitter/微信公众号链接)
  • 添加成功/失败通知(自动推送消息给用户)
  • 支持微信公众号(mp.weixin.qq.com)

最后更新:2026-03-11 20:55 维护者:影

最新改进(2026-03-11)

使用 turndown 库

article-archiver 现在使用业界标准的 turndown 库进行 HTML 到 Markdown 的转换,确保高质量的格式保留。

核心脚本scripts/html-to-markdown-final.js

关键特性: 1. ✅ 代码块支持:正确识别 <pre>/<code> 标签,保留语言标注(bash、json、yaml、text 等) 2. ✅ 图片支持:保留 Markdown 图片语法 !alt,飞书会自动下载并转换 3. ✅ 格式保留:粗体、列表、标题、引用块、链接等全部正确转换 4. ✅ GFM 扩展:支持 GitHub Flavored Markdown(表格、删除线等)

技术栈: - Playwright:无头浏览器,处理动态加载的内容 - turndown:HTML 转 Markdown(8.8k GitHub stars) - turndown-plugin-gfm:GFM 扩展支持

代码块处理

// 自定义代码块规则:保留语言标注
turndownService.addRule('codeBlock', {
  filter: function (node) {
    return node.nodeName === 'PRE' && node.querySelector('code');
  },
  replacement: function (content, node) {
    const code = node.querySelector('code');
    const language = code.className.replace('language-', '') || '';
    const text = code.textContent;
    return 'nn```' + language + 'n' + text + 'n```nn';
  }
});

图片处理: - turndown 默认将 <img> 转换为 !alt 格式 - 飞书 Markdown 解析器会自动下载外链图片并转换为内部图片 - 无需手动上传图片,速度快,不会超时

元数据头部

> **原始链接:** https://x.com/username/status/123456789
> **归档时间:** 2026-03-11 22:50:00
> **来源:** x.com (Twitter Article)
> **作者:** 作者名 (@username)

---

归档质量

测试案例:mkdir700 的 sub2api 教程 - 原文:https://x.com/mkdir700/status/2020652753190887566 - 归档结果: - ✅ 代码块:16 个(bash、json、yaml、text) - ✅ 图片:24 张(全部自动转换) - ✅ 总块数:229 个 - ✅ 格式:完整保留

性能: - 提取时间:~10 秒 - 上传时间:~3 分钟(包括图片自动下载) - 总耗时:~4 分钟

故障排查

问题 1:代码块丢失 - 原因:turndown 默认规则无法识别某些代码块结构 - 解决:添加自定义 codeBlock 规则,显式处理 <pre>/<code> 标签

问题 2:图片丢失 - 原因:自定义规则移除了图片 - 解决:移除 removeImages 规则,让 turndown 保留图片的 Markdown 语法

问题 3:图片位置不对 - 原因:先收集所有图片,然后按顺序插入,无法保证位置准确 - 解决:使用 turndown 的默认行为,在遍历 DOM 时即时转换图片

问题 4:Cookie 过期 - 症状:无法访问 Twitter Article,返回登录页面 - 解决:更新 config/twitter-cookies.txt 文件

依赖安装

cd ~/.openclaw/workspace/skills/article-archiver
npm install turndown turndown-plugin-gfm playwright

使用最新脚本

# 提取文章(包含代码块和图片)
node scripts/html-to-markdown-final.js 
  "https://x.com/username/status/123456789" 
  "$(cat config/twitter-cookies.txt)" 
  > /tmp/article.json

# 检查提取结果
jq '.content' /tmp/article.json | head -50

# 统计代码块和图片
echo "代码块: $(jq -r '.content' /tmp/article.json | grep -c '```')"
echo "图片: $(jq -r '.content' /tmp/article.json | grep -c '![')"

未来改进

  • [ ] 支持更多平台(Medium、知乎、微信公众号)
  • [ ] 自动检测重复 URL
  • [ ] 支持批量归档
  • [ ] 添加标签和分类
  • [ ] 支持全文搜索

已知限制和解决方案(2026-03-12)

1. 飞书嵌入文档无法抓取

问题: - 某些网站(如火山引擎开发者社区)的文章是嵌入的飞书文档 - 页面显示"暂时无法在飞书文档外展示此内容" - 无法通过普通网页抓取获取内容和图片

识别方法

// 检查是否为飞书嵌入文档
const firstParagraph = document.querySelector('article p')?.textContent;
if (firstParagraph?.includes('暂时无法在飞书文档外展示此内容')) {
  // 这是飞书嵌入文档,无法抓取
}

解决方案: - 跳过归档:这类文章本身就在飞书里,不需要重新归档 - 提示用户:告知用户这是飞书文档,提供原始飞书链接 - 示例:https://developer.volcengine.com/articles/7615547765435432996 - 实际内容在:https://qingzhao.feishu.cn/wiki/ZVCFwN7bci1uyhknLpucA18FnSe

2. 嵌套代码块问题

问题: - 当原文包含 markdown 代码块(教程类文章),飞书的 Markdown 解析器无法正确处理嵌套的代码块 - 例如:markdown 代码块中包含 markdown 代码示例

表现: - 代码块内容会跑到外面,变成普通文本 - 格式混乱

解决方案: - 目前无完美解决方案(飞书 Markdown 解析器限制) - 内容完整,但格式可能不完美 - 建议:接受当前格式,或在飞书中手动调整

2. 嵌套代码块问题

问题: - 某些 Twitter 图片链接格式特殊,飞书无法自动转换 - 例如:https://pbs.twimg.com/media/...?format=jpg&name=small

解决方案: 1. 方案 A(推荐):使用 turndown 提取的 Markdown,一次性写入,飞书自动处理 - 优点:快速、简单 - 缺点:某些特殊格式图片可能无法转换

  1. 方案 B:手动下载并上传图片
  2. 下载图片到本地
  3. 使用 feishu_doc upload_image 逐个上传
  4. 优点:图片一定能显示
  5. 缺点:慢、复杂

  6. 方案 C:分段写入(文本 → 图片 → 文本)

  7. 按原文顺序交替写入文本和图片
  8. 优点:图片位置准确
  9. 缺点:最慢、最复杂

推荐流程: 1. 先尝试方案 A(一次性写入) 2. 如果图片无法显示,再使用方案 B(手动上传)

3. Twitter 图片处理

重要规则: - ❌ 不要在云空间根目录创建文档(用户看不到,没有操作权限) - ✅ 必须在知识库中创建文档(使用 feishu_wiki create) - ✅ 默认位置:NqZvwBqMTiTEtkkMsRoc76rznce(学习资料抓取 → 待阅读)

正确的创建方式

feishu_wiki create 
  --space-id 7527734827164909572 
  --parent-node NqZvwBqMTiTEtkkMsRoc76rznce 
  --title "$TITLE" 
  --obj-type docx

4. 归档位置

功能: - 在归档前检查是否已存在同名文档 - 标题完全一样 → 视为已存在 - 跳过归档,返回已存在文档的链接

使用方法

# 检查去重
RESULT=$(bash scripts/check-duplicate.sh "$URL")

# 解析结果
if [[ $RESULT == EXISTS:* ]]; then
  NODE_TOKEN=$(echo "$RESULT" | cut -d: -f2)
  TITLE=$(echo "$RESULT" | cut -d: -f3)
  echo "⚠️ 文档已存在:https://qingzhao.feishu.cn/wiki/$NODE_TOKEN"
  exit 0
fi

脚本位置scripts/check-duplicate.sh

最佳实践总结

归档流程(推荐)

  1. 去重检查
bash scripts/check-duplicate.sh "$URL"
  1. 提取文章
node scripts/html-to-markdown-final.js "$URL" "$(cat config/twitter-cookies.txt)" > /tmp/article.json
  1. 创建文档(知识库):
TITLE=$(jq -r '.title' /tmp/article.json)
feishu_wiki create --space-id 7527734827164909572 --parent-node NqZvwBqMTiTEtkkMsRoc76rznce --title "$TITLE" --obj-type docx
  1. 添加元数据头部(引用块格式):
// 提取元数据
const metadata = JSON.parse(await exec({ command: 'cat /tmp/article.json' }));
const timestamp = new Date().toISOString().slice(0, 19).replace('T', ' ');

// 判断来源
let source, authorText;
if (url.includes('x.com') || url.includes('twitter.com')) {
  source = 'x.com (Twitter Article)';
  authorText = `${metadata.author} (@${metadata.username})`;
} else if (url.includes('mp.weixin.qq.com')) {
  source = '微信公众号';
  authorText = metadata.author;
} else {
  source = url.match(/https?://([^/]+)/)[1];
  authorText = metadata.author;
}

// 写入元数据头部
await feishu_doc({
  action: 'append',
  doc_token: DOC_TOKEN,
  content: `> **原始链接:** ${url}
> **归档时间:** ${timestamp}
> **来源:** ${source}
> **作者:** ${authorText}

---`
});
  1. 写入正文内容
// 读取正文内容
const content = await read({ path: '/tmp/article.json' });
const articleContent = JSON.parse(content).content;

// 写入正文
await feishu_doc({
  action: 'append',
  doc_token: DOC_TOKEN,
  content: articleContent
});
  1. 验证结果
feishu_doc list_blocks --doc_token $DOC_TOKEN | jq '.blocks | length'

故障排查

问题 1:图片无法显示 - 检查图片 URL 格式 - 尝试手动下载并上传

问题 2:代码块格式错误 - 检查是否有嵌套代码块 - 接受当前格式,或手动调整

问题 3:文档创建在错误位置 - 检查是否使用 feishu_wiki create - 检查 parent_node 参数

问题 4:重复归档 - 使用去重检查脚本 - 检查标题是否完全一致

问题 5:元数据头部没有样式 - 症状:元数据头部是普通文本,没有灰色背景 - 原因:没有使用引用块格式 - 解决: - 每行以 > 开头(引用块) - 字段名用 **粗体** + 冒号 - 参考正确格式:https://qingzhao.feishu.cn/wiki/ZVCFwN7bci1uyhknLpucA18FnSe - 不要用 Emoji(📄、📅、✍️) - 不要用 Markdown 表格

问题 6:内容写入错误(显示命令而非内容) - 症状:文档中显示 $(cat /tmp/file.md) 而不是实际内容 - 原因:在 OpenClaw 中使用了 bash 命令替换,不会被执行 - 解决: - ❌ 错误:feishu_doc append --content "$(cat file.md)" - ✅ 正确:先用 read 工具读取文件,再传递内容 javascript const content = await read({ path: '/tmp/file.md' }); await feishu_doc({ action: 'append', content: content });

问题 7:UTF-8 乱码(字符被截断) - 症状那么��读和自媒体("阅"字显示为 ��) - 原因:多字节 UTF-8 字符在传输或分段时被截断 - 解决: - 确保完整的字符串传递,不要在字符中间截断 - 使用 OpenClaw 的 read 工具读取文件(自动处理编码) - 避免使用 bash 命令的字符串截取(如 head -c

问题 8:归档位置错误 - 症状:文档创建在错误的知识库位置 - 原因:使用了错误的 parent_node_token - 解决: - 检查配置文件:config/feishu-locations.sh - 确认 DEFAULT_PARENT_NODE 的值 - 正确位置:NqZvwBqMTiTEtkkMsRoc76rznce(学习资料抓取 → 待阅读) - 错误位置:YziUwLVlBi9BX7kVtkJcf7nQns2(旧位置)