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

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

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

作者:

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

我的 WordPress 博客积累了大量历史技术文章。其中一部分文章仍然使用旧的 SyntaxHighlighter 代码区块,中文摘要为空,对应的英文文章也需要重新覆盖翻译。

如果完全依赖人工处理,每篇文章都要经历:

Plaintext
转换代码区块
→ 核对代码语言
→ 生成中文摘要
→ 点击覆盖翻译
→ 等待翻译完成
→ 检查结果

英文覆盖翻译通常还需要等待一段时间。一天处理 20 篇时,人工会不断在 WordPress 后台、终端和翻译状态之间来回切换。

因此,我最终整理出一套相对稳定的批处理流程:

Plaintext
仓库筛选并固定当天的 20 篇文章
→ 人工迁移 SyntaxHighlighter 并核对代码语言
→ 仓库记录人工转换完成
→ 仓库执行生产只读验证
→ 批量生成中文摘要
→ 批量覆盖英文译文
→ 单篇失败有限重试
→ 分析最终失败原因
→ 修复根因并恢复失败文章
→ 确认整个批次全部完成

本文既记录这套流程的形成过程,也是一份可以在以后每天直接照着执行的操作手册。


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

这套流程涉及两个独立仓库。

1. WordPress 翻译管线仓库

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

主要负责:

  • SlyTranslate 与 GLM 翻译定制;
  • Gutenberg 结构保护;
  • HTML、短代码和代码块保护;
  • Plaintext 可翻译区域;
  • 受保护令牌验证;
  • 英文覆盖翻译;
  • WordPress 生产 MU 插件。

2. 历史文章摘要补全仓库

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

主要负责:

  • 筛选待处理历史文章;
  • 创建固定批次;
  • 保存中英文文章关系;
  • 生成中文摘要;
  • 调用生产环境覆盖翻译;
  • 保存写入前备份;
  • 记录执行状态;
  • 批量重试与恢复;
  • 汇总整个批次的完成情况。

两个仓库的职责不能混在一起:

Plaintext
翻译结构问题
→ wordpress-ai-translation-pipeline

摘要生成和批次调度问题
→ wordpress-ai-excerpt-backfill

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

仓库负责

仓库负责:

  • 筛选摘要为空的中文文章;
  • 排除已经进入旧批次的文章;
  • 确认文章存在已发布的英文译文;
  • 固定每天需要处理的 20 篇文章;
  • 保存中文文章 ID 和英文文章 ID;
  • 保存后台编辑地址;
  • 记录原 SyntaxHighlighter 数量;
  • 计算迁移后预期的 Code Block Pro 数量;
  • 执行生产只读验证;
  • 调用 GLM 生成中文摘要;
  • 写入中文摘要;
  • 调用 SlyTranslate 覆盖英文文章;
  • 保存执行证据和备份;
  • 对单篇失败进行有限重试;
  • 汇总 completed、failed 和 remaining。

人工负责

人工只负责:

  • 根据仓库清单打开文章;
  • 将 SyntaxHighlighter 转换为 Code Block Pro;
  • 检查代码内容是否完整;
  • 逐块核对代码语言;
  • 检查 Gutenberg 是否存在损坏区块;
  • 保存文章;
  • 明确确认人工转换已经完成。

人工不再负责:

  • 按发布时间寻找文章;
  • 逐篇判断摘要是否为空;
  • 手动点击每一篇覆盖翻译;
  • 守着 WordPress 后台等待翻译完成。

最终职责边界可以概括为:

Plaintext
仓库决定处理哪些文章
→ 人工完成必须人工判断的代码块迁移
→ 仓库完成验证、摘要生成和英文翻译

三、完整状态流转

每篇文章必须按照以下顺序推进:

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

其中最容易遗漏的是:

Plaintext
mark-converted

即使已经在 WordPress 后台完成代码块转换,仓库也不会自动知道这件事。

如果文章仍然处于:

