K_DM Book
用語集
対象者 標準
対象者別の入口 →

対象者の設定はこのブラウザの中にだけ保存されます。

言語 JA

日本語文を安全に分割する

01.7.10

日本語文を安全に分割する

最終更新 2020-06-03 読了時間 1 分
まとめ
日本語テキストを「。」「!」「?」(半角の ! ? も含む)の位置で文単位に分割します。単純な text.split("。") と違い、正規表現 SENTENCE_END の先読みで「直後が閉じ括弧なら切らない」というルールを入れているので、「(本当に?)」の中の疑問符では分割されません。finditer で文末記号の位置を順に拾い、句点のない最後の「了解しました👍」も tail として取り出します。形態素解析や文ごとの感情分析に渡す前の、チャットログや議事録の整形に使います。

直感 #

チャットの「今日は打ち合わせ!(本当に?)気を付けてね…。了解しました👍」を文に分けたいとき、text.split("。") では「!」で切れず、split("?") では括弧の中の「?」でも切れてしまいます。人が読むときの区切りは「文末記号の直後、ただし閉じ括弧が続くときは括弧の外まで」なので、そのルールを正規表現に写します。文末記号を見つけたら、そこまでを 1 文として切り出し、残りの先頭から次を探す。これを繰り返し、最後に記号のない残りがあればそれも 1 文として足します。

Python で分割する #

次のコードは文末記号と先読みを 1 本の正規表現にまとめ、split_sentences が文のリストを返します。サンプルには括弧内の疑問符、三点リーダ、句点のない末尾の文を混ぜています。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import re

SENTENCE_END = re.compile(r"([。!?!?]+)(?=[^\\)」』】》”』』】》」]|\Z)")

def split_sentences(text: str) -> list[str]:
    parts = []
    start = 0
    for match in SENTENCE_END.finditer(text):
        end = match.end()
        sentence = text[start:end].strip()
        if sentence:
            parts.append(sentence)
        start = end
    tail = text[start:].strip()
    if tail:
        parts.append(tail)
    return parts

sample = "今日は打ち合わせ!(本当に?)気を付けてね…。了解しました👍"
for i, sentence in enumerate(split_sentences(sample), start=1):
    print(i, sentence)

実行結果の読み方 #

1 文目は「!」の直後が「(」なので切れ、2 文目は「(本当に?)」の「?」の直後が「)」なので切れず、「…。」の「。」までが 1 文になります。3 文目は句点がないため tail として追加された文です。括弧の中で文が終わる「(本当に?)」を 1 文として独立させたい場合は、先読みの除外文字から「)」を外せば挙動が変わります。

使いどころと落とし穴 #

括弧内の疑問符で分割したくないケースは今のルールで対応できますが、逆に「」の中の「。」でも切りたい場合は、SENTENCE_END の先読みにある除外文字クラスから「」」を外して調整します。Slack などで見かける句点も改行もない長文には、文末記号が 1 つも見つからず全体が 1 文になるので、20〜30 文字ごとに強制分割するフォールバックを split_sentences の後ろに用意すると下流の処理が安定します。高い精度が求められる場合は、日本語に対応した文分割モデル(spaCy と GiNZA の senter、ja_sentence_segmenter など)を選定し、このルールベース分割は依存の少ない簡易版として併用するのがおすすめです。