Python 正規表現の基本と活用法
Python 正規表現の基本構文とメタ文字
正規表現は文字列のパターンを表現するための特殊な記法です。Pythonでは標準ライブラリのre
モジュールを使って正規表現を扱います。まずは基本的なメタ文字から見ていきましょう。
import re # 正規表現を使うには必ずインポートが必要
主なメタ文字とその意味:
メタ文字 | 意味 | 例 | マッチする文字列 |
---|---|---|---|
. | 任意の1文字 | te.t |
“test”, “text”, “te9t” |
^ | 文字列の先頭 | ^Python |
“Python is fun”, “Python3” |
$ | 文字列の末尾 | py$ |
“I love py”, “happy” |
* | 直前の文字の0回以上の繰り返し | co*l |
“cl”, “col”, “coool” |
+ | 直前の文字の1回以上の繰り返し | co+l |
“col”, “cool”, “coool” |
? | 直前の文字の0回か1回の出現 | colou?r |
“color”, “colour” |
[] | 文字クラス(括弧内の任意の1文字) | [abc] |
“a”, “b”, “c” |
[^] | 否定文字クラス | [^0-9] |
数字以外の任意の文字 |
\d | 数字 | \d+ |
“123”, “42” |
\w | 英数字とアンダースコア | \w+ |
“Python_3”, “abc123” |
\s | 空白文字 | a\sb |
“a b”, “a b” |
これらのメタ文字を組み合わせることで、複雑なパターンを表現できます。
Python 正規表現の主要関数とメソッド
re
モジュールには、正規表現を使った文字列操作を行うための様々な関数が用意されています。
主要な関数
- re.match() – 文字列の先頭がパターンにマッチするか調べる
result = re.match(r'Hello', 'Hello, World!') # マッチする print(result.group()) # 'Hello' result = re.match(r'World', 'Hello, World!') # マッチしない print(result) # None
- re.search() – 文字列内でパターンにマッチする最初の部分を探す
result = re.search(r'World', 'Hello, World!') # マッチする print(result.group()) # 'World'
- re.findall() – パターンにマッチするすべての部分を見つけてリストで返す
result = re.findall(r'\d+', 'There are 3 apples and 5 oranges') print(result) # ['3', '5']
- re.sub() – パターンにマッチする部分を置換する
result = re.sub(r'[0-9]+', 'X', 'There are 3 apples and 5 oranges') print(result) # 'There are X apples and X oranges'
- re.compile() – 正規表現パターンをコンパイルして再利用する
pattern = re.compile(r'\d+') result1 = pattern.findall('There are 3 apples and 5 oranges') result2 = pattern.findall('I have 10 fingers and 10 toes')
正規表現パターンを何度も使用する場合は、re.compile()
でコンパイルしておくと効率的です。一度だけ使用する場合は、直接関数を呼び出す方が簡潔に書けます。
Python 正規表現でのグループ化とキャプチャ
正規表現では、括弧()
を使ってパターンの一部をグループ化することができます。グループ化には以下のような利点があります:
- 部分パターンの抽出 – マッチした部分を個別に取得できる
- 後方参照 – 同じパターンを繰り返し使用できる
- 名前付きグループ – グループに名前をつけて管理しやすくする
基本的なグループ化
text = "2023-03-15"
pattern = r'(\d{4})-(\d{2})-(\d{2})'
match = re.match(pattern, text)
if match:
year = match.group(1) # '2023'
month = match.group(2) # '03'
day = match.group(3) # '15'
print(f"年: {year}, 月: {month}, 日: {day}")
名前付きグループ
text = "2023-03-15"
pattern = r'(?P\d{4})-(?P\d{2})-(?P\d{2})'
match = re.match(pattern, text)
if match:
year = match.group('year') # '2023'
month = match.group('month') # '03'
day = match.group('day') # '15'
print(f"年: {year}, 月: {month}, 日: {day}")
名前付きグループを使うと、数字ではなく名前でグループを参照できるため、コードの可読性が向上します。特に複雑な正規表現パターンを扱う場合に便利です。
後方参照(バックリファレンス)
同じパターンが繰り返し出現する場合、後方参照を使って簡潔に表現できます。
# 同じ単語が連続する場合を検出
text = "The the quick brown fox jumps over the lazy dog"
pattern = r'\b(\w+)\s+\1\b' # \1は最初のグループを参照
matches = re.findall(pattern, text, re.IGNORECASE)
print(matches) # ['The']
グループ化と後方参照を使いこなすことで、より複雑なパターンマッチングを効率的に行うことができます。
Python 正規表現の実践的な使用例
正規表現の理論を理解したところで、実際の開発でよく使われるパターンを見ていきましょう。
1. メールアドレスの検証
def is_valid_email(email):
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
return bool(re.match(pattern, email))
# テスト
emails = ["user@example.com", "invalid-email", "user@.com", "user@example..com"]
for email in emails:
print(f"{email}: {is_valid_email(email)}")
2. URLの抽出
text = """
Visit our website at https://www.example.com or http://example.org.
For more information, go to https://info.example.net/page?id=123.
"""
pattern = r'https?://[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(?:/[^\s]*)?'
urls = re.findall(pattern, text)
print(urls)
# ['https://www.example.com', 'http://example.org', 'https://info.example.net/page?id=123']
3. 日本語テキストからの情報抽出
# 電話番号の抽出(日本の形式)
text = "お問い合わせは03-1234-5678または090-1234-5678まで。"
pattern = r'(?:\d{2,4}[-\s]?){2,3}\d{3,4}'
phone_numbers = re.findall(pattern, text)
print(phone_numbers) # ['03-1234-5678', '090-1234-5678']
# 郵便番号の抽出
address = "〒123-4567 東京都渋谷区..."
pattern = r'\d{3}[-\s]?\d{4}'
postal_code = re.search(pattern, address).group()
print(postal_code) # '123-4567'
4. ログファイルの解析
log_line = '192.168.1.1 - - [25/Mar/2023:10:15:32 +0900] "GET /index.html HTTP/1.1" 200 1234'
pattern = r'(\d+\.\d+\.\d+\.\d+).+\[(.+?)\].+"([A-Z]+)\s+(.+?)\s+HTTP.+?"\s+(\d+)\s+(\d+)'
match = re.search(pattern, log_line)
if match:
ip = match.group(1)
date = match.group(2)
method = match.group(3)
path = match.group(4)
status = match.group(5)
size = match.group(6)
print(f"IP: {ip}")
print(f"日時: {date}")
print(f"メソッド: {method}")
print(f"パス: {path}")
print(f"ステータス: {status}")
print(f"サイズ: {size}バイト")
これらの例は、実際の開発シーンで役立つ正規表現パターンです。自分のプロジェクトに合わせてカスタマイズして使用してみてください。
Python 正規表現のパフォーマンス最適化テクニック
正規表現は非常に強力なツールですが、使い方によってはパフォーマンスの問題が発生することがあります。以下に、正規表現を効率的に使うためのテクニックをいくつか紹介します。
1. 正規表現のコンパイル
同じパターンを複数回使用する場合は、re.compile()
でパターンをコンパイルしておくと処理が高速化されます。
# 非効率的な方法
for line in large_file:
if re.search(r'\d+', line):
# 処理...
# 効率的な方法
pattern = re.compile(r'\d+')
for line in large_file:
if pattern.search(line):
# 処理...
2. 貪欲(greedy)と非貪欲(non-greedy)の使い分け
デフォルトでは、*
や+
などの量指定子は「貪欲」に動作し、できるだけ多くの文字にマッチしようとします。?
を追加すると「非貪欲」になり、最小限の文字にマッチします。
text = "<div>Content 1</div><div>Content 2</div>" # 貪欲マッチング - 最長一致 greedy = re.findall(r'<div>.*</div>', text) print(greedy) # ['<div>Content 1</div><div>Content 2</div>'] # 非貪欲マッチング - 最短一致 non_greedy = re.findall(r'<div>.*?</div>', text) print(non_greedy) # ['<div>Content 1</div>', '<div>Content 2</div>']
3. 適切な量指定子の使用
.*
のような広範囲にマッチするパターンは避け、できるだけ具体的なパターンを使用しましょう。
# 非効率的
pattern1 = re.compile(r'.*\d{4}-\d{2}-\d{2}.*')
# 効率的
pattern2 = re.compile(r'\d{4}-\d{2}-\d{2}')
4. 先読み・後読みの活用
先読み(lookahead)と後読み(lookbehind)を使うと、マッチさせたい部分の前後に条件を付けられます。
# 数字の前にドルマーク($)がある場合のみマッチ
text = "Items: $10, €20, ¥30"
prices = re.findall(r'(?<=\$)\d+', text)
print(prices) # ['10']
# 「.com」で終わるドメイン名を抽出
domains = re.findall(r'\w+(?=\.com)', "Visit example.com or test.org or sample.com")
print(domains) # ['example', 'sample']
5. フラグの活用
正規表現のフラグを適切に使用することで、より柔軟なマッチングが可能になります。
# 大文字小文字を区別しない
re.findall(r'python', "Python is great, PYTHON is powerful", re.IGNORECASE)
# ['Python', 'PYTHON']
# 複数行モード - ^と$が各行の先頭と末尾にマッチ
text = """Line 1
Line 2
Line 3"""
re.findall(r'^Line \d', text, re.MULTILINE)
# ['Line 1', 'Line 2', 'Line 3']
# DOTALLモード - .が改行にもマッチ
re.findall(r'Line.*3', text, re.DOTALL)
# ['Line 1\nLine 2\nLine 3']
これらのテクニックを活用することで、正規表現のパフォーマンスを向上させ、より効率的なコードを書くことができます。特に大量のテキストを処理する場合は、これらの最適化が重要になります。
Pythonの公式ドキュメント(日本語版)では、正規表現のより詳細な情報が確認できます
Python 正規表現と自然言語処理の連携活用法
正規表現は自然言語処理(NLP)の前処理段階で非常に役立ちます。テキストのクリーニングやトークン化、特徴抽出などに活用できる方法を見ていきましょう。
テキストのクリーニング
自然言語処理の最初のステップは、通常、テキストのクリーニングです。正規表現を使って不要な文字や特殊記号を除去できます。
import re # HTMLタグの除去 html_text = "<p>This is <b>bold</b> and <i>italic</i> text.</p>" clean_text = re.sub(r'<[^>]+>', '', html_text) print(clean_text) # 'This is bold and italic text.' # 特殊文字と数字の除去 text = "Hello, world! 123 #hashtag @mention" clean_text = re.sub(r'[^\w\s]|\d', '', text) print(clean_text) # 'Hello world hashtag mention' # 余分な空白の除去 text = " Multiple spaces between words " clean_text = re.sub(r'\s+', ' ', text).strip() print(clean_text) # 'Multiple spaces between words'
日本語テキストの処理
日本語テキストでは、漢字、ひらがな、カタカナなどを区別して処理することがあります。
# 日本語の漢字、ひらがな、カタカナを抽出する例 text = "これは日本語のテキストです。English text is also included. 123も含まれています。" # 漢字を抽出 kanji = re.findall(r'[一-龯]', text) print(f"漢字: {kanji}") # ['日', '本', '語'] # ひらがなを抽出 hiragana = re.findall(r'[ぁ-ん]', text) print(f"ひらがな: {hiragana}") # ['こ', 'れ', 'は', 'の', 'て', 'き', 'す', 'と', 'で', 'す', 'も', 'ま', 'れ', 'て', 'い', 'ま', 'す'] # カタカナを抽出 katakana = re.findall(r'[ァ-ン]', text) print(f"カタカナ: {katakana}") # []
形態素解析との連携
正規表現は形態素解析の前処理や後処理として活用できます。例えば、SudachiPyを使った例を見てみましょう。
import re from sudachipy import tokenizer, dictionary # SudachiPyの準備 tokenizer_obj = dictionary.Dictionary().create() mode = tokenizer.Tokenizer.SplitMode.C # 最も分割粒度の細かいモード # テキストの正規化と形態素解析 text = "㈱日本ソフトウェアの開発者が作った自然言語処理ツール" # 半角カタカナを全角に変換 normalized_text = re.sub(r'[ヲ-゚]', lambda x: chr(ord(x.group(0)) + 0xFEC0), text) # 形態素解析 tokens = [m.normalized_form() for m in tokenizer_obj.tokenize(normalized_text, mode)] print(tokens) # ['株式会社', '日本', 'ソフトウェア', 'の', '開発', '者', 'が', '作っ', 'た', '自然', '言語', '処理', 'ツール']
このように、正規表現を使ってテキストを前処理した後に形態素解析を行うことで、より正確な解析結果を得ることができます。
テキストマイニングでの活用
テキストマイニングでは、特定のパターンを持つ情報を抽出するのに正規表現が役立ちます。
import re import pandas as pd # サンプルテキスト(ユーザーレビュー) reviews = [ "この商品は★★★★☆で、とても使いやすいです。2023年5月10日に購入。", "★★☆☆☆ 残念ながら期待はずれでした。購入日: 2023/4/15", "使い勝手は良いですが、耐久性に不安があります。★★★☆☆ (2023.3.20購入)" ] # 評価(星の数)と購入日を抽出 data = [] for review in reviews: # 星の数を抽出 stars = re.search(r'★{1,5}(?:☆{1,5})?', review) if stars: rating = stars.group().count('★') else: rating = None # 購入日を抽出(複数のフォーマットに対応) date_pattern = r'(\d{4})[年/./-](\d{1,2})[月/./-](\d{1,2})' date_match = re.search(date_pattern, review) if date_match: purchase_date = f"{date_match.group(1)}-{date_match.group(2)}-{date_match.group(3)}" else: purchase_date = None data.append({'review': review, 'rating': rating, 'purchase_date': purchase_date}) # データフレームに変換 df = pd.DataFrame(data) print(df)
このように、正規表現を使うことで構造化されていないテキストデータから特定の情報を抽出し、構造化されたデータに変換することができます。
自然言語処理と正規表現を組み合わせることで、テキストデータの前処理や特徴抽出を効率的に行うことができます。特に日本語のような複雑な文字体系を持つ言語では、正規表現の柔軟性が大いに役立ちます。
まとめ
Pythonの正規表現は、文字列操作において非常に強力なツールです。基本的なパターンマッチングから複雑なテキスト処理まで、様々な場面で活用することができます。
-
基本構文とメタ文字:
.
,^
,$
,*
,+
,?
,[]
などのメタ文字を使って様々なパターンを表現できます。 -
主要関数:
re.match()
,re.search()
,re.findall()
,re.sub()
などの関数を使って文字列の検索や置換が可能です。 -
グループ化とキャプチャ:
()
を使ってパターンをグループ化し、マッチした部分を個別に取得できます。 -
実践的な使用例: メールアドレスやURLの検証、ログファイルの解析など、実際の開発で役立つパターンがあります。
-
パフォーマンス最適化: 正規表現のコンパイル、適切な量指定子の使用、先読み・後読みの活用などでパフォーマンスを向上させることができます。
-
自然言語処理との連携: テキストのクリーニングや特徴抽出など、NLPの前処理に正規表現を活用できます。
正規表現は最初は複雑に見えるかもしれませんが、基本的なパターンを理解し、実際に使ってみることで徐々に習得することができます。特にPythonではre
モジュールが標準ライブラリとして提供されているため、すぐに利用を開始できます。
正規表現を使いこなすことで、文字列処理のコードがより簡潔になり、複雑なパターンマッチングも効率的に行えるようになります。ぜひ実際のプロジェクトで活用してみてください。