Plaintext
awaiting_manual_conversion

就直接运行:

Plaintext
validate-live

仓库会拒绝验证:

Plaintext
ERROR: cannot validate live from awaiting_manual_conversion

因此,人工转换完成后,必须先将结果记录到本地协调状态,再执行生产只读验证。


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

进入仓库:

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

执行:

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

重点查看:

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

如果看到:

Plaintext
建议创建下一批: False
建议: continue latest incomplete batch; do not create a new batch

说明仍然存在未完成批次,不能创建新的 20 篇。

只有看到:

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

才可以创建下一批。

需要注意:

Bash
python3 bin/history-migration.py summary

是全局汇总命令,不支持:

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

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

确认所有旧批次完成后,在普通终端执行:

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

status_output="$(
    python3 bin/history-migration.py status
)"

printf '%s\n' "$status_output"

if ! grep -q '建议创建下一批: True' <<<"$status_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="syntaxhighlighter-${date_tag}-${number}"
        candidate_file="data/analysis/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
        mapfile -t preview_files < <(
            find data/analysis \
                -maxdepth 1 \
                -type f \
                -name '*syntaxhighlighter*preview*.csv' \
                -printf '%T@ %p\n' \
            | sort -nr \
            | cut -d' ' -f2-
        )

        if [ "${#preview_files[@]}" -eq 0 ]; then
            echo "没有找到 SyntaxHighlighter preview CSV。"
        else
            preview_file="${preview_files[0]}"

            echo "使用候选文件:$preview_file"
            echo "新批次 ID:$batch_id"
            echo "新批次文件:$batch_file"

            python3 bin/build-syntaxhighlighter-batch.py \
                --preview "$preview_file" \
                --output "$batch_file" \
                --expected-count 20 \
                --batch-id "$batch_id" \
                --pilot-manifest \
                    data/analysis/gutenberg-syntaxhighlighter-migration-pilot-candidates.csv \
                --old-phase1-manifest \
                    data/analysis/gutenberg-cbp-empty-excerpt-candidates.csv

            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="syntaxhighlighter-20260725-01"
csv_file="data/analysis/syntaxhighlighter-migration-batch-20260725-01.csv"

记下这两个值。后面的命令都需要使用它们。

新批次 CSV 属于固定清单,可以单独提交:

Bash
git add "$batch_file"
git diff --cached --check
git commit -m "chore: 固定 ${batch_id} 批次"
git push origin main

不要执行:

Bash
git add .
git add -A

以免把 data/state 等本地运行状态一起提交。


六、查看文章清单和后台编辑地址

设置当天批次变量:

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

batch_id="syntaxhighlighter-20260725-01"
csv_file="data/analysis/syntaxhighlighter-migration-batch-20260725-01.csv"

执行:

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 handle:
    rows = list(csv.DictReader(handle))

remaining = []

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")
        )

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

    if workflow_status == "completed":
        continue

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

    remaining.append(
        {
            "post_id": post_id,
            "english_id": row["english_post_id"],
            "title": row["chinese_title"],
            "syntax_before": row[
                "before_syntaxhighlighter_count"
            ],
            "cbp_expected": row[
                "expected_code_block_pro_count_after"
            ],
            "status": workflow_status,
            "edit_url": edit_url,
        }
    )

print(f"批次:{batch_id}")
print(f"批次总数:{len(rows)}")
print(f"当前待处理:{len(remaining)}")
print()

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

新批次应显示:

Plaintext
批次总数:20
当前待处理:20
图2:仓库列出当天 20 篇文章、代码块数量和后台编辑地址。
图2:仓库列出当天 20 篇文章、代码块数量和后台编辑地址。

七、人工迁移 SyntaxHighlighter

按照清单中的后台地址逐篇打开中文文章。

