抓取链接内容 & 转存知识库
概述
将文章 URL(免费/付费/登录墙)抓取为结构化 Markdown,并自动转存到乐享知识库,实现素材归档和可追溯。
最终产出物
- 1.
<项目子目录>/<原文标题>.md — 完整文章 Markdown(含图片引用) - INLINECODE1 — 结构化元信息(原文链接、作者、发布时间、抓取时间等)
- INLINECODE2 — 所有文章配图
- 乐享知识库中的文档副本(按天维度归档)
文件命名规则(重要)
- - 必须使用原文标题命名,不要用
article.md 等通用名称 - 文件名格式:
<原文标题>.md、 INLINECODE5 - 示例:
How Notion uses Custom Agents.md、 INLINECODE7 - 如果标题中包含文件名不合法字符(
/、\、:等),替换为 INLINECODE11 - 乐享知识库转存时也使用原文标题作为文档标题
工作流程
Step 1:素材收集
抓取方式决策树
根据 URL 类型选择抓取方式(按优先级排列):
- 1. 微信公众号文章(
mp.weixin.qq.com)→ 必须用 fetch_article.py(web_fetch 无法获取图片) - YouTube 视频 → 使用
yt_download_transcribe.py(yt-dlp 下载 + Whisper 转录 + AI 翻译),详见下方「YouTube 视频处理」章节 - 播客音频(小宇宙
xiaoyuzhoufm.com、Apple Podcasts 等)→ yt-dlp 下载音频 + Whisper 转录,详见下方「播客音频处理」章节 - 付费/登录墙文章 → 用
fetch_article.py(Cookie 注入或 CDP 模式) - 免费图文文章(正文含图片/截图/图表)→ 必须用
fetch_article.py(web_fetch 只能返回文本,无法提取和下载页面中的图片) - 免费纯文字文章(正文无配图)→ 可用
web_fetch,内容不完整时切换 INLINECODE21 - 文字观点 → 直接整理
- 图片素材 → 分析图片内容
⚠️ 关键原则:web_fetch 工具只能返回文本内容,无法提取和下载页面中的图片。任何包含图片、截图、图表的文章,都必须使用 fetch_article.py 抓取,否则图片信息会完全丢失。当不确定文章是否含图时,默认用 fetch_article.py。
付费/登录墙文章获取
适用于所有需要登录态才能查看全文的网站(Substack 付费订阅、Medium 会员、知识星球、财新网、The Information 等),使用 fetch_article.py 脚本:
CODEBLOCK0
两种浏览器模式:
| 模式 | 参数 | 原理 | 适用场景 |
|---|
| Cookie 注入 | (默认) | 从 Chrome Cookie DB 提取 cookies → 注入 Playwright 浏览器 | Substack、Medium 等大部分站点 |
| CDP |
--cdp | 通过 Chrome DevTools Protocol 连接用户真实 Chrome(port 9222),复用完整登录态 |
OpenAI、Cloudflare 保护站点、LinkedIn、Google 系网站等会检测自动化浏览器或有反爬的站点 |
何时必须用 CDP 模式:
- 1. Cloudflare 保护站点(如 openai.com):Cloudflare 的 JS challenge 会阻拦 Playwright 自动化浏览器。CDP 模式连接用户真实 Chrome 可以绕过。脚本会自动检测 Cloudflare challenge 页面("Just a moment..." 等)并等待验证通过。
- Google 登录页面(如 LinkedIn "通过 Google 登录"):Google 登录页面会检测 Playwright 并拒绝登录。CDP 模式完全绕过此检测。
自动升级:对于已知的 Cloudflare 站点(openai.com 等),即使未指定 --cdp,脚本也会自动切换到 CDP 模式。
CDP 模式前置条件:确保 Chrome 浏览器已开启 CDP 远程调试端口:
# 启动 Chrome(开启 CDP 远程调试端口 9222)
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222 &
# 验证 CDP 端口可连接
curl -s http://localhost:9222/json/version
如果 Chrome 已在运行但未开启 CDP,脚本会尝试优雅关闭 Chrome 后以 CDP 模式重启。
工作原理:
- 1. 自动从 Chrome 浏览器的 Cookie 数据库提取目标域名的登录 cookies
- 将 cookies 注入 Playwright 浏览器上下文
- 加载页面,自动检测并等待 Cloudflare challenge 通过(如有)
- 滚动加载懒加载内容、下载所有图片
- 自动格式转换:检测下载图片的真实格式(WebP/SVG 伪装成 .png/.jpg 很常见),自动转为真正的 PNG 以确保 PDF 生成和文档嵌入兼容
- 将正文转换为 Markdown(
article.md),图片保存到 images/ 子目录 - 内容提取时自动选择最长的内容容器(避免只抓到免费预览区域)
标题提取增强(多策略回退):
- 1. CSS 选择器优先级:
h1.post-title > article h1 > [class*="title"] h1 > INLINECODE34 - 回退到
<meta property="og:title"> → <meta name="title"> → INLINECODE37 - 自动清理标题中的网站后缀(如
" - Cursor"、" | Substack") - 正文中与已提取标题相同的第一个
<h1> 会被自动去重,避免 MD 中标题重复
作者提取增强:
- - CSS 选择器 +
meta[name="author"] + [rel="author"] + meta[property="article:author"] 多策略回退
微信公众号文章(mp.weixin.qq.com)专项优化:
脚本对微信公众号文章有专门的检测和处理策略:
- 1. 自动检测:识别
mp.weixin.qq.com 域名,自动启用微信模式 - 无需登录:微信公众号文章是公开可读的,跳过登录检测和 Cookie 注入流程
- 专用内容选择器:使用
#js_content / .rich_media_content 精准定位正文区域(而非通用选择器可能匹配到页面其他内容) - 标题提取:
#activity-name > h1.rich_media_title > 通用 h1 > meta 标签回退 - 作者提取:
#js_name(公众号名称)> .rich_media_meta_nickname > 通用选择器回退 - 日期提取:
#publish_time > 通用 time/date 选择器回退 - 图片懒加载增强:
- 微信图片使用
data-src + IntersectionObserver 懒加载
- 滚动速度放慢(300px 步长、200ms 间隔)以确保触发所有 IntersectionObserver
- 强制将未触发的
data-src 复制到
src(兜底策略)
- 图片下载时优先使用
data-src 的高清原图 URL
- 8. 图片格式识别:微信图片 URL 格式特殊(
mmbiz.qpic.cn/...?wx_fmt=png),从 wx_fmt 查询参数推断文件扩展名 - Referer 防盗链:通过 Playwright 页面上下文的
page.request.get() 下载图片,自动携带正确的 Referer 头
Substack 站点(如 www.lennysnewsletter.com)专项优化:
脚本对 Substack 托管的站点(*.substack.com、lennysnewsletter.com 等)有专门的登录检测和登录态缓存机制:
- 1. 登录态缓存:登录成功后自动保存 Playwright
storage_state 到 ~/.substack/storage_state.json,后续抓取直接复用,无需重复登录和邮箱验证 - 优先级:缓存
storage_state > Chrome cookies > 引导登录 - 自动检测登录状态:加载页面后检查右上角是否有用户头像(已登录)还是 "Sign in" 按钮(未登录)
- 已登录 → 直接抓取全文,并刷新缓存延长有效期
- 缓存过期 → 自动清理旧缓存,进入引导登录流程
- 未登录 → 打开可见浏览器窗口引导登录,用户在终端输入
y 确认后二次验证,通过后自动缓存
独立登录命令(推荐首次使用时先执行):
python scripts/fetch_article.py login
此命令单独完成 Substack 登录并缓存,不需要指定文章 URL。后续所有 Substack 文章抓取都会自动复用此登录态。
非 Substack 站点的登录确认机制:
- - 无 Chrome cookies 时自动切换到非无头模式,打开可见浏览器窗口
- 终端提示用户完成登录操作后按回车键继续
- 收到确认信号后重新加载页面并检测付费墙状态
付费墙检测:脚本同时检测以下信号:
- - DOM 元素:
[data-testid="paywall"]、 INLINECODE66 - 文本关键词:
This post is for paid subscribers、Subscribe to read、Upgrade to paid 等 - 注意:不同网站的付费墙 DOM 结构和关键词不同,如遇新网站抓取不完整,需检查页面实际的付费墙标识并更新检测逻辑
判断内容是否完整的方法:
- - 先用
web_fetch 尝试获取,如果明显被截断(内容不完整、出现付费提示),则切换到 INLINECODE71 - 抓取完成后必须告知用户查看
article.md 确认内容完整性 - 关注文章末尾是否有作者署名/总结段落作为完整性标志
- 如果用户反馈内容不完整,检查:(1) 登录账号是否有付费权限 (2) 页面是否有懒加载内容未触发 (3) 内容选择器是否匹配到了免费预览区而非全文区
产出物:
- -
<项目子目录>/<原文标题>.md — 完整文章 Markdown(含图片引用) - INLINECODE74 — 结构化元信息(原文链接、作者、发布时间、抓取时间等)
- INLINECODE75 — 所有文章配图
INLINECODE76 格式:
CODEBLOCK3
X.com / Twitter 帖子抓取(必须用 CDP 模式)
X.com 是登录墙网站的典型代表,web_fetch 和普通 Cookie 注入模式都无法抓取,必须使用 CDP 模式:
CODEBLOCK4
CDP 模式工作原理:
- 1. 通过 Chrome DevTools Protocol (port 9222) 连接用户真实 Chrome 浏览器
- 复用浏览器中已登录的 X 账号会话
- 绕过自动化浏览器检测(X 会检测并阻止 Playwright/Selenium)
CDP 模式前置条件:
CODEBLOCK5
X.com 抓取的特殊处理:
- 1. 帖子内容会转换为 Markdown 格式
- 图片(帖子中的媒体)会下载到
images/ 目录 - 帖子中的链接会转换为 Markdown 链接格式
- 转发数、点赞数等元信息会保留
产出物:
- -
<项目子目录>/<原文标题>.md — 帖子 Markdown - INLINECODE80 — 元信息
- INLINECODE81 — 帖子中的媒体图片
英文文章翻译为中英对照
对于英文文章(如 X 帖子、英文博客等),可以使用 OpenAI API 翻译为中英对照格式:
翻译脚本 (scripts/translate_article.py):
CODEBLOCK6
翻译格式:
CODEBLOCK7
翻译工作流:
- 1. 先用
fetch_article.py 抓取原文 - 用
translate_article.py 翻译为中英对照 - 将翻译后的 Markdown 上传到乐享知识库
依赖:
使用 web_fetch 获取的免费文章
对于通过 web_fetch 获取到完整内容的免费文章,同样需要保存原文:
- 1. 保存原文全文:将
web_fetch 返回的内容直接保存为 Markdown,不做总结、不做摘要、不做改写,保持原文的完整结构和措辞 - 文件名使用原文标题: INLINECODE89
- 手动构建
<原文标题>_meta.json,包含 URL、标题、作者、日期等元信息 - 如果文章包含图片,尽量下载保存到 INLINECODE91
关键区分:web_fetch 工具可能会返回总结/摘要版本而非原文全文。如果返回的内容明显是总结(缺少原始段落、引用、细节),需要在 web_fetch 调用时明确要求"返回完整原始全文内容,不要总结或缩写"。保存到本地的必须是原文全文,而不是经过 AI 总结的摘要。
YouTube 视频处理(yt-dlp + Whisper + 翻译 + 乐享)
当用户提供 YouTube 视频链接时,使用 yt_download_transcribe.py 脚本完成完整的下载-转录-翻译-归档工作流。
⚠️ 重要:不要使用 web_fetch(无法获取视频内容),不要使用 NotebookLM(已替换为本地 Whisper 方案,速度更快、无外部依赖)。
工作流概述:
- 1. yt-dlp 下载视频 → 本地
.mp4 文件 - ffmpeg 提取音频 → WAV 格式(16kHz 单声道)
- Whisper 转录 → 带时间戳的文字稿
- AI 翻译(如果是英文)→ 中英对照格式的 Markdown
- 上传乐享知识库:
- 文字稿:
以在线文档(page)格式上传,支持后续按块维度编辑更新
- 视频文件:以文件(file)格式上传
- 6. 清理:上传成功后删除本地视频文件
Step 1:下载 + 转录 + 翻译
CODEBLOCK8
产出物:
- -
<视频标题>.mp4 — 下载的视频文件 - INLINECODE98 — 文字稿 Markdown(英文视频为中英对照格式)
- INLINECODE99 — 视频元信息
文字稿格式(英文视频,中英对照):
CODEBLOCK9
Whisper 模型选择建议:
| 模型 | 速度 | 精度 | 适用场景 |
|---|
| INLINECODE100 | 最快 | 较低 | 快速预览、非关键内容 |
| INLINECODE101 |
快 | 中等 |
默认推荐,适合大部分场景 |
|
small | 中等 | 较高 | 口音较重、背景噪音较多 |
|
medium | 慢 | 高 | 重要内容、需要高精度 |
|
large | 最慢 | 最高 | 专业内容、学术演讲 |
Step 2:上传到乐享知识库
通过 lexiang MCP 工具完成上传,流程与 Step 2(普通文章转存乐享)一致。前提是 lexiang MCP 已连接(参见 Step 2 的「乐享 MCP 工具的调用方式」章节)。
文字稿上传(在线文档 page 类型):
- 1. 获取知识库根节点 → 检查/创建日期目录(同上述步骤 1-3)
- 调用
entry_import_content(参数:space_id, parent_id=<日期目录ID>, name="<视频标题>", content=<文字稿Markdown内容>, content_type="markdown") - 在线文档支持后续在乐享中按块维度编辑更新(如修正翻译)
视频文件上传(file 类型,三步上传):
- 1.
file_apply_upload(参数:parent_entry_id=<日期目录ID>, name="<视频标题>.mp4", size=<文件字节数>, mime_type="video/mp4", upload_type="PRE_SIGNED_URL") - INLINECODE117
- INLINECODE118 (参数:
session_id=<上一步返回的session_id>)
上传成功后:自动删除本地视频文件(rm -f <视频文件路径>),节省磁盘空间。
依赖:
- -
yt-dlp(推荐 brew install yt-dlp,不要用 pip3 install)— YouTube 视频下载。必须用 brew 安装以获取最新版本,pip 版本受限于系统 Python 版本(如 Python 3.9 无法安装 nightly 版),而 brew 版自带独立 Python 环境 - INLINECODE124 (
pip3 install openai-whisper)— 音频转录 - INLINECODE126 (
brew install ffmpeg)— 音频提取 - INLINECODE128 (
pip3 install openai)— 翻译(需要 OPENAI_API_KEY 环境变量)。如果没有 API Key,可以跳过翻译步骤,由 AI 助手直接在对话中翻译后更新文档
播客音频处理(yt-dlp + Whisper + 乐享)
当用户提供播客链接时(小宇宙FM xiaoyuzhoufm.com、Apple Podcasts 等),使用 yt-dlp 下载音频 + Whisper 转录的方式处理。
⚠️ 重要:yt-dlp 的 generic extractor 可以从播客页面中自动提取音频 URL(m4a/mp3),不需要 cookies,也不需要专门的播客 extractor。
工作流概述:
- 1. yt-dlp 下载音频 → 本地
.m4a 或 .mp3 文件(播客没有视频,直接是音频) - ffmpeg 提取/转换音频 → WAV 格式(16kHz 单声道,Whisper 推荐)
- Whisper 转录 → 带时间戳的文字稿
- 繁简转换(如需要)→ Whisper base 模型对中文会输出繁体,需用
opencc 转为简体 - 上传乐享知识库(通过 lexiang MCP 工具):
- 文字稿:
entry_import_content 创建为在线文档(page)格式
- 音频文件:
file_apply_upload →
curl PUT →
file_commit_upload 三步上传
Step 1:下载音频
CODEBLOCK10
小宇宙链接格式:https://www.xiaoyuzhoufm.com/episode/<episode_id>
yt-dlp 会通过 generic extractor 自动从页面中提取 media.xyzcdn.net 的音频直链。
Step 2:提取 WAV + Whisper 转录
CODEBLOCK11
Step 3:合并段落 + 繁简转换 + 生成 Markdown
使用与 YouTube 视频相同的段落合并逻辑(maxgap=2s, maxduration=60s)。
关键:Whisper base 模型对中文普通话倾向输出繁体字,必须用 opencc 进行繁简转换:
CODEBLOCK12
CODEBLOCK13
文字稿 Markdown 格式(中文播客):
CODEBLOCK14
Step 4:上传到乐享知识库
与 YouTube 视频处理相同的流程(通过 lexiang MCP 工具完成,前提是 MCP 已连接):
- 1. 获取知识库根节点 → 检查/创建日期目录
- 文字稿使用
entry_import_content 创建为在线文档(page 类型) - 音频文件使用三步上传流程(
file_apply_upload → curl PUT → file_commit_upload),注意 MIME 类型为 audio/mp4(m4a)或 audio/mpeg(mp3)
播客 vs YouTube 的关键区别:
| 维度 | YouTube 视频 | 播客音频 |
|---|
| 文件格式 | INLINECODE148 (视频) | INLINECODE149 /.mp3(纯音频) |
| 文件大小 |
较大(HLS 720p ~500MB) | 较小(~60MB/小时) |
| 下载方式 | 需要 HLS 格式避免 403 | 直接下载,无反爬 |
| cookies | 通常需要 | 不需要 |
| Whisper 语言 | 通常是英文(需翻译) | 通常是中文(需繁简转换) |
| 上传 MIME |
video/mp4 |
audio/mp4 或
audio/mpeg |
依赖(额外):
- -
opencc-python-reimplemented(pip3 install opencc-python-reimplemented)— 繁体转简体(Whisper base 模型中文输出为繁体时需要)
结构化分析
输出结构化分析:
CODEBLOCK15
规划图表:第 1 张为总览图,第 2-N 张各聚焦 1 个核心论点。向用户确认图表数量和主题划分。
Step 2:原文保存到乐享知识库
在进入信息图生成流程之前,先将原文完整保存到乐享知识库,确保素材归档和可追溯。
配置文件与初始化
本 skill 的目标知识库等信息通过配置文件管理,不在 SKILL.md 中硬编码。
配置文件路径:config.json(位于 skill 根目录,即与本 SKILL.md 同级)
对话式配置初始化(首次使用时自动触发)
当 config.json 中 _initialized 为 false 或 space_id 为空时,在执行任何乐享操作前,必须先通过对话引导用户完成配置。
核心设计:用户只需要粘贴一个乐享知识库链接,Agent 自动完成所有配置。
链接格式:https://<domain>/spaces/<space_id>?company_from=<company_from>
- - 示例: INLINECODE162
- 从链接中可解析出三个关键信息:域名(
lexiangla.com)、spaceid、companyfrom
流程如下:
第一步:检测 MCP 连接
- 1. 尝试调用任意一个 lexiang MCP 工具(如
whoami)检测 MCP 是否已连接 - 如果调用成功 → MCP 已连接,进入第二步
- 如果调用失败(MCP 未连接)→ 引导用户完成 MCP 鉴权:
⚠️ 乐享 MCP 尚未连接。请先完成鉴权配置:
1. 访问 https://lexiangla.com/mcp 登录后获取 COMPANY_FROM 和 LEXIANG_TOKEN
2. 按照你使用的 Agent 配置 MCP 连接:
- CodeBuddy:在 MCP 管理面板中添加 lexiang server
- OpenClaw:运行 claw install https://github.com/tencent-lexiang/lexiang-mcp-skill
- 其他 Agent:在 MCP 配置文件中添加 lexiang server
3. 完成后告诉我,我会继续配置流程。
不要继续后续步骤,等待用户完成 MCP 连接后重试。
第二步:请求用户提供知识库链接
- 1. 向用户发送引导消息:
🔧 首次使用,需要配置目标知识库。
请粘贴你想用来归档文章的乐享知识库链接,格式如:
https://lexiangla.com/spaces/xxxxx?company_from=yyyyy
💡 获取方式:在乐享中打开目标知识库,复制浏览器地址栏中的链接即可。
- 2. 等待用户输入,不要自行猜测或列举知识库
第三步:解析链接并验证
- 1. 从用户提供的链接中用正则解析出三个字段:
-
domain:链接的域名部分(如
lexiangla.com),用于生成后续访问链接
-
space_id:
/spaces/ 后面的路径段(如
b6013f6492894a29abbd89d5f2e636c6)
-
company_from:
company_from= 参数值(如
e6c565d6d16811efac17768586f8a025)
- 2. 如果链接格式不正确(缺少
space_id 或 company_from)→ 提示用户重新粘贴正确的链接 - 调用
space_describe_space(参数:space_id=<解析出的 space_id>)验证知识库是否存在 - 如果验证失败 → 提示用户检查链接是否正确或是否有该知识库的访问权限
第四步:写入配置并确认
- 1. 将解析和验证得到的信息写入
config.json:
-
lexiang.target_space.space_id = 解析出的 space_id
-
lexiang.target_space.space_name = 从
space_describe_space 返回值获取的知识库名称
-
lexiang.target_space.company_from = 解析出的 company_from
-
lexiang.access_domain.domain = 解析出的域名
-
lexiang.access_domain.page_url_template =
https://<domain>/pages/{entry_id}
-
lexiang.access_domain.space_url_template =
https://<domain>/spaces/{space_id}?company_from={company_from}
-
_initialized =
true
- 2. 向用户确认配置结果:
CODEBLOCK18
重新配置
当用户说「重新配置知识库」、「切换知识库」、「更换目标知识库」等类似意图时:
- 1. 将
config.json 中 _initialized 设为 INLINECODE188 - 重新执行上述对话式初始化流程(从第一步开始)
用户输入容错
用户可能不会粘贴完美的链接,需要处理以下情况:
| 用户输入 | 处理方式 |
|---|
| 完整链接 INLINECODE189 | 直接解析 ✅ |
| 不带 companyfrom 的链接 INLINECODE190 |
提示:「链接中缺少 companyfrom 参数。请在乐享中重新复制完整链接(地址栏中通常会包含 ?company
from=xxx),或者访问 https://lexiangla.com/mcp 获取你的 COMPANYFROM 值告诉我。」|
| 纯 space
id b6013f6492894a29abbd89d5f2e636c6 | 提示:「请提供完整的知识库链接(包含 companyfrom 参数),我需要从链接中同时获取知识库 ID 和企业标识。」|
| 页面链接
https://lexiangla.com/pages/xxx | 提示:「这是一个页面链接,请提供知识库链接(格式:https://lexiangla.com/spaces/xxx?company_from=yyy)。你可以在乐享中进入目标知识库首页,复制地址栏链接。」|
配置结构
CODEBLOCK19
access_domain 会从用户粘贴的链接中自动提取域名,无需手动配置。适配自定义域名的乐享部署。
后续文档中所有 <SPACE_ID>、<COMPANY_FROM>、<ACCESS_DOMAIN> 等占位符,均指从 config.json 中读取的实际值。
乐享 MCP 工具的调用方式(重要 — 多 Agent 适配)
本 skill 需要服务多个 Agent 产品(OpenClaw、CodeBuddy、Claude Desktop 等)。不同 Agent 连接乐享 MCP 的方式不同,但暴露的工具名称和参数完全一致(都是 lexiang MCP server 提供的标准工具)。
核心原则:本 skill 只描述「调用哪个工具 + 传什么参数」,不规定具体的 MCP 调用语法。每个 Agent 按自己的方式调用即可。
各 Agent 产品的 MCP 连接方式:
| Agent 产品 | lexiang MCP 连接方式 | 工具调用方式 |
|---|
| CodeBuddy | 在 ~/.codebuddy/mcp.json 中配置 lexiang server,通过 IDE 的 MCP 管理面板启用连接 | 直接调用 space_describe_space、file_apply_upload 等 lexiang MCP 工具 |
| OpenClaw |
claw install https://github.com/tencent-lexiang/lexiang-mcp-skill,加载 skill 时自动连接 MCP | 同上,通过 skill 暴露的 MCP 工具调用 |
|
Claude Desktop / 其他 MCP 兼容 Agent | 在 Agent 的 MCP 配置文件中添加 lexiang server URL | 同上 |
MCP 连接检测与降级:
在执行乐享操作前,必须先检测 lexiang MCP 是否已连接:
- 1. 读取
config.json,检查 _initialized 和 INLINECODE204 - 如果未初始化 → 先触发对话式配置初始化(参见上方「对话式配置初始化」),初始化流程中会自动完成 MCP 连接检测
- 如果已初始化,尝试调用
space_describe_space(参数:space_id=<config 中的 space_id>)验证 MCP 连接 - 如果调用成功 → MCP 已连接,继续后续流程
- 如果调用失败(MCP 未连接)→ 提示用户检查 MCP 连接,给出对应 Agent 的操作指引:
- CodeBuddy:「请在 MCP 管理面板中确认 lexiang server 已启用并显示为已连接状态」
- OpenClaw:「请确认已安装 lexiang skill(
claw install https://github.com/tencent-lexiang/lexiang-mcp-skill)」
- 其他 Agent:「请确认 MCP 配置中已添加 lexiang server」
⚠️ 禁止降级为 curl 调用 REST API:即使 MCP 未连接,也不要自行编写 curl 调用乐享 REST API,因为:(1) 认证信息硬编码在 curl 中不安全;(2) 不同 Agent 的执行环境差异大,curl 方式不通用;(3) REST API 的 URL 格式和鉴权方式可能变化。应该引导用户修复 MCP 连接。
认证配置(首次使用时需要):
- 1. 访问 https://lexiangla.com/mcp 登录后获取
LEXIANG_TOKEN(访问令牌,格式:lxmcp_xxx)
>
COMPANY_FROM 无需手动获取 — 会从用户粘贴的知识库链接中自动解析
- 2. 配置方式(二选一):
-
环境变量(推荐):
export LEXIANG_TOKEN="lxmcp_xxx"
-
直接修改 MCP 配置:将 MCP server URL 中的
${LEXIANG_TOKEN} 占位符替换为实际值
- 3. 详细配置步骤参见:lexiang-mcp-skill setup.md
目标知识库
从 config.json 的 lexiang.target_space 中读取:
- - 知识库名称: INLINECODE215
- 知识库访问链接:按
config.lexiang.access_domain.space_url_template 格式拼接 - Space ID: INLINECODE217
⚠️ 访问链接域名:用户可访问的乐享前端域名从 config.lexiang.access_domain.domain 读取(默认为 lexiangla.com),不是 mcp.lexiang-app.com(后者是 MCP API 服务端域名,浏览器无法直接访问)。所有展示给用户的链接必须按 config.lexiang.access_domain.page_url_template 格式生成。
目录组织方式
按天维度组织目录:
CODEBLOCK20
操作流程
⚠️ 严格按步骤顺序执行,不得跳步! 必须完成步骤 0→1→2→3→4 的完整流程。尤其是步骤 2(创建日期目录)不可跳过——文档必须上传到当天日期命名的文件夹中,而不是直接上传到知识库根目录。如果跳过步骤 2 直接用 root_entry_id 作为上传目标,文档将错误地出现在根目录下。
通过 lexiang MCP 工具,按以下步骤完成转存:
步骤 0:读取配置(含初始化检测)
- - 读取 skill 目录下的
config.json 文件 - 检查
_initialized 是否为 true 且 lexiang.target_space.space_id 非空 - 如果未初始化(
_initialized 为 false 或 space_id 为空)→ 触发对话式配置初始化流程(参见上方「对话式配置初始化」),完成后再继续 - 提取
lexiang.target_space.space_id、lexiang.access_domain.page_url_template 等配置项
步骤 1:获取知识库根节点
- - 调用
space_describe_space(参数:space_id=<config 中的 SPACE_ID>) - 从返回结果中提取 INLINECODE234
步骤 2:检查/创建当天日期目录
- - 调用
entry_list_children(参数:parent_id=<root_entry_id>)查询根目录下的子条目 - 查找是否已存在以当天日期命名(如
2026-03-18)的 folder 类型条目 - 如不存在,调用
entry_create_entry(参数:parent_entry_id=<root_entry_id>, name="2026-03-18", entry_type="folder")创建
步骤 3:去重检查
- - 调用
entry_list_children(参数:parent_id=<日期目录ID>)查询该日期目录下已有的条目 - 按「名称 + 类型」检查是否已存在同名文档,如果已存在则跳过上传并告知用户
步骤 3.5:非中文文章翻译(⚠️ 不可跳过)
在上传到乐享之前,必须检测原文语言。如果原文不是中文,则需要先翻译为中英对照格式后再归档。
语言检测规则:
- - 读取
<原文标题>.md 的前 500 个字符,统计中文字符(Unicode 范围 \u4e00-\u9fff)占比 - 中文字符占比 ≥ 30% → 判定为中文文章,跳过翻译,直接进入步骤 4
- 中文字符占比 < 30% → 判定为非中文文章,执行翻译
翻译排版格式(中英对照):
- - 按段落逐段翻译,每段原文紧跟对应中文翻译
- 段落之间不加分隔线
---,仅通过空行分隔 - 中文翻译段落开头不加国旗 emoji(🇨🇳),直接以中文开始
- 标题也需要翻译,保留原文标题 + 中文翻译标题
- 列表项、引用块等结构元素同样逐条翻译
- 保留原文中的图片引用(
),图片引用放在对应段落的上方或下方,确保图文对应关系不丢失
CODEBLOCK21
翻译方式(按优先级):
- 1. translate_article.py 脚本(如果
OPENAI_API_KEY 可用):
python3 scripts/translate_article.py "<原文标题>.md" "<原文标题>_translated.md" --model gpt-4o-mini
- 2. AI 助手直接翻译(如果无 API Key):由 Agent 在对话中逐段翻译全文,生成 INLINECODE250
翻译完成后:
- - 本地保存两个文件:
<原文标题>.md(原文)和 <原文标题>_translated.md(中英对照版) - 归档到乐享知识库的必须是翻译后的中英对照版本(
_translated.md),确保知识库中的内容对中文读者友好 - 乐享文档标题使用:
<原文标题中文翻译>(<原文标题>),如: INLINECODE255
步骤 4:图文检测与上传
检查 <原文标题>.md 文件同目录下是否存在 images/ 目录且包含图片文件:
- - 有图片(图文文章) → 先调用
scripts/md_to_pdf.py 将 Markdown 转换为 PDF(嵌入图片),然后通过三步上传流程上传 PDF:
1.
file_apply_upload(参数:
parent_entry_id=<日期目录ID>,
name="<原文标题>.pdf",
size=<文件字节数>,
mime_type="application/pdf",
upload_type="PRE_SIGNED_URL")
2. 使用
curl -X PUT 将 PDF 文件上传到返回的
upload_url(这是预签名 URL 直传 COS,不涉及认证信息)
3.
file_commit_upload(参数:
session_id=<上一步返回的session_id>)
- - 无图片(纯文本文章) → 使用
entry_import_content 创建为在线文档(page 类型):
- 参数:
space_id=<config 中的 SPACE_ID>,
parent_id=<日期目录ID>,
name="<原文标题>",
content=<Markdown文件内容>,
content_type="markdown"
- 在线文档支持在乐享中直接编辑
步骤 5:输出结果
- - 按
config.json 中的 lexiang.access_domain.page_url_template 格式拼接文档链接,告知用户 - 示例:
https://lexiangla.com/pages/<entry_id>(域名从配置读取,不要硬编码)
注意事项
- - 配置初始化是前置条件:首次使用时会自动通过对话引导完成知识库配置,无需手动编辑文件
- MCP 连接是前置条件:必须先确认 lexiang MCP 已连接才能执行操作。不同 Agent 的连接方式不同,参见上方「乐享 MCP 工具的调用方式」
- 访问链接域名:展示给用户的链接一律按
config.json 中 page_url_template 格式生成,不要使用 INLINECODE280 - 上传前自动去重:按「文档名称 + 文档类型」在目标日期目录下查重,避免重复上传
- 图文文章自动转为 PDF 上传(嵌入图片),确保知识库中保留完整图文信息
- 纯文本文章以在线文档(page)格式创建,可在乐享中直接编辑
- PDF 转换依赖
pymupdf 库(pip3 install pymupdf),如未安装则回退为在线文档方式上传 Markdown - 如果同一天多次处理不同文章,它们会归入同一个日期目录下
- 使用
_mcp_fields 参数可以减少返回数据量,如 INLINECODE284
脚本文件
| 文件 | 用途 |
|---|
| INLINECODE285 | 付费/登录墙文章全文抓取脚本(Chrome cookies + Playwright,Substack 登录态缓存,输出 Markdown + 图片 + 元信息 JSON) |
| INLINECODE286 |
Markdown 转 PDF 脚本(使用 pymupdf,嵌入本地图片,正确渲染中文,支持标题回退和拆行标题修复) |
|
scripts/yt_download_transcribe.py | YouTube 视频下载 + Whisper 转录 + AI 翻译脚本(yt-dlp 下载、ffmpeg 提取音频、Whisper 转录、OpenAI 翻译为中英对照 Markdown)。也可用于播客音频转录(跳过视频下载步骤) |
注意:乐享知识库操作不再通过独立脚本(save_to_lexiang.sh/upload_yt_to_lexiang.sh)完成,而是由大模型通过 lexiang MCP 工具直接执行。不同 Agent 产品(OpenClaw、CodeBuddy、Claude Desktop 等)各自管理 MCP 连接,但调用的工具名称和参数完全一致。
经验总结
YouTube 视频下载与转录
核心方案:yt-dlp 下载 → ffmpeg 提取音频 → Whisper 本地转录 → OpenAI API 翻译
为什么不用 NotebookLM / summarize.sh:
- 1. NotebookLM 需要 Google 账号且有额度限制,部分视频可能因版权限制无法提取
- summarize.sh 依赖外部 API(Apify/YouTube 字幕 API),部分视频无字幕时无法工作
- Whisper 本地转录不依赖字幕,直接从音频波形识别语音,覆盖率 100%
yt-dlp 版本与安装(关键!):
- - 必须使用
brew install yt-dlp 安装,不要用 INLINECODE291 - 原因:pip 版本受限于系统 Python 版本(macOS 自带 Python 3.9),无法安装 yt-dlp 的 nightly 版本(需要 Python 3.10+)。而 YouTube 频繁更新反爬策略,旧版 yt-dlp 会遇到 HTTP 403 Forbidden 错误
- brew 安装的 yt-dlp 自带独立 Python 环境,始终能获取最新版本
- 脚本中调用方式:直接用
yt-dlp 命令,不要用 INLINECODE293
YouTube DASH 格式 403 错误(重要!):
- - YouTube 正在强制使用 SABR(Streaming ABR)流媒体协议,传统 DASH 分片下载(
bestvideo+bestaudio)会触发 HTTP 403 Forbidden - 解决方案:优先使用 HLS(m3u8)格式下载,不会被 SABR 拦截
- 脚本中的格式选择顺序: INLINECODE295
-
95-1: 720p HLS(推荐,画质和文件大小的最佳平衡)
-
94-1: 480p HLS
-
93-1: 360p HLS
- 后面是传统 DASH 格式作为回退
- - HLS 格式下载的视频文件会比 DASH 大一些(720p HLS 约 500-600MB vs DASH 约 200-300MB)
- 注意:
--extractor-args "youtube:player_client=android" 不支持 cookies,不是可靠的 403 解决方案
Whisper 转录最佳实践:
- - 音频预处理:16kHz 采样率、单声道 WAV(
ffmpeg -ar 16000 -ac 1),减少文件大小且是 Whisper 推荐格式 - 段落合并策略:相邻 segment 间隔 <2s 且总时长 <60s 则合并,句号/问号结尾时倾向断开
- 模型选择:默认用
base(速度和精度的最佳平衡),重要内容用 small 或 INLINECODE303
翻译策略:
- - 使用 OpenAI
gpt-4o-mini,分批翻译(每批 10 段),避免 token 超限 - 翻译 prompt 要求"自然流畅的中文表达,专业术语保留英文并附中文注释"
- 中英对照格式:每段先展示英文原文,紧跟中文翻译,段间用空行分隔(不加分隔线和国旗 emoji)
- 如果没有 OPENAIAPIKEY:脚本会跳过翻译步骤,输出纯英文文字稿。此时可以由 AI 助手在对话中直接翻译全文,然后用
md_to_page.py --entry-id 更新乐享文档
上传乐享的关键决策:
- - 文字稿使用 在线文档(page)格式而非文件上传,原因:支持在乐享中按块维度编辑更新,可以逐段修正翻译或补充注释
- 视频使用 文件(file)格式上传,因为视频不需要在线编辑
- 上传成功后自动删除本地视频文件,避免占用磁盘空间
视频上传到乐享的正确方式(重要!):
- - 通过 lexiang MCP 工具完成,使用三步上传流程:
1.
file_apply_upload:申请上传凭证(传入
parent_entry_id=日期目录 ID、
upload_type=PRE
SIGNEDURL、
mime_type=video/mp4、
size=文件字节数)
2.
curl -X PUT 上传文件到返回的
upload_url(预签名 URL,直传 COS)
3.
file_commit_upload:确认上传完成(传入
session_id)
- - 518MB 视频的 PUT 上传约需 30-60 秒
播客音频转录
核心方案:yt-dlp(generic extractor)下载音频 → ffmpeg 转 WAV → Whisper 转录 → opencc 繁简转换
yt-dlp 对小宇宙的支持:
- - yt-dlp 没有小宇宙专用 extractor,但 generic extractor 完全够用
- 小宇宙页面中嵌入了
<audio> 标签,音频直链在 INLINECODE316 - 下载不需要 cookies,直接用
yt-dlp --no-playlist -o "%(title)s.%(ext)s" <URL> 即可 - 下载速度约 7MB/s,63 分钟播客(59MB)仅需 8 秒
Whisper 中文转录的繁体问题(重要!):
- - Whisper base 模型对中文普通话倾向输出繁体字(如「歡迎」→ 应为「欢迎」)
- 这是 Whisper 的已知行为,因为训练数据中繁体中文比重较大
- 解决方案:转录后用
opencc-python-reimplemented 的 t2s(Traditional to Simplified)模式批量转换 - 安装: INLINECODE320
- 用法: INLINECODE321
中文播客 vs 英文 YouTube 的流程差异:
- - 中文播客不需要翻译,但需要繁简转换
- 播客音频是直接的 m4a/mp3 文件,不需要从视频中提取音频(但仍需 ffmpeg 转为 WAV 格式给 Whisper)
- Whisper 转录时指定
language='zh' 可以提高中文识别准确率 - 上传乐享时 MIME 类型用
audio/mp4(m4a)或 audio/mpeg(mp3),不是 INLINECODE325
转录性能参考:
- - 63 分钟中文播客 → Whisper base 模型在 CPU 上转录耗时约 115 秒
- 产出 2496 个 segments,合并后 65 个段落
微信公众号图文抓取
核心问题:web_fetch 工具无法获取微信公众号文章的图片(懒加载 + 防盗链),必须使用 fetch_article.py。
技术原理:
- 1. 懒加载机制:微信图片的真实 URL 存放在
data-src 而非 src,依赖 IntersectionObserver 在元素进入视口时才加载。Playwright 无头浏览器通过 window.scrollBy(0, 300) 配合 asyncio.sleep(0.2) 模拟慢速滚动,逐步触发所有图片的懒加载观察器 - 兜底策略:滚动完成后,通过
page.evaluate() 遍历所有 img[data-src],将未被触发的 data-src 强制复制到 INLINECODE336 - 高清图优先:提取图片 URL 时优先使用
data-src(高清原图),而非 src(可能是低分辨率占位图) - 格式识别:微信图片 URL 无常规扩展名(如
mmbiz.qpic.cn/...?wx_fmt=png),需解析 wx_fmt 查询参数推断文件格式 - 防盗链绕过:通过 Playwright 页面上下文的
page.request.get() 下载图片,自动携带正确的 Referer 头 - 专用选择器:微信文章有固定 DOM 结构(
#js_content、#activity-name、#js_name、#publish_time),使用专用选择器比通用选择器更精准可靠
关键决策:
- - 微信文章是公开可读的,跳过登录检测和 Cookie 注入流程
- 滚动参数(300px 步长、200ms 间隔)经实测可平衡速度与懒加载触发成功率
- Markdown 转换时
imageMap 同时匹配 src 和 data-src,确保无论 HTML 中引用哪个属性都能正确替换
验证标准:抓取完成后检查 article_meta.json 中的 image_count 字段,与原文图片数量比对,确认无遗漏。
新平台适配思路
适配新平台时,需依次识别和处理以下 4 个维度:
- 1. 懒加载机制 — 图片是否用
data-src、data-lazy 等延迟加载?需要怎样的滚动策略触发? - 专用 DOM 结构 — 正文、标题、作者、日期的选择器是什么?
- 图片 URL 格式 — 扩展名是否在路径中?是否需要从查询参数推断?
- 防盗链策略 — 是否需要正确的 Referer?是否有其他鉴权机制?
得到 APP 文章抓取(dedao.cn)
核心问题:得到 APP(www.dedao.cn)的文章内容是付费内容 + SPA 动态渲染,web_fetch 和 fetch_article.py 的通用提取逻辑都无法直接获取正文。
技术原因:
- 1. SPA 架构:得到网页版是 React SPA,文章正文通过 JS 异步渲染,
web_fetch 只能拿到空白壳页面 - 付费墙:文章属于付费专栏内容,必须有已登录且已订阅的账号才能查看全文
- DOM 结构特殊:正文容器使用
.iget-articles 类名,不在 fetch_article.py 的默认选择器列表(article、.post-content 等)中。通用 article 选择器只匹配到极少内容(~167 字符),而真正的正文在 .iget-articles 中有 6000+ 字符 - 内容区混杂:正文容器中混入了标题重复、音频时长、"划重点"、用户评论等非正文内容,需要清理
抓取方案:使用 CDP 模式连接已登录得到的 Chrome 浏览器:
CODEBLOCK23
已知限制:
- -
fetch_article.py 的通用内容提取逻辑对得到 DOM 结构匹配不佳,抓取结果可能不完整 - 正确做法是通过 Playwright CDP 连接后,手动指定
.iget-articles 选择器提取正文:
CODEBLOCK24
内容清理要点:
- - 去掉正文开头的标题重复、日期、音频时长等元信息(通常在
凡哥杂谈,你好 或类似开场白之前) - 去掉正文末尾的"划重点"、"添加到笔记"、"首次发布"、"用户留言"等非正文内容
- 如果是多篇系列文章(如上/下篇),合并时用
## 上篇 / ## 下篇 分隔 - 作者信息需要手动确认(通用提取器可能抓错)
适用场景:得到 APP 专栏文章(www.dedao.cn/course/article?id=xxx)
TODO:考虑在 fetch_article.py 中增加得到专用检测和选择器(类似微信公众号的 _is_wechat_article 机制),自动使用 .iget-articles 提取正文。
Python 兼容性
脚本使用 from __future__ import annotations 以兼容 Python 3.9(str | None 联合类型语法在 3.9 中不可用)。
常见问题
| 问题 | 原因 | 修复方法 |
|---|
| YouTube 视频下载 HTTP 403 Forbidden | yt-dlp 版本过旧 + YouTube 强制 SABR 流媒体协议,传统 DASH 分片下载被拦截 | ① brew install yt-dlp 升级到最新版(不要用 pip);② 脚本已配置优先使用 HLS(m3u8) 格式(95-1/94-1/93-1),自动回退 |
| INLINECODE376 无法安装最新版 |
macOS 自带 Python 3.9,yt-dlp nightly 版需要 Python 3.10+ | 改用
brew install yt-dlp,brew 版自带独立 Python 环境 |
| 脚本中
python3 -m yt_dlp 调用失败 | pip 安装的旧版 yt-dlp 与 brew 安装的新版不一致 | 脚本已修改为直接调用
yt-dlp 命令(brew 安装的版本) |
| 视频上传乐享报"不支持的文件格式" | 旧版 COS API(
/kb/files/upload-params)不识别视频格式 | 通过 lexiang MCP 工具使用三步上传流程:
file_apply_upload →
curl PUT →
file_commit_upload |
| Whisper 转录速度极慢 | 模型太大或音频太长 | 换用
tiny 或
base 模型;对于长视频(>1h),考虑用
--whisper-model tiny 先快速预览 |
| 翻译结果为空 | 未设置
OPENAI_API_KEY 环境变量 |
export OPENAI_API_KEY=sk-xxx;或使用
--skip-translate 跳过翻译,由 AI 助手在对话中直接翻译全文后用
md_to_page.py --entry-id 更新乐享文档 |
| 中英对照格式段落错位 | AI 翻译返回的段落数与原文不匹配 | 脚本已有容错处理(缺少翻译的段落会跳过),可手动补充翻译 |
| 视频上传乐享超时 | 视频文件过大(>500MB)| 使用 MCP 的
file_apply_upload 预签名 URL 方式上传,518MB 文件约 30-60 秒即可完成 |
| Whisper 中文转录输出繁体字 | Whisper base 模型对中文普通话倾向输出繁体 | 用
opencc-python-reimplemented 的
t2s 模式进行繁简转换:
opencc.OpenCC("t2s").convert(text) |
| 小宇宙播客下载提示 generic extractor | yt-dlp 没有小宇宙专用 extractor | 正常现象,generic extractor 能自动从页面提取音频直链(
media.xyzcdn.net),下载完全正常 |
| 微信文章图片丢失 |
web_fetch 无法触发懒加载和绕过防盗链 |
必须使用
fetch_article.py,脚本自动检测微信域名并启用专用处理策略 |
| 乐享知识库操作失败 | MCP 连接异常或 Token 过期 | ① 确认当前 Agent 的 lexiang MCP 已连接(CodeBuddy 检查 MCP 面板、OpenClaw 检查 skill 安装状态);② Token 过期时访问 https://lexiangla.com/mcp 获取新 Token 并更新 MCP 配置 |
| 文件上传到了知识库根目录而非日期目录 | 跳过了步骤 2(创建日期目录)和步骤 3(去重检查),直接以
root_entry_id 作为
parent_entry_id 上传 | 严格按照步骤 1→2→3→4 顺序执行,步骤 2 中先
entry_list_children 检查日期目录是否存在,不存在则创建 |
| 展示给用户的乐享链接无法访问 | 使用了 MCP API 域名
mcp.lexiang-app.com 而非用户可访问的前端域名 | 所有展示给用户的链接必须按
config.json 中
page_url_template 格式生成(默认为
https://lexiangla.com/pages/<entry_id>) |
| PDF 中缺少标题 |
fetch_article.py 的
processNode 将正文
<h1> 转为
# 标题,与手动拼接的元信息头标题重复;某些网站(如 Lenny's Newsletter)标题在
articleEl 外部导致 MD 文件第一行
# 为空 | 已修复:(1)
processNode 中自动去重正文中与已提取 title 相同的第一个 h1 (2) 标题提取增加
og:title、
meta[name="title"]、
document.title 多策略回退 (3)
md_to_pdf.py 增加标题回退——当 MD 中无有效 h1 时从
article_meta.json 补充 |
| PDF 中缺少子标题 | 某些网站的 HTML 结构导致
### # 从 Tab 到 Agents 被拆为两行:
### # 和
从 Tab 到 Agents,
parse_markdown 将
# 视为无效标题丢弃 | 已修复:
parse_markdown 增加拆行标题检测——当标题文字为
# 或空时,检查下一行是否为实际标题文字并合并 |