关于 x-article-publisher
x-article-publisher 是面向 Claude 技能工作流的 X Articles 发布辅助技能,可将 Markdown 转换为富文本并整理图片与分割线。它通过浏览器自动化保存草稿,不会自动发布。
主要功能
- 01解析 Markdown 标题、图片和分割线
- 02将 Markdown 转换为富文本 HTML
- 03通过剪贴板粘贴文字和图片
- 04将表格与 Mermaid 图表转为图片
- 05按位置插入图片和分割线
- 06保存草稿并预览排版
适用场景
- 01将 Markdown 文章整理到 X Articles
- 02为文章转换表格或 Mermaid 图表
- 03按正文位置插入封面图和内容图片
MCP 相关
依赖 Playwright MCP 执行 X Articles 编辑器的浏览器自动化操作。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ wshuyi/ x-article-publisher-skill --skill x-article-publisher安装后在 Claude Code 中输入/x-article-publisher确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
scripts/copy_to_clipboard.pyparse_markdown.pytable_to_image.py详细介绍
X 文章发布器
将 Markdown 内容发布到 X(Twitter)文章编辑器,并通过富文本转换保留格式。
前提条件
- 用于浏览器自动化的 Playwright MCP
- 用户已登录 X,并订阅 Premium Plus
- Python 3.9+ 及依赖项:
- macOS:
pip install Pillow pyobjc-framework-Cocoa - Windows:
pip install Pillow pywin32 clip-util
- macOS:
- Mermaid 图表所需:
npm install -g @mermaid-js/mermaid-cli
脚本
位于 ~/.claude/skills/x-article-publisher/scripts/:
parse_markdown.py
解析 Markdown 并提取结构化数据:
python parse_markdown.py <markdown_file> [--output json|html] [--html-only]
返回包含以下字段的 JSON:title、cover_image、content_images、dividers(含用于定位的 block_index)、html、total_blocks
copy_to_clipboard.py
将图片或 HTML 复制到系统剪贴板(跨平台):
# Copy image (with optional compression)
python copy_to_clipboard.py image /path/to/image.jpg [--quality 80]
# Copy HTML for rich text paste
python copy_to_clipboard.py html --file /path/to/content.html
table_to_image.py
将 Markdown 表格转换为 PNG 图片:
python table_to_image.py <input.md> <output.png> [--scale 2]
当 X 文章不支持原生表格渲染或需要统一样式时使用。
预处理(可选)
发布前,扫描 Markdown,查找需要转换的元素:
表格 → PNG
# Extract table to temp file, then convert
python ~/.claude/skills/x-article-publisher/scripts/table_to_image.py /tmp/table.md /tmp/table.png
# Replace table in markdown with: 
Mermaid 图表 → PNG
# Extract mermaid block to .mmd file, then convert
mmdc -i /tmp/diagram.mmd -o /tmp/diagram.png -b white -s 2
# Replace mermaid block with: 
分隔线(---)
parse_markdown.py 会自动检测分隔线,并将其输出到 dividers 数组中。必须通过 X 文章的 Insert > Divider 菜单插入(X 会忽略 HTML <hr> 标签)。
工作流
策略:“先文后图后分割线”
对于包含图片和分隔线的文章,先粘贴所有文本内容,然后根据区块索引在正确位置插入图片和分隔线。
- **(可选)**预处理:将表格/Mermaid 图表转换为图片
- 使用 Python 脚本解析 Markdown → 获取标题、图片、含 block_index 的 dividers 和 HTML
- 前往 X 文章编辑器
- 上传封面图片(第一张图片)
- 填写标题
- 将 HTML 复制到剪贴板(Python)→ 使用 Cmd+V 粘贴
- 在 block_index 指定的位置插入正文图片
- 在 block_index 指定的位置插入分隔线(通过 Insert > Divider 菜单)
- 保存为草稿(绝不自动发布)
高效执行原则
目标:尽量缩短各项操作之间的等待时间,实现流畅的自动化体验。
1. 避免不必要的 browser_snapshot
大多数浏览器操作(click、type、press_key 等)都会在返回结果中包含页面状态。不要在每次操作后单独调用 browser_snapshot,直接使用操作返回的页面状态即可。
❌ 错误做法:
browser_click → browser_snapshot → 分析 → browser_click → browser_snapshot → ...
✅ 正确做法:
browser_click → 从返回结果中获取页面状态 → browser_click → ...
2. 避免不必要的 browser_wait_for
仅在以下情况下使用 browser_wait_for:
- 等待图片上传完成(
textGone="正在上传媒体") - 等待页面初始加载(极少数情况)
不要使用 browser_wait_for 等待按钮或输入框出现——页面加载完成后,它们会立即可用。
3. 并行执行独立操作
两个操作之间没有依赖关系时,可以在同一条消息中并行调用多个工具:
✅ 可以并行:
- 填写标题 (browser_type) + 复制HTML到剪贴板 (Bash)
- 解析Markdown生成JSON + 生成HTML文件
❌ 不能并行(有依赖):
- 必须先点击create才能上传封面图
- 必须先粘贴内容才能插入图片
4. 连续执行浏览器操作
每个浏览器操作返回的页面状态都包含所需的所有元素引用。直接使用这些引用执行下一步操作:
# 理想流程(每步直接执行,不额外等待):
browser_navigate → 从返回状态找create按钮 → browser_click(create)
→ 从返回状态找上传按钮 → browser_click(上传) → browser_file_upload
→ 从返回状态找应用按钮 → browser_click(应用)
→ 从返回状态找标题框 → browser_type(标题)
→ 点击编辑器 → browser_press_key(Meta+v)
→ ...
5. 提前完成准备工作
开始浏览器操作前,先完成所有准备工作:
- 解析 Markdown,获取 JSON 数据
- 将 HTML 文件生成到 /tmp/
- 记录 title、cover_image、content_images 等信息
这样,浏览器操作阶段便可连续执行,无需中途停下来处理数据。
第 1 步:解析 Markdown(Python)
使用 parse_markdown.py 提取所有结构化数据:
python ~/.claude/skills/x-article-publisher/scripts/parse_markdown.py /path/to/article.md
输出 JSON:
{
"title": "Article Title",
"cover_image": "/path/to/first-image.jpg",
"cover_exists": true,
"content_images": [
{"path": "/path/to/img2.jpg", "original_path": "/md/dir/assets/img2.jpg", "exists": true, "block_index": 5, "after_text": "context..."},
{"path": "/path/to/img3.jpg", "original_path": "/md/dir/assets/img3.jpg", "exists": true, "block_index": 12, "after_text": "another..."}
],
"html": "<p>Content...</p><h2>Section</h2>...",
"total_blocks": 45,
"missing_images": 0
}
关键字段:
block_index:应在此索引处的区块元素之后插入图片(从 0 开始计数)total_blocks:HTML 中区块元素的总数after_text:仅供参考/调试,不用于定位exists:是否找到了图片文件(若为 false,上传将失败)original_path:从 Markdown 解析出的路径(自动搜索前)path:实际使用的路径(若经过自动搜索,可能与 original_path 不同)missing_images:在所有位置均未找到的图片数量
将 HTML 保存到临时文件,以便复制到剪贴板:
python parse_markdown.py article.md --html-only > /tmp/article_html.html
第 2 步:打开 X 文章编辑器
浏览器错误处理
如果遇到 Error: Browser is already in use 错误:
# 方案1:先关闭浏览器再重新打开
browser_close
browser_navigate: https://x.com/compose/articles
# 方案2:如果 browser_close 无效(锁定),提示用户手动关闭 Chrome
# 方案3:使用已有标签页,直接导航
browser_tabs action=list # 查看现有标签
browser_navigate: https://x.com/compose/articles # 在当前标签导航
最佳实践:每次开始前先使用 browser_tabs action=list 检查状态,避免创建多余的空白标签页。
前往编辑器
browser_navigate: https://x.com/compose/articles
重要:页面加载后会显示草稿列表,而不是编辑器。需要:
- 等待页面加载完成:使用
browser_snapshot检查页面状态 - 立即点击 "create" 按钮:不要等待“添加标题”等编辑器元素,它们只有点击 create 后才会出现
- 等待编辑器加载:点击 create 后,等待编辑器元素出现
# 1. 导航到页面
browser_navigate: https://x.com/compose/articles
# 2. 获取页面快照,找到 create 按钮
browser_snapshot
# 3. 点击 create 按钮(通常 ref 类似 "create" 或带有 create 标签)
browser_click: element="create button", ref=<create_button_ref>
# 4. 现在编辑器应该打开了,可以继续上传封面图等操作
注意:不要使用 browser_wait_for text="添加标题" 等待页面加载,因为此文本只有点击 create 后才会出现,否则会导致超时。
如果需要登录,请提示用户手动登录。
步骤 3:上传封面图片
- 点击“添加照片或视频”按钮
- 使用 browser_file_upload 上传封面图片路径(来自 JSON 输出)
- 确认图片已上传
步骤 4:填写标题
- 查找占位文本为“添加标题”的文本框
- 使用 browser_type 输入标题(来自 JSON 输出)
步骤 5:粘贴文本内容(Python 剪贴板)
使用 Python 将 HTML 复制到系统剪贴板,然后粘贴:
# Copy HTML to clipboard
python ~/.claude/skills/x-article-publisher/scripts/copy_to_clipboard.py html --file /tmp/article_html.html
然后在浏览器中:
browser_click on editor textbox
browser_press_key: Meta+v
这样可以保留所有富文本格式(H2、粗体、链接、列表)。
步骤 6:插入内容图片(按文字搜索定位)
推荐方法:使用 after_text 文字搜索定位,比 block_index 更直观可靠。
定位原理
每张图片的 after_text 字段记录了它前一个段落的末尾文字(最多 80 个字符)。在编辑器中搜索包含该文字的段落,点击后插入图片。
操作步骤
对于每张内容图片(来自 content_images 数组),按 block_index 从大到小的顺序:
# 1. Copy image to clipboard (with compression)
python ~/.claude/skills/x-article-publisher/scripts/copy_to_clipboard.py image /path/to/img.jpg --quality 85
# 2. 在 browser_snapshot 中搜索包含 after_text 的段落
# 找到该段落的 ref
# 3. Click the paragraph containing after_text
browser_click: element="paragraph with target text", ref=<paragraph_ref>
# 4. **关键步骤**: 按 End 键移动光标到行尾
# 这一步非常重要!避免点击到段落中的链接导致位置偏移
browser_press_key: End
# 5. Paste image
browser_press_key: Meta+v
# 6. Wait for upload (only use textGone, no time parameter)
browser_wait_for textGone="正在上传媒体"
为什么需要按 End 键?
问题:当段落包含链接时(如 [链接文字](url)),点击段落可能会:
- 触发链接编辑弹窗
- 将光标定位在链接内部而非段落末尾
解决方案:点击段落后立即按 End 键:
- 确保光标移动到段落末尾
- 避免链接干扰
- 图片将正确插入在该段落之后
定位策略
在 browser_snapshot 返回的结构中,搜索 after_text 的关键词:
textbox [ref=editor]:
generic [ref=p1]:
- StaticText: "元旦假期我在家里翻手机相册..." # 如果 after_text 包含这段文字,点击 p1
heading [ref=h1]:
- StaticText: "演示"
generic [ref=p2]:
- StaticText: "这东西到底有多省事儿?"
- link [ref=link1]: "Claude Code" # 注意:段落可能包含链接
...
反向插入示例
如果有 3 张图片,block_index 分别为 5、12、27:
- 先插入 block_index=27 的图片(搜索 after_text + 按 End + 粘贴)
- 再插入 block_index=12 的图片
- 最后插入 block_index=5 的图片
按从大到小的顺序插入可以避免位置偏移问题。
步骤 6.5:插入分割线(通过菜单)
重要:Markdown 中的 --- 分割线不能通过 HTML <hr> 标签粘贴(X Articles 会忽略它)。必须通过 X Articles 的“Insert”菜单插入。
操作步骤
对于每条分割线(来自 dividers 数组),按 block_index 从大到小的顺序:
# 1. Click the block element at block_index position
browser_click on the element at position block_index in the editor
# 2. Open Insert menu (Add Media button)
browser_click on "Insert" or "添加媒体" button
# 3. Click Divider menu item
browser_click on "Divider" or "分割线" menuitem
# Divider is inserted at cursor position
与图片的插入顺序
建议先插入所有图片,再插入所有分割线。两者都按 block_index 从大到小的顺序:
- 插入所有图片(从最大的 block_index 开始)
- 插入所有分割线(从最大的 block_index 开始)
步骤 7:保存草稿
- 确认内容已粘贴(检查字数指示器)
- 草稿会自动保存;如有需要,也可以点击“Save”按钮
- 点击“预览”以确认格式
- 报告:“草稿已保存。请检查并手动发布。”
关键规则
- 绝不发布——只保存草稿
- 第一张图片作为封面——将第一张图片上传为封面图片
- 富文本转换——粘贴前始终将 Markdown 转换为 HTML
- 使用剪贴板 API——通过剪贴板粘贴以确保格式正确
- 按块索引定位——使用 block_index 精确定位图片和分割线
- 按反向顺序插入——按 block_index 从大到小插入图片和分割线
- H1 标题处理——H1 仅用作标题,不包含在正文中
- 通过菜单插入分割线——Markdown
---必须通过 Insert > Divider 菜单插入(HTML<hr>会被忽略)
支持的格式
| 元素 | 支持情况 | 备注 |
|---|---|---|
H2 (##) | 原生支持 | 章节标题 |
粗体 (**) | 原生支持 | 强调 |
斜体 (*) | 原生支持 | 强调 |
链接 ([](url)) | 原生支持 | 超链接 |
| 有序列表 | 原生支持 | 1. 2. 3. |
| 无序列表 | 原生支持 | - 项目符号 |
引用块 (>) | 原生支持 | 引用文本 |
| 代码块 | 已转换 | → 引用块 |
| 表格 | 已转换 | → PNG 图片(使用 table_to_image.py) |
| Mermaid | 已转换 | → PNG 图片(使用 mmdc) |
分割线 (---) | 通过菜单插入 | → Insert > Divider |
流程示例
用户:“将 /path/to/article.md 发布到 X”
# Step 1: Parse Markdown
python ~/.claude/skills/x-article-publisher/scripts/parse_markdown.py /path/to/article.md > /tmp/article.json
python ~/.claude/skills/x-article-publisher/scripts/parse_markdown.py /path/to/article.md --html-only > /tmp/article_html.html
- 前往 https://x.com/compose/articles
- 上传封面图片(仅对封面使用 browser_file_upload)
- 填写标题(来自 JSON:
title) - 复制并粘贴 HTML:
然后:browser_press_key Meta+vpython ~/.claude/skills/x-article-publisher/scripts/copy_to_clipboard.py html --file /tmp/article_html.html - 对每张内容图片,按 block_index 从大到小的顺序:
python copy_to_clipboard.py image /path/to/img.jpg --quality 85- 点击
block_index位置的块元素 - browser_press_key Meta+v
- 等待上传完成
- 点击
- 在预览中确认
- “草稿已保存。请检查并手动发布。”
最佳实践
为什么使用 block_index,而不是文字匹配?
- 精确定位:不依赖文字内容,即使多处文字相似,也能正确定位
- 可靠性:索引是确定性的,不会因文字相似而混淆
- 便于调试:保留
after_text供人工核验
为什么使用 Python,而不是浏览器内的 JavaScript?
- 本地处理更可靠:Python 直接操作系统剪贴板,不受浏览器沙盒限制
- 图片压缩:上传前压缩图片(--quality 85),减少上传时间
- 代码复用:脚本固定不变,无需每次重新编写转换逻辑
- 便于调试:脚本可单独测试,容易定位问题
等待策略
重要发现: Playwright MCP 的 browser_wait_for 实际行为是 先等待 time 秒,再检查条件,而非轮询!
// 实际执行的代码:
await new Promise(f => setTimeout(f, time * 1000)); // 先固定等待
await page.getByText("xxx").waitFor({ state: 'hidden' }); // 再检查
正确用法:
- ✅ 只用
textGone,不设time:让 Playwright 自己轮询等待 - ✅ 只用
time:固定等待指定秒数 - ❌ 同时用
textGone+time:会先等 time 秒再检查,浪费时间
# 推荐:只用 textGone,让它自动等待条件满足
browser_wait_for textGone="正在上传媒体"
# 或者:用 browser_snapshot 轮询检查状态
# 每次操作后检查返回的页面状态,无需额外等待
图片插入效率
每张图片的浏览器操作从5步减少到2步:
- 旧: 点击 → 添加媒体 → 媒体 → 添加照片 → file_upload
- 新: 点击段落 → Meta+v
封面图 vs 内容图
- 封面图: 使用 browser_file_upload(因为有专门的上传按钮)
- 内容图: 使用 Python 剪贴板 + 粘贴(更高效)
故障排除
MCP 连接问题
如果 Playwright MCP 工具不可用(报错 No such tool available 或 Not connected):
方案1:重新连接 MCP(推荐)
执行 /mcp 命令,选择 playwright,选择 Restart
方案2:清理残留进程后重连
# 杀掉所有残留的 playwright 进程
pkill -f "mcp-server-playwright"
pkill -f "@playwright/mcp"
# 然后执行 /mcp 重新连接
配置文件位置: ~/.claude/mcp_servers.json
浏览器错误处理
如果遇到 Error: Browser is already in use 错误:
# 方案1:先关闭浏览器再重新打开
browser_close
browser_navigate: https://x.com/compose/articles
# 方案2:杀掉 Chrome 进程
pkill -f "Chrome.*--remote-debugging"
# 然后重新 navigate
图片位置偏移
如果图片插入位置不正确(特别是点击含链接的段落时):
原因: 点击段落时可能误触链接,导致光标位置错误
解决方案: 点击后必须按 End 键移动光标到行尾
# 正确流程
1. browser_click 点击目标段落
2. browser_press_key: End # 关键步骤!
3. browser_press_key: Meta+v # 粘贴图片
4. browser_wait_for textGone="正在上传媒体"
图片路径找不到
如果 Markdown 中的相对路径图片找不到(如 ./assets/image.png 实际在其他位置):
自动搜索: parse_markdown.py 会自动在以下目录搜索同名文件:
~/Downloads~/Desktop~/Pictures
stderr 输出示例:
[parse_markdown] Image not found at '/path/to/assets/img.png', using '/Users/xxx/Downloads/img.png' instead
JSON 字段说明:
original_path: Markdown 中指定的路径(解析后的绝对路径)path: 实际使用的路径(如果自动搜索成功,会不同于 original_path)exists:true表示找到文件,false表示未找到(上传会失败)
如果仍然找不到:
- 检查 JSON 输出中的
missing_images字段 - 手动将图片复制到 Markdown 文件同目录的
assets/子目录 - 或修改 Markdown 中的图片路径为绝对路径