每篇文章需要完成:

  1. 将全部 SyntaxHighlighter 转换为 Code Block Pro;
  2. 检查代码内容是否完整;
  3. 逐个核对代码语言;
  4. 检查 Gutenberg 是否存在损坏区块;
  5. 保存文章。

原区块明确声明了语言

设置为对应语言,例如:

  • PHP
  • JavaScript
  • Bash
  • JSON
  • HTML
  • CSS
  • SQL
  • Python
  • Nginx
  • YAML

.yml.yaml 文件都选择:

Plaintext
YAML

原区块没有声明语言

设置为:

Plaintext
Plaintext

Code Block Pro 有时会继承上一次使用的语言,因此保存前必须逐块核对。

每篇保存前应确认:

Plaintext
SyntaxHighlighter 数量已经归零
Code Block Pro 数量符合批次清单预期
代码内容没有丢失
代码语言设置正确
Gutenberg 没有损坏区块提示
图3:历史文章中原有的 SyntaxHighlighter 代码区块。
图3:历史文章中原有的 SyntaxHighlighter 代码区块。
图4:转换为 Code Block Pro 后,逐个核对代码语言。
图4:转换为 Code Block Pro 后,逐个核对代码语言。

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

WordPress 后台保存成功后,还需要将人工完成结果写入本地协调状态。

本节负责:

Plaintext
awaiting_manual_conversion
→ awaiting_readonly_validation

执行:

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

batch_id="syntaxhighlighter-20260725-01"
csv_file="data/analysis/syntaxhighlighter-migration-batch-20260725-01.csv"

read -r -p \
  "确认当前批次待处理文章均已完成代码块转换和逐块语言核对,输入 YES 继续:" \
  answer

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

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

module_path = root / "bin/history-migration.py"

spec = importlib.util.spec_from_file_location(
    "history_migration_runtime",
    module_path,
)

if spec is None or spec.loader is None:
    raise SystemExit(
        "无法加载 history-migration.py"
    )

module = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = module
spec.loader.exec_module(module)

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


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

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

    return json.loads(
        state_path.read_text(encoding="utf-8")
    )


targets = []

for row in rows:
    post_id = int(row["chinese_post_id"])
    status = read_state(post_id).get(
        "workflow_status"
    )

    if status == "awaiting_manual_conversion":
        targets.append(row)

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

success = []
failed = []

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

    try:
        module.mark_converted(
            root,
            post_id,
            syntax_before,
            cbp_after,
            True,
        )

        final_status = read_state(
            post_id
        ).get("workflow_status")

        if (
            final_status
            == "awaiting_readonly_validation"
        ):
            success.append(post_id)
            print(
                f"[{index}/{len(targets)}] "
                f"已记录:zh={post_id} "
                f"status={final_status}"
            )
        else:
            failed.append(post_id)
            print(
                f"[{index}/{len(targets)}] "
                f"状态异常:zh={post_id} "
                f"status={final_status}"
            )

    except Exception as error:
        failed.append(post_id)
        print(
            f"[{index}/{len(targets)}] "
            f"记录失败:zh={post_id} "
            f"{type(error).__name__}: {error}"
        )

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

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

新批次全部记录成功后,应显示:

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

这一步只更新本地状态,不会:

  • 调用 GLM;
  • 调用 SlyTranslate;
  • 修改 WordPress;
  • 生成中文摘要;
  • 覆盖英文文章。

九、执行生产只读验证

本节负责:

Plaintext
awaiting_readonly_validation
→ ready_for_execution

validate-live 是单篇命令,只支持:

Plaintext
--post-id

不支持:

Plaintext
--batch-id

因此需要从批次 CSV 中读取文章 ID,再逐篇执行验证。

先确认登录环境变量仍然存在:

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

然后执行:

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

batch_id="syntaxhighlighter-20260725-01"
csv_file="data/analysis/syntaxhighlighter-migration-batch-20260725-01.csv"

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
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 handle:
    rows = list(csv.DictReader(handle))


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

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

    return json.loads(
        state_path.read_text(encoding="utf-8")
    )


