閉じる

【Python】正規表現(re)入門: 基礎からよく使うパターン集まで

テキスト処理で「部分一致だけしたい」「数字だけ抜き出したい」という場面はとても多いです。

そうしたときに力を発揮するのが正規表現です。

本記事ではPythonのreモジュールを使い、基礎の使い方から、メールやURLなどすぐ使える実用パターンまでを、初心者向けに丁寧に解説します。

Pythonの正規表現(re)の基本

正規表現とは

正規表現は、文字列パターンを表現する小さな言語です。

短いパターンで複雑な条件にマッチできるため、ログ解析、データクレンジング、入力チェックなどで広く使われます。

Pythonではreモジュールを利用し、検索(マッチング)、抽出、置換を行います。

まずは最小限の関数から使い始めると習得が早いです。

まずは動かしてみる

Python
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文字列はバックスラッシュをそのままの文字として扱うので、正規表現の記述ミスを防げます。

Python
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を使うとよい場面が多いです。

Python
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がメモリ効率に優れます。

Python
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は一致部分を置換します。

キャプチャグループを使うと、一部だけ残す/伏せるなど柔軟に加工できます。

Python
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コンパイル済みパターンを作ると高速です。

フラグもここで指定します。

Python
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ブロック扱い

実例で確認します。

Python
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に通します。

Python
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を指定します。

Python
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つのスペースにします。

テキスト整形の第一歩です。

Python
import re

s = "Python   正規表現\t入門   です\nよろしく "
normalized = re.sub(r"\s+", " ", s).strip()
print(normalized)
実行結果
Python 正規表現 入門 です よろしく

行頭・行末の一致(^/$)

行頭や行末での一致判定は^$を使います。

複数行テキストではre.MULTILINEと組み合わせます。

Python
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を使います。

Python
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完全準拠は非常に複雑なので、実務では簡易チェックに留めるのが現実的です。

以下はよく使われる簡易版です。

Python
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を文中から抽出します。

末尾に来やすい記号をいくつか除外しています。

Python
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}です。

数字に隣接する他の数字を避けるために前後を否定の先読み/後読みで囲むと安全です。

Python
import re

text = "〒100-0001 東京都千代田区。別の例は123-4567です。"
print(re.findall(r"(?<!\d)\d{3}-\d{4}(?!\d)", text))
実行結果
['100-0001', '123-4567']

電話番号(ハイフン区切り)

多様ですが、代表的な固定/携帯/フリーダイヤルをざっくり拾う例です。

Python
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)

月と日の範囲を制限した簡易チェックです。

うるう年までは考慮しません。

Python
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で検証しましょう。

Python
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桁ごとのカンマ区切りを検出します。

連続した数字に埋もれないように前後をチェックします。

Python
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']

初心者がつまずきやすい注意点

貪欲と最短一致(* と *?)

量指定子はデフォルトで貪欲(できるだけ多く取る)です。

必要に応じて*?+?のような最短一致を使います。

Python
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は「単語境界」ですが、通常文字列では「バックスペース」に化けます。

Python
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は部分一致なので通ってしまうことがあります。

Python
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でパターン分解を確認する方法もあります。

Python
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点は、初心者が最初に身につけたい鉄則です。

あとは小さなテキストで挙動を確かめつつ、必要な場面で表現を少しずつ強化していくのが近道です。

正規表現は慣れるほど強力な道具になります。

ぜひ日々のテキスト処理に取り入れてみてください。

この記事を書いた人
エーテリア編集部
エーテリア編集部

人気のPythonを初めて学ぶ方向けに、文法の基本から小さな自動化まで、実際に手を動かして理解できる記事を書いています。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!