没有不值得去解决的问题,也没有不值得去学习的技术!

每天批量处理 20 篇 Mixed WordPress 历史文章:Classic 转 Gutenberg、SyntaxHighlighter 迁移、摘要补全与英文覆盖翻译完整 SOP

图 2:复杂 Classic 一次转换成多个 Gutenberg 区块

作者:

WordPress AI 翻译工程实战

WordPress 经典编辑器代码块无损迁移:批量转换 SyntaxHighlighter 短代码为 Gutenberg 区块

(1) WordPress 经典编辑器代码块无损迁移:批量转换 SyntaxHighlighter 短代码为 Gutenberg 区块

点击翻译按钮后,遇到一个弹窗“神秘”闪退问题及解决方法

(2) 点击翻译按钮后,遇到一个弹窗“神秘”闪退问题及解决方法

图5:DeepL API 注册页面,国家或地区列表中没有中国大陆

(3) 中国大陆 WordPress 博客想提升英文翻译质量,为什么这么麻烦?一次 Polylang + AutoPoly + DeepL API 排查复盘

图5:DeepSeek 的评分结果截图

(4) Chrome Built-in AI、Yandex Translate、ChatGPT Plus 翻译质量对比:我为什么决定重点英文文章改用 ChatGPT Plus

[截图 2:浏览器 Network 中直接请求 translate.yandex.net]

(5) 从 AutoPoly 到 Yandex 网页版:一次 WordPress 英文自动翻译质量验证

【图1:AutoPoly Free 与 Pro 功能对比截图】

(6) AutoPoly Pro + OpenAI API 能否自动化 WordPress 英文翻译?一次从技术到支付的可行性分析

【图2:点击“立即试用”后,ChatGPT 输入框中出现“代理模式”】

(7) ChatGPT Agent 能否替代 ChatGPT Plus 完成 WordPress 英文重译?

【图 7,SlyTranslate 显示翻译完成并创建文章 19426】

(8) SlyTranslate + 智谱 GLM-5.2 长文翻译排障:从 600 秒超时到完整生成英文 WordPress 文章

【图 5,54 个代码块精确匹配、差异数量为 0】

(9) WordPress SlyTranslate + GLM-5.2 长文单次翻译实战:排查 invalid_translation_runaway_output 假失败

【图 2,SlyTranslate 的 Additional Instructions 保持为空】

(10) SlyTranslate + GLM-5.2 翻译质量继续优化:从关闭随机采样、内置提示词到深度思考与模型 A/B 测试

【图6,终端中的完整测试结果】

(11) 在 GLM 5.2 之后测试 DeepSeek:一次面向 WordPress 长文翻译的模型对比与取舍

【图 4,“英文博客翻译质量优化”项目中已经整理完成的会话列表】

(12) 为英文博客翻译质量优化建立独立 ChatGPT 项目:整理历史会话并统一后续评测标准

【图3:Gemini 3.5 Flash 首次成功调用结果】

(13) 暂缓 OpenAI 后,我在本地测试了 Gemini:与 GLM 5.2 的 WordPress 技术文章翻译对比

图3:清理英文 Host 下的对象与术语关系缓存后,英文文章重新显示 SEO Tools、Yoast SEO 和 Blog SEO Log

(14) 为 SlyTranslate 的 GLM 5.2 整篇翻译补上 Plaintext 智能处理,并修复 Code Block Pro 区块失效

图5:英文文章发布后,前台特色图片、分类、标签和 Series 顺序均正常

(15) SlyTranslate + GLM-5.2 翻译 WordPress 长文章后,分类、标签、Series 与特色图片同步问题排查

图1:Codex 输出 Token 校验诊断结果

(16) SlyTranslate 全文翻译报错排查:完善 GLM 5.2 的段落与占位符约束

图1:创建 wordpress-ai-translation-pipeline 私有 GitHub 仓库

(17) 将 WordPress AI 翻译优化项目提交到 GitHub:一次阶段性的收尾

图1:文章列表中因摘要为空而重复出现两个 Post Views

(18) 从空摘要、历史格式混杂到重新翻译:我如何规划 WordPress AI 翻译工程的下一阶段

图4:最终批处理输出 42/42 完成、待处理 0、异常状态 0。

(19) 从只读审计到 42/42 完成:用 GLM 与 SlyTranslate 安全补全 WordPress 历史文章摘要

图3:GitHub 已正确识别仓库的 MIT License。

(20) 将 WordPress AI 翻译优化仓库从私有切换为公共:从敏感信息审计到最小改动发布

图1:仓库显示所有历史批次已经完成,并允许创建下一批。

(21) 每天批量处理 20 篇 WordPress 历史文章:代码块迁移、摘要补全与英文覆盖翻译完整 SOP

图 2:复杂 Classic 一次转换成多个 Gutenberg 区块