targets = []
already_ready = []
completed = []
unexpected = []

for row in rows:
    post_id = int(row["chinese_post_id"])
    status = read_state(post_id).get(
        "workflow_status"
    )

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

print(f"批次:{batch_id}")
print(f"本次待验证:{len(targets)}")
print(f"已经 ready:{len(already_ready)}")
print(f"已经 completed:{len(completed)}")
print()

success = []
failed = []

for index, post_id in enumerate(targets, 1):
    print(
        f"[{index}/{len(targets)}] "
        f"开始只读验证:zh={post_id}"
    )

    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 = read_state(
        post_id
    ).get("workflow_status")

    if (
        result.returncode == 0
        and final_status
        == "ready_for_execution"
    ):
        success.append(post_id)
        print(
            f"[{index}/{len(targets)}] "
            f"验证通过:zh={post_id} "
            f"status={final_status}"
        )
    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"status={final_status} "
            f"returncode={result.returncode} "
            f"error={error}"
        )

    print()

status_counts = {}

for row in rows:
    post_id = int(row["chinese_post_id"])
    status = read_state(post_id).get(
        "workflow_status",
        "unknown",
    )

    status_counts[status] = (
        status_counts.get(status, 0) + 1
    )

print("========== 只读验证最终汇总 ==========")
print(f"本次待验证:{len(targets)}")
print(f"本次通过:{len(success)}")
print(f"本次失败:{len(failed)}")
print(
    "批次当前 ready:"
    f"{status_counts.get('ready_for_execution', 0)}"
)
print(
    "批次已经 completed:"
    f"{status_counts.get('completed', 0)}"
)

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

