北海的鱼 - 建站心得
https://blog.qsuu.cn/tag/%E5%BB%BA%E7%AB%99%E5%BF%83%E5%BE%97/
-
想让 AI 代发文章?Typecho 后台自动化对接踩坑实录
https://blog.qsuu.cn/archives/AI-Typecho.html
2026-10-10T15:23:25+08:00
最近在折腾一件事:让 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-coreplaywright-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() : '?');
});几条安全提醒折腾完回看,这几条比技术细节更重要:密码不要写进任何笔记或脚本。登录一次拿到 state.json 就够了,后续全靠它state.json 等同于账号权限,别提交到公开仓库,也别随手发给别人发文章前先看一眼草稿。自动化发错分类、发错时间,删起来很烦采集来的内容要重写再发,直接搬运既有版权风险,也不利于收录文章里涉及自己站点的后台路径、账号名,一律用 example.com 之类替代小结三个坑其实指向同一件事:现代站点前面挡着 CDN,自动化得按真实浏览器的路子走。curl 走不通,无头浏览器走不通,用本机浏览器 + 有头模式反而最省事。真正跑通之后,体验是值得的:内容整理、排版、配图、发布一气呵成,人只做最后的判断。这条路走顺了,后面就是批量化的事了。