Blogger 主题 XML 自动上传 — 使用 Playwright + 9222 CDP 实现 0 次手动点击

2 min read · 414 words

实用技巧 / 博客运营 / Python · 自动化
约 2,500 字

Blogger 的一个缺点是,每次修改主题 XML 时,在管理后台 UI 中点击“主题 → 备份/还原 → 上传”需要 3~4 次点击 + 登录确认 + 等待应用,这一套流程下来每次要花将近 5 分钟。我们通过 Playwright + Chrome 9222 CDP 将其自动化,实现了 0 次人工点击。本文将详细介绍开发初衷、实际运行机制、效果以及验证方法。

开发初衷

在频繁修改主题的时期,一天可能需要修改 5~10 次。改一行颜色、改一行字体粗细、改一行侧边栏位置,每次为了在前端查看效果都要花 5 分钟,这会严重打断工作流。

此外,Blogger 旧版的“还原主题”路径存在触发第一代经典模式(Classic mode,如之前的 BTP 网站)的开关,一旦不小心点错,整个网站的布局就会退回到旧版第一代。在经历过两次这种情况后,我们在代码中永久屏蔽了“还原”路径。唯一安全的路径是:直接在管理后台的“修改当前主题的源代码”界面(CodeMirror 编辑器)中,通过 setValue 直接写入整个 XML。

为了避免每次都手动操作这一流程,我们开发了一个自动化模块。

工作原理

核心机制是让 Playwright 连接到用户已经通过 9222 端口启动的 Chrome 浏览器(CDP attach)并发送指令。这样不需要启动新的浏览器实例,从而可以保持 Google 登录状态,自动绕过 CAPTCHA / 双重验证(2FA)。

步骤如下:

  1. 确认 Chrome 9222 是否存活 — 如果 http://127.0.0.1:9222/json/version 响应为 200,则表示存活。如果未启动,则向用户发送“请开启 9222 Chrome”的通知(通过 Discord)。自动启动则根据操作系统进行分支处理。
  2. Playwright CDP attach — 仅需一行代码 playwright.chromium.connect_over_cdp("http://127.0.0.1:9222")。无需打开新浏览器。
  3. 寻找目标标签页 — 如果已有打开的标签页匹配 blogger.com/blog/themes/,则直接跳转过去;否则新建一个标签页。
  4. 点击“修改当前主题的源代码”按钮 — 使用 aria-label 或文本选择器(selector)。同时支持韩语/英语两种语言环境(locale)。
  5. 等待 CodeMirror 编辑器激活 — 轮询直到 document.querySelector('.CodeMirror') 出现。
  6. 调用 CodeMirror.setValue(xml) — 在 Playwright 的 evaluate 中直接调用。不是通过剪贴板或模拟输入。即使是 50KB 的 XML 也能瞬间写入。
  7. 点击“保存主题”按钮 — 保存后,当弹出“主题已更新”的提示(toast)时即表示成功。
  8. 线上抽样检查(Spot check) — 等待 60 秒后 fetch https://blog-url/。如果响应中包含 theme.xml 的哨兵注释(sentinel comment,例如 ),则 verified=True

全过程平均耗时 30 秒。人工点击 0 次。

实际效果

  • 单次上传时间:5 分钟 → 30 秒(缩短 90%)
  • 累计自动上传次数:启用后约 320 次
  • 因误点“还原”路径导致退回到第一代经典模式的事故:引入前 2 起 → 引入后 0 起
  • 失败案例:9222 Chrome 崩溃 4 起 / Google 登录过期 2 起 / 验证码(CAPTCHA)1 起。均通过通知人工进行手动恢复。
  • 备份:每次上传前自动保存 theme.xml.before.。如果上传出错,可在 1 秒内回滚到上一个版本。

附加效果是,进行“改一行主题试试看”这种轻量级尝试的频率增加了。如果每次要花 5 分钟,难免会犹豫;但如果是 30 秒,就会毫不犹豫地去尝试。结果就是设计迭代的速度变快了。

验证方法

我们进行了三种验证。

XML 往返(Round-trip)验证 — 上传后,在同一页面的 CodeMirror 中再次调用 getValue(),确认该值是否与我们发送的 XML 逐字节(byte-by-byte)完全一致。这是为了捕获 Blogger 的 SkinVariables 解析器静默拒绝(silent reject)的情况(例如 CDATA 中含有原始 HTML 标记等)。在 320 次中,共捕获了 5 起静默拒绝案例。

线上抽样检查 — 上传后等待 60 秒,然后 fetch 线上网站。确认响应中是否包含 theme.xml 的哨兵标记。320/320 全部通过。

防止退回第一代经典模式测试 — 通过单元测试确认,如果我们的自动化程序试图故意点击“还原”按钮,是否会在代码层面被拦截。在 assert "restore" not in click_targets 中始终通过,拦截钩子(hook)正常工作。

打造方法

整个模块比较庞大,实际应用中只需提取以下两个核心步骤即可。

首先,在 9222 端口启动 Chrome。


# Windows
"C:\Program Files\Google\Chrome\Application\chrome.exe" \
 --remote-debugging-port=9222 \
 --user-data-dir=C:\chrome_debug_profile

# macOS
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
 --remote-debugging-port=9222 \
 --user-data-dir=$HOME/chrome_debug_profile

然后,使用 Playwright 进行连接(attach)。


import asyncio
from playwright.async_api import async_playwright

BLOG_ID = "1234567890"
THEME_XML = open("theme.xml", encoding="utf-8").read()

async def upload_theme(xml: str):
 async with async_playwright() as p:
 browser = await p.chromium.connect_over_cdp("http://127.0.0.1:9222")
 ctx = browser.contexts[0]
 page = await ctx.new_page()
 await page.goto(f"https://www.blogger.com/blog/themes/{BLOG_ID}")
 # "修改当前主题的源代码"
 await page.click("text=현재 테마의 소스 코드 수정")
 await page.wait_for_selector(".CodeMirror", timeout=30000)
 await page.evaluate(
 "(xml) => document.querySelector('.CodeMirror').CodeMirror.setValue(xml)",
 xml,
 )
 await page.click("button:has-text('테마 저장')")
 await page.wait_for_selector("text=테마가 업데이트되었습니다", timeout=60000)
 await browser.close()
 print("uploaded")

asyncio.run(upload_theme(THEME_XML))

这里的核心就是一行 connect_over_cdp 和一行 CodeMirror.setValue。其余的工作只是微调选择器(selector)。

千万不要点击还原路径。同一页面上也有“还原”按钮,这就是我们之前发生事故的原因。一旦点击,就会退回到第一代经典模式,导致线上网站崩溃。在自动化脚本中,最好在代码层面显式屏蔽该选择器,这样更安全。

总结:只需在 9222 端口启动 Chrome,用 Playwright 连接,然后调用一行 CodeMirror.setValue,即可实现 Blogger 主题的自动上传。完成首次设置后,之后的所有更新都可以通过一行命令自动完成。

Category Coverage Notice

This article follows our label-specific editorial criteria. Details:

ToolSignal Pro Editorial

ToolSignal Pro는 AI·IT·소프트웨어 트렌드를 다루는 종합 IT 인사이트 매거진입니다.

이전 글 다음 글