if failed:
    print(
        "验证失败文章:"
        + ", ".join(
            str(value) for value in 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
批次当前 ready:20
批次已经 completed:0

以及:

Plaintext
模式: preview
完整性: ok
selected_count: 20
allowed_count: 20
图5:20 篇文章完成生产只读验证,全部进入 ready 状态。
图5:20 篇文章完成生产只读验证,全部进入 ready 状态。

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

正式执行需要三个环境变量:

Plaintext
WP_ADMIN_COOKIE
WP_REST_NONCE
ZHIPU_API_KEY

执行:

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

batch_id="syntaxhighlighter-20260725-01"
missing=0

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

if [ "$missing" -ne 0 ]; then
    echo "已停止,未执行批次。"
else
    python3 bin/history-migration.py run-ready \
        --batch-id "$batch_id" \
        --execute
fi

执行过程中会实时显示每篇文章的结果。

图6:终端实时显示中文摘要生成和英文覆盖翻译进度。
图6:终端实时显示中文摘要生成和英文覆盖翻译进度。

十一、有限重试能解决什么

当前批量入口会对单篇文章进行有限次数自动尝试。

它适合解决:

  • 临时请求超时;
  • 短暂网络失败;
  • 偶发 HTTP 502 或 503;
  • 服务端短时间不可用;
  • 一次性的模型响应异常。

但它不能自动解决:

  • 提示词和校验规则冲突;
  • Plaintext 结构规则过严;
  • 占位符遗漏;
  • Gutenberg 结构损坏;
  • 校验正则误判;
  • 模型每次都返回同一种不合格格式。

因此,三次重试后仍然失败时,不能继续盲目增加重试次数。

正确流程应该是:

Plaintext
自动有限重试
→ 输出最终失败原因
→ 判断是瞬时故障还是确定性故障
→ 瞬时故障才继续 resume
→ 确定性故障先修复根因

十二、这次批次中暴露的两个真实问题

本次 20 篇文章首次批量执行后:

Plaintext
16 篇完成
3 篇 translation_failed
1 篇 excerpt_failed

表面上看,最终失败率达到了 20%。

进一步检查后发现,4 篇失败并不是随机网络问题,而是两个确定性规则问题。

1. Plaintext 行数校验过严

三篇文章分别报错:

Plaintext
Plaintext line count changed during translation:
source=3, translated=1

Plaintext line count changed during translation:
source=11, translated=10

Plaintext line count changed during translation:
source=94, translated=93

中文翻译成英文时,模型自然可能合并或拆分换行。

因此,对于允许翻译的普通 Plaintext 内容,不应该要求译前译后物理行数完全相同。

最终修复为:

  • 取消可翻译 Plaintext 的逐行等长要求;
  • 保留 START/STOP 区域边界;
  • 保留令牌数量、唯一性、编号和顺序校验;
  • 非空 Plaintext 不能整体变空;
  • Plaintext 不能吞入相邻 Gutenberg 或受保护区域;
  • 代码、命令、路径、URL、状态码等不可翻译内容继续原样保护;
  • 结构错误使用 HTTP 422 和 retryable=false

生产 MU 插件版本更新为:

Plaintext
2026.07.24.18

提示词策略版本更新为:

Plaintext
2026-07-24-v12

离线测试结果:

Plaintext
115 项通过
0 项失败

部署后,三篇失败文章依次执行:

Bash
python3 bin/history-migration.py resume \
    --post-id 文章ID \
    --execute

三篇全部成功完成。

2. Ctrl + S 被误判为 Markdown 列表

剩余一篇文章生成的摘要实际上是正常单段文本,例如:

Plaintext
在 VS Code 中按下 Ctrl + S 时文件内容意外被还原……

但本地校验器使用的旧正则是:

Python
r"(^|\s)(?:#{1,6}\s|[-*+]\s|\d+[.)]\s)|[*_]{2}"

其中:

Plaintext
(^|\s)

允许匹配正文任意位置的空白。

因此:

Plaintext
Ctrl + S

中的:

 + 

被误判成 Markdown 的 + 项目符号。

修复后的正则只允许在行首识别列表:

Python
r"(?m)^[ \t]*(?:#{1,6}[ \t]+|[-*+][ \t]+|\d+[.)][ \t]+)"

修复后:

  • Ctrl + S 不再误判;
  • A + B 不再误判;
  • x - y 不再误判;
  • C++ 不再误判;
  • 行首的 - 第一项+ 第一项1. 第一项 仍然会被拒绝。

同时,GLM 初始摘要提示词也明确要求:

  • 一个连续的中文纯文本段落;
  • 不使用 Markdown;
  • 不使用项目符号和编号列表;
  • 不使用标题、引用、表格或代码块;
  • 不换行;
  • 不添加“摘要:”等前缀;
  • 内容可以直接保存到 WordPress post_excerpt

完整测试结果:

Plaintext
334 项通过
0 项失败

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

批量执行结束后,不能只看:

Plaintext
selected_count: 0

因为它只代表当前没有处于 ready_for_execution 的文章,不一定代表全部完成。

必须同时执行:

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

batch_id="syntaxhighlighter-20260725-01"

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
completed=20
remaining=0
excerpt_failed=0
translation_failed=0
next_action=当前批次已完成

十四、列出未完成文章和失败原因

如果看到:

Plaintext
completed=16
remaining=4

先执行只读诊断:

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

batch_id="syntaxhighlighter-20260725-01"

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

batch_id = sys.argv[1]
state_dir = (
    Path("data/state/history-migration")
    / batch_id
)
execution_dir = Path(
    "data/backups/single-candidate"
)

items = []

for state_path in state_dir.glob(
    "chinese-*.json"
):
    state = json.loads(
        state_path.read_text(encoding="utf-8")
    )

    if state.get("workflow_status") == "completed":
        continue

    post_id = int(
        state["chinese_post_id"]
    )

    execution_path = (
        execution_dir
        / f"chinese-{post_id}.execution.json"
    )

    execution = {}

    if execution_path.is_file():
        execution = json.loads(
            execution_path.read_text(
                encoding="utf-8"
            )
        )

    failure = state.get("last_failure") or {}

    items.append(
        {
            "position": state.get(
                "batch_position",
                999999,
            ),
            "post_id": post_id,
            "english_id": state.get(
                "english_post_id"
            ),
            "workflow": state.get(
                "workflow_status"
            ),
            "execution": execution.get(
                "status"
            ),
            "error": execution.get("error"),
            "error_response": execution.get(
                "error_response"
            ),
            "last_stage": failure.get("stage"),
            "last_reason": failure.get("reason"),
            "retry_counts": state.get(
                "retry_counts"
            ),
        }
    )

items.sort(
    key=lambda item: item["position"]
)

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']}"
    )
    print(
        f"    workflow={item['workflow']} "
        f"execution={item['execution']}"
    )
    print(
        f"    last_stage={item['last_stage']} "
        f"last_reason={item['last_reason']}"
    )
    print(f"    error={item['error']}")
    print(
        f"    error_response="
        f"{item['error_response']}"
    )
    print(
        f"    retry_counts="
        f"{item['retry_counts']}"
    )
    print()
PY


十五、不同失败状态怎样处理

1. translation_failed

如果中文摘要已经写入,而英文覆盖翻译失败,使用:

Python
python3 bin/history-migration.py resume \
    --post-id 文章ID \
    --execute

这个命令只继续英文翻译,不重新生成中文摘要。

但是,在执行前必须先看真实错误。

如果是临时网络错误,可以直接 resume

如果是:

Plaintext
Plaintext line count changed
占位符遗漏
Gutenberg 结构损坏

等确定性错误,应先修复翻译管线,再执行 resume

2. excerpt_failed

先检查最近保存的被拒绝摘要:

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

post_id="文章ID"

python3 - "$post_id" <<'PY'
import sys
from pathlib import Path

post_id = sys.argv[1]

rejected_dir = Path(
    "data/backups/single-candidate/rejected"
)

files = sorted(
    rejected_dir.glob(
        f"chinese-{post_id}-glm47-rejected-attempt-*.txt"
    ),
    key=lambda path: path.stat().st_mtime,
)[-3:]

print(f"找到最近被拒绝的摘要:{len(files)}")
print()

for index, path in enumerate(files, 1):
    text = path.read_text(encoding="utf-8")

    print("=" * 80)
    print(f"第 {index} 次:{path}")
    print("-" * 80)
    print(text)
    print("-" * 80)
    print(repr(text))
    print()
PY

检查摘要是真的包含 Markdown 或列表,还是被校验器误判。

不要在不知道真实内容的情况下继续无限重试。

3. 修复后重新执行单篇完整流程

excerpt_failed 不能使用 resume

修复提示词或校验器后,执行:

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

batch_id="syntaxhighlighter-20260725-01"
post_id="文章ID"

python3 bin/execute-single-candidate.py \
    --post-id "$post_id" \
    --manifest \
      "data/analysis/history-migration-validation/${batch_id}/chinese-${post_id}.execution-candidate.csv" \
    --expected-candidate-count 1 \
    --backup-dir data/backups/single-candidate \
    --execute

成功时会显示:

JSON
{"chinese_post_id": 文章ID, "english_post_id": 英文文章ID, "status": "completed"}

4. 同步单篇执行证据

直接调用 execute-single-candidate.py 后,执行证据可能已经是 completed,但协调状态仍然保留旧的 excerpt_failed

先预览:

Bash
python3 bin/history-migration.py sync-execution \
    --json

确认只计划同步目标文章后,执行:

Bash
python3 bin/history-migration.py sync-execution \
    --apply \
    --json

这一步只更新本地协调状态,不会重新调用 GLM,也不会再次修改 WordPress。


十六、怎样判断整个批次真正完成

最终执行:

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

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

本次成功完成后的真实结果为:

Plaintext
固定文章: 88
执行证据: completed=88
failed=0
pending=0
other=0
no_execution_evidence=0

完整性: ok
conflicts=0
errors=0

ready=0
in_progress=0
translation_resume=0
excerpt_failed=0
translation_failed=0
validation_failed=0
blocked=0
remaining=0

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

当前 20 篇批次显示:

Plaintext
total=20
completed=20
remaining=0
next_action=当前批次已完成

需要注意:

Plaintext
retry_exhausted=9

是历史累计重试耗尽记录,不代表当前仍有 9 篇失败。

判断当前是否完成,应看:

Plaintext
excerpt_failed
translation_failed
blocked
remaining

而不是只看累计的 retry_exhausted


十七、本地运行状态不要提交

批次处理后,git status 可能出现:

Plaintext
M data/state/history-migration/某批次/chinese-文章ID.json
M data/state/history-migration/某批次/events.jsonl
?? data/state/history-migration/某批次/

这些属于本地运行状态和执行证据。

应当:

  • 保留;
  • 不提交;
  • 不删除;
  • 不执行 git add .
  • 不执行 git add -A
  • 不为了让工作区看起来干净而清除状态。

需要保留的内容包括:

Plaintext
data/state/
events.jsonl
data/backups/
*.execution.json
*.pre-write.json
rejected/

代码修改应精确指定文件暂存,例如:

Bash
git add \
    src/candidate_execution.py \
    src/glm47_excerpt_client.py \
    tests/test_candidate_execution.py \
    tests/test_excerpt_clients.py

十八、每天真正需要执行的固定顺序

以后每天按照以下顺序处理:

Plaintext
1. 执行 status 和 summary
2. 确认没有未完成批次
3. 创建新的 20 篇固定批次
4. 提交并推送新批次 CSV
5. 输出文章清单和后台编辑地址
6. 人工迁移 SyntaxHighlighter
7. 逐块核对 Code Block Pro 语言
8. 批量记录 mark-converted
9. 批量执行 validate-live
10. 确认 selected_count=20、allowed_count=20
11. 执行 run-ready --execute
12. 查看 status 和 summary
13. 如果 remaining>0,先读取准确失败原因
14. translation_failed 根据原因执行 resume
15. excerpt_failed 先检查被拒绝摘要
16. 确定性错误先修复根因
17. 必要时执行单篇完整流程
18. 执行 sync-execution 同步协调状态
19. 确认 completed=20、remaining=0
20. 保留本地运行状态

最关键的原则是:

Plaintext
不要因为有自动重试,就把所有失败都视为网络波动。

不要因为 selected_count=0,就认定批次已经完成。

不要在没有查看真实错误和真实模型输出前继续盲目重试。

十九、最终结论

这套流程的目标并不是建立一个理论上最复杂的自动化系统,而是在可接受的时间、成本和维护量下,稳定完成 WordPress 历史文章迁移。

最终工作方式是:

Plaintext
仓库筛选文章
→ 人工处理代码块
→ 仓库完成验证、摘要和英文覆盖翻译
→ 自动重试偶发故障
→ 人工只处理真正的确定性异常

本次 20 篇文章最终全部完成,也验证了几个重要结论:

  1. 有限重试可以解决偶发问题,但不能替代准确的错误分类;
  2. 中文翻译成英文时,Plaintext 物理行数发生变化是合理现象;
  3. 初始提示词应该从第一次请求开始与本地校验完全对齐;
  4. 校验规则必须使用真实技术文本测试,避免把 Ctrl + S 之类的表达误判为 Markdown;
  5. 直接单篇执行成功后,还要同步协调状态;
  6. 只有 completed 等于批次总数、remaining=0,才算真正完成。

经过这次修复,明天开始的新批次可以直接沿用本文流程,不需要再临时寻找命令,也不需要重新摸索失败文章应该怎样处理。

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

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

我是拥有 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 来减少垃圾评论。了解你的评论数据如何被处理