想让 AI 代发文章?Typecho 后台自动化对接踩坑实录

最近在折腾一件事:让 AI 助手直接登录博客后台,帮我整理并发布文章。想法很朴素——内容采集、改写、排版都交给它,我只负责最后确认。

真动手才发现,这条路比想象中绕。前后踩了三个坑,记录一下,省得你重走。

第一个坑:curl 登录,看起来成功其实是假的

最直觉的做法是命令行模拟表单提交。Typecho 的登录表单长这样:

<form action="https://example.com/index.php/action/login?_=xxxx" method="post">
  <input type="text" name="name" />
  <input type="password" name="password" />
  <input type="hidden" name="referer" value="https://example.com/admin/" />
</form>

于是:

curl -c cookie.txt -L "https://example.com/admin/" -o login.html
TOKEN=$(grep -oE 'login\?_=[a-zA-Z0-9]+' login.html | head -1 | cut -d= -f2)
curl -b cookie.txt -c cookie.txt -L \
  --data-urlencode "name=你的账号" \
  --data-urlencode "password=你的密码" \
  --data-urlencode "referer=https://example.com/admin/" \
  "https://example.com/index.php/action/login?_=$TOKEN"

诡异的是,返回 302 且 Location 指向后台——看着像成功了。但一访问后台,还是登录页。

关键判断点在这里:

curl ... -D - -o /dev/null "..." | grep -i "set-cookie"

如果响应头里没有 Set-Cookie,那就是没登录成功,不管 Location 跳去哪。Typecho 登录成功一定下发 __typecho_authCode。

为什么失败?看完整响应头就能发现线索:

EO-Cache-Status: MISS
EO-LOG-UUID: 17057971157060924413

站点在腾讯云 EdgeOne 这类 CDN 后面,非浏览器请求在会话层就被处理掉了,压根到不了 PHP。结论:curl 这条路对这类站点不通,别浪费时间调参数。

第二个坑:浏览器工具下载 Chromium 会超时

既然要真实浏览器,那就装自动化工具。agent-browser 这类 CLI 有个 install 子命令负责拉 Chromium,约 198MB。

实际结果:

40/198 MB (20%)
Retrying download (attempt 2/3)
Retrying download (attempt 3/3)
✗ Download error: operation timed out

国内网络拉 Google 的 chrome-for-testing 源,超时是常态。不要在这步死等。

第三个坑:headless 模式也会被拦

绕过下载的办法:你机器上大概率已经有 Chrome 或 Edge,直接拿来用。

npm install playwright-core

playwright-core 不含浏览器,只有驱动,几 MB,秒装。然后指向本机 Chrome:

const browser = await chromium.launch({
  executablePath: 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
  headless: true
});

结果访问后台直接 30 秒超时。改成 headless: false 立刻正常——同样的 cookie、同样的 URL。CDN 对无头浏览器同样有识别。

最终能跑通的方案

const { chromium } = require('playwright-core');

(async () => {
  const browser = await chromium.launch({
    executablePath: 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
    headless: false,
    args: ['--disable-blink-features=AutomationControlled']
  });
  const ctx = await browser.newContext({
    viewport: { width: 1280, height: 860 },
    userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36'
  });
  const page = await ctx.newPage();

  await page.goto('https://example.com/admin/', { waitUntil: 'domcontentloaded' });
  await page.fill('#name', '你的账号');
  await page.fill('#password', '你的密码');
  await page.check('#remember');          // 勾选记住登录,cookie 有效期更长
  await page.click('button[type="submit"]');
  await page.waitForTimeout(5000);

  console.log(await page.title());        // 输出「网站概要」即为成功
  await ctx.storageState({ path: 'state.json' });   // 保存登录态
  await browser.close();
})();

跑完看到标题变成「网站概要 - ...」,就算进去了。

storageState 这一步很值钱:登录态存成 state.json 后,后续所有脚本都这样复用,不用再输一次密码:

const ctx = await browser.newContext({ storageState: 'state.json' });

发布文章:先摸清表单字段

不同主题的写作页字段差异不小(特别是加了自定义字段的主题)。先探测再填,别猜:

document.querySelectorAll('input[name], select[name], textarea[name]').forEach(el => {
  console.log(el.tagName, el.name, el.type, el.id);
});

Typecho 原生字段一般是这些:

  • title 标题、text 正文(Markdown)、slug 别名
  • category[] 分类(checkbox,值是分类 ID)
  • tags 标签、date 日期
  • visibility 可见性、allowComment 允许评论

带自定义字段的主题还会有 fields[excerpt](摘要)、fields[imgst](封面图)、fields[catalog](目录开关)这类,按需填。

填完点「发布文章」提交:

await page.fill('#title', '文章标题');
await page.evaluate(t => { document.querySelector('#text').value = t; }, 正文内容);
await page.check('#category-1');        // 勾选分类,注意是 ID 不是名字
await page.fill('#tags', '标签1,标签2');
await page.click('button:has-text("发布文章")');
await page.waitForTimeout(5000);

摘要里那句「注意是 ID 不是名字」是血泪:分类复选框的 id 是 category-1 这种,对应的是分类 ID,得先遍历一遍把 ID 和名称对应上:

document.querySelectorAll('input[name="category[]"]').forEach(el => {
  const lab = document.querySelector(`label[for="${el.id}"]`);
  console.log(el.value, lab ? lab.textContent.trim() : '?');
});

几条安全提醒

折腾完回看,这几条比技术细节更重要:

  1. 密码不要写进任何笔记或脚本。登录一次拿到 state.json 就够了,后续全靠它
  2. state.json 等同于账号权限,别提交到公开仓库,也别随手发给别人
  3. 发文章前先看一眼草稿。自动化发错分类、发错时间,删起来很烦
  4. 采集来的内容要重写再发,直接搬运既有版权风险,也不利于收录
  5. 文章里涉及自己站点的后台路径、账号名,一律用 example.com 之类替代

小结

三个坑其实指向同一件事:现代站点前面挡着 CDN,自动化得按真实浏览器的路子走。curl 走不通,无头浏览器走不通,用本机浏览器 + 有头模式反而最省事。

真正跑通之后,体验是值得的:内容整理、排版、配图、发布一气呵成,人只做最后的判断。这条路走顺了,后面就是批量化的事了。

打赏
评论区
头像