Pythonでプログラミングを行っていると、膨大なテキストデータの中から特定のパターンに一致する文字列を効率よく抽出したい場面が多々あります。
ログファイルの解析やスクレイピングしたデータの整理において、Pythonの標準ライブラリであるreモジュールは非常に強力な武器となります。
その中でも、条件に合致するすべての箇所を一度に取得できるre.findall関数は、最も頻繁に利用されるメソッドの一つです。
本記事では、re.findallの基本的な書き方から、実務で役立つ応用的な正規表現のテクニックまで、具体例を交えて詳しく紹介します。
正規表現の文法に慣れていない初心者の方でも、この記事を読み終える頃には、自由自在に文字列を抽出できるようになるでしょう。
Pythonのre.findallとは?
re.findallは、Pythonの正規表現操作を担うreモジュールに含まれる関数です。
この関数の最大の特徴は、対象の文字列内を左から右へスキャンし、パターンに一致したすべての部分をリスト形式で返すという点にあります。
他のメソッドであるre.searchが「最初に見つかった1件」のみを返すのに対し、re.findallは「見つかったものすべて」を対象とします。
そのため、文章の中から電話番号をすべて抜き出したり、特定のタグに囲まれた値を一括で取得したりする際に非常に便利です。
正規表現自体は、文字列の集合を一つの形式で表現するための言語であり、Pythonに限らず多くのプログラミング言語で採用されています。
Pythonでこの機能を利用するためには、まずimport reを記述してモジュールを読み込む必要があります。
re.findallの基本的な使い方
まずは、re.findallの基本的な構文を確認しましょう。
基本的な呼び出し方は、re.findall(パターン, 対象文字列)という形式になります。
import re
# 対象となる文字列
text = "私の郵便番号は123-4567です。以前は987-6543でした。"
# 数字3桁-数字4桁のパターンを指定
pattern = r"\d{3}-\d{4}"
# 抽出実行
result = re.findall(pattern, text)
print(result)
['123-4567', '987-6543']
上記のコードでは、ハイフンで区切られた郵便番号の形式に一致する箇所をすべて抽出しています。
結果がリスト形式で返ってくるため、そのままfor文などでループ処理に回すことも可能です。
第1引数:パターン(正規表現)
第1引数には、抽出したい文字列のパターンを指定します。
このとき、raw文字列(r”…”)を使用することが推奨されます。
バックスラッシュをエスケープ文字としてではなく、正規表現のメタ文字として正しく扱うためです。
第2引数:対象文字列
第2引数には、検索の対象となるテキストデータを渡します。
ここには通常の文字列型(str)を指定します。
第3引数:フラグ(オプション)
第3引数には、検索の挙動を制御するためのオプションフラグを指定できます。
例えば、大文字と小文字を区別せずに検索したい場合は、re.IGNORECASE(またはre.I)を指定します。
import re
text = "Python, python, PYTHON"
# 大文字小文字を無視して抽出
result = re.findall(r"python", text, re.IGNORECASE)
print(result)
['Python', 'python', 'PYTHON']
よく使われる正規表現のメタ文字一覧
re.findallを使いこなすためには、正規表現で使われる「メタ文字」を理解しておく必要があります。
メタ文字とは、特定の文字そのものではなく、文字の種類や繰り返し回数を表す特殊な記号のことです。
| 記号 | 意味 | 例 |
|---|---|---|
. | 任意の1文字(改行を除く) | a.c → abc, acc, a1c |
\d | 任意の数字(0-9) | \d\d → 12, 99 |
\D | 数字以外の文字 | \D → A, あ, – |
\w | 英数字およびアンダースコア | \w+ → python_3 |
\s | 空白文字(スペース、タブ、改行など) | \s → ” “ |
* | 直前の文字の0回以上の繰り返し | ab* → a, ab, abb |
+ | 直前の文字の1回以上の繰り返し | ab+ → ab, abb |
? | 直前の文字が0回または1回出現 | ab? → a, ab |
{n} | 直前の文字のn回の繰り返し | \d{3} → 123, 456 |
[] | カッコ内のいずれかの1文字 | [abc] → a, b, cのいずれか |
これらの記号を組み合わせることで、複雑な条件の文字列抽出が可能になります。
キャプチャグループを利用した抽出の仕組み
re.findallの挙動で最も注意すべきなのが、丸括弧()を使用した「グループ化」です。
正規表現内にグループが含まれている場合、re.findallはパターン全体ではなく、グループに一致した部分だけを返します。
単一グループの場合
パターンの中に一つだけ丸括弧がある場合、その部分に合致した文字列がリストの要素となります。
import re
text = "apple: 100円, orange: 200円, banana: 150円"
# 数字の部分だけをグループ化して抽出
result = re.findall(r"(\d+)円", text)
print(result)
['100', '150', '200']
この例では「円」という文字を検索条件には含めていますが、抽出結果からは除外されています。
特定のキーワードに続く数値だけが欲しい場合に非常に有効なテクニックです。
複数グループの場合
一つのパターンの中に複数の丸括弧がある場合、結果は「タプルを要素に持つリスト」になります。
import re
text = "2026/05/16, 2026/12/25"
# 年、月、日をそれぞれグループ化
result = re.findall(r"(\d{4})/(\d{2})/(\d{2})", text)
print(result)
[('2026', '05', '16'), ('2026', '12', '25')]
このように、構造化されたデータを一度に切り出すことができるため、データのパース(解析)作業が大幅に効率化されます。
re.findallと他のメソッドの違い
reモジュールには似たような関数がいくつか存在します。
適切な場面で使い分けができるよう、それぞれの違いを整理しましょう。
re.searchとの違い
re.searchは、文字列全体の中で最初に見つかったマッチ箇所を「マッチオブジェクト」として返します。
一方、re.findallはすべてのマッチ箇所を「リスト(またはタプル)」として返します。
マッチした箇所の位置情報(インデックス)が必要な場合はre.search、中身の値だけが大量に欲しい場合はre.findallを選びます。
re.matchとの違い
re.matchは、文字列の先頭がパターンに一致しているかどうかを判定します。
文章の途中にあるキーワードを探す場合には適していません。
re.findallは文字列のどこにあっても抽出対象とするため、検索の自由度が高いと言えます。
re.finditerとの違い
re.finditerは、re.findallと同じくすべてのマッチ箇所を探しますが、結果をリストではなく「イテレータ」として返します。
大規模なテキストファイルを処理する場合、一度にすべての結果をメモリ上に展開するre.findallはメモリを大量に消費する恐れがあります。
メモリ効率を重視するならre.finditerを使用し、手軽にリストで扱いたいならre.findallを使用するのが定石です。
実践的な活用例
ここからは、実務でよく遭遇する具体的なケースでの活用方法を紹介します。
メールアドレスの抽出
テキスト内からメールアドレスの形式に合致するものをすべて抜き出します。
import re
content = "お問い合わせは support@example.com または info@tech-blog.jp まで。"
email_pattern = r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}"
emails = re.findall(email_pattern, content)
print(emails)
['support@example.com', 'info@tech-blog.jp']
HTMLタグ内のテキスト抽出
特定のHTMLタグに囲まれたデータだけを取得する例です。
import re
html = "<li>Python</li><li>Java</li><li>Go</li>"
# 非強欲マッチ(.*?)を使用してタグの中身を取得
tags = re.findall(r"<li>(.*?)</li>", html)
print(tags)
['Python', 'Java', 'Go']
ここで使用している.*?は「最短一致」と呼ばれ、次の終了タグが現れるまでの最小範囲にマッチさせるための重要な記述です。
re.findall使用時の注意点とパフォーマンス
便利なre.findallですが、使用にあたっていくつか注意すべき点があります。
重複するマッチングの扱い
re.findallは、重なり合ったマッチングは無視されるという仕様を持っています。
例えば、"aaaaa"という文字列に対して"aaa"を検索した場合、インデックス0〜2の"aaa"は見つかりますが、インデックス1〜3のような重複部分はスキャン対象から外れます。
もし重複を含めて抽出したい場合は、先読み言明(Lookahead assertion)などの高度なテクニックが必要になります。
re.compileによる高速化
同じパターンを繰り返し使用する場合は、あらかじめコンパイルしておくことで実行速度を向上させることができます。
import re
# パターンをコンパイル
regex = re.compile(r"\d+")
# コンパイル済みのオブジェクトからfindallを呼び出す
result = regex.findall("100 units, 200 items, 300 boxes")
print(result)
['100', '200', '300']
ループの中で何度もre.findallを呼び出すような処理では、この方式を採用することで処理時間を短縮できます。
まとめ
Pythonのre.findallは、正規表現を用いて文字列を効率的に抽出するための非常に強力な関数です。
基本的な使い方をマスターするだけでも、日常的なデータ処理の工数を劇的に削減することができます。
特に、キャプチャグループを利用した特定の箇所の切り出しや、メタ文字を組み合わせたパターン定義は、実務において欠かせないスキルです。
まずは単純な数字や英単語の抽出から始め、徐々に複雑なパターンに挑戦してみてください。
正規表現は一度習得すれば、Python以外の環境でも一生使える強力なスキルとなります。
本記事を参考に、ぜひ日々のコーディングにre.findallを取り入れてみてください。
