mcpField
R
read-claude-web-conversation

read-claude-web-conversation

github.com/daymade生产力1.5k337 次安装今天更新
代码仓库

关于 read-claude-web-conversation

read-claude-web-conversation 用于导出 Claude.ai 网页聊天和分享链接中的结构化记录。它借助已登录的浏览器会话获取消息与工具调用,并检查记录是否完整。

主要功能

  1. 01导出完整消息记录
  2. 02包含渲染后的工具调用
  3. 03处理聊天与分享链接
  4. 04通过已登录浏览器访问
  5. 05检查记录完整性

适用场景

  1. 01归档 Claude.ai 网页对话
  2. 02捕获研究对话中的工具调用
  3. 03导出分享链接中的对话

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/daymade/claude-code-skills --skill read-claude-web-conversation

安装后在 Claude Code 中输入/read-claude-web-conversation确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

.gitignore
references/
applescript_fallback_channel.md
claude-web-api-extraction.md
scripts/
cdp_channel.py
download_files.js
export_conversation.js
fetch_cdp.js
render_transcript.py
runjs.applescript
selftest_channel.py
selftest_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 步的保真度检查就是为了让这些问题显现出来。

  1. 登录墙。 curl 和 WebFetch 不会触发身份验证重定向,而是会收到 Cloudflare 挑战页面(HTTP 403,Just a moment...)。修改请求头无济于事;该页面受用户 Chrome 中会话的保护。在这里绝不要使用 curl,哪怕只是“检查一下”也不行。
  2. 虚拟滚动。 即使对话已打开,get_page_text 和 DOM 抓取也只能看到当前渲染的少数几条消息——通常只有最后一条。一个有 40 条消息的对话,结果可能只有 1 条。
  3. 默认渲染会折叠工具调用。 API 的默认渲染会将每个工具调用都转换成“你的当前设备不支持”占位内容。在研究或 Agent 对话中,工具内容本身就是对话内容:在一段真实对话中测得,默认渲染只返回了 9.4k 个字符,而 render_all_tools=true 时为 173k 个字符——仅 5%。务必请求完整渲染。
  4. 适用于 /chat/ 的渲染器会悄无声息地丢弃 /share/ 内容块。 分享内容会将 web_search 命中项作为 knowledge 条目返回,而不是 text 条目。只处理 text 的渲染器会为它们返回空字符串,且不会报错——同一段真实对话的渲染结果仅为 8k 个字符,而非 146k(保留率 4.8%),看起来却完全正常。

可靠做法: 在用户已登录的页面内运行 JavaScript,让 fetch 自动使用会话 Cookie,然后通过保真度检查在本地渲染。

步骤 0 — 选择注入渠道

有三种渠道可以在用户页面内执行 JS。它们只在实现方式上有所不同,各自也有独特的失败情形,因此请按以下顺序选择并验证,不要想当然。

顺序渠道使用条件失败情形
1claude-in-chrome 扩展list_connected_browsers 返回浏览器返回 [] → 扩展中的 claude.ai 登录账号与 Claude Code 账号不同。这是结构性问题——重试、重新安装和 switch_browser 都无济于事。请立即改用下一种渠道。
2CDP(scripts/cdp_channel.py probe)probe 显示 available: true——这是最佳渠道通常不可用,而且这很正常,并非故障——见下文。Probe 成本低;相信它的结果并继续尝试下一种渠道。
3AppleScript(仅限 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 不可读)修正调用
2TAB_NOT_FOUND使用 open,或请用户打开页面
3CDP 不可用回退到其他通道——这是正常结果
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.json
    
    awaitPromise 会在一次调用中解析异步获取操作,结果会通过 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

评论

支持 Markdown