テキスト処理で「部分一致だけしたい」「数字だけ抜き出したい」という場面はとても多いです。
そうしたときに力を発揮するのが正規表現です。
本記事ではPythonのre
モジュールを使い、基礎の使い方から、メールやURLなどすぐ使える実用パターンまでを、初心者向けに丁寧に解説します。
Pythonの正規表現(re)の基本
正規表現とは
正規表現は、文字列パターンを表現する小さな言語です。
短いパターンで複雑な条件にマッチできるため、ログ解析、データクレンジング、入力チェックなどで広く使われます。
Pythonではre
モジュールを利用し、検索(マッチング)、抽出、置換を行います。
まずは最小限の関数から使い始めると習得が早いです。
まずは動かしてみる
import re
text = "Order ID: A-1024, price: 3,240 JPY"
# 数字(1桁以上)にマッチする \d+ の例
numbers = re.findall(r"\d+", text) # raw文字列 r"..." を推奨
print(numbers) # ['1024', '3', '240']
['1024', '3', '240']
r”(raw文字列)の書き方
パターン文字列はraw文字列(r”…”)で書くのが定石です。
通常の文字列では\n
が改行になるなど、エスケープが先に解釈されてしまうためです。
raw文字列はバックスラッシュをそのままの文字として扱うので、正規表現の記述ミスを防げます。
import re
# 通常の文字列: "\n" は1文字(改行)
print(len("\n"))
# raw文字列: r"\n" は2文字のまま
print(len(r"\n"))
# 正規表現例: \d+ は「1桁以上の数字」
print(re.findall(r"\d+", "A1B22C333"))
1
2
['1', '22', '333']
matchとsearchの違い
re.match
は文字列の先頭からマッチします。
先頭で合わなければNone
です。
一方、re.search
は文字列のどこかに一致があればマッチします。
迷ったらsearch
を使うとよい場面が多いです。
import re
text = "abc123"
print(bool(re.match(r"\d+", text))) # 先頭が数字ではないので False
print(re.search(r"\d+", text).group()) # 途中の "123" にヒット
False
123
findallとfinditerの使い分け
findall
は一致した文字列をリストで全て返します。
finditer
はイテレータで1件ずつ返し、マッチ位置(span()
)など詳細にアクセスできます。
大きなテキストにはfinditer
がメモリ効率に優れます。
import re
s = "a1 b22 c333"
print(re.findall(r"\d+", s))
for m in re.finditer(r"\d+", s):
print(m.group(), m.span()) # マッチ文字列と(開始, 終了)位置
['1', '22', '333']
1 (1, 2)
22 (4, 6)
333 (8, 11)
subで置換する
re.sub
は一致部分を置換します。
キャプチャグループを使うと、一部だけ残す/伏せるなど柔軟に加工できます。
import re
s = "ID: A-001, B-002"
print(re.sub(r"\d", "X", s)) # 数字をXでマスク
phone = "03-1234-5678"
masked = re.sub(r"(\d{2,4})-(\d{2,4})-(\d{4})", r"-****-", phone)
print(masked) # 中央4桁を伏せる
ID: A-XXX, B-XXX
03-****-5678
パターンの再利用(compile)
同じパターンで何度も検索するならre.compile
でコンパイル済みパターンを作ると高速です。
フラグもここで指定します。
import re
word_pat = re.compile(r"[A-Za-z]+")
print(word_pat.findall("abc 123 DEF"))
print(word_pat.search("123abc").group())
['abc', 'DEF']
abc
メタ文字とアンカー(^ $ . [] () | ? * + { })
正規表現の「記号」をまとめます。
これらの意味を理解すると、表現力が一気に広がります。
メタ文字 | 意味 | 例 |
---|---|---|
^ | 行頭(先頭) | ^Hello は行頭のHello |
$ | 行末(末尾) | world$ は行末のworld |
. | 任意の1文字(改行除く) | a.c はabc, a-cなど |
[] | 文字クラス | [abc] はaまたはbまたはc |
() | グループ/キャプチャ | (foo|bar) 代替のまとまり |
| | 代替(OR) | cat|dog |
? | 直前の0回または1回(最短量指定子にも使用) | colou?r |
* | 0回以上 | ab* はa, ab, abbbなど |
+ | 1回以上 | ab+ はab, abbbなど |
{m,n} | 繰り返し回数 | \d{2,4} は2〜4桁 |
これらメタ文字自体を文字として扱いたい場合は.
のようにバックスラッシュでエスケープします。
安全に全部エスケープしたいときはre.escape(text)
が便利です。
よく使うショートハンド(\d \w \s)
ショートハンドは書く量を減らす略記です。
Python 3ではUnicode対応である点に注意します。
ショートハンド | 展開(概念) | 例/注意 |
---|---|---|
\d | 数字(Unicodeの数字全般) | アラビア数字以外の数字も含むことあり。ASCII限定はre.ASCII フラグ |
\w | 単語構成文字(英数アンダースコアに加えUnicodeの文字) | 日本語やアクセント付き文字も含む |
\s | 空白(スペース, タブ, 改行など) | 行をまたぐ可能性に注意 |
\w は「英字だけ」ではない点が初心者の落とし穴です。
ASCIIに限定するならフラグre.ASCII
を使います。
フラグの基本(IGNORECASE/MULTILINE/DOTALL)
フラグはマッチの挙動を切り替えます。
複数同時指定も可能です(例: re.IGNORECASE | re.MULTILINE
)。
フラグ | 意味 | 典型用途 |
---|---|---|
re.IGNORECASE | 大文字小文字を無視 | キーワード検索 |
re.MULTILINE | ^/$ を各行の先頭/末尾に | 複数行テキストの行単位判定 |
re.DOTALL | . が改行にもマッチ | 複数行をまとめて1ブロック扱い |
実例で確認します。
import re
text = "Hello\nworld"
# 大文字小文字を無視
print(bool(re.search(r"hello", text, re.IGNORECASE)))
# 複数行: 各行の先頭を抽出
print(re.findall(r"^\w+", text, re.MULTILINE))
# 改行を含めて一気にマッチ
print(re.findall(r"Hello.*world", text, re.DOTALL))
True
['Hello', 'world']
['Hello\nworld']
検索・置換の基本パターン
数字を抽出する(\d+)
数字を全部抜き出す基本パターンは\d+
です。
整数化したいときはint
に通します。
import re
s = "合計: 12個, 単価: 345円, 小計: 4,140円"
nums = re.findall(r"\d+", s)
print(nums)
print([int(n) for n in nums]) # 文字列を整数に変換
['12', '345', '4', '140']
[12, 345, 4, 140]
単語を抽出する(\w+)
\w+
は英数アンダースコアに加え、日本語なども含みます。
ASCIIに限定するにはre.ASCII
を指定します。
import re
s = "foo bar_1 café 東京"
print(re.findall(r"\w+", s)) # デフォルト(Unicode対応)
print(re.findall(r"\w+", s, re.ASCII)) # ASCIIに限定
['foo', 'bar_1', 'café', '東京']
['foo', 'bar_1', 'caf']
空白の正規化(複数空白→1つ)
複数の空白やタブ、改行をまとめて1つのスペースにします。
テキスト整形の第一歩です。
import re
s = "Python 正規表現\t入門 です\nよろしく "
normalized = re.sub(r"\s+", " ", s).strip()
print(normalized)
Python 正規表現 入門 です よろしく
行頭・行末の一致(^/$)
行頭や行末での一致判定は^
と$
を使います。
複数行テキストではre.MULTILINE
と組み合わせます。
import re
print(bool(re.match(r"^\d+$", "123"))) # 全体が数字のみ
print(bool(re.match(r"^\d+$", "123a"))) # 末尾に文字がある
print(re.findall(r"^.+$", "A\nB\nC", re.MULTILINE))
True
False
['A', 'B', 'C']
任意文字と改行対応(.(dot)/DOTALL)
.
は改行以外の任意1文字です。
改行も含めてマッチしたい場合はre.DOTALL
を使います。
import re
text = "A\nB\nC"
print(re.findall(r"A.*C", text)) # 改行に阻まれてマッチしない
print(re.findall(r"A.*C", text, re.DOTALL)) # 改行込みでマッチ
[]
['A\nB\nC']
実用例(メールアドレス/URLなど)
メールアドレスの簡易チェック
RFC完全準拠は非常に複雑なので、実務では簡易チェックに留めるのが現実的です。
以下はよく使われる簡易版です。
import re
emails = [
"user@example.com",
"USER.name+tag@sub.domain.co",
"invalid@domain",
"a@b",
"spam@@example.com",
]
pattern = re.compile(r"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}")
for e in emails:
ok = bool(pattern.fullmatch(e)) # 全体一致で厳しめに
print(e, "=>", ok)
user@example.com => True
USER.name+tag@sub.domain.co => True
invalid@domain => False
a@b => False
spam@@example.com => False
実際の現場ではバリデーションを軽めにし、確認メールによる検証や既存ライブラリの利用を検討します。
URL(http/https)の検出
httpまたはhttpsのURLを文中から抽出します。
末尾に来やすい記号をいくつか除外しています。
import re
text = "公式サイト: https://example.com ドキュメント: http://docs.example.com/guide?lang=ja#top 以上"
urls = re.findall(r"https?://[^\s)>\]\"'、。,,]+", text)
print(urls)
['https://example.com', 'http://docs.example.com/guide?lang=ja#top']
より厳密なURL解析が必要ならurllib.parse
やサードパーティのURLバリデータを使います。
日本の郵便番号(123-4567)
基本形は\d{3}-\d{4}
です。
数字に隣接する他の数字を避けるために前後を否定の先読み/後読みで囲むと安全です。
import re
text = "〒100-0001 東京都千代田区。別の例は123-4567です。"
print(re.findall(r"(?<!\d)\d{3}-\d{4}(?!\d)", text))
['100-0001', '123-4567']
電話番号(ハイフン区切り)
多様ですが、代表的な固定/携帯/フリーダイヤルをざっくり拾う例です。
import re
text = "代表: 03-1234-5678, 携帯: 090-9876-5432, フリーダイヤル: 0120-12-345"
phones = re.findall(r"0\d{1,4}-\d{1,4}-\d{3,4}", text)
print(phones)
['03-1234-5678', '090-9876-5432', '0120-12-345']
厳密な判定は割当て規則に依存します。
実運用では要件に合わせて調整してください。
日付(YYYY-MM-DD)
月と日の範囲を制限した簡易チェックです。
うるう年までは考慮しません。
import re
text = "OK: 2024-02-29 NG: 2024-13-01 2024-00-10 2024-11-31"
pat = re.compile(r"(?:19|20)\d{2}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])")
print(pat.findall(text))
# 実在しない日付(2024-11-31)でも正規表現的には通ってしまう例
print(bool(pat.fullmatch("2024-11-31")))
['2024-02-29', '2024-11-31']
True
小の月やうるう年の考慮まで正規表現で行うことは難しいです。
カレンダー妥当性が必要なら、正規表現で年月日を取得したあと、datetime
で検証しましょう。
import re
from datetime import datetime
text = "OK: 2024-02-29 NG: 2024-13-01 2024-00-10 2024-11-31"
pat = re.compile(r"(?:19|20)\d{2}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])")
candidates = pat.findall(text)
valid_dates = []
for d in candidates:
try:
datetime.strptime(d, "%Y-%m-%d")
valid_dates.append(d)
except ValueError:
pass
print("Matched by regex:", candidates)
print("Valid dates only:", valid_dates)
Matched by regex: ['2024-02-29', '2024-11-31']
Valid dates only: ['2024-02-29']
このコードは正規表現で日付っぽい文字列を抽出し、datetime.strptime
で実際に存在する日付か検証しています。
正規表現だけでは検出できない「11月31日」などを除外できます。
カンマ区切り数値(1,234)
3桁ごとのカンマ区切りを検出します。
連続した数字に埋もれないように前後をチェックします。
import re
s = "売上: 1,234円、前年比: 12,345,678円、単体: 1234円"
pattern = r"(?<!\d)(?:\d{1,3})(?:,\d{3})+(?!\d)"
print(re.findall(pattern, s))
['1,234', '12,345,678']
初心者がつまずきやすい注意点
貪欲と最短一致(* と *?)
量指定子はデフォルトで貪欲(できるだけ多く取る)です。
必要に応じて*?
や+?
のような最短一致を使います。
import re
text = 'say "a" and "b"'
print(re.findall(r'"(.*)"', text)) # 貪欲: 最初の"から最後の"まで
print(re.findall(r'"(.*?)"', text)) # 最短一致: 各ペアごと
['a" and "b']
['a', 'b']
エスケープのミスを防ぐ(r’…’)
raw文字列を使わないと意図せぬ解釈になります。
例えば\b
は「単語境界」ですが、通常文字列では「バックスペース」に化けます。
import re
print(bool(re.search("\bcat\b", "a cat b"))) # 誤り: \b がバックスペースに
print(bool(re.search(r"\bcat\b", "a cat b"))) # 正: raw文字列で単語境界
False
True
正規表現リテラルは常に r”…” を使う習慣をつけましょう。
全体一致はfullmatch()
入力全体がパターンに一致するかはre.fullmatch
で確認します。
search
は部分一致なので通ってしまうことがあります。
import re
print(bool(re.search(r"\d+", "123abc"))) # 部分一致はTrue
print(bool(re.fullmatch(r"\d+", "123abc"))) # 全体一致はFalse
True
False
過度に厳密な検証は避ける(メール/URL)
メールやURLの完全検証は規格が非常に複雑です。
正規表現で頑張りすぎると保守不能になります。
最低限の形式チェック+実際の確認(メール送信/URLアクセス)や、専用ライブラリの利用を推奨します。
小さく試してから使う
正規表現は短くても表現力が高い分、意図せぬ一致が起きがちです。
小さな文字列でfindall
/search
を試し、ケースを増やして検証しましょう。
必要に応じてre.DEBUG
でパターン分解を確認する方法もあります。
import re
def test(pat, text, flags=0):
print("PAT:", pat, "| TEXT:", repr(text))
print("=>", re.findall(pat, text, flags))
test(r"\d+", "a1b22c")
test(r"^https?://[^\s)>\]\"'、。,,]+$", "https://example.com")
PAT: \d+ | TEXT: 'a1b22c'
=> ['1', '22']
PAT: ^https?://[^\s)>\]\"'、。,,]+$ | TEXT: 'https://example.com'
=> ['https://example.com']
まとめ
本記事ではPythonのre
モジュールについて、基礎(マッチ手法、置換、コンパイル、フラグ、メタ文字)から、よく使う実践パターン(メール、URL、郵便番号、電話、日付、カンマ数値)までを駆け足で解説しました。
特にraw文字列を使う、fullmatchで全体一致を確認する、貪欲と最短一致を意識するの3点は、初心者が最初に身につけたい鉄則です。
あとは小さなテキストで挙動を確かめつつ、必要な場面で表現を少しずつ強化していくのが近道です。
正規表現は慣れるほど強力な道具になります。
ぜひ日々のテキスト処理に取り入れてみてください。