CHAPTER 5 ・ ファイルとテキストの実践 / Lesson 20

文字のパターンで探す(正規表現)

請求書番号・電話番号・金額を、文章の中から抜き出す。よく使う型だけを、使えるようにします。

目安 22分 ・ インストール不要

このレッスンが終わったら

「正規表現は、文字の並びのパターンを書く方法。re.search で探し、() で必要な部分だけ取り出せる」と言える。

STEP 1

こんな場面、ありませんか?

営業のメモや、メールの文面の中から、請求番号・電話番号・金額だけを抜き出したい。そんな場面を考えます。次の notes.txt は、営業担当のメモです。

notes.txtサンプルファイル
【10/03】アオイ電機 経理部 山田様 TEL 03-1234-5678
請求書 INV-2025-0001 を送付。金額は 1,320,000円 です。
【10/05】ミドリ商事 佐々木様 携帯 090-1111-2222
見積 Q-2025-014 の件。金額 484,000円(税込)。納期は 11月15日。
【10/09】サクラ印刷 田村様 06-6543-2109
請求書 INV-2025-0003 の入金予定は 10月29日。792,000円。
【10/12】タチバナ工業 中村様 06-6000-1234
INV-2025-0004 の金額が違うとの連絡あり。231,000円のはず。

請求番号(INV-2025-0001 のような形)は、基礎編の道具で取り出せるでしょうか。INV- の位置を find で探して、決まった長さで切り出してみます。

基礎編の書き方:find で位置を探して切り出すサンプルファイル付き・書き換えOK
Ctrl / ⌘ + Enter でも実行

この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。

結果
▶ 実行を押すと、ここに結果が出ます

請求番号は取れました。ただし、これは「INV- で始まる」「13文字」と分かっているから、うまくいっただけです。電話番号(03-1234-5678 も 090-1111-2222 もある)や、金額(桁数が違う)には、位置も長さも決まった目印がなく、この方法は使えません。

こうした「形は決まっているが、中身は毎回違う」文字を探すのが、正規表現(せいきひょうげん)の役目です。

STEP 2

パターンで探してみる

正規表現では、探したい文字の形(パターン)を書きます。「INV-、数字4桁、ハイフン、数字4桁」という形を、次のように書きます。

re.findall で、請求番号・電話番号・金額を全部取り出すサンプルファイル付き・書き換えOK
Ctrl / ⌘ + Enter でも実行

この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。

結果
▶ 実行を押すと、ここに結果が出ます

3行とも、メモ全体から、形に合うものがすべて取り出されました。re.findall は「合うものを全部、リストで返す」道具です。最初の1つだけ欲しいときは re.search を使います。

re.search で、最初の1つを調べるサンプルファイル付き・書き換えOK
Ctrl / ⌘ + Enter でも実行

この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。

結果
▶ 実行を押すと、ここに結果が出ます

re.search は、見つかると「見つかった情報」(Match)を返し、.group() で中身の文字が取れます。

STEP 3

仕組みを読む

パターンは、記号を組み合わせた文字の並びの設計図です。INV-\d{4}-\d{4} を分けてみます。

r"INV-\d{4}-\d{4}" の部品
INV-そのままの文字
\d数字1文字
{4}を、ちょうど4回
-そのままの文字
\d{4}数字4桁

最初の r"..." の r は、バックスラッシュ(\)をそのままの文字として扱う目印(raw文字列)です。正規表現では \d を多用するので、r を付けて書くのが決まりです。

よく使う記号を、表にまとめます。

書き方意味例
\d数字1文字\d\d は「12」に合う
\d+数字が1文字以上続く「2025」全体に合う
\d{3,4}数字が3〜4桁「123」「1234」に合う
[A-Z]英大文字1文字[A-Z]+ は「INV」に合う
[\d,]数字かカンマ1文字[\d,]+ は「1,320,000」に合う
( )取り出す部分をまとめるStep 4 で使います

「または」は INV|Q のように縦線で書きます。Step 2 の1行目を、日本語にしてみます。

re.findall を、1行ずつ日本語にしてみる
import re正規表現の道具(re)を使う
re.findall(r"INV-\d{4}-\d{4}",「INV-」のあと、数字4桁、ハイフン、数字4桁、という形のものを全部
text)この文章の中から探して、リストで受け取る

日本語にすると

text の中から、「INV-」で始まって数字4桁・ハイフン・数字4桁と続く文字を、すべて探して、リストにしてください。

記号は他にもたくさんありますが、実務で必要なのは、上の表の形がほとんどです。「近い形を見つけて少し直す」「AIに書いてもらって確かめる」(Step 6)で十分なので、全部は覚えなくて大丈夫です。

STEP 4

変えて、試す

