第 9 章 · 语料建设

语料对齐

概

什么是对齐?

对齐是确认哪部分译文对应哪部分原文,可以按整篇、整段或句子建立对应关系。

先读意思,再确认对应

例如,“Close the valve.”应与“关闭阀门。”配对;若配成“检查油量。”,即使两句都通顺,也不能作为同一句的翻译参考。

层

怎样核对对应关系

文档级

整篇文章 ↔ 整篇译文

例如,同一份采购合同的英文版和中文版可以先建立文档对应。标题、编号和日期有助于查找,但还要核对是否为同一版本、是否有删节。

↓
段落级

第 N 段英文 ↔ 第 N 段中文

本例页面的段落按“英、中、英、中”交替排列,可先按相邻位置形成候选段落对。这称为结构对齐:利用页面顺序帮助配对;页面夹入广告或缺段时,顺序就可能失效。

↓
句级

一句或相邻几句原文 ↔ 对应译文

分句只是在标点等位置切开文字;对齐还要确认意思对应。例如,英文两句可能合译成一句中文。下方代码只生成顺序相同的候选,不能自动解决这类情况。

难

对齐的难题

译文可以重组句子,序号相同并不证明对应。检查时先看信息是否完整,再看是否需要合并或拆分候选记录。

错位 (Misalignment)

例如,“Payment is due within 30 days.”误配到“货物应于周五送达。”,这是错位。两边都非空、长度也可能接近,程序未必能发现。

EN ¶3 ↔ ZH ¶4 ✗

拆句或合句(一对多/多对一)

例如,“Payment is due within 30 days, and late payment incurs interest.”译为“款项应在30日内支付。逾期须付利息。”这是一句译两句,可能完全正确,不应直接当成漏译。

1 EN → 2 ZH

干扰内容 (Noise)

例如,系统把“Subscribe now”当成原文,与“关闭阀门。”配在一起。应回查正文提取范围;页码等残留需核对后处理。

<p>...</p> [2]

码

段落配对 → 句子对齐

requests获取页面,BeautifulSoup提取段落,re分句,zip()按顺序配对。输出是候选句对;数量相同不代表意思对应。

re和zip分别负责什么

re.split()按标点模式分句;zip()只把两个列表中同序号的内容放在一起,不判断意思。缩写“Mr.”也可能被误当作句尾。

候选句对怎样成为可供参考的记录

原文“Close the valve. Check the pressure.”和译文“关闭阀门。检查压力。”可分成两组核对。若中文只有“关闭阀门。”,应回查缺少的信息,不能为了配平数量丢掉第二句英文。

Python代码
import re
import requests
from bs4 import BeautifulSoup

# ① 获取页面
url = "https://cn.nytimes.com/china/20260916/china-english-language-education/dual/"
html = requests.get(url, headers={"User-Agent": "CorpusBot/1.0"}, timeout=30).text

# ② 提取交替排列的双语段落
paragraphs = BeautifulSoup(html, "html.parser").select("div.article-paragraph")
# 假设文本已清洗完毕,此处提取文字

if not paragraphs or len(paragraphs) % 2 != 0:
    raise ValueError('正文段落数量异常,需回查页面与选择器')

pairs = []
for i in range(0, len(paragraphs), 2):
    en_para = paragraphs[i].get_text().strip()
    zh_para = paragraphs[i + 1].get_text().strip()

    # ③ 段落内分句
    en_sents = [s.strip() for s in re.split(r'(?<=[.!?])\s+', en_para) if s.strip()]
    zh_sents = [s.strip() for s in re.split(r'(?<=[。!?])\s*', zh_para) if s.strip()]

    if not en_sents or not zh_sents or len(en_sents) != len(zh_sents):
        raise ValueError(f'第 {i // 2 + 1} 组句数异常,停止并回查原段')

    # ④ 数量核对后形成同序号候选;语义仍须复核
    for en, zh in zip(en_sents, zh_sents):
        if en.strip() and zh.strip():
            pairs.append({
                "sentence_english": en.strip(),
                "sentence_chinese": zh.strip()
            })

print(f"生成 {len(pairs)} 个候选句对,仍需复核")
# pairs 可直接写入 JSON 的 content 字段

结构对齐的关键

range(0, len(paragraphs), 2) 利用 NYT 页面「英、中、英、中」的 DOM 规律, 以步长 2 遍历,将相邻两个 div 视为一对。

zip() 的局限

当英中句数不等时,zip() 会静默截断多余句子—— 因此本例在配对前核对句数。不等长时停止并回查原段;不能期待事后空值检查找回已丢失的句子。

例

案例:NYT 双语页对齐流程

此类双语页的英中段落交替排列,可按相邻位置形成候选段落对。段落内仍需分句、核对对应关系并检查内容。

flowchart TD
    A["requests 下载 HTML"] --> B["BeautifulSoup 提取 div.article-paragraph"]
    B --> C["步长 2 配对英中段落"]
    C --> D["re.split 段落内分句"]
    D --> E["zip 生成候选"]
    E --> F["核对原译文,再保存记录"]
                    

笔译练习:一句译两句是否完整

用途:为设备测试说明寻找保留完整操作要求的译例;原文door指舱门。

原文:Keep the door closed until the test is complete.

译文:请保持舱门关闭。测试完成后方可开启。

系统提示:“原文1句,译文2句,数量不同。”

请判断:这组译文是否保留原文要求?能否仅凭句数不同认定有错?

参考答案

两句中文共同保留了“测试完成前保持舱门关闭”的要求,可以作为一组候选译例。句数不同本身不是错误;若据此判错,就属于误报。不能为凑相同句数删掉其中一句中文。