CSDN自动发布工具开发全纪录:从 0 到 1 的完整实战
🔥 3天攻克 CSDN 自动发布:从 0 到 1 的完整实战记录
“自动化就是为了偷懒,但偷懒也需要技术!”——这是我开发 CSDN 自动发布工具的初衷。

📖 目录
背景故事
作为一个技术博主,我经常需要发布文章到 CSDN。每次手动发布的流程是这样的:
- 打开 CSDN
- 点击"写博客"
- 粘贴标题和内容
- 点击"发布文章"
- 填写标签
- 上传封面图
- 点击"发布文章"(确认发布)
一套流程下来至少 5 分钟,如果还要生成封面图,那就更久了。
于是我想:能不能一键搞定?
技术选型
为什么选择 Playwright?
| 框架 | 优点 | 缺点 | 结论 |
|---|---|---|---|
| Selenium | 成熟稳定、社区活跃 | 速度慢、API 复杂 | ❌ |
| Puppeteer | 快速、原生 Chrome | 只支持 JavaScript | ❌ |
| Playwright | 快速、跨浏览器、API 优雅 | 相对较新 | ✅ 首选 |
Playwright 的杀手级特性:
- 自动等待元素
- 支持异步操作
- 强大的文件上传 API
- 可绕过元素遮挡
为什么选择 GLM-Image?
封面图是博客的"门面",但每次都要找图片很麻烦。GLM-Image 是智谱 AI 的图像生成模型,特点是:
- ✅ 中文 prompt 友好
- ✅ 生成速度快(5-10 秒)
- ✅ 质量不错
- ✅ API 简单
核心挑战与解决方案
挑战 1:两个"发布文章"按钮
问题:CSDN 页面有两个"发布文章"按钮:
- MD 编辑器的发布按钮
- 发布模态框的发布按钮
点击错误的按钮会失败或跳转。
解决:
# 查找所有发布按钮,选择模态框内的
all_buttons = await self.page.query_selector_all('button:has-text("发布文章")')
final_button = all_buttons[-1] # 最后一个是模态框内的
挑战 2:元素遮挡
问题:标签输入框遮挡了"从本地上传"按钮,无法直接点击。
解决:使用 JavaScript 强制点击
# ❌ 错误:会被遮挡
await button.click()
# ✅ 正确:强制点击
await button.evaluate('element => element.click()')
挑战 3:新标签页干扰
问题:点击"发布文章"后,CSDN 会同时:
- 打开发布模态框
- 打开富文本编辑器新标签页
焦点会切换到新标签页,导致后续操作失败。
解决:
# 强制切换回原页面
await self.page.bring_to_front()
挑战 4:文件上传
问题:Playwright 的 expect_file_chooser() 返回值处理容易出错。
解决:
# ❌ 错误
file_chooser = fc_info.value
# ✅ 正确
file_chooser = await fc_info.value # 需要 await!
挑战 5:图片上传等待
问题:点击"确认上传"后立即发布,图片还未上传完成。
解决:
await confirm_btn.click()
print("等待图片上传完成...")
await asyncio.sleep(5) # 等待 5 秒
完整工作流程
经过反复调试,最终实现了 13 步完整流程:
关键代码解析
1. Cookie 登录
async def load_cookies(self, cookie_file='csdn_cookies.json'):
"""加载 Cookie 登录"""
with open(cookie_file, 'r') as f:
cookies = json.load(f)
await self.context.add_cookies(cookies)
print("✓ Cookie 加载成功")
2. 自动生成封面图
async def generate_cover(self, title: str, tags: list) -> str:
"""使用 GLM-Image 生成封面图"""
# 构建配图描述
prompt = f"""
技术博客封面图,主题:{title}
关键词:{', '.join(tags)}
风格:科技感、简洁、专业
"""
# 调用 GLM-Image API
response = client.images.generations(
model="cogview-3",
prompt=prompt,
size="1280x720" # CSDN 封面图推荐尺寸
)
# 下载并保存
image_url = response.data[0].url
await self._download_image(image_url, save_path)
return save_path
3. 强制点击被遮挡元素
# 等待并捕获文件选择对话框
async with self.page.expect_file_chooser() as fc_info:
# 强制点击 - 绕过遮挡
await upload_button.evaluate('element => element.click()')
# 获取文件选择器(注意 await)
file_chooser = await fc_info.value
await file_chooser.set_files(cover_path)
4. 精准选择模态框按钮
# 查找所有发布按钮
all_buttons = await self.page.query_selector_all('button:has-text("发布文章")')
# 选择模态框内的按钮(最后一个)
if len(all_buttons) > 1:
final_button = all_buttons[-1]
print(f"✓ 找到 {len(all_buttons)} 个发布按钮,选择模态框内的")
踩坑记录
坑 1:CSS 选择器失效
症状:Timeout 30000ms exceeded
原因:CSDN 使用 Element UI,DOM 结构复杂,CSS 选择器容易失效。
解决:
- 使用 Playwright 文本定位器:
get_by_text("按钮文字") - 使用 GLM-4.6V 视觉识别辅助定位
坑 2:图片路径错误
症状:文件上传失败
原因:使用了相对路径,Playwright 无法找到文件。
解决:
# 使用绝对路径
absolute_path = Path(save_path).resolve()
await file_chooser.set_files(str(absolute_path))
坑 3:等待时间不足
症状:发布成功但无封面图
原因:点击"确认上传"后立即发布,图片还未上传完成。
解决:
await asyncio.sleep(5) # 等待图片上传完成
坑 4:元素未出现
症状:找不到"确认上传"按钮
原因:图片编辑模态框需要时间加载。
解决:
await asyncio.sleep(3) # 等待模态框出现
await self.page.screenshot(path='debug.png') # 截图调试
成果展示
成功案例
✓ Cookie 加载成功
✓ 找到编辑器
✓ 标题已填写:测试文章 - CSDN 自动发布工具演示
✓ 内容已填写
✓ 预览已生成
✓ 弹出窗口已打开
✓ 已切换到主页面
✓ 标签已添加:Python, 自动化, 工具
✓ 摘要已填写
✓ 封面图已生成
✓ 封面图已上传
✓ 已点击图片编辑模态框的确认按钮
等待图片上传完成...
✓ 已点击确认发布按钮
✓ 文章已正式发布!
核心数据
- 总开发时间: 3 天
- 代码行数: 570+ 行
- 核心函数: 15 个
- 调试截图: 5 张
- 成功发布: ✅
技术亮点
1. GLM-4.6V 视觉识别
当 CSS 选择器失效时,使用视觉识别辅助定位:
# 截图当前状态
await self.page.screenshot(path='current_state.png')
# 使用 GLM-4.6V 分析
response = client.chat.completions.create(
model='glm-4.6v',
messages=[{
'role': 'user',
'content': [
{'type': 'image_url', 'image_url': {'url': image_base64}},
{'type': 'text', 'text': '找到确认按钮的位置'}
]
}]
)
# 根据识别结果更新选择器
button_text = response.choices[0].message.content
2. 抗干扰机制
# 点击发布按钮后,强制切换回原页面
await self.page.bring_to_front()
3. 智能等待
# 关键操作后智能等待
await asyncio.sleep(1) # 模态框稳定
await asyncio.sleep(3) # 图片编辑器加载
await asyncio.sleep(5) # 图片上传完成
未来展望
短期优化
- 支持批量发布多篇文章
- 支持自定义封面图模板
- 添加发布失败重试机制
- 支持定时发布
长期规划
- 支持更多平台(知乎、掘金、博客园)
- 集成 AI 内容生成
- 支持文章排版优化
- 添加数据分析功能
总结
这次开发经历让我深刻体会到:
- 自动化测试的难点在于 UI 的不确定性 - 元素会变、遮挡会出现、弹窗会干扰
- 调试工具的重要性 - 截图、视觉识别是解决问题的关键
- 文档的价值 - 详细记录每个坑,避免重复踩坑
最重要的是:自动化是为了解放双手,但实现自动化本身需要投入大量精力。不过,一旦成功,那种成就感是无价的!
📚 完整代码
完整代码欢迎评论、点赞后私信联系,包括:
- 核心发布器:
csdn_publisher.py - 封面图生成器:
csdn_cover_generator.py - 技能文档:
SKILL.md
🙏 致谢
感谢以下开源项目:
如果这篇文章对你有帮助,欢迎点赞、收藏、关注! 👍
你有没有遇到过类似的自动化难题?欢迎在评论区分享你的经验! 💬
更多推荐

所有评论(0)