① 一部分だけ取り出す(グループ)。 金額の「円」は不要です。取り出したい部分を ( ) で囲むと、グループになり、findall はその部分だけを返します。カンマを外して数字にし、合計も出してみます。

( ) で、数字の部分だけを取り出して合計するサンプルファイル付き・書き換えOK
Ctrl / ⌘ + Enter でも実行

この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。

結果
▶ 実行を押すと、ここに結果が出ます

② 名前を付けて取り出す/置き換える。 グループには (?P<名前>...) で名前を付けられ、group("名前") で取れます。re.sub は「合う部分を別の文字に置き換える」道具です。

名前付きグループと、re.subコードは書き換えられます
Ctrl / ⌘ + Enter でも実行
結果
▶ 実行を押すと、ここに結果が出ます

\1 \2 \3 は、1番目・2番目・3番目のグループの中身です。ここでは、電話番号のハイフンを取り除きました。

③ 欲張りマッチの落とし穴。 .(なんでも1文字)と *(0回以上)を組み合わせた .* は、できるだけ長く合わせようとします。「できるだけ短く」にしたいときは .*? と書きます。

欲張りな .* と、控えめな .*?コードは書き換えられます
Ctrl / ⌘ + Enter でも実行
結果
▶ 実行を押すと、ここに結果が出ます

1行目は、最初の 【 から最後の 】 までを取ってしまいました。これが欲張りマッチです。

④ 実務の突き合わせ。 銀行の入金明細(payments.csv)の摘要から請求番号を取り出し、請求の一覧(invoices_billed.csv)と比べて、まだ入金のない請求を探します。(集合の差は Lesson 07 で学びます。)

摘要から請求番号を取り出して、未入金を探すサンプルファイル付き・書き換えOK
Ctrl / ⌘ + Enter でも実行

この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。

結果
▶ 実行を押すと、ここに結果が出ます

4件の請求番号が、未入金として出ました。なお、摘要は半角カナ混じりですが、請求番号の部分は半角の英数字なので、そのまま探せています。

⑤ 見つからないとき。 re.search は、見つからないと None を返します。そのまま .group() を呼ぶと、エラーで止まります。

わざと間違える:見つからなかったのに .group() を呼んだコードは書き換えられます
Ctrl / ⌘ + Enter でも実行
結果
▶ 実行を押すと、ここに結果が出ます

AttributeError: 'NoneType' object has no attribute 'group' と出ます。④のように if m: で、見つかったか確かめてから使います。

STEP 5

自分で書く

お題① notes.txt から、請求番号(INV- で始まるもの)をすべて取り出して、リストで表示してください。(Q-2025-014 のような見積番号は含めません)

お題①:請求番号の抽出サンプルファイル付き・書き換えOK
Ctrl / ⌘ + Enter でも実行

この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。

結果
▶ 実行を押すと、ここに結果が出ます
ヒントを見る

re.findall(r"INV-\d{4}-\d{4}", text) を print します。

答えの例を見る
import re

with open("notes.txt", encoding="utf-8") as f:
  text = f.read()

print(re.findall(r"INV-\d{4}-\d{4}", text))

お題② notes.txt に出てくる金額(「〇〇円」)の合計を、数字で表示してください。

お題②:金額の合計サンプルファイル付き・書き換えOK
Ctrl / ⌘ + Enter でも実行

この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。

結果
▶ 実行を押すと、ここに結果が出ます
ヒントを見る

re.findall(r"([\d,]+)円", text) で、数字とカンマだけが取れます。replace(",", "") でカンマを外し、int() に直して足します。

答えの例を見る
import re

with open("notes.txt", encoding="utf-8") as f:
  text = f.read()

amounts = [int(x.replace(",", "")) for x in re.findall(r"([\d,]+)円", text)]
print(sum(amounts))

お題③ payments.csv の摘要のうち、請求番号が書かれていない行は何行ありますか。件数を表示してください。

お題③:請求番号のない入金の件数サンプルファイル付き・書き換えOK
Ctrl / ⌘ + Enter でも実行

この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。

結果
▶ 実行を押すと、ここに結果が出ます
ヒントを見る

m = re.search(r"INV-\d{4}-\d{4}", row["摘要"]) として、if m is None: のときに count = count + 1 とします。

答えの例を見る
import csv
import re

count = 0
with open("payments.csv", encoding="utf-8") as f:
  for row in csv.DictReader(f):
      if re.search(r"INV-\d{4}-\d{4}", row["摘要"]) is None:
          count = count + 1

print(count)
STEP 6

仕事ならどう使う?

理解チェックre.findall(r"\d+", "INV-2025-0001") の結果は、どれでしょう?

理解チェックre.search で何も見つからなかったとき、返ってくるものは?