Pythonのreモジュールで正規表現をマスター!文字列検索・置換を完全解説
PythonをコマンドプロンプトやPowerShellでPC上で動かすには、Pythonをダウンロードしてインストールする必要があります。
まだの方はPythonのインストールと開発環境の構築の記事を参考にして、Pythonをインストールしてください。
Webサイトの制作やデータ処理をしていると、「このテキストの中から特定のパターンの文字列だけ抜き出したい!」「フォーマットがバラバラなデータを一括で整形したい!」といった場面によく出くわしますよね。そんな時に絶大なパワーを発揮するのが「正規表現」です。なんだか難しそう?いえいえ、Pythonの`re`モジュールを使えば、初心者の方でも驚くほど簡単に文字列操作の達人になれるんです!
この記事では、Pythonの`re`モジュールを使った正規表現の基本から応用まで、たくさんのコピペOKなコード例を交えて、とことん分かりやすく解説します。まずはコードを動かして「なるほど、こうやって使うのか!」という「動く」体験をしてみてください。さあ、一緒に正規表現の世界へ飛び込みましょう!
正規表現とreモジュールって、そもそも何?
正規表現とは、一言でいうと「文字列のパターンを表現するための特殊な文字列」です。例えば、「数字が3つ並んだもの」や「メールアドレスの形をしたもの」といった、あいまいな条件で文字列を検索・置換するための共通言語のようなものです。
そして、Pythonでその正規表現を扱うための標準機能(ライブラリ)が`re`モジュールです。`re`モジュールをインポートするだけで、複雑な文字列処理が数行のコードで実現できてしまいます。
まずは基本!文字列を「探す」ための4つの関数
`re`モジュールには文字列を検索するための関数がいくつか用意されていますが、まずは最もよく使われる4つをマスターしましょう。これらの関数は、引数として「正規表現パターン」と「検索対象の文字列」を受け取ります。
1. `re.search()` - 最初に見つかった1つを返す
`re.search()`は、文字列全体を調べて、パターンに最初にマッチした部分の情報を返します。見つからなかった場合は`None`を返します。最も一般的で使いやすい検索関数です。
例えば、文章の中から「Python」という単語を探してみましょう。
<?php
import re
text = "Hello, this is a pen. I love Python programming!"
pattern = "Python"
# 文字列の中からパターンを検索
match = re.search(pattern, text)
if match:
print(f"見つかりました!: '{match.group()}'")
print(f"開始位置: {match.start()}, 終了位置: {match.end()}")
else:
print("見つかりませんでした。")
# 実行結果:
# 見つかりました!: 'Python'
# 開始位置: 31, 終了位置: 37
?>
2. `re.match()` - 文字列の「先頭」がマッチするかチェック
`re.match()`は`re.search()`と似ていますが、文字列の先頭からパターンにマッチするかどうかだけをチェックする、という大きな違いがあります。文字列の途中にあるパターンは無視されます。
先ほどの文章で、今度は`re.match()`を使ってみましょう。「Hello」ならマッチしますが、「Python」ではマッチしません。
<?php
import re
text = "Hello, this is a pen. I love Python programming!"
# パターン1: "Hello" (文字列の先頭にある)
match1 = re.match("Hello", text)
if match1:
print(f"'Hello'の検索結果: マッチしました! -> {match1.group()}")
else:
print("'Hello'の検索結果: マッチしませんでした。")
# パターン2: "Python" (文字列の途中にある)
match2 = re.match("Python", text)
if match2:
print(f"'Python'の検索結果: マッチしました! -> {match2.group()}")
else:
print("'Python'の検索結果: マッチしませんでした。")
# 実行結果:
# 'Hello'の検索結果: マッチしました! -> Hello
# 'Python'の検索結果: マッチしませんでした。
?>
3. `re.findall()` - マッチした「すべて」をリストで返す
パターンにマッチする部分をすべて探し出し、文字列のリストとして返してくれるのが`re.findall()`です。文章中に含まれるすべての数字を抜き出したい、といった場合に非常に便利です。
ここでは、正規表現パターンの`\d+`を使ってみましょう。これは「1つ以上の数字の並び」を意味します。
<?php
import re
text = "商品Aは1個100円、商品Bは3個で250円です。注文番号は8801です。"
# \d+ は「1つ以上の連続する数字」を意味する正規表現
pattern = r"\d+"
# マッチするすべての部分をリストで取得
numbers = re.findall(pattern, text)
print(f"見つかった数字のリスト: {numbers}")
# 実行結果:
# 見つかった数字のリスト: ['1', '100', '3', '250', '8801']
?>
※正規表現パターンの前についている `r` は「raw文字列」を意味し、バックスラッシュ `\` をエスケープ文字として扱わないようにするおまじないです。正規表現を書くときは、付けておくと余計なエラーを防げるのでおすすめです。
4. `re.finditer()` - マッチしたすべてを「イテレータ」で返す
`re.finditer()`は`re.findall()`と似ていますが、結果をリストではなく「イテレータ」で返す点が異なります。イテレータは、forループなどで一つずつ要素を取り出して処理するのに適しています。
マッチした部分の文字列だけでなく、`search`や`match`が返したようなマッチオブジェクト(開始位置などの情報を持つ)が欲しい場合に便利です。
<?php
import re
text = "私の誕生日は1995年12月31日で、彼の誕生日は2003年5月5日です。"
# 4桁の数字を探す
pattern = r"\d{4}"
# マッチしたものをイテレータとして取得
matches = re.finditer(pattern, text)
print("見つかった西暦:")
for m in matches:
print(f"- {m.group()} (位置: {m.start()}-{m.end()})")
# 実行結果:
# 見つかった西暦:
# - 1995 (位置: 6-10)
# - 2003 (位置: 21-25)
?>
文字列を自由自在に「置き換える」- re.sub()
正規表現のもう一つの強力な機能が「置換」です。`re.sub()`を使えば、パターンにマッチした部分を別の文字列に一括で置き換えることができます。
例えば、文章中の電話番号をプライバシー保護のために「(非公開)」という文字列に置き換えてみましょう。日本の電話番号(例: 080-1234-5678)にマッチする正規表現 `\d{2,4}-\d{2,4}-\d{4}` を使います。
<?php
import re
text = "お問い合わせは、サポート担当の佐藤(080-1111-2222)まで。もしくは営業担当の鈴木(03-3333-4444)にご連絡ください。"
# 電話番号にマッチする正規表現
pattern = r"\d{2,4}-\d{2,4}-\d{4}"
replacement = "(非公開)"
# パターンにマッチした部分を置換
new_text = re.sub(pattern, replacement, text)
print(new_text)
# 実行結果:
# お問い合わせは、サポート担当の佐藤((非公開))まで。もしくは営業担当の鈴木((非公開))にご連絡ください。
?>
応用編:グループを使って置換する
`re.sub()`の真価は、正規表現の「グループ」機能と組み合わせることで発揮されます。パターンの一部を`()`で囲むと、その部分(グループ)を置換後の文字列で再利用できるのです。
例えば、「姓-名」の順になっている名前を「名 姓」の順番に入れ替えてみましょう。
<?php
import re
text = "登場人物: Tanjiro-Kamado, Zenitsu-Agatsuma, Inosuke-Hashibira"
# (\w+)がグループ。1番目が姓、2番目が名にマッチする
pattern = r"(\w+)-(\w+)"
# \2で2番目のグループ(名)、\1で1番目のグループ(姓)を参照
replacement = r"\2 \1"
new_text = re.sub(pattern, replacement, text)
print(new_text)
# 実行結果:
# 登場人物: Kamado Tanjiro, Agatsuma Zenitsu, Hashibira Inosuke
?>
【コピペで動く】正規表現チェッカーを作ってみよう!
ここまで学んだ知識を使って、ブラウザ上で正規表現の動作をリアルタイムに確認できる「正規表現チェッカー」を作ってみましょう!
以下のHTMLコードをまるごとコピーして、`checker.html`のような名前でファイルに保存し、ブラウザで開いてみてください。テキストエリアに文章を、正規表現入力欄に試したいパターン(例: `\d+` や `[A-Za-z]+` など)を入力して「ハイライト」ボタンを押すと、マッチした部分が水色で表示されます。まさに「動く」を体験できるサンプルです!
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>正規表現ハイライトチェッカー</title>
<style>
body {
background-color: #202124;
color: #e8eaed;
font-family: sans-serif;
line-height: 1.6;
padding: 20px;
}
.container {
max-width: 800px;
margin: 0 auto;
}
h1 {
color: #669df6;
border-bottom: 1px solid #5f6368;
padding-bottom: 10px;
}
textarea, input[type="text"] {
width: 100%;
padding: 10px;
margin-bottom: 10px;
background-color: #3c4043;
color: #e8eaed;
border: 1px solid #5f6368;
border-radius: 4px;
box-sizing: border-box; /* paddingを含めた幅計算に */
}
button {
padding: 10px 20px;
background-color: #8ab4f8;
color: #202124;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: bold;
}
button:hover {
opacity: 0.9;
}
#result {
margin-top: 20px;
padding: 15px;
border: 1px solid #5f6368;
border-radius: 4px;
white-space: pre-wrap; /* 改行をそのまま表示 */
word-wrap: break-word; /* 長い単語を折り返す */
}
.highlight {
background-color: #3367d6; /* 明るい青でハイライト */
color: #ffffff;
border-radius: 3px;
padding: 0 2px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
</style>
</head>
<body>
<div class="container">
<h1>正規表現ハイライトチェッカー</h1>
<label for="text-input">テストしたい文字列:</label>
<textarea id="text-input" rows="8">Python 3.10 is the latest version. My email is sample-user@example.com. Please call me at 090-1234-5678. The event is on 2025/07/26.</textarea>
<label for="regex-input">正規表現パターン:</label>
<input type="text" id="regex-input" value="\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b">
<button onclick="highlightMatches()">ハイライト実行</button>
<label for="result" style="margin-top: 20px;">結果:</label>
<div id="result"></div>
</div>
<script>
function highlightMatches() {
const text = document.getElementById('text-input').value;
const regexPattern = document.getElementById('regex-input').value;
const resultDiv = document.getElementById('result');
if (!text || !regexPattern) {
resultDiv.textContent = '文字列と正規表現パターンを入力してください。';
return;
}
try {
// gフラグ(全体検索)とiフラグ(大文字小文字区別しない)を追加
const regex = new RegExp(regexPattern, 'gi');
const highlightedText = text.replace(regex, (match) => {
return `<span class="highlight">${match}</span>`;
});
resultDiv.innerHTML = highlightedText;
} catch (e) {
resultDiv.textContent = '無効な正規表現パターンです: ' + e.message;
}
}
// 初期表示時にも実行
document.addEventListener('DOMContentLoaded', highlightMatches);
</script>
</body>
</html>
知っておくと差がつく!注意点と便利テクニック
最後に、正規表現をより使いこなすためのいくつかのテクニックと、初心者が陥りがちな罠について解説します。
貪欲マッチ(Greedy)と非貪欲マッチ(Non-Greedy)
正規表現の `*` や `+` は、デフォルトでは「貪欲(Greedy)」に振る舞います。これは、できるだけ長い文字列にマッチしようとすることを意味します。
例えば、`<p>contents</p>` という文字列から `<p>` と `</p>` の中身だけを取り出したいとします。`<.*>` というパターンを使うとどうなるでしょうか。
<?php
import re
html = "<p>first paragraph</p><p>second paragraph</p>"
# 貪欲マッチ(Greedy)
greedy_match = re.search(r"<.*>", html)
print(f"貪欲マッチ: {greedy_match.group()}")
# 非貪欲マッチ(Non-Greedy): * の後ろに ? をつける
non_greedy_match = re.search(r"<.*?>", html)
print(f"非貪欲マッチ: {non_greedy_match.group()}")
# 実行結果:
# 貪欲マッチ: <p>first paragraph</p><p>second paragraph</p>
# 非貪欲マッチ: <p>
?>
貪欲マッチでは、最初の `<` から最後の `>` まで、マッチできる限界まで文字列を取得してしまいました。一方、`*` の後ろに `?` を付けた `*?` は「非貪欲(Non-Greedy)」、つまりできるだけ短い文字列にマッチしようとします。これにより、最初の `>` が来た時点でマッチを終了させることができます。この違いはHTMLやXMLの解析で非常に重要になります。
`re.compile()`でパフォーマンス向上
もし同じ正規表現パターンをプログラム中で何度も繰り返し使うのであれば、`re.compile()` を使ってパターンをあらかじめ「コンパイル」しておくことをお勧めします。
これにより、Pythonが毎回パターンを解析する手間が省け、処理速度が向上します。コンパイルされたパターンオブジェクトは、`search()` や `findall()` などのメソッドを持っています。
<?php
import re
# メールアドレスのパターンをコンパイル
email_pattern = re.compile(r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}")
# コンパイルしたオブジェクトを使って検索
text1 = "連絡先は a@b.com です。"
match1 = email_pattern.search(text1)
if match1:
print(f"Text1から発見: {match1.group()}")
text2 = "サポート窓口は support@example.co.jp です。"
match2 = email_pattern.search(text2)
if match2:
print(f"Text2から発見: {match2.group()}")
# 実行結果:
# Text1から発見: a@b.com
# Text2から発見: support@example.co.jp
?>
コードが整理され、見通しが良くなるというメリットもありますね。
まとめ
今回はPythonの`re`モジュールを使った正規表現の基本をご紹介しました。最初は少しとっつきにくいかもしれませんが、一度その強力さを知ってしまうと、もう正規表現なしのテキスト処理は考えられなくなるはずです。
- `re.search()`: 文字列中からパターンに最初にマッチするものを探す。
- `re.match()`: 文字列の先頭がパターンにマッチするか調べる。
- `re.findall()`: パターンにマッチするものをすべてリストで取得する。
- `re.sub()`: パターンにマッチした部分を置換する。
これらの基本を組み合わせ、メタ文字を使いこなすことで、あなたのデータ処理能力は飛躍的に向上します。ぜひ、この記事のコードをコピペして、いろいろなパターンを試しながら正規表現と仲良くなってみてください!
次のステップへ
正規表現でテキストデータを自由に扱えるようになったら、次はファイル操作に挑戦してみませんか?特にCSVファイルは、多くのWebアプリケーションやデータ分析で使われる重要な形式です。以下の記事で、Pythonの`csv`モジュールを使ったCSVファイルの読み書き方法をマスターしましょう。