ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

用 Playwright 实现 CSDN 博客自动发布:完整思路与代码实战

用 Playwright 实现 CSDN 博客自动发布:完整思路与代码实战 以前手动在 CSDN 发一篇博客得登录、打开创作中心、粘贴 Markdown、等编辑器渲染、填标题标签、选封面分类最后还要小心翼翼地点“发布”整套流程走下来怎么也得几分钟。我就是嫌这个操作太重复干脆用 Playwright 自动化工具写了个脚本把自己写的 Markdown 文档直接“扔”进去脚本自动完成发布。这篇文章我会把完整思路、代码、踩坑记录都放出来适合有一定 Python 基础、想用 Playwright 提升博客更新效率的开发者参考也欢迎对博客自动化发布感兴趣的新手直接照着抄作业。先说一个我的核心观点Playwright 做这种事不是简单的“录制回放”它更像给浏览器装上了一个可控的“机械臂”。你不仅能模拟点击输入还能控制登录态、等待条件、并发页面甚至能在发布失败时自动截图留证。这份实战指南会从环境搭建、核心脚本、问题排查一直聊到批量发布场景把我在真实使用中遇到的细节全部摊开讲。1. 为什么选择 Playwright 而不是自己硬点1.1 手动发布博客的真实痛点写博客的人都清楚真正耗时间的不是敲字而是把内容搬运到平台上的过程。我自己的固定流程是本地用 Markdown 写好文章然后打开浏览器登录 CSDN新建文章把 Markdown 全文粘贴进去等编辑器把代码块和图片渲染好再补标题、摘要、标签、封面最后点发布。看起来每个步骤都不难但一旦涉及批量更新旧博客、修改历史文章或者同时维护多个平台这个流程就会变成纯粹的体力活。还有一个很多人容易忽略的痛点人的操作存在随机误差。手动发布时偶尔会漏填标签或者选了错误的文章类型甚至因为不小心按到刷新键导致草稿丢失。我最初想做一个自动化脚本不仅是想省时间更想让发布过程变得稳定可控每次操作都按照既定顺序执行减少人为失误。1.2 Playwright 的三个关键设计市面上能做浏览器自动化的工具不少但我在项目里选择 Playwright是因为它的三个设计确实解决了实际问题。第一它带有一套完整的自动等待机制。Playwright 的元素定位方法默认会等待元素出现、可见、可操作而不是像早期方案那样靠固定 sleep 硬等。这意味着当你把文章正文塞进编辑器后不需要额外猜测“渲染要多久”元素操作自然会等到条件满足。第二它支持持久化登录上下文。通过 storage_state 把登录态保存下来下次运行可以直接复用不用每次启动都人工扫码或输密码。第三它的调试体验很舒服Playwright Inspector 能逐步查看每一行代码对应的操作配合录制功能可以很快摸清页面结构。如果你想快速验证一个思路也可以用 Playwright 自带的代码生成器录制一段操作再手动改成稳定的脚本。但我建议不要只依赖录制因为录制的选择器往往不够健壮页面一改就失效最好还是理解页面结构后再调整。1.3 自动化发布前先想清楚边界自动化发布博客这件事技术难度并不高但使用界限要想清楚。我自己的原则是脚本只做“替代人工重复操作”这件事不搞任何绕过平台策略、批量注册、恶意刷量之类的行为。像 CSDN 这类内容平台本身是鼓励作者持续创作的自动发布自己的原创文章本质上是把投稿动作自动化这是合理场景。但在实际运行中平台可能会因为“疑似机器人操作”而弹出验证码或行为验证。遇到这种情况我建议脚本做出明确感知暂停流程并提示人工处理而不是假装看不懂继续硬来。毕竟自动化的目的是辅助人不是挑战平台规则。理解了边界之后我们再来看具体怎么搭环境。2. 环境准备从零搭出一套能跑的自动化环境2.1 Python 虚拟环境与依赖安装我默认使用 Python 3.9 以上的版本因为 Playwright 的 Python API 在更高版本上兼容性更好。首先建议建一个虚拟环境避免依赖污染系统 Python。python -m venv venv source venv/bin/activate # Windows 下用 venv\Scripts\activate pip install playwright安装完成后需要把浏览器内核下载下来。这一步我踩过一个坑如果直接运行playwright install chromium在国内网络环境下可能下载很慢。我一般会先配置镜像变量再执行下载速度会快很多。注意这里只装免费的 Chromium 内核就够用了不需要下全量浏览器包。2.2 安装 Chromium 内核的两种方式如果你希望一次性安装默认浏览器直接执行playwright install chromium如果想控制下载速度或离线安装可以设置PLAYWRIGHT_DOWNLOAD_HOST环境变量。离线场景下让运维同事先在有网环境下载好浏览器包再拷贝到目标机器解压也是可行的。安装完成后可以用一段非常简单的代码验证环境是否正常from playwright.sync_api import sync_playwright with sync_playwright() as p: browser p.chromium.launch(headlessFalse) page browser.new_page() page.goto(https://www.csdn.net/) print(page.title()) browser.close()这里我特意设置了headlessFalse因为第一次跑自动发布脚本时最好亲眼看到浏览器做了什么检查元素定位是否准确。等脚本稳定了再考虑无头模式。2.3 用 storage_state 保存登录态CSDN 登录一般涉及账号密码登录也有扫码登录。如果每次都让脚本输入账号密码不仅容易触发安全验证还要在脚本里保存明文密码非常不妥。更稳妥的做法是先用浏览器手动登录一次然后把登录凭证保存到本地文件以后每次脚本启动都加载这份凭证。手动登录时需要打开一个持久的浏览器上下文我一般会写一个小工具from playwright.sync_api import sync_playwright with sync_playwright() as p: browser p.chromium.launch(headlessFalse) context browser.new_context(storage_statelogin.json) if Path(login.json).exists() else browser.new_context() page context.new_page() page.goto(https://passport.csdn.net/login) input(登录完成后回车保存登录态...) context.storage_state(pathlogin.json) browser.close()注意storage_state保存的是 cookie 和 localStorage 内容属于敏感信息不要把login.json提交到 Git 仓库建议在.gitignore中忽略。后续运行发布脚本时直接加载这个文件就能保持登录状态。2.4 用 Codegen 快速获取元素定位拿到登录态后我们就可以开始定位 CSDN 编辑器里的标题输入框、正文编辑区、标签输入框和发布按钮。手动一个个去 DevTools 里看选择器太麻烦Playwright 提供了 codegen可以边操作边生成定位代码。playwright codegen --load-storagelogin.json https://editor.csdn.net/md/?not_checkout1运行之后会自动打开一个浏览器窗口和一个代码生成面板。你在浏览器里点击输入框、填写内容、切换按钮生成面板就会实时推荐不同的定位策略包含placeholder、>from pathlib import Path def parse_markdown(md_path: str): content Path(md_path).read_text(encodingutf-8) lines content.splitlines() meta {} body_lines [] in_front False for index, line in enumerate(lines): if index 0 and line.strip() ---: in_front True continue if in_front: if line.strip() ---: in_front False continue if : in line: key, value line.split(:, 1) meta[key.strip()] value.strip() else: body_lines.append(line) body \n.join(body_lines).strip() return meta, body解析时要注意正文中的 Markdown 内容必须原样保留尤其是代码块、引用和图片链接不要因为解析 front matter 而破坏换行符。我遇到过一个问题Windows 系统下读取文件要用utf-8编码否则中文会乱码所以要显式指定。3.3 打开创作中心并等待编辑器加载CSDN 的 Markdown 创作地址是https://editor.csdn.net/md/?not_checkout1。打开之后编辑器会异步加载直接去填写内容大概率会失败。这里要用 Playwright 的显式等待例如等待编辑区域出现或者等待某个文本内容出现。我习惯先等页面标题包含“CSDN”或者等编辑器容器可见。page.goto(https://editor.csdn.net/md/?not_checkout1) page.wait_for_selector(.editor-wrapper, timeout30000)3.4 填入标题、正文与标签CSDN 编辑器里标题框是一个明显的 input 区域正文则是一个可编辑的容器类似于 contenteditable。填入正文时不能用fill直接给 div 赋值而是需要先点击编辑区再通过键盘输入或者模拟粘贴。我这里推荐用剪贴板粘贴的方式因为 Markdown 正文很长逐字输入会很慢而且容易被浏览器输入限制打断。实现思路如下import pyperclip pyperclip.copy(body_markdown) editor.click() page.keyboard.press(ControlOrMetaV)关于粘贴Playwright 支持ControlOrMeta这种写法在 Windows/Linux 下会自动变成 Control在 macOS 下变成 Meta省去自己判断系统的麻烦。粘贴后编辑器会自动解析 Markdown但渲染会有延迟所以需要等一下再继续操作。标签输入框通常是一个可以输入关键词后回车确认的组件。我一般这样做tag_input page.locator(input.ant-select-search-field) for tag in tag_list: tag_input.click() tag_input.fill(tag) tag_input.press(Enter)这里注意每输入一个标签后需要清空输入框内容或者等待组件自动复位。如果标签重复平台可能会忽略但我还是会提前去重。3.5 处理文章概要、分类与封面文章摘要也就是 description 字段在编辑页下方有“摘要”文本框。通常可以直接fill它是一个真正的 input 或 textarea。分类一般是下拉框或级联选择器点击后可能需要再选择“技术分享”或者自定义分类。不同版本的编辑器结构不太一样最好的办法是用 codegen 录制一次手动选择分类的过程观察点击后出现哪些 DOM 节点再替换成稳定的定位方式。封面图我一般不建议脚本自动上传因为涉及本地图片路径和上传控件的兼容性。更多时候我会提前在编辑页手动选好封面或者把封面图地址写在 front matter 里脚本只负责将 Markdown 中的图片链接保留原样。如果你确实要自动化上传封面需要注意 input[typefile] 的定位方式Playwright 的set_input_files可以直接传入本地路径但这块需要单独验证。3.6 点击发布与结果确认点击发布按钮前最好先确认标题、正文、标签、摘要都已经正确填写避免发布到半路却弹出一个“请补充标题”的校验框。发布按钮在不同状态下文本可能是“发布文章”或者“发布”所以我建议用文本匹配publish_btn page.get_by_role(button, name发布文章) publish_btn.click()点击后可能会出现一个确认弹窗里面包含文章可见性、发布时间等选项。如果有“确认发布”按钮就继续点击。发布成功后页面会跳转到文章详情页可以通过检查页面 URL 是否包含blog.csdn.net以及是否出现“文章发布成功”等提示来确认。3.7 完整脚本示例上面所有环节串起来就是下面这个可以直接参考的最小版本。我故意把异常处理和日志打印做得详细一点方便你调试。import time from pathlib import Path from playwright.sync_api import sync_playwright MARKDOWN_PATH post.md STORAGE_STATE login.json def parse_markdown(md_path: str): content Path(md_path).read_text(encodingutf-8) lines content.splitlines() meta {} body_lines [] in_front False for index, line in enumerate(lines): if index 0 and line.strip() ---: in_front True continue if in_front: if line.strip() ---: in_front False continue if : in line: key, value line.split(:, 1) meta[key.strip()] value.strip() else: body_lines.append(line) return meta, \n.join(body_lines).strip() def publish_to_csdn(meta: dict, body: str): with sync_playwright() as p: browser p.chromium.launch(headlessFalse, slow_mo200) context browser.new_context(storage_stateSTORAGE_STATE) page context.new_page() page.set_default_timeout(30000) page.goto(https://editor.csdn.net/md/?not_checkout1) page.wait_for_selector(.editor-wrapper) title_input page.locator(#title) title_input.fill(meta.get(title, 未命名文章)) editor page.locator(div.editor__inner) editor.click() page.keyboard.press(ControlOrMetaA) page.keyboard.press(ControlOrMetaV) time.sleep(2) tag_list [tag.strip() for tag in meta.get(tags, ).split(,)] for tag in tag_list: tag_input page.locator(input.ant-select-search-field) tag_input.click() tag_input.fill(tag) tag_input.press(Enter) abstract_input page.locator(textarea.abstract) abstract_input.fill(meta.get(abstract, )) publish_btn page.get_by_role(button, name发布文章) publish_btn.click() confirm_btn page.get_by_role(button, name确认发布) if confirm_btn.is_visible(): confirm_btn.click() page.wait_for_load_state(networkidle) time.sleep(3) print(发布结束当前页面:, page.url) browser.close() if __name__ __main__: meta, body parse_markdown(MARKDOWN_PATH) publish_to_csdn(meta, body)注意这段脚本中我用了time.sleep(2)实际上这是最后的兜底。更理想的做法是使用显式等待比如等待正文渲染完成、标签计数增加。不过在示例代码里保留一点 sleep 更容易跑通因为初次运行时不希望因为某个元素渲染状态判断错误而反复报错。我在实际使用中还会加入一步发布完成后立刻截图留档。只要在browser.close()前调用page.screenshot(pathresult.png)即使后续想复查发布效果也有图可看。4. 常见问题与排查技巧实录4.1 编辑器元素找不到或者超时这是我被问得最多的问题。最常见的原因是 CSDN 编辑器改版后某些类名和 ID 发生了变化。以前是#title改版后可能变成input#title或者其他结构。不要紧使用 codegen 重新录制一遍把新的定位方式替换进去即可。还有一个原因是编辑器加载过慢。Markdown 长文进入编辑区后代码高亮、图片预览、目录生成都会导致主线程卡顿。这时候如果去定位标签输入框可能还在渲染等待中。我的处理方法是先把超时时间调大一点比如page.set_default_timeout(60000)并且在进入标签操作前增加一个“等待编辑区不再有明显 loading”的判断。4.2 登录态失效与验证码弹窗登录态一般能维持很久但 CSDN 偶尔会强制重新登录。如果发现打开编辑器后跳转到登录页说明storage_state里的凭证失效了。这时不要硬写账号密码建议脚本检查当前 URL 是否包含passport.csdn.net如果包含就暂停提示人工扫码扫码完成后再保存新的login.json。验证码弹窗比较麻烦。我目前的做法是检测页面中是否出现验证码相关元素如果出现就等待用户手动处理。脚本里可以加一个轮询每两秒检查一次弹窗是否消失超过五分钟则放弃。不要尝试用图像识别去自动破解这既不稳定也容易触碰平台合规红线。4.3 标签输入偶尔空了标签输入框是 CSDN 编辑器里最容易出问题的组件之一。有时候fill(tag)后组件的值并没有真正被后端接收点击 Enter 后发现标签没有生成。我排查了很久发现是因为输入前没有点击到正确的可编辑区域或者输入太快组件还处于“延迟响应”状态。解决方式有两个一是在fill之前先点击输入框然后用page.keyboard.type逐字符输入替代一次性填充二是输入后等待某个“选中标签条”出现再判断是否进入下一个标签。这里推荐后者因为等待真实状态比盲目等待稳定得多。4.4 发布按钮点击后没有任何反应如果点击“发布文章”后页面没有跳转最常见的情况是某个必填项缺失比如标题为空、标签为空、摘要超出字数限制。CSDN 的提示可能是 toast 形式一闪而过不容易被观察到。为了定位问题可以在点击按钮前先把页面上的所有输入内容做一次检查打印出当前标题、标签数量、摘要长度等信息。还有一个隐蔽原因点击时按钮被其他浮层遮挡事件没有真正触发。Playwright 的click默认会检查元素可操作性但如果浮层在点击瞬间出现就可能出现“元素被拦截”的异常。对这种问题我一般用publish_btn.scroll_into_view_if_needed()先滚动到按钮位置再点击。4.5 错误速查表下面是我整理的速查表遇到问题可以先对照排查。问题表现大概率原因处理建议打开后跳登录页storage_state 过期重新运行登录保存脚本找不到标题输入框编辑器改版用 codegen 重新定位正文粘贴后无渲染剪贴板权限异常改为逐字输入或点击后手动粘贴标签无法添加输入太快/组件未复位等待标签条出现后再继续摘要填不进定位到隐藏元素确认 textarea 可见后再 fill点击发布无响应有必填项缺失发布前打印输入内容检查发布成功后 URL 异常确认弹窗被漏点显式等待并点击确认按钮这张表是我真实调试点记录下来的不同时间、不同页面版本可能表现不一样但排查思路是通用的。5. 实战经验批量发布、定时任务和更多玩法5.1 我踩过的几个坑第一个坑是“过度设计等待条件”。我最初把每个操作后面都写了wait_for_selector结果页面稍微慢一点就报错后来发现不是所有元素都需要显式等待只在关键节点等待就够了。Playwright 的 action 本身自带的等待已经覆盖大部分场景多余等待反而让脚本更脆弱。第二个坑是“把前端类名当接口用”。CSDN 前端偶尔会改组件的 class 名称这属于前端正常演进。我后来把定位器尽量改成基于文本和可见性比如get_by_placeholder、get_by_role这类定位方式比脆弱的类名稳定得多。第三个坑是“发布完成后马上关闭浏览器”。如果browser.close()执行太快后端可能还没完全保存文章导致偶尔文章丢了。我现在会在发布成功后等待网络空闲再额外等待 3 到 5 秒确保页面跳转完成。5.2 把脚本变成批量发布工具当你只有一篇文章时手把手点几下也没关系。但如果你有一堆 Markdown 笔记需要同步批量发布的价值就体现出来了。思路很简单遍历目录下所有.md文件逐个解析、逐个发布每次发布之间随机暂停几秒避免短时间请求频率过高。随机暂停要做得自然不完全固定。可以用random.uniform(3, 8)生成一个 3 到 8 秒的间隔。这样做不是为了规避什么而是给后端留出处理时间也降低自己在操作大量数据时触雷的概率。批量脚本还需要记录发布状态。我给每篇文章维护一个published_articles.json发布成功后写入时间戳下次运行直接跳过已发布的文件。这个状态记录对增量发布特别重要。5.3 结合本地文档库维护发布清单本地文章一般散落在多个目录有些是我自己的知识库有些是长期未整理的笔记。为了不让脚本乱发我加了一层“发布清单”机制在配置文件里手动指定这次要发布的文件列表或者筛选某个目录下带特定 front matter 标记的文章。例如只有 front matter 中published: false的文章才会被自动发布发布成功后脚本把该字段改成true。这种设计的好处是你不需要在命令行里修改脚本逻辑只需要编辑 Markdown 头部即可完成发布决策非常符合写博客的自然流程。5.4 关于自动化工具的边界与建议自动化发布脚本能帮你节省大量重复时间但我不建议把它当作一种“刷内容”的手段。内容质量永远是博客的核心工具只是把内容从本地送到平台的通道。我个人在运行批量发布时仍然会每篇文章人工检查一遍因为机器无法判断一篇文章是否真的适合公开发布也无法替代你对内容质量的判断。另外要留意平台对自动化操作的容忍度。不同平台对“自动登录、自动发布”这类行为的策略并不相同。建议把脚本频率控制在一个合理范围内不要在极短时间内发布几十篇文章也不要同时开多个浏览器实例反复操作。善用自动化的前提是尊重平台规则保护自己的账号安全。回到开头的问题为什么我用 Playwright 来做这件事因为它让我有更多时间打磨内容而不是消耗在重复点击上。整个方案跑稳之后我现在更新博客的流程变成本地写好 Markdown跑一遍发布脚本再打开网页检查一遍效果。两步就走完了以前十分钟的流程。如果你也想做类似的博客自动化发布工具我建议先从小范围脚本开始跑通一篇文章的发布再逐步完善状态管理、批量执行和异常处理这样踩坑成本最低也最容易长期维护。
返回列表