1. python scripts/csdn_publish.py <markdown文件> [--title "标题"]
2. 未登录 → 自动生成微信二维码 → 扫码后自动保存 storage_state
3. 登录后 → 打开编辑器 → 注入内容 → 点发布
4. 操作 modal 弹窗(设置原创/摘要/AI声明)→ 发布成功
cd ~/.hermes/skills/csdn-publisher
python scripts/csdn_publish.py /tmp/article.md --title "文章标题"
效果: 首次扫码登录,之后直接复用 storage_state,无需再次登录。
| 要点 | 说明 |
|---|---|
| ------ | ------ |
| storage_state | Playwright context.storage_state() 保存 localStorage(含阿里云网关令牌),比单纯 Cookie 更完整。写 API 返回 401 的问题靠这个解决 |
| quality-score 拦截 | 发布前必须拦截 get-quality-score API。用 re.compile(r'get-quality-score') 注册 route,/get-quality-score 通配符可能不匹配。mock 响应需含 recommendPublish: true |
发布弹窗是 .modal | 真正的发布弹窗是 |
| 弹窗操作顺序 | 点发布 → 弹 modal → 设原创 → 填摘要 → 选 AI 声明 → 添加标签 → 点"发布文章"按钮 |
| 标签必填 + 已解决 | ✅ 已攻克! CSDN 发布弹窗强制标签。正确操作:点添加文章标签→ 点右侧面板的 标签芯片(非左侧分类tab)→ 关闭面板(style.display='none')→ 点发布。详见下方「标签操作细节」 |
# 微信扫码 → passport 处理 → 保存 storage_state
ctx = browser.new_context(...)
page.goto(WX_URL)
# 扫码后等待 passport 页面完成登录
for i in range(30):
page.wait_for_timeout(1000)
ck = {c["name"]: c["value"] for c in ctx.cookies()}
if "UserName" in ck and "UserNick" in ck:
ctx.storage_state(path=STORAGE_STATE)
break
ctx = browser.new_context(storage_state=STORAGE_STATE)
# 拦截 quality-score
import re
def intercept(route):
if 'get-quality-score' in route.request.url:
route.fulfill(status=200, content_type='application/json;charset=UTF-8',
body=json.dumps({"code": 200, "data": {"qualityScore": 92, "pass": True, "recommendPublish": True}}))
else:
route.continue_()
ctx.route(re.compile(r'get-quality-score'), intercept)
# 打开编辑器
page.goto("https://editor.csdn.net/md/")
page.evaluate("() => { document.querySelectorAll('[class*=\"passport\"]').forEach(el => el.remove()); }")
# 注入 title + content
page.evaluate(f"() => {{ const inp = document.querySelector('input[placeholder*=\"标题\"]'); ... }}")
page.evaluate(f"() => {{ const el = document.querySelector('pre.editor__inner'); ... }}")
# 点发布 → 操作 modal
page.evaluate("() => { document.querySelector('.btn-publish').click(); }")
page.wait_for_timeout(3000)
# modal 操作(关键!)
page.evaluate("""() => {
const modal = document.querySelector('.modal');
// 设原创
modal.querySelector('input[value="original"]').click();
// 填摘要
const ta = modal.querySelector('textarea');
Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value').set.call(ta, '摘要内容');
ta.dispatchEvent(new Event('input', {bubbles: true}));
}
# 添加标签(新!关键!)
# 1. 点按钮打开面板
modal.querySelector('button.tag__btn-tag').click();
await page.wait_for_timeout(1500)
# 2. 点 el-tag 芯片(不是左侧分类tab!)
page.evaluate("""() => {
const tags = document.querySelector('.mark_selection_box .el-tag');
tags.click(); // 点第一个可见标签
}""")
# 3. 关闭面板(否则覆盖发布按钮)
page.evaluate("() => { document.querySelector('.mark_selection_box').style.display = 'none'; }")
await page.wait_for_timeout(500)
# 点发布
page.evaluate("""() => {
const btns = document.querySelector('.modal').querySelectorAll('button');
for (const b of btns) {
if (b.textContent.trim() === '发布文章') { b.click(); break; }
}
}""")
scripts/csdn_publish.py # 🏆 一键发布脚本(带自动登录 + 图片上传)
scripts/csdn_delete.py # 一键删除文章
scripts/csdn_wechat_login.py # 仅登录
scripts/inject-content.js # 内容注入 JS
python scripts/csdn_publish.py article.md --title "标题" --article-id 160913376
pip install playwright -i https://pypi.org/simple/
playwright install chromium
| 问题 | 解决 |
|---|---|
| ------ | ------ |
storage_state 过期 | 删掉 ~/.hermes/credentials/csdn-storage-state.json,重新运行自动登录 |
| modal 弹不出 | quality-score 拦截没生效,检查 route 是否匹配 |
| 发布按钮点不了 | 弹窗被遮罩挡住,用 JS element.click() 而不是 Playwright 的 click() |
| 微信二维码没人扫 | 5 分钟超时,重新运行脚本即可 |
| 保存 API 401 | 缺少 storage_state(localStorage 网关令牌),重新扫码登录 |
| 发布后 status=404 | 文章在审核队列中,等几分钟再查 |
发布失败 — 控制台打印 {status: false, text: "请设置文章标签"} | ✅ 已解决! 见上方「标签操作细节」。要点右侧面板的 el-tag 芯片(非左侧分类 tab),并且关闭面板后才能点发布 |
> 核心原则:所有图片必须上传到 CSDN 图床(i-blog.csdnimg.cn),不允许直接引用外部 URL。
>
> 发布脚本已内置自动处理:扫描 Markdown → 下载网络图片 → 上传 CSDN 图床 → 替换 URL。
> 全程自动化,无需手动操作。
# 直接运行,脚本会自动处理所有图片
python scripts/csdn_publish.py article.md --title "标题"
脚本自动完成:
!alt 提取所有图片/tmp/csdn_images/| 参数 | 作用 |
|---|---|
| ------ | ------ |
| 默认 | 自动处理所有图片 |
--no-images | 跳过图片处理(纯文字文章节省时间) |
--only-images | 仅上传图片到 CSDN 图床,不发布文章(获取 URL 用) |
# 核心操作(Playwright)
page.locator('.uploadPicture input[type="file"]').set_input_files(str(local_path))
# 上传后 CSDN 自动插入 → 
# 已就绪 ✅
python scripts/csdn_delete.py --article-id 160926527
| 要求 | 说明 |
|---|---|
| ------ | ------ |
| H1 标题 | 不写在正文里,由 --title 参数填入编辑器标题栏 |
| 正文起始层级 | 从 # 开始 |
| 小标题格式 | 全部用 加粗(正文用 ## 标题 渲染,但 Markdown 里加 **) |
| 层级递减 | ## → ### → ####,不要跳级 |
| 列举内容 | 优先用无序列表 - 或有序列表 1. |
| 段落长度 | 每段 2-4 句,单句成段用于强调 |
| 语言风格 | 口语化、第一人称、短段落、有观点 |
zhihu-publisher — 知乎发布hot-topic-research — 热点调研content-planner — 选题决策publish-orchestrator — 编排调度共 1 个版本