正規表現(Regular Expression、略してregex)とは、文字列のパターンを表現するための特別な文法です。
たとえば、こんな場面で威力を発揮します:
- 「メールアドレスが正しい形式かチェックしたい」
- 「テキストから電話番号だけを抽出したい」
- 「HTMLタグを取り除きたい」
- 「特定のパターンの文字列を一括で置換したい」
Pythonではre
モジュールを使うことで、かんたんに正規表現が扱えます。
この記事では、実用的なパターンをチートシート形式で紹介し、すぐに使える形でお届けします!
基本の使い方(Pythonでの記述方法)
reモジュールのインポート
import re
基本的な使用例
import re
text = "私の電話番号は090-1234-5678です"
pattern = r"\d{3}-\d{4}-\d{4}"
# パターンにマッチする部分を検索
result = re.search(pattern, text)
if result:
print(result.group()) # → 090-1234-5678
print("マッチしました!")
else:
print("マッチしませんでした")
重要なポイント
r""
:raw文字列。バックスラッシュをそのまま使えるため、正規表現では必須
re.search()
:最初にマッチした1件を取得
.group()
:マッチした文字列を取得
文字クラス(基本的な記号)
基本的な文字クラス
記号 | 意味 | 使用例 | マッチする例 |
---|
. | 任意の1文字(改行以外) | a.c | abc , a1c , a@c |
\d | 数字(0~9) | \d{3} | 123 , 999 |
\D | 数字以外 | \D+ | abc , こんにちは |
\w | 英数字とアンダースコア | \w+ | hello , test_123 |
\W | 英数字とアンダースコア以外 | \W+ | @#$ , !? |
\s | 空白文字(スペース、タブ、改行) | \s+ | , \t , \n |
\S | 空白文字以外 | \S+ | hello , 123 |
カスタム文字クラス
記号 | 意味 | 使用例 | マッチする例 |
---|
[abc] | a、b、cのいずれか | [abc]+ | a , abc , cab |
[a-z] | aからzまでの小文字 | [a-z]+ | hello , world |
[A-Z] | AからZまでの大文字 | [A-Z]+ | HELLO , WORLD |
[0-9] | 0から9までの数字 | [0-9]+ | 123 , 999 |
[^abc] | a、b、c以外の文字 | [^0-9]+ | hello , こんにちは |
実用例
import re
# 英数字のみをチェック
text1 = "hello123"
if re.match(r"^[a-zA-Z0-9]+$", text1):
print("英数字のみです")
# ひらがなのみをチェック
text2 = "こんにちは"
if re.match(r"^[ひ-ん]+$", text2):
print("ひらがなのみです")
# 特殊文字を含むかチェック
text3 = "password@123"
if re.search(r"[!@#$%^&*]", text3):
print("特殊文字が含まれています")
量詞(繰り返しパターン)
基本的な量詞
記号 | 意味 | 使用例 | マッチする例 |
---|
* | 0回以上の繰り返し | ab* | a , ab , abbb |
+ | 1回以上の繰り返し | ab+ | ab , abbb (a はマッチしない) |
? | 0回または1回 | ab? | a , ab (abb はマッチしない) |
{n} | ちょうどn回 | a{3} | aaa |
{n,} | n回以上 | a{2,} | aa , aaa , aaaa |
{n,m} | n回以上m回以下 | a{2,4} | aa , aaa , aaaa |
貪欲マッチと非貪欲マッチ
記号 | 意味 | 動作 |
---|
* | 貪欲マッチ | できるだけ長くマッチ |
*? | 非貪欲マッチ | できるだけ短くマッチ |
+? | 非貪欲マッチ | 1回以上、できるだけ短く |
?? | 非貪欲マッチ | 0回または1回、できるだけ短く |
実用例
import re
# HTMLタグの抽出(貪欲 vs 非貪欲)
html = "<div>Hello</div><p>World</p>"
# 貪欲マッチ:全体をマッチしてしまう
greedy = re.search(r"<.*>", html)
print(greedy.group()) # → <div>Hello</div><p>World</p>
# 非貪欲マッチ:最初のタグのみマッチ
non_greedy = re.search(r"<.*?>", html)
print(non_greedy.group()) # → <div>
# 郵便番号の検証
zipcode = "123-4567"
if re.match(r"^\d{3}-\d{4}$", zipcode):
print("正しい郵便番号です")
アンカー(位置指定)
基本的なアンカー
記号 | 意味 | 使用例 | 説明 |
---|
^ | 行の先頭 | ^Hello | 行の最初が”Hello”で始まる |
$ | 行の末尾 | world$ | 行の最後が”world”で終わる |
\b | 単語境界 | \bcat\b | 独立した単語”cat” |
\B | 非単語境界 | \Bcat\B | 単語の一部としての”cat” |
\A | 文字列の先頭 | \AStart | 文字列全体の最初 |
\Z | 文字列の末尾 | End\Z | 文字列全体の最後 |
実用例
import re
# 完全一致のチェック
def is_valid_email_simple(email):
pattern = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
return bool(re.match(pattern, email))
print(is_valid_email_simple("user@example.com")) # → True
print(is_valid_email_simple("invalid-email")) # → False
# 単語境界の活用
text = "I have a cat and a catch"
# "cat"という独立した単語のみマッチ
cats = re.findall(r"\bcat\b", text)
print(cats) # → ['cat']
# 行の先頭・末尾での検索
multiline_text = """
Hello World
Goodbye World
Hello Python
"""
# 各行の先頭が"Hello"で始まる行を検索
hello_lines = re.findall(r"^Hello.*$", multiline_text, re.MULTILINE)
print(hello_lines) # → ['Hello World', 'Hello Python']
グループ化と選択
グループの種類
記号 | 意味 | 使用例 | 説明 |
---|
(...) | キャプチャグループ | (ab)+ | グループ化して後で参照可能 |
(?:...) | 非キャプチャグループ | (?:ab)+ | グループ化するが後で参照不可 |
(?P<name>...) | 名前付きグループ | (?P<year>\d{4}) | 名前で参照可能 |
` | ` | OR(選択) | `cat |
実用例
import re
# 日付の抽出と分解
date_text = "今日は2024年6月19日です"
pattern = r"(?P<year>\d{4})年(?P<month>\d{1,2})月(?P<day>\d{1,2})日"
match = re.search(pattern, date_text)
if match:
print(f"年: {match.group('year')}") # → 年: 2024
print(f"月: {match.group('month')}") # → 月: 6
print(f"日: {match.group('day')}") # → 日: 19
print(f"全体: {match.group(0)}") # → 全体: 2024年6月19日
# 複数の選択肢
pets = "I have a cat, a dog, and a bird"
animals = re.findall(r"\b(cat|dog|bird|fish)\b", pets)
print(animals) # → ['cat', 'dog', 'bird']
# 電話番号の分解
phone = "090-1234-5678"
pattern = r"(\d{3})-(\d{4})-(\d{4})"
match = re.match(pattern, phone)
if match:
area = match.group(1) # → 090
exchange = match.group(2) # → 1234
number = match.group(3) # → 5678
print(f"{area} {exchange} {number}")
Pythonのreモジュール関数
主要な関数
関数 | 用途 | 戻り値 |
---|
re.search() | 最初のマッチを検索 | MatchオブジェクトまたはNone |
re.match() | 文字列の先頭からマッチ | MatchオブジェクトまたはNone |
re.findall() | すべてのマッチを検索 | リスト |
re.finditer() | すべてのマッチをイテレータで | イテレータ |
re.sub() | 置換 | 文字列 |
re.split() | 分割 | リスト |
re.compile() | パターンのコンパイル | Patternオブジェクト |
使用例
import re
text = "価格は100円、500円、1000円です。税込み価格は110円、550円、1100円です。"
# 1. re.search() - 最初のマッチのみ
first_price = re.search(r"\d+円", text)
print(first_price.group()) # → 100円
# 2. re.findall() - すべてのマッチをリストで
all_prices = re.findall(r"\d+円", text)
print(all_prices) # → ['100円', '500円', '1000円', '110円', '550円', '1100円']
# 3. re.finditer() - すべてのマッチをイテレータで
for match in re.finditer(r"\d+円", text):
print(f"位置 {match.start()}-{match.end()}: {match.group()}")
# 4. re.sub() - 置換
masked_text = re.sub(r"\d+円", "***円", text)
print(masked_text) # → 価格は***円、***円、***円です。税込み価格は***円、***円、***円です。
# 5. re.split() - 分割
sentence = "apple,banana;orange:grape"
fruits = re.split(r"[,;:]", sentence)
print(fruits) # → ['apple', 'banana', 'orange', 'grape']
# 6. re.compile() - パターンの再利用
pattern = re.compile(r"\d+円")
prices1 = pattern.findall("100円です")
prices2 = pattern.findall("200円と300円")
print(prices1, prices2) # → ['100円'] ['200円', '300円']
よく使う実用パターン集
メールアドレス
import re
def validate_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", # Valid
"test.email+tag@domain.co.jp", # Valid
"invalid-email", # Invalid
"@domain.com", # Invalid
]
for email in emails:
print(f"{email}: {validate_email(email)}")
URLの抽出
import re
def extract_urls(text):
pattern = r"https?://[^\s<>\"']+"
return re.findall(pattern, text)
text = """
公式サイト: https://www.example.com
APIドキュメント: https://api.example.com/docs
参考記事: http://blog.example.com/article/123
"""
urls = extract_urls(text)
for url in urls:
print(url)
電話番号
import re
def format_phone_number(phone):
# ハイフンや空白を除去
cleaned = re.sub(r"[-\s()]", "", phone)
# 携帯電話の形式
mobile_pattern = r"^(070|080|090)(\d{4})(\d{4})$"
mobile_match = re.match(mobile_pattern, cleaned)
if mobile_match:
return f"{mobile_match.group(1)}-{mobile_match.group(2)}-{mobile_match.group(3)}"
# 固定電話の形式(簡易版)
landline_pattern = r"^(\d{2,4})(\d{2,4})(\d{4})$"
landline_match = re.match(landline_pattern, cleaned)
if landline_match:
return f"{landline_match.group(1)}-{landline_match.group(2)}-{landline_match.group(3)}"
return "不正な電話番号"
# テスト
phones = [
"09012345678",
"090-1234-5678",
"03 1234 5678",
"0312345678"
]
for phone in phones:
print(f"{phone} → {format_phone_number(phone)}")
パスワードの強度チェック
import re
def check_password_strength(password):
checks = {
"長さ8文字以上": len(password) >= 8,
"大文字を含む": bool(re.search(r"[A-Z]", password)),
"小文字を含む": bool(re.search(r"[a-z]", password)),
"数字を含む": bool(re.search(r"\d", password)),
"特殊文字を含む": bool(re.search(r"[!@#$%^&*(),.?\":{}|<>]", password))
}
passed = sum(checks.values())
total = len(checks)
print(f"パスワード: {password}")
for check, result in checks.items():
print(f" {check}: {'✓' if result else '✗'}")
if passed == total:
return "非常に強い"
elif passed >= 4:
return "強い"
elif passed >= 3:
return "普通"
else:
return "弱い"
# テスト
passwords = [
"password",
"Password123",
"P@ssw0rd!",
"Str0ng!P@ssw0rd"
]
for pwd in passwords:
strength = check_password_strength(pwd)
print(f"強度: {strength}\n")
日本語テキストの処理
import re
def extract_japanese_elements(text):
patterns = {
"ひらがな": r"[ひ-ん]+",
"カタカナ": r"[ア-ン]+",
"漢字": r"[一-龯]+",
"数字": r"\d+",
"英字": r"[a-zA-Z]+"
}
results = {}
for name, pattern in patterns.items():
matches = re.findall(pattern, text)
results[name] = matches
return results
text = "私はPythonプログラミングを学習中です。バージョンは3.9を使用しています。"
elements = extract_japanese_elements(text)
for element_type, matches in elements.items():
print(f"{element_type}: {matches}")
フラグ(オプション)
主要なフラグ
フラグ | 効果 | 使用例 |
---|
re.IGNORECASE または re.I | 大文字小文字を区別しない | re.search(r"hello", text, re.I) |
re.MULTILINE または re.M | ^と$が各行で動作 | re.findall(r"^Hello", text, re.M) |
re.DOTALL または re.S | .が改行文字にもマッチ | re.search(r".*", text, re.S) |
re.VERBOSE または re.X | 改行・コメントを許可 | パターンを見やすく記述 |
使用例
import re
# 大文字小文字を区別しない検索
text = "Hello WORLD hello world"
matches = re.findall(r"hello", text, re.IGNORECASE)
print(matches) # → ['Hello', 'hello']
# 複数行モード
multiline_text = """First line
Second line
Third line"""
# 通常モード:文字列全体の先頭のみ
normal_matches = re.findall(r"^.+", multiline_text)
print("通常:", normal_matches) # → ['First line']
# 複数行モード:各行の先頭
multiline_matches = re.findall(r"^.+", multiline_text, re.MULTILINE)
print("複数行:", multiline_matches) # → ['First line', 'Second line', 'Third line']
# 詳細モード(VERBOSE)
email_pattern = re.compile(r"""
^ # 文字列の開始
[a-zA-Z0-9._%+-]+ # ユーザー名部分
@ # アットマーク
[a-zA-Z0-9.-]+ # ドメイン名
\. # ドット
[a-zA-Z]{2,} # トップレベルドメイン
$ # 文字列の終了
""", re.VERBOSE)
print(email_pattern.match("user@example.com")) # → Matchオブジェクト
エラー処理とデバッグ
よくあるエラーと対処法
import re
# 1. 無効な正規表現パターン
try:
pattern = r"[abc" # 閉じ括弧がない
re.compile(pattern)
except re.error as e:
print(f"正規表現エラー: {e}")
# 2. グループ参照エラー
text = "2024-06-19"
pattern = r"(\d{4})-(\d{2})-(\d{2})"
match = re.search(pattern, text)
if match:
try:
year = match.group(1) # OK
month = match.group(2) # OK
day = match.group(3) # OK
# invalid_group = match.group(4) # IndexError!
print(f"{year}/{month}/{day}")
except IndexError as e:
print(f"グループ参照エラー: {e}")
# 3. None の確認
def safe_regex_search(pattern, text):
match = re.search(pattern, text)
if match:
return match.group()
else:
return "マッチしませんでした"
print(safe_regex_search(r"\d+", "abc")) # → マッチしませんでした
print(safe_regex_search(r"\d+", "abc123")) # → 123
デバッグのコツ
import re
def debug_regex(pattern, text):
"""正規表現のデバッグヘルパー関数"""
print(f"パターン: {pattern}")
print(f"テキスト: {text}")
print("-" * 40)
# コンパイル可能かチェック
try:
compiled_pattern = re.compile(pattern)
print("✓ パターンは有効です")
except re.error as e:
print(f"✗ パターンエラー: {e}")
return
# マッチ結果
match = re.search(pattern, text)
if match:
print(f"✓ マッチしました: '{match.group()}'")
print(f" 位置: {match.start()}-{match.end()}")
# グループがある場合
if match.groups():
for i, group in enumerate(match.groups(), 1):
print(f" グループ{i}: '{group}'")
else:
print("✗ マッチしませんでした")
# すべてのマッチ
all_matches = re.findall(pattern, text)
if all_matches:
print(f"すべてのマッチ: {all_matches}")
print()
# デバッグ例
debug_regex(r"\d{3}-\d{4}", "電話番号は090-1234です")
debug_regex(r"(\w+)@(\w+\.\w+)", "メールはuser@example.comです")
高度なテクニック
先読み・後読み
import re
# 先読み(Lookahead)
text = "password123 username456"
# 数字が後に続く単語を検索(数字は含まない)
words_before_numbers = re.findall(r"\w+(?=\d)", text)
print(words_before_numbers) # → ['password', 'username']
# 数字が後に続かない単語を検索
words_not_before_numbers = re.findall(r"\w+(?!\d)", text)
print(words_not_before_numbers) # → ['password', 'username']
# 後読み(Lookbehind)
prices = "商品A: $100, 商品B: ¥200, 商品C: $300"
# ドル記号の後の数字のみ抽出
dollar_amounts = re.findall(r"(?<=\$)\d+", prices)
print(dollar_amounts) # → ['100', '300']
# 円記号の後の数字のみ抽出
yen_amounts = re.findall(r"(?<=¥)\d+", prices)
print(yen_amounts) # → ['200']
条件付きマッチ
import re
def extract_quotes(text):
"""シングルクォートまたはダブルクォートで囲まれた文字列を抽出"""
# グループ1で開始クォートをキャプチャし、同じクォートで終了
pattern = r"""
(["']) # グループ1: 開始クォート
(.*?) # グループ2: 内容(非貪欲)
\1 # グループ1と同じクォートで終了
"""
matches = re.findall(pattern, text, re.VERBOSE)
return [match[1] for match in matches] # 内容のみ返す
text = 'これは"テスト"で、こちらは\'例\'です'
quotes = extract_quotes(text)
print(quotes) # → ['テスト', '例']
置換での高度な使用
import re
def smart_replace(text):
"""様々な置換パターンの例"""
# 1. 関数を使った置換
def capitalize_match(match):
return match.group().upper()
# 単語の最初の文字を大文字に
result1 = re.sub(r"\b\w", capitalize_match, "hello world python")
print(f"1. {result1}") # → Hello World Python
# 2. グループを使った置換
date_text = "今日は2024/06/19です"
result2 = re.sub(r"(\d{4})/(\d{2})/(\d{2})", r"\1年\2月\3日", date_text)
print(f"2. {result2}") # → 今日は2024年06月19日です
# 3. 条件付き置換
def format_number(match):
num = int(match.group())
if num > 1000:
return f"{num:,}" # カンマ区切り
else:
return str(num)
numbers_text = "価格は500円と1500円と25000円です"
result3 = re.sub(r"\d+", format_number, numbers_text)
print(f"3. {result3}") # → 価格は500円と1,500円と25,000円です
smart_replace("test")
パフォーマンスの最適化
コンパイルの活用
import re
import time
# 大量のデータを処理する場合の最適化例
def performance_comparison():
text_list = ["user@example.com", "invalid-email", "test@domain.co.jp"] * 1000
pattern = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
# 方法1: 毎回コンパイル(遅い)
start_time = time.time()
for text in text_list:
re.match(pattern, text)
time1 = time.time() - start_time
# 方法2: 事前にコンパイル(速い)
compiled_pattern = re.compile(pattern)
start_time = time.time()
for text in text_list:
compiled_pattern.match(text)
time2 = time.time() - start_time
print(f"毎回コンパイル: {time1:.4f}秒")
print(f"事前コンパイル: {time2:.4f}秒")
print(f"改善率: {time1/time2:.2f}倍速い")
# performance_comparison()
実践問題とその解答
問題1: ログファイルの解析
import re
def parse_log_line(log_line):
"""アクセスログを解析する"""
pattern = r'(?P<ip>\d+\.\d+\.\d+\.\d+) - - \[(?P<datetime>[^\]]+)\] "(?P<method>\w+) (?P<path>[^"]*)" (?P<status>\d+) (?P<size>\d+|-)'
match = re.match(pattern, log_line)
if match:
return match.groupdict()
return None
# テスト用のログデータ
sample_log = '192.168.1.1 - - [19/Jun/2024:10:30:45 +0900] "GET /index.html" 200 1234'
result = parse_log_line(sample_log)
if result:
print("解析結果:")
for key, value in result.items():
print(f" {key}: {value}")
問題2: CSVファイルのパース
import re
def parse_csv_line(csv_line):
"""CSV行を解析(カンマやクォートを考慮)"""
# カンマ区切りだが、クォート内のカンマは無視
pattern = r'(?:^|,)("(?:[^"]|"")*"|[^,]*)'
matches = re.findall(pattern, csv_line)
# クォートを除去し、エスケープされたクォートを復元
fields = []
for match in matches:
field = match.strip('"')
field = field.replace('""', '"')
fields.append(field)
return fields
# テスト
csv_data = [
'name,age,city',
'John,25,Tokyo',
'"Smith, Jr.",30,"New York"',
'"He said ""Hello""",45,Osaka'
]
for line in csv_data:
parsed = parse_csv_line(line)
print(f"入力: {line}")
print(f"結果: {parsed}")
print()
問題3: マークダウンテキストの処理
import re
def parse_markdown_headers(markdown_text):
"""マークダウンのヘッダーを抽出して目次を作成"""
header_pattern = r'^(#{1,6})\s+(.+)
headers = []
for line_num, line in enumerate(markdown_text.split('\n'), 1):
match = re.match(header_pattern, line)
if match:
level = len(match.group(1)) # #の数
title = match.group(2).strip()
headers.append({
'level': level,
'title': title,
'line': line_num
})
return headers
def generate_toc(headers):
"""目次を生成"""
toc = []
for header in headers:
indent = " " * (header['level'] - 1)
anchor = re.sub(r'[^\w\s-]', '', header['title']).strip()
anchor = re.sub(r'[\s]+', '-', anchor).lower()
toc.append(f"{indent}- [{header['title']}](#{anchor})")
return '\n'.join(toc)
# テスト用マークダウン
markdown_sample = """# メインタイトル
## 第1章
### セクション1.1
### セクション1.2
## 第2章
# まとめ"""
headers = parse_markdown_headers(markdown_sample)
toc = generate_toc(headers)
print("抽出されたヘッダー:")
for header in headers:
print(f" レベル{header['level']}: {header['title']} (行{header['line']})")
print("\n生成された目次:")
print(toc)
トラブルシューティング
よくある間違いと解決策
import re
def common_mistakes_examples():
"""よくある間違いとその解決策"""
print("=== よくある間違い例 ===\n")
# 1. エスケープ忘れ
print("1. エスケープ忘れ")
text = "価格は100$です"
# 間違い: $は特殊文字
try:
wrong = re.search(r"100$", text) # 行末の100を探してしまう
print(f" 間違い結果: {wrong}")
except:
pass
# 正解: $をエスケープ
correct = re.search(r"100\$", text)
print(f" 正解結果: {correct.group() if correct else 'None'}")
print()
# 2. 貪欲マッチの問題
print("2. 貪欲マッチの問題")
html = "<b>太字</b>と<i>斜体</i>"
# 間違い: 貪欲マッチで全体を取得
wrong = re.search(r"<.*>", html)
print(f" 間違い結果: {wrong.group() if wrong else 'None'}")
# 正解: 非貪欲マッチまたは否定文字クラス
correct1 = re.search(r"<.*?>", html)
correct2 = re.search(r"<[^>]*>", html)
print(f" 正解結果1: {correct1.group() if correct1 else 'None'}")
print(f" 正解結果2: {correct2.group() if correct2 else 'None'}")
print()
# 3. 文字クラスの間違い
print("3. 文字クラスの間違い")
text = "a-z"
# 間違い: ハイフンがレンジとして解釈される
try:
wrong = re.search(r"[a-z]", text) # aからzの任意の文字
print(f" 間違い(意図と異なる): {wrong.group() if wrong else 'None'}")
except:
pass
# 正解: ハイフンをエスケープまたは末尾に配置
correct1 = re.search(r"[a\-z]", text)
correct2 = re.search(r"[az-]", text)
print(f" 正解結果1: {correct1.group() if correct1 else 'None'}")
print(f" 正解結果2: {correct2.group() if correct2 else 'None'}")
common_mistakes_examples()
パフォーマンス問題の診断
import re
import time
def diagnose_performance_issues():
"""パフォーマンス問題の診断と解決"""
# 問題のあるパターン例
problematic_text = "a" * 1000 + "b"
print("=== パフォーマンス問題の例 ===\n")
# 1. 破滅的バックトラッキング
print("1. 破滅的バックトラッキング")
problematic_pattern = r"(a+)+b" # 危険なパターン
print(" 注意: 以下のパターンは非効率的です")
print(f" パターン: {problematic_pattern}")
print(" → 大量のバックトラッキングが発生する可能性")
# より効率的な代替案
efficient_pattern = r"a+b"
print(f" 改善案: {efficient_pattern}")
print()
# 2. 不必要なキャプチャグループ
print("2. 不必要なキャプチャグループ")
text_list = ["test@example.com"] * 1000
# 重いパターン(不要なキャプチャ)
heavy_pattern = r"([a-zA-Z0-9._%+-]+)@([a-zA-Z0-9.-]+)\.([a-zA-Z]{2,})"
# 軽いパターン(非キャプチャグループ)
light_pattern = r"(?:[a-zA-Z0-9._%+-]+)@(?:[a-zA-Z0-9.-]+)\.(?:[a-zA-Z]{2,})"
# 単純にマッチするだけなら
simple_pattern = r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}"
print(" 用途に応じてパターンを選択:")
print(f" データ抽出用: {heavy_pattern}")
print(f" 検証のみ: {simple_pattern}")
diagnose_performance_issues()
国際化対応
Unicode文字の処理
import re
def unicode_examples():
"""Unicode文字の処理例"""
# 日本語文字の分類
text = "私はPythonエンジニアです。年齢は25歳、給料は¥500,000です。"
patterns = {
"ひらがな": r"[\u3041-\u3096]+",
"カタカナ": r"[\u30A1-\u30F6]+",
"漢字": r"[\u4E00-\u9FAF]+",
"全角数字": r"[0-9]+",
"半角数字": r"[0-9]+",
"全角英字": r"[A-Za-z]+",
"半角英字": r"[A-Za-z]+",
"通貨記号": r"[¥¥$€£]+",
}
print("=== Unicode文字の分類 ===")
print(f"対象テキスト: {text}\n")
for name, pattern in patterns.items():
matches = re.findall(pattern, text)
if matches:
print(f"{name}: {matches}")
print()
# 絵文字の処理
emoji_text = "今日は良い天気ですね!😊🌞 Pythonの勉強をします📚💻"
# 絵文字のパターン(簡易版)
emoji_pattern = r"[\U0001F600-\U0001F64F]|[\U0001F300-\U0001F5FF]|[\U0001F680-\U0001F6FF]|[\U0001F1E0-\U0001F1FF]"
emojis = re.findall(emoji_pattern, emoji_text)
print("=== 絵文字の抽出 ===")
print(f"テキスト: {emoji_text}")
print(f"絵文字: {emojis}")
# 絵文字を除去
clean_text = re.sub(emoji_pattern, "", emoji_text)
print(f"絵文字除去後: {clean_text.strip()}")
unicode_examples()
多言語対応
import re
def multilingual_patterns():
"""多言語対応のパターン例"""
# 各言語の文字パターン
language_patterns = {
"日本語": {
"ひらがな": r"[\u3041-\u3096]",
"カタカナ": r"[\u30A1-\u30F6]",
"漢字": r"[\u4E00-\u9FAF]",
"全体": r"[\u3041-\u3096\u30A1-\u30F6\u4E00-\u9FAF]"
},
"韓国語": {
"ハングル": r"[\uAC00-\uD7AF]",
"全体": r"[\uAC00-\uD7AF]"
},
"中国語": {
"簡体字": r"[\u4E00-\u9FFF]",
"全体": r"[\u4E00-\u9FFF]"
},
"アラビア語": {
"基本": r"[\u0600-\u06FF]",
"全体": r"[\u0600-\u06FF]"
},
"ロシア語": {
"キリル文字": r"[\u0400-\u04FF]",
"全体": r"[\u0400-\u04FF]"
}
}
# テストテキスト
multilingual_text = "Hello こんにちは 안녕하세요 你好 مرحبا Привет"
print("=== 多言語テキストの分析 ===")
print(f"テキスト: {multilingual_text}\n")
for language, patterns in language_patterns.items():
pattern = patterns["全体"]
matches = re.findall(pattern + "+", multilingual_text)
if matches:
print(f"{language}: {matches}")
multilingual_patterns()
実用的なライブラリと組み合わせ
pandasとの組み合わせ
import re
import pandas as pd
def pandas_regex_examples():
"""pandasと正規表現の組み合わせ例"""
# サンプルデータ
data = {
'name': ['田中太郎', '佐藤花子', 'John Smith', 'Mary Johnson'],
'email': ['tanaka@example.com', 'sato@test.co.jp', 'john@domain.org', 'mary@invalid'],
'phone': ['090-1234-5678', '03-1234-5678', '080-9999-0000', 'invalid-phone'],
'text': ['価格は1000円です', 'セール中!500円!', '定価2500円→1800円', '無料サンプル']
}
df = pd.DataFrame(data)
print("=== 元データ ===")
print(df)
print()
# 1. メールアドレスの検証
email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}
df['valid_email'] = df['email'].str.match(email_pattern)
# 2. 電話番号からハイフンを除去
df['phone_clean'] = df['phone'].str.replace(r'[-\s]', '', regex=True)
# 3. テキストから数字を抽出
df['numbers'] = df['text'].str.findall(r'\d+')
# 4. 名前から姓と名を分離(日本語名のみ)
japanese_name_pattern = r'^([^\s]+)([^\s]+)
df['surname'] = df['name'].str.extract(japanese_name_pattern)[0]
df['given_name'] = df['name'].str.extract(japanese_name_pattern)[1]
print("=== 処理後データ ===")
print(df[['name', 'valid_email', 'phone_clean', 'numbers', 'surname', 'given_name']])
# pandas_regex_examples()
よく使うパターンのまとめ
# 保存版:よく使う正規表現パターン集
COMMON_PATTERNS = {
# 基本的な形式
'email': r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,},
'url': r'https?://[^\s<>"\']+',
'ipv4': r'\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b',
# 日本固有
'zipcode_jp': r'\d{3}-\d{4}',
'phone_mobile_jp': r'(070|080|090)-\d{4}-\d{4}',
'phone_landline_jp': r'\d{2,4}-\d{2,4}-\d{4}',
# 日付・時刻
'date_yyyy_mm_dd': r'\d{4}[-/]\d{1,2}[-/]\d{1,2}',
'time_hh_mm': r'[0-2]?[0-9]:[0-5][0-9]',
'datetime_iso': r'\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}',
# 数値
'integer': r'-?\d+',
'decimal': r'-?\d+(\.\d+)?',
'currency_jp': r'[¥¥]?[\d,]+円?',
# パスワード(基本)
'password_strong': r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,},
# HTML/XML
'html_tag': r'<[^>]+>',
'html_comment': r'<!--.*?-->',
# プログラミング
'variable_name': r'[a-zA-Z_][a-zA-Z0-9_]*',
'hex_color': r'#[0-9a-fA-F]{6}',
# 日本語文字
'hiragana': r'[\u3041-\u3096]+',
'katakana': r'[\u30A1-\u30F6]+',
'kanji': r'[\u4E00-\u9FAF]+',
'japanese': r'[\u3041-\u3096\u30A1-\u30F6\u4E00-\u9FAF]+',
}
# 使用例関数
def validate_with_common_patterns(text, pattern_name):
"""共通パターンを使用した検証"""
if pattern_name in COMMON_PATTERNS:
pattern = COMMON_PATTERNS[pattern_name]
return bool(re.match(pattern, text))
else:
return False
# テスト
test_data = [
('user@example.com', 'email'),
('123-4567', 'zipcode_jp'),
('090-1234-5678', 'phone_mobile_jp'),
('2024-06-19', 'date_yyyy_mm_dd'),
('こんにちは', 'hiragana'),
]
for data, pattern_name in test_data:
result = validate_with_common_patterns(data, pattern_name)
print(f"{data} → {pattern_name}: {'✓' if result else '✗'}")
コメント