关于 read-claude-web-conversation
read-claude-web-conversation 用于导出 Claude.ai 网页聊天和分享链接中的结构化记录。它借助已登录的浏览器会话获取消息与工具调用,并检查记录是否完整。
主要功能
- 01导出完整消息记录
- 02包含渲染后的工具调用
- 03处理聊天与分享链接
- 04通过已登录浏览器访问
- 05检查记录完整性
适用场景
- 01归档 Claude.ai 网页对话
- 02捕获研究对话中的工具调用
- 03导出分享链接中的对话
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ daymade/ claude-code-skills --skill read-claude-web-conversation安装后在 Claude Code 中输入/read-claude-web-conversation确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
.gitignorereferences/applescript_fallback_channel.mdclaude-web-api-extraction.mdscripts/cdp_channel.pydownload_files.jsexport_conversation.jsfetch_cdp.jsrender_transcript.pyrunjs.applescriptselftest_channel.pyselftest_fidelity.py详细介绍
读取 Claude.ai 网页对话
将 Claude.ai 网页对话提取为完整的结构化记录——包括每条消息和每次工具调用,而不只是当前屏幕上显示的内容。
截至 2026 年 7 月,已通过 Claude.ai 网页 API 验证。这些是 Claude.ai 前端调用的同一组私有 JSON 端点;它们并非有文档记录或版本稳定的公共 API,因此如果请求返回 404,请从 Network 选项卡重新推导数据结构(参见 references/claude-web-api-extraction.md)。
本技能与同系列技能的区别
根据你手头的数据来源选择,而不是看它是否属于“对话”:
| 来源 | 使用 |
|---|---|
实时的 claude.ai/chat/… 或 claude.ai/share/… URL | 本技能 |
本地 Claude Code 会话(~/.claude/projects/*.jsonl) | read-claude-code-history |
已导出的 .txt / .json 对话文件 | claude-export-txt-better |
常见方法为何会失败(请先阅读)
有四个陷阱,每一个都会悄无声息地失败——你拿到的内容看起来像是完整导出,只有在你知道对话实际有多长时,才会发现内容丢失。默认假设结果不可信;第 4 步的保真度检查就是为了让这些问题显现出来。
- 登录墙。
curl和WebFetch不会触发身份验证重定向,而是会收到 Cloudflare 挑战页面(HTTP 403,Just a moment...)。修改请求头无济于事;该页面受用户 Chrome 中会话的保护。在这里绝不要使用 curl,哪怕只是“检查一下”也不行。 - 虚拟滚动。 即使对话已打开,
get_page_text和 DOM 抓取也只能看到当前渲染的少数几条消息——通常只有最后一条。一个有 40 条消息的对话,结果可能只有 1 条。 - 默认渲染会折叠工具调用。 API 的默认渲染会将每个工具调用都转换成“你的当前设备不支持”占位内容。在研究或 Agent 对话中,工具内容本身就是对话内容:在一段真实对话中测得,默认渲染只返回了 9.4k 个字符,而
render_all_tools=true时为 173k 个字符——仅 5%。务必请求完整渲染。 - 适用于 /chat/ 的渲染器会悄无声息地丢弃 /share/ 内容块。 分享内容会将 web_search 命中项作为
knowledge条目返回,而不是text条目。只处理text的渲染器会为它们返回空字符串,且不会报错——同一段真实对话的渲染结果仅为 8k 个字符,而非 146k(保留率 4.8%),看起来却完全正常。
可靠做法: 在用户已登录的页面内运行 JavaScript,让 fetch 自动使用会话 Cookie,然后通过保真度检查在本地渲染。
步骤 0 — 选择注入渠道
有三种渠道可以在用户页面内执行 JS。它们只在实现方式上有所不同,各自也有独特的失败情形,因此请按以下顺序选择并验证,不要想当然。
| 顺序 | 渠道 | 使用条件 | 失败情形 |
|---|---|---|---|
| 1 | claude-in-chrome 扩展 | list_connected_browsers 返回浏览器 | 返回 [] → 扩展中的 claude.ai 登录账号与 Claude Code 账号不同。这是结构性问题——重试、重新安装和 switch_browser 都无济于事。请立即改用下一种渠道。 |
| 2 | CDP(scripts/cdp_channel.py probe) | probe 显示 available: true——这是最佳渠道 | 通常不可用,而且这很正常,并非故障——见下文。Probe 成本低;相信它的结果并继续尝试下一种渠道。 |
| 3 | AppleScript(仅限 macOS) | 扩展无法配对时的实际备用方案 | 参见下文的路由陷阱——请先检查这一点,再归咎于用户 |
| ✗ | curl / WebFetch | 绝不使用 | Cloudflare 403。没有任何请求头能解决这个问题。 |
⚠️ 预期 CDP 不可用,切勿尝试强行启用。 从 Chrome 136(2025 年 4 月)起,默认 user-data-dir 下的调试端口会被 忽略——这是为防止恶意软件利用 CDP 窃取 Cookie 而采取的刻意加固措施。你可能仍会发现有监听套接字,磁盘上也有过时的 DevToolsActivePort,但 WebSocket 握手始终得不到响应,且 /json/* 会返回 404。 |
而且它的可用性时好时坏。 在一台机器、一个浏览器会话上通过 Chrome 150 验证:端点先是正常工作,接着完全停止响应,之后又恢复响应——期间没有重启。因此,每次都要探测,绝不要缓存探测结果。“CDP 五分钟前还能用”不能证明它现在可用,“CDP 失败过一次”也不能证明这台机器无法使用它。探测开销很低,正是为了让你可以随时重新检查。
接下来你可能会尝试的做法才是陷阱。网上每个解决方案都说“用 --user-data-dir=/tmp/whatever 重新启动 Chrome”。对这个技能来说,这不但毫无帮助,反而更糟:新配置文件处于退出登录状态,而未登录的浏览器无法读取用户的任何对话。 你需要的会话恰好就在 Chrome 拒绝公开的那个配置文件中。因此,先探测,根据结果采取行动,然后回退——绝不要为了追查端口而重新配置或重新启动用户的浏览器。
如果 Chrome 是在非默认 --user-data-dir 下有意启动,并且已登录 claude.ai,CDP 确实可以工作。这种情况确实会出现;一旦可用,这个通道就远胜其他通道(无需切换菜单选项、不受下文 Apple Events 捕获方式影响、可枚举所有标签页,并在 stdout 上返回完整载荷)。因此,回退之前先进行一次低成本探测是值得的。
尝试通道 1 时,在一次 ToolSearch 调用中加载扩展的工具(这些工具是延迟加载的;API 路径无需 get_page_text / read_page):
ToolSearch: select:mcp__claude-in-chrome__tabs_context_mcp,mcp__claude-in-chrome__navigate,mcp__claude-in-chrome__javascript_tool
然后调用 list_connected_browsers。如果返回空列表,说明账户不匹配——转到通道 2,不要重试。
尝试通道 2 时:
uv run --with websockets python scripts/cdp_channel.py probe
probe 会同时回答三个问题:CDP 是否可用、它是否指向用户的实际浏览器(页面数量,以及已打开的任何 claude.ai 标签页),以及是否有多个 Chrome 实例正在运行。它的输出决定了下文的所有操作——包括是否使用 AppleScript,因为 chrome_instances.automation 列表会告诉你 Apple Events 是否能触达正确的浏览器。
退出码代表明确约定,因此你无需猜测是否该回退:
| code | 含义 | 操作 |
|---|---|---|
| 0 | 成功 | 继续 |
| 1 | 输入错误(例如 --js 不可读) | 修正调用 |
| 2 | TAB_NOT_FOUND | 使用 open,或请用户打开页面 |
| 3 | CDP 不可用 | 回退到其他通道——这是正常结果 |
| 4 | 浏览器返回错误 | 命令有误,或标签页在调用期间消失;回退也无济于事 |
退出码 3 仍会打印 chrome_instances,因为获取这部分信息不需要调试端口。这是路由信息,不是错误:读取它,转到通道 3,且不要向用户提及调试端口。
AppleScript 路由陷阱(在责怪用户之前请先阅读)
AppleScript 通过 bundle id 定位 Chrome。如果有第二个 Chrome 实例正在运行——chrome-devtools-mcp、puppeteer、playwright,或任何使用自己的 --user-data-dir 启动的实例——Apple Events 可能会转而发送给那个实例,而 macOS 不提供按 pid 定位第一个实例的方法。没有解决办法。
真正危险的是它呈现出来的样子:自动化配置文件关闭了“Allow JavaScript from Apple Events”,因此你的 JS 尝试会失败,并显示 “Executing JavaScript through AppleScript is turned off”。你可能会以为是用户忘了切换菜单选项,然后让他们去启用,但他们的真实浏览器从始至终都启用了该选项。实际观察到的情况是:AppleScript 报告 windows=1 tabs=1,而用户实际的 Chrome 开着 35 个标签页。
因此,在回退到 AppleScript 之前,先运行 probe(或从 cdp_channel.py 调用 chrome_instances())并查看 automation 列表。如果列表非空,请明确说明——"自动化 Chrome 正占用 Apple Events 路由,因此 AppleScript 会操作错误的浏览器"——然后改用 CDP,或询问是否可以关闭该实例。不要转述无法确认原因的切换选项错误。
通道详情和 base64 二进制桥接:references/applescript_fallback_channel.md。
步骤 1 — 确认账户情况(这决定了你能获取哪些内容)
你能获取多少对话内容,取决于浏览器登录的是哪个账户。 尽早确认这一点——这关系到拿到的是完整存档,还是存在无法恢复的缺失;在交付之前,用户有权知道自己拿到的是哪一种。
| 情况 | 信号 | 可获取内容 |
|---|---|---|
| A · 未登录 | /api/organizations 未返回任何组织 | 无法获取 /chat/ 链接的内容。停止操作,并请用户在 Chrome 中登录 claude.ai——切勿自动执行登录。 |
| B · 已登录,对话属于此账户 | GET .../chat_conversations/<conversation-id> 返回 200 | 全部内容,包括已上传附件的内容。只要此路径可用,就优先使用。 |
| C · 已登录,但分享链接属于他人 | 获取对话返回 404;快照中的 creator ≠ 此账户 | 仅有快照。平台会删除所有涉及分享者上传内容的工具调用的参数和结果。 无法通过该链接恢复。 |
对于 /share/… 链接,快照负载本身会告诉你属于哪种情况:其中包含 conversation_uuid(原始对话)和 creator。因此,先获取快照,然后用该 conversation_uuid 尝试获取原始对话——如果返回 200,则属于情况 B,应从原始对话重新导出,因为快照的信息严格来说更少。如果返回 404,则属于情况 C:明确告知用户,并让渲染步骤在转录记录的标题中说明缺失内容(该步骤会自动处理)。
情况 C 中的缺失是真实存在的,值得准确说明,因为它们看起来像是导出出了问题,但实际并非如此:上传文件的 view 会保留文件名,却丢失 path 和文件的全部内容。用户自己的文件副本通常还在磁盘上——导出并未出错,只是平台拒绝分享该文件,因此无法导出其内容。
步骤 2 — 打开对话
扩展程序:使用 { "createIfEmpty": true } 调用 tabs_context_mcp,然后调用 navigate。
CDP:cdp_channel.py open --url <url>(复用现有标签页;等待 Cloudflare 中间页结束,该页面会由浏览器自行清除)。
无论采用哪种方式,都要确认页面确实已加载且已登录——标签页标题应为对话名称,而不是“Log in”。如果进入登录页面,请停止操作并告知用户;不要自动执行登录。
第 3 步 — 获取载荷
**始终请求完整的工具渲染内容。**如果没有 render_all_tools=true,分析内容所在位置就会显示占位符(见上文陷阱 3)。
| 链接类型 | 端点 |
|---|---|
/chat/<conversation-id> | GET /api/organizations/<org-uuid>/chat_conversations/<conversation-id>?tree=True&rendering_mode=messages&render_all_tools=true |
/share/<snapshot-id> | GET /api/chat_snapshots/<snapshot-id>?rendering_mode=messages&render_all_tools=true |
请注意,共享端点使用 URL 中的 快照 ID,而不是会话 ID——将快照 ID 传给 chat_conversations 会返回 404;这表示“ID 错误”,而不是“你没有访问权限”。两类 ID 都是不透明的;请始终从打开的 URL 中获取,切勿硬编码。
- 扩展通道 → 在页面中运行 scripts/export_conversation.js(触发后轮询的说明见其文件头),然后通过多个约 16k 的
.slice()窗口分页读取window.__claudeExport.rawJson。 - CDP 通道 → 运行 scripts/fetch_cdp.js,它会直接以 Promise 返回完整的会话 JSON,因此
cdp_channel.py eval可在一次调用中解析,并将结果流式写入文件:uv run --with websockets python scripts/cdp_channel.py eval \ --match <conversation-or-snapshot-id> --js scripts/fetch_cdp.js --out conversation.jsonawaitPromise会在一次调用中解析异步获取操作,结果会通过 stdout 返回,因此数兆字节的载荷无需经过上下文窗口。 - AppleScript 通道 → 通过 scripts/runjs.applescript 运行 scripts/export_conversation.js,然后轮询并读出
window.__claudeExport.rawJson(触发后轮询;参见通道参考)。
对于没有工具调用、只需立即获取文本的普通聊天,references/claude-web-api-extraction.md 中的内联代码片段可在页面内通过一次调用组装聊天记录。
步骤 4 — 在本地渲染,通过保真度门槛
uv run python scripts/render_transcript.py conversation.json -o transcript.md \
--source-url <url>
它会处理两种负载形式(/chat/ 和 /share/),渲染每次工具调用,将工具输出折叠到 <details> 中,并把 web_search 引用汇总到“引用来源”列表中。
它还会拒绝写出有损的对话记录。 在输出任何内容之前,它会审计每个区块——此区块包含 N 个字符;渲染器是否为它输出了任何内容?——如果任何区块包含文本却没有产生输出,就会以 2 退出。这就是陷阱 4 导致的失败;没有这项检查,它便无从察觉:Markdown 看起来干净,退出代码为 0,但 95% 的对话内容都丢失了。预算是根据原始负载衡量的,刻意不经过渲染路径,因为如果门槛要询问解析器有多少内容需要渲染,就只能确认解析器自身的盲点。
如果失败,它会指出出问题的区块,以及用于检查该区块的命令:为 result_item_text()/render_block() 添加对新形式的支持。只有在你明确认定损失可以接受时,才使用 --allow-lossy——这几乎从来都不是正确的答案,而且绝不是第一个该考虑的答案。
被平台清空的区块(情况 C)会单独计数,作为已披露的缺失项,而非损失,并会在对话记录标题中说明。成功运行时会输出自己的统计信息:
fidelity: 143,088/143,088 chars rendered (100.0%)
known gap: 12 tool blocks were emptied by the platform (view) — disclosed in the
transcript header, NOT recoverable from a shared link
该门槛按条目而非区块进行审计——条目才是内容实际丢失的粒度;按区块检查时,只要区块的任意部分有输出,整个区块就会被视为已渲染,因此一个消失的 38k 字符条目会被一个 16 字符的同级条目掩盖,得分仍是 100%。它还会覆盖仅审计 content[] 在结构上无法看到的内容:消息级附件正文(粘贴的文档位于此处,而非某个区块中)、顶层 text,以及活动路径遍历从未到达的消息。没有任何内容的负载得分为 0%,而非 100%——空的获取结果正是此门槛要防止的内容损失的极端情况。
如果你修改了渲染器,请运行回归套件。 其中的每个用例,都是曾骗过早期版本门槛的形式:
uv run python scripts/selftest_fidelity.py
输出格式与导航(--toc)
默认输出格式为 Obsidian,因为 Obsidian 的实时预览不会渲染 HTML <details>,否则可折叠的工具输出、思考内容和文件正文都会变成一片扁平的噪声。默认格式会将每个 <details> 区块改写为原生的 > [!info]- 可折叠提示框:
uv run python scripts/render_transcript.py conversation.json -o transcript.md \
--source-url <url> --toc
如果需要旧版 HTML <details> 输出,请使用 --format markdown。
只有在 <details> Markdown 通过保真度门槛后,才会运行转换——它只是一个外观处理后置步骤,不会触碰任何负载字符串,因此绝不会影响保留情况的证明(不要把它移到门槛之前)。--toc 会在开头添加带链接的目录,并插入每条消息的锚点(<a id="turn-N">),以便浏览较长的对话。--extract-file 输出永远不会被重新格式化。
其他模式保持不变:--list-files 会列出所有可下载文件及各自所需的端点系列;--extract-file <sandbox-path> 会通过重放其 create_file 以及此后每个 str_replace 来重建沙盒创建的文件;如果某个替换缺失或存在歧义,它会拒绝输出任何内容,而不是返回过期内容。
完成检查——文件仍未下载时,渲染就尚未完成
render_transcript.py 写入转录内容时会向 stderr 输出两行。
宣布渲染完成前,请同时阅读这两行:
fidelity: 143,088/143,088 chars rendered (100.0%) across 7 message(s)
files: 2 downloadable file(s) in this conversation — the transcript names them but does NOT carry the bytes, so a text-only export is an INCOMPLETE archive. ...
fidelity: 行是保留完整性的证明。files: 行是完成信号:如果它报告了任何文件,转录内容只会按名称引用这些文件,不包含文件数据——仅文本导出并非完整归档。之所以让这一行与 fidelity: 一起输出到同一个 stderr 流中,是因为完成度驱动机制否则会看到 100% 就停止,文件还没下载完;这正是文本导出被误认为完整归档的原因。(完全没有 files: 行,表示对话中没有可下载文件;仅凭渲染结果确实已完整。)
因此,当 files: 报告 N > 0 时,下载这些文件是此步骤的一部分,并非可选的后续操作。 用户说“归档 / 导出 / 拉到本地”,要的就是完整归档;如果文件还未下载,却只把转录内容交给用户,那交付的就是不完整归档。(--list-files / --extract-file 不会输出此提示——它们会在渲染分支之前返回。)
使用 scripts/download_files.js 清点并通过正确的端点系列下载所有上传文件、助手图片和沙盒交付文件。此脚本是与通道无关的 JS;通过获取内容时使用的同一通道运行。CDP(一次调用,结果输出到 stdout):
# 1. Fire the inventory/download
uv run --with websockets python scripts/cdp_channel.py eval \
--match <conversation-or-snapshot-id> --js scripts/download_files.js
# 2. Poll until the status line is no longer 'pending' (a few seconds)
echo "window.__dlStatus" > /tmp/check_dl.js
uv run --with websockets python scripts/cdp_channel.py eval \
--match <conversation-or-snapshot-id> --js /tmp/check_dl.js
# 3. Extract one file at a time (repeat for each name the status line listed)
echo "window.__dl['filename.png']" > /tmp/read_one.js
uv run --with websockets python scripts/cdp_channel.py eval \
--match <conversation-or-snapshot-id> --js /tmp/read_one.js 2>/dev/null \
| tr -d '\n' | base64 -d > filename.png
AppleScript 通道——当 CDP 不可用时的实际备用方案(步骤 0 说明在默认配置文件上通常会忽略 CDP),因此这里也提供了完整示例,而非只提示替换:
# 1. Fire the inventory/download
osascript scripts/runjs.applescript scripts/download_files.js <conversation-id>
# 2. Poll until the status line is no longer 'pending' (a few seconds)
echo "window.__dlStatus" > /tmp/check_dl.js
osascript scripts/runjs.applescript /tmp/check_dl.js <conversation-id>
# 3. Extract one file at a time (repeat for each name the status line listed)
echo "window.__dl['filename.png']" > /tmp/read_one.js
osascript scripts/runjs.applescript /tmp/read_one.js <conversation-id> \
2>/dev/null | tr -d '\n' | base64 -d > filename.png
扩展通道:通过 javascript_tool 运行脚本内容,并像处理其他大型返回结果一样,以约 16k 的切片分页获取 window.__dl。
使用 file <name> 验证每个文件(检查魔数——预期为 PNG image data、Microsoft Excel 2007+,而不是 ASCII text),并将其字节大小与对话 JSON 中的元数据进行比对(上传文件看 files[].size_bytes;交付文件看 download_files.js 的状态行)。将下载文件保存在转录内容旁边——通常放在同级的 <transcript-basename>_附件/ 目录中——并将转录内容中的图片引用改为指向本地副本。
步骤 5 — 分页(仅限扩展通道)
javascript_tool 会截断较大的返回值。先获取 chars 计数,然后重新运行并返回后续窗口——保持获取操作完全相同,只更改最后一个表达式:
transcript.slice(14000, 32000); // then (32000, 50000) … until you've covered `chars`
窗口大小建议为约 14–18k;更大则可能再次触及限制。CDP 和 AppleScript 通道都不需要这些操作——两者都会将完整负载返回到 stdout,因此处理大型归档导出时优先使用它们会更好。
注意事项
sender的值是'human'和'assistant'(不是'user'/'claude')。- 一条消息可以同时有
m.text和m.content[]。 Agent 轮次通常会在m.text中包含最终答案,并在content[]中包含thinking/tool_use/tool_result块。应先根据content[]构建,再合并m.text——绝不要使用m.text || (content…),因为只要设置了m.text,就会触发短路并丢弃所有块。如果消息显示为空,请检查一条原始消息:Object.keys(msgs[0])和msgs[0].content?.map(b => b.type)。 /share/负载的结构与/chat/不同。name为 null(标题位于snapshot_name中);web_search 结果是knowledge条目(title/url/text),而不是text条目;citations则附在文本块上。捆绑的渲染器能处理这三种情况,而手写的渲染器通常不能,这就是陷阱 4。- 工具块中的空
input: {}/content: []不是错误——在分享快照中,这是平台在隐藏分享者的私有文件内容。请说明这一点;不要将其渲染成令人困惑的空操作,也不要将其算作数据丢失。 - 如果
rendering_mode=raw返回空正文或较短的正文,请改用rendering_mode=messages重试。 两种模式公开的字段略有不同。 - 对话中的文件也可以下载——包括上传文件,以及下载卡片背后的交付文件。共有两类端点,使用不同的标识方式(图片使用 uuid;上传文件/输出文件则通过
conversations/<id>/wiggle/download-file使用沙盒路径)。对上传文件猜测 uuid 会返回 404,看起来像是“不受支持”,但事实并非如此。scripts/download_files.js 会枚举对话中的文件,并通过正确的端点逐个下载。尝试之前,不要断定文件不可用。 tree=True会返回整棵树,包括因编辑和重新生成而被放弃的分支。请从current_leaf_message_uuid出发,通过parent_message_uuid遍历当前路径,避免将无效分支泄漏到文字记录中或增加消息计数。渲染器会执行此操作;如果这些字段不存在,则会回退到数组顺序(适用于单链对话,也适用于本身已线性排列的分享快照)。- 组织可能不止一个:
orgs[0]可能不是正确的组织。如果对话返回 404,请列出组织——orgs.map(o => ({uuid: o.uuid, name: o.name}))——并在判定无法访问之前,遍历所有组织尝试获取。 - 只读取,不要点击。 此技能无需操作对话界面;获取数据期间请避免触发导航或对话框。
- 切勿重新启动用户的浏览器以打开调试端口。 除了通常的理由(那是用户的浏览器,不是你的),这在此处也适得其反:Chrome 只会在非默认配置文件中启用该端口,而全新的配置文件处于未登录状态,因此刚启动的浏览器无法读取用户的任何对话。你会为了一个端口而放弃所需的会话。端口不可用是需要绕开的事实,而不是需要更改的设置。
完整的端点表、逐字段响应架构、完整导出脚本和故障排查表:references/claude-web-api-extraction.md。
下一步
写好文字记录并下载其文件后——第 4 步的完成检查中 files: 这一行标准错误输出会告知是否存在文件;只要该计数非零,仅导出文本就仍不完整——请建议自然的后续操作。由用户选择,绝不自动执行:
Got the full conversation (<N> messages, "<title>", <retention>% fidelity);
<N> files downloaded to <dir>.
Options:
A) Clean it up — run transcript-fixer if it's ASR/garbled (only if relevant)
B) Summarize / extract the decisions and action items
C) Save it to a file — tell me where
D) Nothing else — you just needed it read