(22) 每天批量处理 20 篇 Mixed WordPress 历史文章:Classic 转 Gutenberg、SyntaxHighlighter 迁移、摘要补全与英文覆盖翻译完整 SOP

上一篇我整理了:

《每天批量处理 20 篇 WordPress 历史文章:代码块迁移、摘要补全与英文覆盖翻译完整 SOP》

那一阶段主要处理的是已经属于 Gutenberg 格式、但正文中仍然存在 SyntaxHighlighter 的历史文章。

完成这一阶段以后,我发现站内仍然有不少中文历史文章包含 SyntaxHighlighter。

进一步检查后确认,这些文章并不是此前漏扫,而是属于另一类历史格式:

Plaintext
Gutenberg 区块
+
Classic / 经典编辑器内容
+
SyntaxHighlighter

分析器将它们识别为:

Plaintext
editor_format = mixed

此前 Gutenberg + SyntaxHighlighter 阶段只处理 editor_format=gutenberg 的文章,因此这些 Mixed 文章被留到了下一阶段。

在历史快照中,共有 717 篇 Mixed 候选,其中 712 篇的主要问题就是:

Plaintext
editor-format-mixed

另外 5 篇还同时存在其他代码格式或结构异常,暂时单独保留。

因此我开始第二阶段:

Plaintext
Mixed Gutenberg + SyntaxHighlighter
→ Classic / Mixed 内容规范化为 Gutenberg
→ SyntaxHighlighter 转 Code Block Pro
→ 核对代码语言
→ 生产只读验证
→ 自动生成中文摘要
→ 自动覆盖英文译文
→ completed

整体流程继续沿用上一篇已经熟悉的操作方式,只增加 Mixed 阶段真正需要的步骤。


一、两个仓库分别负责什么

整个流程仍然涉及两个仓库。

1. WordPress 翻译管线

Plaintext
/home/wangqiang/code/wordpress-ai-translation-pipeline

主要负责:

  • SlyTranslate 与 GLM 翻译定制;
  • Gutenberg 结构保护;
  • HTML、代码和短代码保护;
  • 占位符校验;
  • 英文整篇覆盖翻译;
  • WordPress 生产环境相关 MU 插件。

2. 历史文章迁移仓库

Plaintext
/home/wangqiang/code/wordpress-ai-excerpt-backfill

主要负责:

  • 管理历史候选;
  • 创建固定批次;
  • 保存中英文文章关系;
  • 管理人工转换状态;
  • 执行生产只读验证;
  • 生成中文摘要;
  • 调用现有英文覆盖翻译入口;
  • 保存执行证据;
  • 对偶发错误进行有限重试;
  • 汇总整个批次状态。

整体职责仍然保持:

Plaintext
人工处理文章格式
→ wordpress-ai-excerpt-backfill 验证和调度
→ wordpress-ai-translation-pipeline 完成英文覆盖翻译

二、人工和仓库的职责边界

仓库负责

Mixed 阶段由仓库负责:

  • 从历史数据中筛选符合条件的 Mixed + SyntaxHighlighter 文章;
  • 排除已经进入旧批次或已经完成的文章;
  • 按发布时间从新到旧排序;
  • 每批最多固定 20 篇;
  • 保存中英文文章 ID;
  • 保存原 SyntaxHighlighter 数量;
  • 管理人工转换状态;
  • 验证最终文章是否已经成为规范 Gutenberg;
  • 验证 SyntaxHighlighter 是否归零;
  • 验证 Code Block Pro;
  • 自动生成中文摘要;
  • 自动覆盖已有英文译文;
  • 单篇失败有限重试;
  • 汇总整个批次最终状态。

人工负责

人工负责:

  • 打开仓库指定的 20 篇文章;
  • 将 Classic / Mixed 内容转换为 Gutenberg;
  • 检查自动转换后的正文排版;
  • 必要时重新拆分段落、标题、编号等;
  • 将 SyntaxHighlighter 转换成 Code Block Pro;
  • 核对 Code Block Pro 的语言;
  • 检查图片、caption、链接和正文结构;
  • 保存中文文章;
  • 最后确认这批人工处理已经完成。

人工不需要:

  • 自己寻找下一批文章;
  • 自己按发布时间排序;
  • 手工生成中文摘要;
  • 手工逐篇点击英文覆盖翻译。

三、完整状态流转

正常主流程仍然与上一篇一致:

Plaintext
awaiting_manual_conversion
→ mark-converted
→ awaiting_readonly_validation
→ validate-live
→ ready_for_execution
→ run-ready --execute
→ completed

区别主要集中在人工处理和生产验证两个阶段。

Mixed 阶段除了代码块转换和语言核对,还必须确认:

Plaintext
整篇 Gutenberg normalization 已完成

生产只读验证则额外要求:

Plaintext
editor_format = gutenberg
classic_outside_blocks = false

因此:

Plaintext
Classic 区块已经消失

并不能直接等价于:

Plaintext
Gutenberg normalization 已经完成

还必须人工确认转换后的正文结构和排版。


四、每天开始前检查仓库状态

进入仓库:

Bash
cd ~/code/wordpress-ai-excerpt-backfill

执行:

Bash
python3 bin/history-migration.py status
python3 bin/history-migration.py summary

重点查看:

Plaintext
最新未完成批次
下一步
建议创建下一批

如果显示:

Plaintext
最新未完成批次: mixed-syntaxhighlighter-XXXXXXXX-XX
建议创建下一批: False

就继续完成当前批次。

不要提前创建新的 20 篇。

只有出现:

Plaintext
最新未完成批次: 无
建议创建下一批: True
建议: all batches complete

再创建下一批。


五、创建新的 20 篇 Mixed 固定批次

Mixed 阶段使用:

Plaintext
bin/build-mixed-syntaxhighlighter-batch.py

候选排序固定为:

Plaintext
published_at DESC
chinese_post_id DESC

也就是优先处理发布时间更晚的历史文章。

不会优先选择:

Plaintext
SyntaxHighlighter 最少
内容最短
HTML 最简单
人工最好处理

如果最后不足 20 篇,则直接处理全部剩余候选。

确认上一批已经完成以后:

Bash
cd ~/code/wordpress-ai-excerpt-backfill

summary_output="$(
    python3 bin/history-migration.py summary
)"

printf '%s\n' "$summary_output"

if ! grep -q '^建议创建下一批: True$' <<<"$summary_output"; then
    echo
    echo "当前不允许创建下一批,请先完成最新未完成批次。"
else
    date_tag="$(date +%Y%m%d)"
    batch_id=""
    batch_file=""

    for number in $(seq -w 1 99)
    do
        candidate_id="mixed-syntaxhighlighter-${date_tag}-${number}"
        candidate_file="data/analysis/mixed-syntaxhighlighter-migration-batch-${date_tag}-${number}.csv"
        candidate_state_dir="data/state/history-migration/${candidate_id}"

        if [ ! -e "$candidate_file" ] && [ ! -d "$candidate_state_dir" ]; then
            batch_id="$candidate_id"
            batch_file="$candidate_file"
            break
        fi
    done

    if [ -z "$batch_id" ]; then
        echo "无法生成可用的新批次编号。"
    else
        preview_file="data/analysis/gutenberg-syntaxhighlighter-empty-excerpt-preview.csv"
        translations_file="data/raw/wordpress-zh-translation-links-20260721.jsonl"

        mapfile -t raw_files < <(
            find data/raw \
                -maxdepth 1 \
                -type f \
                \( \
                    -name 'wordpress-zh-posts-20260720*.jsonl' \
                    -o \
                    -name 'wordpress-zh-posts-20260721*.jsonl' \
                \) \
                | sort
        )

        if [ "${#raw_files[@]}" -eq 0 ]; then
            echo "没有找到 WordPress 原始历史快照。"
        elif [ ! -f "$preview_file" ]; then
            echo "找不到 preview:$preview_file"
        elif [ ! -f "$translations_file" ]; then
            echo "找不到翻译关系:$translations_file"
        else
            echo
            echo "新批次 ID:$batch_id"
            echo "新批次文件:$batch_file"

            python3 bin/build-mixed-syntaxhighlighter-batch.py \
                "${raw_files[@]}" \
                --preview "$preview_file" \
                --translations "$translations_file" \
                --output "$batch_file" \
                --batch-id "$batch_id" \
                --maximum 20 \
            && python3 bin/history-migration.py init-state --apply \
            && echo \
            && echo "新批次已经创建:" \
            && echo "batch_id=\"$batch_id\"" \
            && echo "csv_file=\"$batch_file\""
        fi
    fi
fi

正常最后应看到类似:

Plaintext
batch_id="mixed-syntaxhighlighter-20260730-01"
csv_file="data/analysis/mixed-syntaxhighlighter-migration-batch-20260730-01.csv"

六、设置批次变量,并查看后台编辑地址

为了后面的操作更方便,每天需要变化的两个变量单独设置。

例如:

Bash
batch_id="mixed-syntaxhighlighter-20260730-01"
csv_file="data/analysis/mixed-syntaxhighlighter-migration-batch-20260730-01.csv"

后面的命令统一复用:

Plaintext
$batch_id
$csv_file

不再到长命令中寻找日期和批次 ID。

然后一次性输出当前批次的后台编辑地址:

Bash
python3 - "$batch_id" "$csv_file" <<'PY'
import csv
import json
import sys
from pathlib import Path

batch_id = sys.argv[1]
csv_path = Path(sys.argv[2])
state_dir = Path("data/state/history-migration") / batch_id

