🔥 3天攻克 CSDN 自动发布:从 0 到 1 的完整实战记录

“自动化就是为了偷懒,但偷懒也需要技术!”——这是我开发 CSDN 自动发布工具的初衷。

在这里插入图片描述

📖 目录


背景故事

作为一个技术博主,我经常需要发布文章到 CSDN。每次手动发布的流程是这样的:

  1. 打开 CSDN
  2. 点击"写博客"
  3. 粘贴标题和内容
  4. 点击"发布文章"
  5. 填写标签
  6. 上传封面图
  7. 点击"发布文章"(确认发布)

一套流程下来至少 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登录
2. 填写标题
3. 填写正文
4. 点击发布
5. 切换焦点
6. 添加标签
7. 填写摘要
8. 生成封面
9. 点击上传
10. 上传图片
11. 确认上传
12. 等待上传
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 选择器容易失效。

解决

  1. 使用 Playwright 文本定位器:get_by_text("按钮文字")
  2. 使用 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 内容生成
  • 支持文章排版优化
  • 添加数据分析功能

总结

这次开发经历让我深刻体会到:

  1. 自动化测试的难点在于 UI 的不确定性 - 元素会变、遮挡会出现、弹窗会干扰
  2. 调试工具的重要性 - 截图、视觉识别是解决问题的关键
  3. 文档的价值 - 详细记录每个坑,避免重复踩坑

最重要的是:自动化是为了解放双手,但实现自动化本身需要投入大量精力。不过,一旦成功,那种成就感是无价的!


📚 完整代码

完整代码欢迎评论、点赞后私信联系,包括:

  • 核心发布器:csdn_publisher.py
  • 封面图生成器:csdn_cover_generator.py
  • 技能文档:SKILL.md

🙏 致谢

感谢以下开源项目:


如果这篇文章对你有帮助,欢迎点赞、收藏、关注! 👍

你有没有遇到过类似的自动化难题?欢迎在评论区分享你的经验! 💬

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