Playwright 无头浏览器自动发布 CSDN 图文草稿的实践(持久化 profile + 图床直传)

目录

一、整体思路

二、登录:一次有头登录,长期无头复用

[三、正文:写 CKEditor 而不是写 DOM](#三、正文:写 CKEditor 而不是写 DOM)

[四、配图:本地图片怎么变成图床 URL](#四、配图:本地图片怎么变成图床 URL)

五、封面:候选只来自正文图片

六、保存草稿与状态码

七、小结


面向要做内容自动化的 Python 开发者。要解决的问题是:把本地写好的 markdown 图文文章自动送进 CSDN 创作中心成为草稿,并且不依赖人工值守浏览器------正文与封面的本地图片必须先换成图床地址,而不是留下打不开的本地路径。

一、整体思路

整条链路分两条独立的线:会话线负责登录态,内容线负责把文章写进编辑器。两者只在「打开编辑器页面」这一处交汇。

关键取舍有三个:

  • 登录态用持久化用户目录保存,而不是每次重放登录流程;
  • 正文写入走编辑器自己的 API(CKEDITOR.instances.editor.setData()),而不是直接改 DOM;
  • 保存动作由脚本点「保存草稿」,发布由人工确认,避免不可撤回的误发。

二、登录:一次有头登录,长期无头复用

CSDN 网页登录页默认只给「微信登录 / 验证码登录 / APP 登录」三个入口,密码表单要显式带参数才出现:

text 复制代码
https://passport.csdn.net/login?code=account

表单控件没有 id,用 autocomplete 属性定位最稳。提交按钮初始是 disabled,Vue 组件只认真实键盘事件,因此必须逐字输入:

python 复制代码
user = page.locator('input[autocomplete="username"]')
user.press_sequentially(username, delay=45)
page.locator('input[autocomplete="current-password"]').press_sequentially(password, delay=45)
page.locator(".login-form button.base-button").click()

登录成功的判据不能靠页面 URL。创作中心 mp.csdn.net 下的页面在未登录时也会正常渲染外壳、不跳转登录页 ,用 URL 或 DOM 判断会误判为已登录。实测可靠的信号是 .csdn.net 域下出现 UserName 与 UserToken 两个 cookie:

python 复制代码
def login_state(ctx) -> str:
    names = {c["name"] for c in ctx.cookies()}
    return "ok" if {"UserName", "UserToken"} <= names else "none"

首次登录用有头模式跑一次,遇到滑块或短信验证人工完成即可;之后 launch_persistent_context() 指向同一个用户目录,全程无头,不占用桌面。

三、正文:写 CKEditor 而不是写 DOM

新版创作中心的正文是 CKEditor 4,实例挂在 window.CKEDITOR.instances.editor。直接改 iframe 里的 body.innerHTML 不会被编辑器的数据模型感知,保存下来的可能是空文;用 setData() 才会同步内部状态:

python 复制代码
page.wait_for_function("()=>window.CKEDITOR&&CKEDITOR.instances.editor", timeout=40000)
page.evaluate("(h)=>CKEDITOR.instances.editor.setData(h)", html_body)

标题与摘要是普通 textarea,标签输入框要先点开「添加文章标签」再逐个输入并回车。按钮定位有个坑:页面上存在两个同名「保存草稿」元素,其中一个不可见,必须加可见性约束,否则点击会一直超时:

python 复制代码
page.locator("button:has-text('保存草稿')").locator("visible=true").first.click()

四、配图:本地图片怎么变成图床 URL

这是图文自动化最容易做错的一步。正文里写 ![](img/arch.png) 直接提交,文章里就是打不开的本地路径。

实测可用的上传链路是「粘贴即上传」。把本地图片读成字节,在页面里构造 File 与 DataTransfer,向编辑器可编辑区派发一次 paste 事件,编辑器自带的粘贴上传逻辑就会完成后续两步:先向 resource-api 申请直传签名,再把图片字节表单直传到对象存储,直传接口的响应里就带回图床地址。

脚本用 page.expect_response() 截住那个直传请求,从响应 JSON 里取 data.imageUrl:

python 复制代码
with page.expect_response(
    lambda r: r.request.method == "POST" and ".myhuaweicloud.com" in r.url,
    timeout=60000,
) as info:
    page.evaluate(PASTE_JS, {"b64": b64, "name": path.name, "mime": mime})
url = info.value.json()["data"]["imageUrl"]

返回的就是可以公开访问的地址:

text 复制代码
https://i-blog.csdnimg.cn/direct/86e801ae4db8436a97c5226dd207d675.png

拿到 URL 之后,映射关系按 markdown 里写的原始 src 字符串 建立,而不是按文件名后缀匹配------同名不同目录的两张图用后缀匹配会互相串图。替换完成后再 setData() 写入最终正文,粘贴过程中留下的临时 <img> 一并被覆盖掉。

这里还有一个容易踩的坑:过滤上传响应时不要写成「URL 同时包含 image 和 upload」。签名接口的地址同样满足这个条件,而它的响应体里没有 imageUrl,脚本会误判成上传失败并反复重贴,产生一堆重复图片。

五、封面:候选只来自正文图片

封面面板的候选项是 .img-selection-item,内容完全来自正文里已有的图片 ,而且要等正文 setData() 之后才刷新,所以封面这一步必须排在正文写入之后。点选候选图后出现「图片编辑 / 封面图预览 / 确认上传」,点确认,平台会把裁好的封面再传一次图床并回传一个新的封面专用地址。

python 复制代码
page.locator(".img-selection-item:has(img[src*='%s'])" % key).first.click()
page.locator("text=确认上传").locator("visible=true").first.click()

六、保存草稿与状态码

点「保存草稿」触发的是 POST https://bizapi.csdn.net/blog-console-api/v1/postedit/saveArticle,请求体里 status: 2 就是草稿态,文章页会标注「草稿」,草稿箱计数加一,不对外可见。

不要试图绕开页面直接调这个接口。bizapi.csdn.net 需要 Web 端签名头,脚本自己发只会得到:

json 复制代码
{"message": "X-Ca-Key is not exist"}

签名由前端按请求体计算,复刻成本高且随前端改版失效。因此保存走 UI 点击,再用 page.expect_response() 截获响应,从 data.article_id 与 data.url 拿到结果,既不用碰签名,又能稳定取回草稿链接。

七、小结

回看开头的问题:本地 markdown 图文能否自动进 CSDN 草稿,难点不在点击,而在三处判据要选对------登录态看 cookie 不看 URL,正文写编辑器实例不写 DOM,图片先拿图床 URL 再回写正文并按原始 src 建立映射。把这三件事做对之后,脚本可以稳定地产出带图带封面的草稿,而发布这一步始终留给人工,避免自动化误发。

另外两条实践建议:批量发文要串行执行并留出间隔,避免触发平台风控;页面改版时用探测脚本重采 DOM,把选择器集中放在脚本顶部常量里,维护成本会低很多。

标签 :Python Playwright CSDN 自动化 博客运营

背景说明:本文内容来自一次真实的工具链搭建过程,所有接口行为、选择器与状态码均在 2026 年 10 月的 CSDN 创作中心实测得到,示意图为作者自绘。文中不含账号、密钥等敏感信息;自动化操作第三方平台需自行遵守平台规则并承担相应风险。

相关推荐
jason.zeng@15022072 小时前
(六)Prompt 优化
python·ai·langchain·prompt·ai编程·llama
全栈练习生3 小时前
大模型推理全链路
python·ai
凌云若寒3 小时前
BarTender提示#807错误:无法在拥有其他许可证的Licensing Service上激活节点锁定的 Professional 版许可证 的解决办法
运维·服务器·前端·学习·软件需求
泡海椒3 小时前
JQuick-Excel VALIDATION 实战:用作用范围约束 Excel 导入校验
开发语言·python·excel
Julien20043 小时前
管理 Ansible 配置文件
linux·运维·服务器·ssh·学习方法
MicrosoftCloud3 小时前
性能排查 01|free 显示内存用了 90% 就是快满了吗?buff/cache 与 available 一次讲清
linux·运维·内存·free·buff/cache
灯澜忆梦4 小时前
【docker】#1 | Docker 初识
运维·docker·容器
风寄巴山秋4 小时前
OpenBMC:Web 页面功能异常排查
运维·服务器·前端·架构
小白的码BUG之路5 小时前
Jenkins -- 连接gitee
运维·gitee·jenkins