with csv_path.open(encoding="utf-8-sig", newline="") as f:
    rows = list(csv.DictReader(f))

items = []

for row in rows:
    post_id = int(row["chinese_post_id"])
    state_path = state_dir / f"chinese-{post_id}.json"

    state = {}
    if state_path.is_file():
        state = json.loads(state_path.read_text(encoding="utf-8"))

    status = state.get("workflow_status", "uninitialized")

    if status == "completed":
        continue

    edit_url = (
        "https://admin.shuijingwanwq.com/wp-admin/"
        f"post.php?post={post_id}&action=edit"
    )

    items.append({
        "post_id": post_id,
        "english_id": row["english_post_id"],
        "title": row["chinese_title"],
        "syntax": row["before_syntaxhighlighter_count"],
        "status": status,
        "edit_url": edit_url,
    })

print(f"批次:{batch_id}")
print(f"当前待处理:{len(items)}")
print()

for index, item in enumerate(items, 1):
    print(
        f"{index:02d}. "
        f"zh={item['post_id']} "
        f"en={item['english_id']} "
        f"SH={item['syntax']} "
        f"status={item['status']}"
    )
    print(f"    标题:{item['title']}")
    print(f"    编辑:{item['edit_url']}")
    print()
PY

这样可以一次拿到当天需要处理的所有后台链接。


七、人工规范化 Gutenberg,并迁移 SyntaxHighlighter

Mixed 阶段最主要的变化,就在这一节。

每篇文章需要处理两个部分:

Plaintext
Classic / Mixed → Gutenberg
SyntaxHighlighter → Code Block Pro

1. 将 Classic 转换为 Gutenberg

选中 Classic 区块后,可以使用 WordPress 提供的:

Plaintext
转换为区块

简单情况下,例如 Classic 中只有:

Plaintext
3. 最终成功运行日志

转换后可以直接得到:

HTML
<!-- wp:paragraph -->
<p>3. 最终成功运行日志</p>
<!-- /wp:paragraph -->

这种简单内容基本不需要继续处理。

图 1:简单 Classic 内容转换成 Gutenberg Paragraph
图 1:简单 Classic 内容转换成 Gutenberg Paragraph

对于比较复杂的 Classic,一个 Classic 区块也可以一次拆成多个 Gutenberg 区块。

例如转换后可能得到:

Plaintext
Paragraph
Paragraph
Image
Paragraph
Code Block Pro
Paragraph
……

不需要对 Classic 中的每一段内容手工重新创建区块。

图 2:复杂 Classic 一次转换成多个 Gutenberg 区块
图 2:复杂 Classic 一次转换成多个 Gutenberg 区块

2. 自动转换以后必须检查排版

这是实际操作中很重要的一点。

WordPress 能够成功转换成 Gutenberg,不代表正文结构一定正确。

复杂 Classic 转换后,原本独立的:

Plaintext
章节标题
普通段落
1. 第一项
2. 第二项
结论
下一节标题

可能被合并成一个很大的 Paragraph。

也可能出现:

Plaintext
原来的换行消失
编号和正文连在一起
小标题变成普通段落
多个逻辑段落合并

因此:

Plaintext
已经点击“转换为区块”

并不代表人工工作已经完成。

图 3:Classic 自动转换成功,但部分正文结构和排版发生错乱
图 3:Classic 自动转换成功,但部分正文结构和排版发生错乱

我最终会继续人工整理:

  • 段落;
  • 标题和小标题;
  • 编号;
  • 列表;
  • 换行;
  • 图片位置;
  • caption;
  • 链接;
  • 正文顺序。

直到恢复正常阅读结构。

图 4:人工重新整理后的 Gutenberg 正文
图 4:人工重新整理后的 Gutenberg 正文

所以最终判断条件应该是:

Plaintext
Classic 已转换
+
正文 Gutenberg 结构正常
+
排版正常
+
内容没有遗漏

3. SyntaxHighlighter 转换成 Code Block Pro

Gutenberg 正文整理好以后,再处理 SyntaxHighlighter。

正常代码、配置、终端输出等迁移为:

Plaintext
Code Block Pro

最终必须满足:

Plaintext
SyntaxHighlighter = 0

4. 核对 Code Block Pro 语言

原 SyntaxHighlighter 已经明确语言时,使用对应语言,例如:

Plaintext
PHP
JavaScript
Bash
JSON
HTML
CSS
SQL
Python
Nginx
YAML

原区块没有声明语言时:

Plaintext
Plaintext

Code Block Pro 有可能继承上一个代码块使用的语言,因此不能只看转换是否成功,还要逐块核对语言。

5. 保存前检查

每篇文章保存以前至少确认:

Plaintext
Classic / Mixed 内容已经规范化为 Gutenberg
自动转换后的排版已经人工检查
SyntaxHighlighter = 0
Code Block Pro 内容完整
Code Block Pro language 正确
图片、caption、链接没有遗漏
正文内容没有重复
内容顺序没有发生异常
不存在 Gutenberg 损坏区块

这里不要手工生成摘要,也不要修改英文译文。


八、记录人工转换已经完成

全部 20 篇人工处理完成以后,需要从:

Plaintext
awaiting_manual_conversion

进入:

Plaintext
awaiting_readonly_validation

先确认批次变量:

Bash
echo "$batch_id"
echo "$csv_file"

然后执行:

Bash
read -r -p \
  "确认当前批次文章均已完成 Gutenberg 规范化、SyntaxHighlighter 迁移和 Code Block Pro 语言核对,输入 YES 继续:" \
  answer

if [ "$answer" != "YES" ]; then
    echo "未确认,已停止,没有修改状态。"
else
    python3 - "$batch_id" "$csv_file" <<'PY'
import csv
import json
import subprocess
import sys
from pathlib import Path

batch_id = sys.argv[1]
csv_path = Path(sys.argv[2])
state_dir = Path("data/state/history-migration") / batch_id

with csv_path.open(encoding="utf-8-sig", newline="") as f:
    rows = list(csv.DictReader(f))


def status_of(post_id):
    path = state_dir / f"chinese-{post_id}.json"
    if not path.is_file():
        raise RuntimeError(f"缺少状态文件:{path}")
    return json.loads(path.read_text(encoding="utf-8"))["workflow_status"]


targets = [
    row for row in rows
    if status_of(int(row["chinese_post_id"]))
    == "awaiting_manual_conversion"
]

print(f"批次:{batch_id}")
print(f"等待记录人工转换:{len(targets)}")
print()

success = []
failed = []

for index, row in enumerate(targets, 1):
    post_id = row["chinese_post_id"]
    syntax_before = row["before_syntaxhighlighter_count"]
    cbp_after = row["expected_code_block_pro_count_after"]

    result = subprocess.run(
        [
            sys.executable,
            "bin/history-migration.py",
            "mark-converted",
            "--post-id", post_id,
            "--syntax-count-before", syntax_before,
            "--cbp-count-after", cbp_after,
            "--language-review-confirmed",
            "--gutenberg-normalization-confirmed",
        ],
        text=True,
        capture_output=True,
        check=False,
    )

    final_status = status_of(int(post_id))

    if (
        result.returncode == 0
        and final_status == "awaiting_readonly_validation"
    ):
        success.append(post_id)
        print(f"[{index}/{len(targets)}] 已记录:zh={post_id}")
    else:
        failed.append(post_id)

        output = (result.stderr or result.stdout or "").strip().splitlines()
        error = output[-1] if output else "没有错误摘要"

        print(
            f"[{index}/{len(targets)}] "
            f"记录失败:zh={post_id} "
            f"error={error}"
        )

print()
print("========== 人工转换记录汇总 ==========")
print(f"待记录:{len(targets)}")
print(f"成功:{len(success)}")
print(f"失败:{len(failed)}")

if failed:
    print("失败文章:" + ", ".join(failed))
    raise SystemExit(1)
PY
fi

正常主要观察:

Plaintext
========== 人工转换记录汇总 ==========
待记录:20
成功:20
失败:0

不再额外输出所有历史批次和完整 show-current,避免真正重要的结果被大量信息淹没。


九、执行生产只读验证

这一阶段负责:

Plaintext
awaiting_readonly_validation
→ ready_for_execution

1. 检查环境变量

Bash
for name in WP_ADMIN_COOKIE WP_REST_NONCE
do
    if [ -z "${!name-}" ]; then
        echo "缺少环境变量:$name"
    else
        echo "已设置:$name"
    fi
done

如果缺少:

Bash
read -rsp "粘贴 WP_ADMIN_COOKIE,然后回车:" WP_ADMIN_COOKIE
echo
export WP_ADMIN_COOKIE

read -rsp "粘贴 WP_REST_NONCE,然后回车:" WP_REST_NONCE
echo
export WP_REST_NONCE

2. 批量执行只读验证

第二批实际执行时,20 篇中第一次有 18 篇成功,另外两篇出现:

Plaintext
batch read-only SSH query timed out

它们仍然保持:

Plaintext
awaiting_readonly_validation

随后重新验证,两篇都在第一次重试成功。

因此这里直接加入有限次数自动重试。

Bash
missing=0

for name in WP_ADMIN_COOKIE WP_REST_NONCE
do
    if [ -z "${!name-}" ]; then
        echo "缺少环境变量:$name"
        missing=1
    fi
done

if [ "$missing" -ne 0 ]; then
    echo "已停止,未执行生产只读验证。"
