正規表現だけでは難しい:PythonとNLPによる箇条書き・リストからの構造化データ抽出
はじめに
業務ドキュメントには、議事録の決定事項、仕様書の要件リスト、手順書のステップ、顧客からの要望リストなど、箇条書きやリスト形式で重要な情報が記述されていることが多くあります。これらの非構造化、あるいは半構造化されたテキストから、特定の情報をプログラムで効率的に抽出し、データベースに取り込んだり、タスク管理システムと連携させたりといったニーズは少なくありません。
単純な箇条書きであれば正規表現で各項目を抽出できる場合もあります。しかし、表現の揺れ、複雑なネスト、項目内の多様な情報(担当者、期限、状態など)を含む場合、正規表現だけでは網羅的な抽出が困難になります。
本記事では、Pythonと自然言語処理(NLP)のライブラリを活用し、このような箇条書きやリスト形式のテキストから、より高精度かつ柔軟に情報を抽出し、構造化する手法について解説します。特に、実務でよく遭遇する議事録からのタスク抽出を例に、具体的なコードとともに手順を示します。
箇条書き・リスト形式テキスト抽出の課題
箇条書きやリスト形式のテキスト抽出における主な課題は以下の通りです。
- 多様なマーカー:
-
,*
,+
,1.
,(a)
,■
など、箇条書きを示す記号や番号の形式が多様です。 - インデントとネスト: サブ項目や補足情報がインデントされて記述される場合があり、項目間の親子関係を把握する必要があります。
- 項目内の情報構造: 各項目には、単なるタスク名だけでなく、担当者、期日、ステータス、関連する数値など、複数の情報が含まれていることが一般的です。これらの情報が非構造化なテキストとして記述されているため、特定の要素を識別し、抽出する必要があります。
- 表現の揺れ: 同じ意味内容でも、異なる単語や表現が使われる可能性があります。
- 自然言語の曖昧さ: 句読点の使い方、修飾語の範囲、省略などにより、テキストの構造や意味を正確に把握することが難しい場合があります。
正規表現は強力ですが、これらの複雑さや多様性を網羅的にルール化するのは非常に骨が折れる作業であり、メンテナンス性も低くなりがちです。ここでNLPの技術が有効になります。
NLPを活用したアプローチの概要
NLPを活用した箇条書き・リスト形式テキスト抽出は、一般的に以下のステップで考えられます。
- テキストの前処理: 入力テキストを扱いやすい形式に整形します。
- 箇条書き行の識別: テキスト内のどの行が箇条書きの項目であるかを判定します。正規表現と、必要に応じてNLPによる補助を用います。
- 行内の情報抽出: 識別された箇条書きの各行から、目的とする情報(例: 担当者、期日、タスク内容など)を抽出します。ここでNLPライブラリ(SpaCyなど)が中心的な役割を果たします。
- 構造化: 抽出した情報を構造化されたデータ形式(例: リスト、辞書、DataFrameなど)にまとめます。
以降では、Pythonの正規表現モジュール re
と、軽量かつ高性能なNLPライブラリである SpaCy
を用いた具体的な実装方法を解説します。
箇条書き行の識別
まずは、入力テキストから箇条書きと思われる行を抽出します。ここでは正規表現が一定の効果を発揮します。一般的な箇条書きマーカーに対応する正規表現パターンを定義し、各行にマッチするかを確認します。
例:議事録のサンプルテキスト
sample_text = """
## 第N回定例会議議事録
議事概要:〇〇プロジェクトの進捗確認、課題検討
決定事項:
- 資料Aの最終確認と承認(担当:山田、期日:来週金曜)
- 仕様書Bのレビュー指摘事項反映(担当:佐藤)
- 外部連携APIの仕様詳細検討(担当:田中、期日:3月末まで)
- 連携方式について技術検証を行う。
- 次回会議の日程調整(担当:議事録担当者、期日:〇月〇日)
- 顧客ヒアリング結果の分析と報告
"""
上記のテキストから、「- 」で始まる行を抽出するシンプルな正規表現は以下のようになります。
import re
def extract_bullet_points_simple(text):
# 行頭の空白の後に '-' とスペースが続くパターン
# ^\s* は行頭に0個以上の空白があることを意味します
# - はハイフンリテラルにマッチします
# \s+ は1個以上の空白にマッチします
pattern = re.compile(r"^\s*-\s+(.*)")
lines = text.strip().split('\n')
bullet_points = []
for line in lines:
match = pattern.match(line)
if match:
# グループ1((.*))が箇条書きの内容です
bullet_points.append(match.group(1).strip())
return bullet_points
extracted_points = extract_bullet_points_simple(sample_text)
print("--- 単純な正規表現による抽出 ---")
for point in extracted_points:
print(point)
実行結果:
--- 単純な正規表現による抽出 ---
資料Aの最終確認と承認(担当:山田、期日:来週金曜)
仕様書Bのレビュー指摘事項反映(担当:佐藤)
外部連携APIの仕様詳細検討(担当:田中、期日:3月末まで)
連携方式について技術検証を行う。
次回会議の日程調整(担当:議事録担当者、期日:〇月〇日)
顧客ヒアリング結果の分析と報告
この方法では、サブ項目も同じパターンにマッチするため抽出されます。また、1.
, *
など異なるマーカーや、インデントのみで階層を示すケースには対応できません。より複雑な正規表現を記述することも可能ですが、すべてのパターンを網羅するのは現実的ではありません。
より堅牢な実装では、行のインデントレベルを考慮したり、連続する箇条書き項目を一つのリストとして扱ったりといった前処理が必要になりますが、本記事では、各行が抽出された後の「行内の情報抽出」に焦点を当てます。上記の単純な方法で抽出された各行を対象として、NLPによる処理を進めます。
行内の情報抽出にNLPを活用する
抽出された各箇条書きの行は、比較的短いテキストであることが多いです。この短いテキストから、タスク内容、担当者、期日などの特定の情報を抜き出す際に、SpaCyのようなNLPライブラリが非常に役立ちます。
SpaCyは、テキストのトークン化、品詞タグ付け、固有表現抽出(NER)、依存構造解析などを高速に行うことができます。これらの機能を使って、行内の単語の関係性や意味的な役割を捉え、必要な情報を識別します。
まず、SpaCyをインストールします。
pip install spacy
python -m spacy download ja_core_news_sm # 日本語モデルのダウンロード
次に、抽出された各箇条書きの行に対してSpaCyを適用し、NERや依存構造解析を行います。
import spacy
# 日本語モデルをロード
nlp = spacy.load("ja_core_news_sm")
def extract_info_from_task_line(line):
doc = nlp(line)
# 抽出する情報の格納用辞書
info = {
"task": line, # 初期値は行全体
"assignee": None,
"deadline": None,
"mentions": [] # その他の固有表現などを格納
}
# 固有表現抽出 (Named Entity Recognition: NER)
# SpaCyの標準モデルは日本語の人名、組織名、日付などを識別できます
for ent in doc.ents:
# PERSONエンティティを担当者候補とする
if ent.label_ == "PERSON":
# 行内に「担当:〇〇」のようなパターンがあれば、それを優先する
# この例では単純にPERSONを拾います
if info["assignee"] is None: # 最初のPERSONを担当者とする(簡易)
info["assignee"] = ent.text
# 例外的に、エンティティが「担当」のような役割を示唆する場合は無視するなどの考慮が必要
# 例:「担当者」という文字列自体がPERSONとして検出される場合など
# DATEエンティティを期日候補とする
elif ent.label_ == "DATE":
info["deadline"] = ent.text
# その他、業務に必要なエンティティを拾う
elif ent.label_ in ["ORG", "PRODUCT", "LOCATION"]: # 例: 組織、製品、場所
info["mentions"].append({"text": ent.text, "label": ent.label_})
# 依存構造解析 (Dependency Parsing) や品詞タグ付け (POS)
# より詳細な情報や関係性を抽出する場合に利用します
# 例:「〇〇の確認」「〇〇を作成する」といった動詞と目的語の関係を捉える
task_phrases = []
for token in doc:
# 特定の品詞や依存関係を持つ単語を抽出
# 例:root(文の中心動詞)やdobj(直接目的語)など
if token.dep_ in ["ROOT", "dobj", "obl"]: # 簡易的な例
task_phrases.append(token.text)
# 担当者や期日情報が「担当:〇〇」「期日:〇〇」のような形式の場合
# 正規表現と組み合わせることでより正確に抽出できる
match_assignee = re.search(r"担当[::](\S+)", token.text) # 例: 「担当:山田」
if match_assignee:
info["assignee"] = match_assignee.group(1).strip(")") # 末尾の括弧などを削除
match_deadline = re.search(r"期日[::](\S+)", token.text) # 例: 「期日:来週金曜)
if match_deadline:
info["deadline"] = match_deadline.group(1).strip(")")
# タスク内容をより正確に定義する(ここでは簡易的に全体行としている)
# 実際には、動詞とその目的語、関連名詞などを組み合わせてタスク内容とする
# 例:「資料Aの最終確認と承認」 -> 「確認」「承認」(対象:資料A)
# 簡易的に、NERで抽出した担当者や期日部分を除外するなどの方法も考えられます
return info
# 単純正規表現で抽出した行に対して情報抽出を適用
extracted_structured_data = []
for line in extracted_points:
info = extract_info_from_task_line(line)
extracted_structured_data.append(info)
print("\n--- NLP(SpaCy)による情報抽出 ---")
for item in extracted_structured_data:
print(item)
実行結果 (例):
--- NLP(SpaCy)による情報抽出 ---
{'task': '資料Aの最終確認と承認(担当:山田、期日:来週金曜)', 'assignee': '山田', 'deadline': '来週金曜', 'mentions': [{'text': '資料A', 'label': 'PRODUCT'}]}
{'task': '仕様書Bのレビュー指摘事項反映(担当:佐藤)', 'assignee': '佐藤', 'deadline': None, 'mentions': [{'text': '仕様書B', 'label': 'PRODUCT'}]}
{'task': '外部連携APIの仕様詳細検討(担当:田中、期日:3月末まで)', 'assignee': '田中', 'deadline': '3月末', 'mentions': [{'text': '外部連携API', 'label': 'PRODUCT'}]}
{'task': '連携方式について技術検証を行う。', 'assignee': None, 'deadline': None, 'mentions': []}
{'task': '次回会議の日程調整(担当:議事録担当者、期日:〇月〇日)', 'assignee': '議事録担当者', 'deadline': '〇月〇日', 'mentions': []}
{'task': '顧客ヒアリング結果の分析と報告', 'assignee': None, 'deadline': None, 'mentions': []}
上記の例では、doc.ents
を使った固有表現抽出と、簡単な正規表現 (担当:〇〇
, 期日:〇〇
) を組み合わせることで、担当者と期日を抽出しています。SpaCyの標準NERが日本語の日付や人名をある程度捉えられるため、この組み合わせは比較的効果的です。
ただし、担当:〇〇
のような明確なパターンがない場合や、標準NERで識別できないカスタムエンティティ(例: 社内の特定のプロジェクト名、ステータス表現など)を抽出したい場合は、以下の発展的な手法が必要になります。
より高度な情報抽出のためのテクニック
-
カスタムエンティティ抽出 (Custom NER): 業務固有のエンティティ(例: 社内のシステム名、製品コード、特定の契約用語など)は、標準のSpaCyモデルでは識別できません。これらのエンティティを抽出するには、カスタムモデルを学習させるか、ルールベースのMatcherを使用します。SpaCyの
Matcher
やPhraseMatcher
を使うと、辞書やパターンのリストに基づいて高速に文字列やトークンのパターンにマッチさせることができます。例:特定のステータス表現(「対応中」「完了」「要確認」など)を抽出するMatcherの利用
```python from spacy.matcher import PhraseMatcher
Statusエンティティとして抽出したい語句リスト
status_patterns = ["対応中", "完了", "要確認", "ペンディング"]
matcher = PhraseMatcher(nlp.vocab, attr="TEXT") # トークンテキストにマッチ matcher.add("STATUS", [nlp(text) for text in status_patterns])
def extract_info_with_matcher(line): doc = nlp(line) info = {"task": line, "status": None}
# Matcherの適用 matches = matcher(doc) for match_id, start, end in matches: span = doc[start:end] # 一旦最初に見つかったステータスを採用(簡易) if info["status"] is None: info["status"] = span.text # 複数見つかる場合はリストにするなどの工夫が必要 return info
例: ステータスを含む箇条書き行
sample_line_with_status = "資料Bのレビュー指摘事項反映(担当:佐藤) -> 完了" info_with_status = extract_info_with_matcher(sample_line_with_status) print("\n--- Matcherによるステータス抽出 ---") print(info_with_status) ```
実行結果 (例):
--- Matcherによるステータス抽出 --- {'task': '資料Bのレビュー指摘事項反映(担当:佐藤) -> 完了', 'status': '完了'}
-
依存構造や品詞情報に基づいたルール: 「〇〇のレビュー指摘事項反映」のようなフレーズから「レビュー指摘事項反映」がタスク内容であり、「〇〇」が対象であることを特定するには、依存構造解析が役立ちます。SpaCyの
token.dep_
やtoken.pos_
を調べ、特定の動詞とその目的語、または関連する名詞句を組み合わせるルールを構築します。例:「〇〇のレビュー」のようなパターンを検出する(概念的なコード)
```python def extract_task_target(line): doc = nlp(line) task_target = {"task_verb_phrase": None, "target_noun": None}
# 簡単な例: 動詞の目的語やそれに先行する名詞を対象とする for token in doc: # 「レビュー」のような名詞に注目 if token.lemma_ == "レビュー" and token.pos_ == "NOUN": # この名詞に依存する前置詞句などを探す(例: 「〇〇の」) for child in token.children: if child.dep_ == "nmod" or child.dep_ == "obl": # 例: 「〇〇の」の「〇〇」 task_target["target_noun"] = child.text task_target["task_verb_phrase"] = token.text + "指摘事項反映" # 例として結合 break # 一旦最初に見つかったパターンで終了 return task_target
sample_line = "仕様書Bのレビュー指摘事項反映(担当:佐藤)" task_info = extract_task_target(sample_line) print("\n--- 依存構造/品詞に基づくタスク要素抽出 ---") print(task_info) ``` (注:上記のコード例は依存構造解析の概念を示すための簡易的なものです。実際の依存関係はより複雑で、robustなルール構築には試行錯誤が必要です。)
これらの高度なテクニックを組み合わせることで、より複雑な情報構造を持つ箇条書きから、担当者、期日、ステータス、関連するオブジェクト、タスク内容といった要素を分離し、構造化されたデータとして抽出することが可能になります。
抽出した情報の構造化
抽出した情報をどのように構造化するかは、その後の利用目的によって異なります。一般的なのは、各箇条書き項目を1つのレコードとして、抽出した要素をフィールドとする形式です。Pythonであれば、辞書のリストとして表現するのが直感的です。
# 前述の extracted_structured_data は既に辞書のリスト形式です
# [{'task': '...', 'assignee': '...', 'deadline': '...', 'mentions': [...]}, ...]
# このリストをPandas DataFrameに変換すると、さらに分析や加工が容易になります
import pandas as pd
df = pd.DataFrame(extracted_structured_data)
print("\n--- Pandas DataFrameへの変換 ---")
print(df)
実行結果 (例):
--- Pandas DataFrameへの変換 ---
task assignee deadline mentions
0 資料Aの最終確認と承認(担当:山田、期日:来週金曜) 山田 来週金曜 [{'text': '資料A', 'label': 'PRODUCT'}]
1 仕様書Bのレビュー指摘事項反映(担当:佐藤) 佐藤 None [{'text': '仕様書B', 'label': 'PRODUCT'}]
2 外部連携APIの仕様詳細検討(担当:田中、期日:3月末まで) 田中 3月末 [{'text': '外部連携API', 'label': 'PRODUCT'}]
3 連携方式について技術検証を行う。 None None []
4 次回会議の日程調整(担当:議事録担当者、期日:〇月〇日) 議事録担当者 〇月〇日 []
5 顧客ヒアリング結果の分析と報告 None None []
DataFrame形式にすることで、特定の担当者のタスクを絞り込んだり、期日が近いタスクを抽出したりといったデータ操作が容易になります。
実務への応用と考慮事項
応用事例
- 議事録からのタスク自動登録: 決定事項の箇条書きから担当者、期日、タスク内容を抽出し、BacklogやJiraなどのタスク管理ツールにAPI経由で自動登録する。
- 顧客からの要望リスト分析: 顧客からのフィードバックや要望リストの箇条書きから、要望の種類、関連製品、緊急度などを抽出し、集計・分析して製品改善の優先順位付けに役立てる。
- 手順書からのステップ抽出: 複雑な手順書の箇条書きや番号付きリストから、各ステップの内容と順序を抽出し、チャットボットのFAQ応答データやRPAのシナリオ作成に活用する。
- エラーログからのパターン抽出: エラーログ内の箇条書き形式の要約や原因リストから、エラーコード、原因、推奨対応などを抽出し、ナレッジベースを構築する。
パフォーマンスと注意点
- 処理速度: SpaCyは比較的軽量で高速ですが、非常に大量のテキスト(例: 数万行の議事録テキストを一括処理)を処理する場合、それなりの時間がかかります。バッチ処理や、リアルタイム性が求められる場合は非同期処理を検討してください。
- モデルサイズ: SpaCyのモデルはサイズが異なります(sm, md, lgなど)。利用するモデルのサイズによってメモリ使用量やダウンロードサイズが変わります。サーバー環境に合わせて適切なモデルを選択してください。多くの場合、
ja_core_news_sm
でも十分な精度が得られます。 - 精度の限界: NLPによる情報抽出は、テキストの曖昧さや多様性があるため、100%の精度を保証するものではありません。特に、学習データにない表現や複雑な言い回しに対しては誤抽出や抽出漏れが発生する可能性があります。抽出結果のレビューや、手動での修正を組み合わせるワークフローが必要になる場合もあります。
- メンテナンス: 抽出ルール(正規表現、Matcherパターン、依存構造ルール)は、対象となるテキストの形式や表現の変化に合わせてメンテナンスが必要です。
- エラーハンドリング: 想定外のテキスト形式や、NLP処理中に発生しうるエラー(例: モデルロード失敗)に対する堅牢なエラーハンドリングを実装することが重要です。
まとめ
本記事では、PythonとSpaCyを組み合わせることで、正規表現だけでは対応が難しい箇条書きやリスト形式のテキストから、担当者、期日、タスク内容といった具体的な情報を効率的に抽出・構造化する手法を解説しました。
- 箇条書きの多様な形式に対応するためには、正規表現とNLPによる行内の情報抽出を組み合わせるアプローチが有効です。
- SpaCyの固有表現抽出(NER)や、Matcherを用いたルールベース抽出は、項目内の特定の要素(人名、日付、固有のキーワードなど)を識別するのに役立ちます。
- 抽出した情報を辞書やPandas DataFrameとして構造化することで、その後の処理や分析が容易になります。
- 実務への応用においては、議事録からのタスク自動登録や、要望リストの分析など、様々なケースが考えられます。
- 精度の限界やメンテナンスの必要性を理解し、必要に応じて手動レビューやルールの改善を行うことが、実用的なシステム構築においては重要です。
これらの技術は、非構造化テキストからの情報活用を進める上で強力なツールとなります。ぜひ、皆様の業務課題解決にご活用ください。