如何将文本分割为句子?
使用 Intl.Segmenter 通过支持本地化的边界检测,将文本分割为句子,自动处理标点符号、缩写和语言特定规则。
简介
在处理文本以进行翻译、分析或显示时,通常需要将其分割为单独的句子。仅用正则表达式进行分割的方法并不可靠,因为句子边界远比句点加空格复杂。句子可能以问号、感叹号或省略号结尾。缩写如 "Dr." 或 "Inc." 中的句点并不表示句子结束。不同语言还会使用不同的标点符号作为句子终止符。
Intl.Segmenter API 解决了这个问题,能够根据本地化规则检测句子边界。它理解不同语言中识别句子边界的规则,并能自动处理缩写、数字和复杂标点等特殊情况。
仅用句点分割存在的问题
你可以尝试通过句点加空格来分割文本为句子。
const text = "Hello world. How are you? I am fine.";
const sentences = text.split(". ");
console.log(sentences);
// ["Hello world", "How are you? I am fine."]
这种方法存在多个问题。首先,它无法处理问号或感叹号。其次,它会在包含句点的缩写处错误分割。第三,除了最后一句外,每个句子的句点都会被移除。第四,当句点后有多个空格时,该方法也无法正确处理。
const text = "Dr. Smith works at Acme Inc. He starts at 9 a.m.";
const sentences = text.split(". ");
console.log(sentences);
// ["Dr", "Smith works at Acme Inc", "He starts at 9 a.m."]
在 "Dr." 和 "Inc." 处文本被错误分割,因为这些缩写中包含句点。你需要一种更智能的方法,能够理解句子边界规则。
使用更复杂的正则表达式
你可以通过改进正则表达式来处理更多情况。
const text = "Hello world. How are you? I am fine!";
const sentences = text.split(/[.?!]\s+/);
console.log(sentences);
// ["Hello world", "How are you", "I am fine", ""]
这种方法会在句点、问号和感叹号后跟空白字符时进行分割。它能处理更多情况,但在缩写处仍然会出错,并且会产生空字符串。同时,它也会移除每个句子的标点符号。
const text = "Dr. Smith works at Acme Inc. He starts at 9 a.m.";
const sentences = text.split(/[.?!]\s+/);
console.log(sentences);
// ["Dr", "Smith works at Acme Inc", "He starts at 9 a", "m", ""]
正则表达式方法无法可靠地区分句末的句点和缩写中的句点。要构建一个能处理所有边界情况的完整正则表达式几乎不现实。你需要一个能够理解语言规则的解决方案。
使用 Intl.Segmenter 进行句子分割
Intl.Segmenter 构造函数会根据本地化规则创建一个分段器,用于按区域设置分割文本。你需要指定 locale,并将 granularity 选项设置为 "sentence"。
const segmenter = new Intl.Segmenter("en", { granularity: "sentence" });
const text = "Hello world. How are you? I am fine!";
const segments = segmenter.segment(text);
for (const segment of segments) {
console.log(segment.segment);
}
// "Hello world. "
// "How are you? "
// "I am fine!"
segment() 方法会返回一个可迭代对象,依次生成分段对象。每个分段对象都包含一个 segment 属性,存储该分段的文本。分段器会保留每个句子末尾的标点和空白字符。
你可以使用 Array.from() 将分段转换为数组。
const segmenter = new Intl.Segmenter("en", { granularity: "sentence" });
const text = "Hello world. How are you? I am fine!";
const segments = segmenter.segment(text);
const sentences = Array.from(segments, s => s.segment);
console.log(sentences);
// ["Hello world. ", "How are you? ", "I am fine!"]
这样会创建一个数组,每个元素都是带有原始标点和空格的句子。
Intl.Segmenter 如何处理缩写
分段器能够识别常见的缩写模式,不会在缩写中的句点处分割。
const segmenter = new Intl.Segmenter("en", { granularity: "sentence" });
const text = "Dr. Smith works at Acme Inc. He starts at 9 a.m.";
const segments = segmenter.segment(text);
const sentences = Array.from(segments, s => s.segment);
console.log(sentences);
// ["Dr. Smith works at Acme Inc. ", "He starts at 9 a.m."]
文本会被正确分割为两句话。"Dr."、"Inc." 和 "a.m." 中的句点不会触发断句,因为分段器能识别这些是缩写。正是这种对边界情况的自动处理,使 Intl.Segmenter 优于正则表达式方法。
去除句子中的空白字符
分段器会将句子末尾的空白字符包含在每个句子中。如有需要,你可以去除这些空白。
const segmenter = new Intl.Segmenter("en", { granularity: "sentence" });
const text = "Hello world. How are you? I am fine!";
const segments = segmenter.segment(text);
const sentences = Array.from(segments, s => s.segment.trim());
console.log(sentences);
// ["Hello world.", "How are you?", "I am fine!"]
trim() 方法会移除每个句子的首尾空白字符。当你需要干净的句子边界且不希望有多余空格时,这非常有用。
获取分段元数据
每个分段对象都包含该分段在原始文本中的位置信息元数据。
const segmenter = new Intl.Segmenter("en", { granularity: "sentence" });
const text = "Hello world. How are you?";
const segments = segmenter.segment(text);
for (const segment of segments) {
console.log({
text: segment.segment,
index: segment.index,
input: segment.input
});
}
// { text: "Hello world. ", index: 0, input: "Hello world. How are you?" }
// { text: "How are you?", index: 13, input: "Hello world. How are you?" }
index 属性表示该分段在原始文本中的起始位置。input 属性包含完整的原始文本。当你需要追踪句子位置或重建原始文本时,这些元数据非常有用。
不同语言的句子切分
不同语言有不同的句子边界规则。分段器会根据指定的 locale 自动调整其行为。
在日语中,句子可以以全角句号 。(称为 kuten)结尾。
const segmenter = new Intl.Segmenter("ja", { granularity: "sentence" });
const text = "私は猫です。名前はまだない。";
const segments = segmenter.segment(text);
const sentences = Array.from(segments, s => s.segment);
console.log(sentences);
// ["私は猫です。", "名前はまだない。"]
文本会在日语句子终止符处正确切分。如果分段器配置为英文,则无法正确识别这些边界。
在印地语中,句子可以以竖线 ।(称为 purna viram)结尾。
const segmenter = new Intl.Segmenter("hi", { granularity: "sentence" });
const text = "यह एक वाक्य है। यह दूसरा वाक्य है।";
const segments = segmenter.segment(text);
const sentences = Array.from(segments, s => s.segment);
console.log(sentences);
// ["यह एक वाक्य है। ", "यह दूसरा वाक्य है।"]
分段器能够识别天城文句号作为句子边界。这种基于 locale 的行为对于国际化文本处理至关重要。
多语言文本使用正确的 locale
处理包含多种语言的文本时,应选择与文本主要语言相匹配的 locale。分段器会根据指定的 locale 判断适用的边界规则。
const englishText = "Hello world. How are you?";
const japaneseText = "私は猫です。名前はまだない。";
const englishSegmenter = new Intl.Segmenter("en", { granularity: "sentence" });
const japaneseSegmenter = new Intl.Segmenter("ja", { granularity: "sentence" });
const englishSentences = Array.from(
englishSegmenter.segment(englishText),
s => s.segment
);
const japaneseSentences = Array.from(
japaneseSegmenter.segment(japaneseText),
s => s.segment
);
console.log(englishSentences);
// ["Hello world. ", "How are you?"]
console.log(japaneseSentences);
// ["私は猫です。", "名前はまだない。"]
为每种语言分别创建分段器可以确保边界检测的准确性。如果处理的文本语言未知,可以使用 "en" 这样的通用 locale 作为回退,但这会降低非英文文本的准确性。
处理无句子边界的文本
当文本中没有句子终止符时,分段器会将整个文本作为一个分段返回。
const segmenter = new Intl.Segmenter("en", { granularity: "sentence" });
const text = "Hello world";
const segments = segmenter.segment(text);
const sentences = Array.from(segments, s => s.segment);
console.log(sentences);
// ["Hello world"]
这种行为是正确的,因为文本中没有任何句子边界。分段器不会人为地拆分构成单个句子的文本。
处理空字符串
分段器通过返回一个空迭代器来处理空字符串。
const segmenter = new Intl.Segmenter("en", { granularity: "sentence" });
const text = "";
const segments = segmenter.segment(text);
const sentences = Array.from(segments, s => s.segment);
console.log(sentences);
// []
这会生成一个空数组,这是空输入时的预期结果。
复用分段器以提升性能
创建分段器有一定的开销。当你需要用相同的 locale 和选项分段多个文本时,应只创建一次分段器并复用。
const segmenter = new Intl.Segmenter("en", { granularity: "sentence" });
const texts = [
"First text. With two sentences.",
"Second text. With three sentences. And more.",
"Third text."
];
texts.forEach(text => {
const sentences = Array.from(segmenter.segment(text), s => s.segment);
console.log(sentences);
});
// ["First text. ", "With two sentences."]
// ["Second text. ", "With three sentences. ", "And more."]
// ["Third text."]
复用分段器比为每个文本都新建一个分段器更高效。
构建句子计数函数
你可以使用分段器统计文本中的句子数。
function countSentences(text, locale = "en") {
const segmenter = new Intl.Segmenter(locale, { granularity: "sentence" });
const segments = segmenter.segment(text);
return Array.from(segments).length;
}
console.log(countSentences("Hello world. How are you?"));
// 2
console.log(countSentences("Dr. Smith works at Acme Inc. He starts at 9 a.m."));
// 2
console.log(countSentences("Single sentence"));
// 1
console.log(countSentences("私は猫です。名前はまだない。", "ja"));
// 2
该函数会创建分段器,分割文本,并返回分段数量。它能正确处理缩写和特定语言的边界。
构建句子提取函数
你可以创建一个函数,通过索引从文本中提取指定的句子。
function getSentence(text, index, locale = "en") {
const segmenter = new Intl.Segmenter(locale, { granularity: "sentence" });
const segments = Array.from(segmenter.segment(text), s => s.segment);
return segments[index] || null;
}
const text = "First sentence. Second sentence. Third sentence.";
console.log(getSentence(text, 0));
// "First sentence. "
console.log(getSentence(text, 1));
// "Second sentence. "
console.log(getSentence(text, 2));
// "Third sentence."
console.log(getSentence(text, 3));
// null
该函数会返回指定索引处的句子,如果索引越界,则返回 null。
检查浏览器和运行时支持情况
Intl.Segmenter API 在现代浏览器和 Node.js 中均可用。它自 2024 年 4 月起成为 Web 平台基线的一部分,并已被所有主流浏览器引擎支持。
你可以在使用前检查该 API 是否可用。
if (typeof Intl.Segmenter !== "undefined") {
const segmenter = new Intl.Segmenter("en", { granularity: "sentence" });
const text = "Hello world. How are you?";
const sentences = Array.from(segmenter.segment(text), s => s.segment);
console.log(sentences);
} else {
console.log("Intl.Segmenter is not supported");
}
对于不支持的环境,你需要提供降级方案。简单的降级方式是用基础的正则表达式分割,但这会失去 locale 感知分段的准确性。
function splitSentences(text, locale = "en") {
if (typeof Intl.Segmenter !== "undefined") {
const segmenter = new Intl.Segmenter(locale, { granularity: "sentence" });
return Array.from(segmenter.segment(text), s => s.segment);
}
// Fallback for older environments
return text.split(/[.!?]\s+/).filter(s => s.length > 0);
}
console.log(splitSentences("Hello world. How are you?"));
// ["Hello world. ", "How are you?"]
此函数在可用时会使用 Intl.Segmenter,在较旧的环境下则回退为正则表达式分割。回退方案会丢失如缩写处理和特定语言边界检测等功能,但能提供基本功能。