else
    python3 - "$batch_id" "$csv_file" <<'PY'
import csv
import json
import subprocess
import sys
import time
from pathlib import Path

batch_id = sys.argv[1]
csv_path = Path(sys.argv[2])
state_dir = Path("data/state/history-migration") / batch_id

with csv_path.open(encoding="utf-8-sig", newline="") as f:
    rows = list(csv.DictReader(f))


def status_of(post_id):
    path = state_dir / f"chinese-{post_id}.json"

    if not path.is_file():
        raise RuntimeError(f"缺少状态文件:{path}")

    return json.loads(
        path.read_text(encoding="utf-8")
    )["workflow_status"]


targets = []
unexpected = []

for row in rows:
    post_id = int(row["chinese_post_id"])
    status = status_of(post_id)

    if status == "awaiting_readonly_validation":
        targets.append(post_id)
    elif status not in ("ready_for_execution", "completed"):
        unexpected.append((post_id, status))

print(f"批次:{batch_id}")
print(f"本次待验证:{len(targets)}")
print()

success = []
failed = []

for index, post_id in enumerate(targets, 1):
    passed = False

    for attempt in range(1, 4):
        result = subprocess.run(
            [
                sys.executable,
                "bin/history-migration.py",
                "validate-live",
                "--post-id",
                str(post_id),
            ],
            text=True,
            capture_output=True,
            check=False,
        )

        final_status = status_of(post_id)

        if (
            result.returncode == 0
            and final_status == "ready_for_execution"
        ):
            success.append(post_id)

            if attempt == 1:
                print(
                    f"[{index}/{len(targets)}] "
                    f"验证通过:zh={post_id}"
                )
            else:
                print(
                    f"[{index}/{len(targets)}] "
                    f"重试通过:zh={post_id} attempts={attempt}"
                )

            passed = True
            break

        output = (
            result.stderr
            or result.stdout
            or ""
        ).strip().splitlines()

        error = output[-1] if output else "没有错误摘要"

        print(
            f"[{index}/{len(targets)}] "
            f"第 {attempt}/3 次失败:zh={post_id} "
            f"status={final_status} "
            f"error={error}"
        )

        if final_status != "awaiting_readonly_validation":
            break

        if attempt < 3:
            time.sleep(5)

    if not passed:
        failed.append(post_id)

print()
print("========== 只读验证最终汇总 ==========")
print(f"本次待验证:{len(targets)}")
print(f"本次通过:{len(success)}")
print(f"本次失败:{len(failed)}")

if unexpected:
    print(
        "非预期状态:"
        + ", ".join(
            f"{post_id}:{status}"
            for post_id, status in unexpected
        )
    )

if failed:
    print(
        "验证失败文章:"
        + ", ".join(map(str, failed))
    )

if failed or unexpected:
    raise SystemExit(1)
PY

    result=$?

    echo

    if [ "$result" -eq 0 ]; then
        echo "========== 验证后的待执行文章 =========="

        python3 bin/history-migration.py run-ready \
            --batch-id "$batch_id"
    else
        echo "存在验证失败或非预期状态,暂不进入正式执行。"
    fi
fi

正常应看到:

Plaintext
========== 只读验证最终汇总 ==========
本次待验证:20
本次通过:20
本次失败:0

随后:

Plaintext
模式: preview
完整性: ok
selected_count: 20
allowed_count: 20

Mixed 阶段除了原有代码块检查之外,还会确认:

Plaintext
editor_format = gutenberg
classic_outside_blocks = false

SSH 超时和 validation_failed 是两种不同情况

例如第二批遇到:

Plaintext
batch read-only SSH query timed out

而状态仍然是:

Plaintext
awaiting_readonly_validation

这属于生产只读查询偶发超时,可以有限次数重试。

如果真正进入:

Plaintext
validation_failed

则不要继续盲目重试,应检查文章并修复。

修复后再执行:

Bash
python3 bin/history-migration.py validate-live \
    --post-id 文章ID \
    --refresh

十、批量生成摘要并覆盖英文译文

正式执行需要:

Plaintext
WP_ADMIN_COOKIE
WP_REST_NONCE
ZHIPU_API_KEY

检查:

Bash
for name in WP_ADMIN_COOKIE WP_REST_NONCE ZHIPU_API_KEY
do
    if [ -z "${!name-}" ]; then
        echo "缺少环境变量:$name"
    else
        echo "已设置:$name"
    fi
done

如果只缺 API Key:

Bash
read -rsp "粘贴 ZHIPU_API_KEY,然后回车:" ZHIPU_API_KEY
echo
export ZHIPU_API_KEY

全部准备好以后:

Bash
python3 bin/history-migration.py run-ready \
    --batch-id "$batch_id" \
    --execute

执行期间会实时显示:

Plaintext
[1/20] 开始处理:zh=...
[1/20] 处理完成:zh=...
[2/20] 开始处理:zh=...
……

单篇出现偶发错误时,系统会有限次数重试,而不是立即停止整个批次。

第一批实际遇到过:

Plaintext
GLM HTTP 400
英文覆盖翻译 HTTP 500
Polylang SSH timeout

均在后续有限重试中恢复。

这里不需要人工看到一次 HTTP 错误就立即中止整个批次。

先让整个批次执行结束,再根据最终状态判断。


十一、批次结束后的完整检查

正式执行完成以后:

Bash
python3 bin/history-migration.py run-ready \
    --batch-id "$batch_id"

python3 bin/history-migration.py status
python3 bin/history-migration.py summary

真正完成时应该看到:

Plaintext
selected_count: 0
allowed_count: 0

同时批次状态必须满足:

Plaintext
completed = 批次总数
remaining = 0
translation_failed = 0
validation_failed = 0
blocked = 0
integrity = ok
next_action = 当前批次已完成

最终全局状态可以进一步出现:

Plaintext
最新未完成批次: 无
建议创建下一批: True
建议: all batches complete

selected_count=0 不等于批次完成

第二批执行完成后,我实际看到:

Plaintext
模式: preview
完整性: ok
selected_count: 0
allowed_count: 0

status 同时显示:

Plaintext
completed=19
translation_failed=1
remaining=1
下一步: 可以 resume

也就是说:

Plaintext
selected_count=0

只说明当前没有可以直接由 run-ready 执行的文章。

它并不能证明:

Plaintext
整个批次已经完成

最终必须同时检查:

Plaintext
completed
remaining
失败状态
next_action

十二、异常状态与恢复

日常不要把所有异常都统一理解成“再跑一次”。

不同状态应该走不同恢复路径。

1. awaiting_readonly_validation + SSH timeout

例如:

Plaintext
batch read-only SSH query timed out

并且状态仍然是:

Plaintext
awaiting_readonly_validation

说明只是只读查询没有成功完成。

第九节已经加入有限重试逻辑,一般无需额外人工处理。

2. validation_failed

说明已经形成明确验证失败。

先检查并修复 WordPress 中文文章。

然后:

Bash
python3 bin/history-migration.py validate-live \
    --post-id 文章ID \
    --refresh

3. translation_failed

第二批最终有一篇:

Plaintext
zh=9147
status=translation_failed

当时整个批次:

Plaintext
completed=19
translation_failed=1
remaining=1
next_action=可以 resume

先查看当前批次:

Plaintext
python3 bin/history-migration.py show-current

定位失败文章以后:

Bash
python3 bin/history-migration.py resume \
    --post-id 9147 \
    --execute

实际结果:

Plaintext
模式: execute
完整性: ok
selected_count: 1
- zh=9147 result=completed category=completed returncode=0 error=

随后再次检查,最终:

Plaintext
最新未完成批次: 无
建议创建下一批: True
建议: all batches complete

因此:

Plaintext
translation_failed

并不需要重新跑整个批次。

只需要针对失败文章 resume

4. ready_for_execution

如果文章仍然是:

Plaintext
ready_for_execution

先预览:

Bash
python3 bin/history-migration.py run-ready \
    --batch-id "$batch_id"

确认以后:

Bash
python3 bin/history-migration.py run-ready \
    --batch-id "$batch_id" \
    --execute

5. blocked

如果出现:

Plaintext
blocked

先检查:

Plaintext
last_failure
execution evidence
生产实际状态

不要直接重复执行。


十三、每天真正需要做的固定流程

经过两批实际验证以后,整个日常操作已经可以压缩成下面这套固定流程。

第一步:检查状态

Bash
cd ~/code/wordpress-ai-excerpt-backfill

python3 bin/history-migration.py status
python3 bin/history-migration.py summary

确认:

Plaintext
最新未完成批次: 无
建议创建下一批: True

第二步:创建新的 Mixed 固定批次

最多:

Plaintext
20 篇

排序:

Plaintext
published_at DESC
chinese_post_id DESC

第三步:设置当天两个变量

Bash
batch_id="mixed-syntaxhighlighter-YYYYMMDD-01"
csv_file="data/analysis/mixed-syntaxhighlighter-migration-batch-YYYYMMDD-01.csv"

第四步:输出后台地址

一次获得当天 20 篇中文文章的后台编辑地址。

第五步:逐篇人工处理

Plaintext
Classic / Mixed → Gutenberg

检查并修复自动转换后的排版

SyntaxHighlighter → Code Block Pro

核对 Code Block Pro language

保存

第六步:批量记录人工完成

目标:

Plaintext
待记录:20
成功:20
失败:0

第七步:生产只读验证

程序自动对仍然处于:

Plaintext
awaiting_readonly_validation

的文章进行有限重试。

最终目标:

Plaintext
本次通过:20
本次失败:0

以及:

Plaintext
selected_count: 20
allowed_count: 20

第八步:正式执行

检查:

Plaintext
WP_ADMIN_COOKIE
WP_REST_NONCE
ZHIPU_API_KEY

然后:

Bash
python3 bin/history-migration.py run-ready \
    --batch-id "$batch_id" \
    --execute

第九步:最终检查

Bash
python3 bin/history-migration.py run-ready \
    --batch-id "$batch_id"

python3 bin/history-migration.py status
python3 bin/history-migration.py summary

最终必须确认:

Plaintext
completed = 批次总数
remaining = 0
无失败状态
integrity = ok

只有达到这些条件,才能开始下一批。


十四、连续两批实际验证结果

为了确认这套流程不是只在理论上可行,我连续处理了两批真实历史文章。

第一批

批次:

Plaintext
mixed-syntaxhighlighter-20260729-01

共:

Plaintext
20 篇

结果:

Plaintext
人工 Gutenberg 规范化:20/20

mark-converted:20/20

生产只读验证:20/20

run-ready:
selected_count=20
allowed_count=20

最终:
completed=20

正式执行期间出现过:

Plaintext
GLM HTTP 400
英文覆盖翻译 HTTP 500
Polylang SSH timeout

但均被有限重试恢复。

第二批

批次:

Plaintext
mixed-syntaxhighlighter-20260730-01

同样:

Plaintext
20 篇

生产只读验证第一次:

Plaintext
通过:18
失败:2

两篇失败原因都是:

Plaintext
batch read-only SSH query timed out

并且仍然停留在:

Plaintext
awaiting_readonly_validation

有限重试以后:

Plaintext
待重试:2
成功:2
失败:0

随后:

Plaintext
selected_count: 20
allowed_count: 20

正式执行结束以后:

Plaintext
completed=19
translation_failed=1
remaining=1

针对唯一失败文章执行:

Plaintext
resume

成功以后最终:

Plaintext
最新未完成批次: 无
建议创建下一批: True
建议: all batches complete

因此两批共:

Plaintext
40 篇

最终全部完成。


总结

此前 Gutenberg + SyntaxHighlighter 阶段完成以后,站内仍然存在大量 SyntaxHighlighter,并不是此前流程漏掉了文章。

真正原因是这些文章属于:

Plaintext
Mixed Gutenberg / Classic + SyntaxHighlighter

因此不能直接套用只处理 Gutenberg 的候选规则。

但进入实际迁移以后,我并没有重新设计整套系统。

原来的:

Plaintext
fixed batch
→ manual conversion
→ mark-converted
→ readonly validation
→ excerpt
→ English overwrite translation
→ completed

仍然可以继续使用。

Mixed 阶段真正新增的核心内容只有:

Plaintext
Classic / Mixed → Gutenberg

以及人工确认:

Plaintext
--gutenberg-normalization-confirmed

生产验证再额外确认:

Plaintext
editor_format = gutenberg
classic_outside_blocks = false

连续两批、40 篇真实历史文章运行以后,除了正常主流程,也实际验证了:

Plaintext
只读 SSH 超时后的有限重试
HTTP 400 / 500 等偶发错误自动重试
translation_failed 单篇 resume
selected_count=0 时继续检查 remaining 和失败状态

因此现在这套流程已经从一次性的迁移测试,变成了可以重复执行的日常 SOP。

后续只需要每天继续:

Plaintext
创建 20 篇固定批次
→ 人工 Gutenberg 规范化
→ SyntaxHighlighter 转 Code Block Pro
→ 生产验证
→ 中文摘要
→ 英文覆盖翻译
→ 检查 completed

直到这一批 Mixed 历史文章全部迁移完成。

每天批量处理 20 篇 WordPress 历史文章:代码块迁移、摘要补全与英文覆盖翻译完整 SOP

需要长期技术维护或远程问题排查?

我是拥有 15+ 年经验的 PHP / Go 后端工程师,长期关注已有系统维护、Bug 修复、性能优化、服务器排查、WordPress 网站维护和小功能迭代。

如果你的项目遇到以下情况,可以先从一次小问题排查开始合作:

  • ✅ PHP / Laravel / Yii2 老项目无人维护
  • ✅ Go / Gin 后端接口需要排查或优化
  • ✅ WordPress 网站访问慢、报错或插件冲突
  • ✅ Nginx / MySQL / Redis / Linux 服务器异常
  • ✅ CDN / Cloudflare / DNS / HTTPS 配置问题
  • ✅ 需要长期远程技术支持或兼职维护

更多介绍请查看:关于我 & 合作

微信:13980074657
邮箱:shuijingwanwq@gmail.com
Telegram:@shuijingwan
GitHub:https://github.com/shuijingwan

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理