魔法の文字列でテキストを自由自在に:Python正規表現入門
![]()
【Yuki】
Hirokiくん、こんにちは。今日はPythonの「正規表現(Regular Expression)」について、一緒にゆっくり学んでいけたらな、と思っています。正規表現って、最初は暗号のように見えて少し怖いかもしれませんけれど...覚えてしまうと、とても心強い味方になってくれるんですよ。
![]()
【Hiroki】
Yukiさん、よろしくお願いします!正規表現、エンジニアの人が黒い画面で複雑な記号を打ち込んでいるイメージがあって、僕に理解できるかちょっと不安です。でも、Pythonで文字列を扱うときによく出てくるって聞いたので、頑張ってマスターしたいです。
![]()
【Yuki】
その意気ですよ、Hirokiくん。まずは難しいことは考えずに、「特定のルールに沿った文字列を探し出すための、特別な書き方」だと思ってください。例えば、大量のテキストの中から「電話番号だけ」を抜き出したり、入力された「メールアドレスの形式」が正しいかチェックしたりするのに使われます。
正規表現の第一歩:reモジュールと基本的な検索
![]()
【Yuki】
Pythonで正規表現を扱うには、まず標準ライブラリのreモジュールをインポートする必要があります。まずは一番シンプルなre.search()という関数を使ってみましょうか。これは、文字列の中に特定のパターンが含まれているかを確認するためのものです。
![]()
【Hiroki】
reモジュールですね。どんな風に書くんですか?
![]()
【Yuki】
はい、こんな風に書いてみてください。
import re
text = "今日はとても良い天気ですね。"
pattern = "天気"
result = re.search(pattern, text)
if result:
print("見つかりました!")
else:
print("見つかりませんでした...")
![]()
【Hiroki】
あ、これなら普通の文字列検索とあまり変わらない気がします。"天気"という文字を探しているんですよね?
![]()
【Yuki】
その通りです。でも、正規表現の本当の力は、ここに「メタ文字」という特別な記号を混ぜた時に発揮されるんです。わたしは...この正規表現のような、小さくても洗練されたツールが、複雑な問題を一瞬で解決するのを見ると、なんだかとても素敵なことだなって思うんです。誰かの作業を少しでも楽にするために生まれた知恵、という感じがして。
便利な記号「メタ文字」を使ってみる
![]()
【Hiroki】
メタ文字...。それが、あの暗号の正体ですね。具体的にどんなものがあるんですか?
![]()
【Yuki】
たくさんありますが、まずは代表的なものをいくつか紹介しますね。
.(ドット):任意の1文字(何でもいい1文字)を表します。^(ハット):文字列の先頭を表します。$(ドル):文字列の末尾を表します。*(アスタリスク):直前の文字の0回以上の繰り返しを表します。
例えば、r"あ.い" というパターンは、「あ」で始まり、何かの1文字を挟んで「い」で終わる、「あい」「あおい」「あかい」などにマッチします。
![]()
【Hiroki】
なるほど。そのパターンの前についている r って何ですか?
![]()
【Yuki】
おっ、鋭いですね。それは「raw文字列」といって、バックスラッシュ(\)をそのままの文字として扱うためのPythonの書き方です。正規表現ではバックスラッシュをよく使うので、基本的には r"" の中にパターンを書くのがマナーだと思っておけば大丈夫ですよ。
文字の種類を指定する「文字クラス」
![]()
【Hiroki】
「何でもいい1文字」じゃなくて、「数字だけ」とか「英文字だけ」を探したいときはどうすればいいんですか?
![]()
【Yuki】
そういう時は「文字クラス」を使います。角括弧 [] で囲むと、「その中のどれか1文字」という意味になります。
[abc]:a、b、cのいずれか1文字。[0-9]:0から9までの数字のいずれか1文字。[a-z]:小文字のアルファベットのいずれか1文字。
また、よく使われるものは「特殊シーケンス」として短く書くこともできるんですよ。
\d:数字([0-9]と同じ)\w:英数字とアンダースコア\s:空白文字(スペースや改行など)
![]()
【Hiroki】
少しずつ正規表現らしくなってきましたね。例えば、日本の郵便番号「3桁の数字 - 4桁の数字」を表現したいときはどう書けばいいんでしょう?
![]()
【Yuki】
いい例えですね。繰り返しを表現する {} を使って、こんな風に書けます。
import re
text = "僕の郵便番号は123-4567です。"
# \d{3}は数字3回、 \d{4}は数字4回という意味です
pattern = r"\d{3}-\d{4}"
result = re.search(pattern, text)
if result:
print(f"郵便番号が見つかりました: {result.group()}")
![]()
【Hiroki】
おお! result.group() で、見つかった部分を取り出せるんですね。
繰り返しの表現:量指定子
![]()
【Yuki】
先ほどの {3} もそうですが、パターンの繰り返し回数を指定する記号を「量指定子」と呼びます。これもよく使うので覚えておくと便利ですよ。
+:直前の文字が 1回以上 繰り返す。?:直前の文字が 0回または1回 現れる(あってもなくても良い)。{n,m}:直前の文字が n回からm回 繰り返す。
![]()
【Hiroki】
+ と * は似ていますけど、1回以上か0回以上かの違いがあるんですね。
![]()
【Yuki】
そうなんです。例えば、apple と apples の両方にマッチさせたいときは、最後の s に ? をつけて r"apples?" と書いたりします。控えめな表現ですけれど、これだけで柔軟性がぐっと上がるんですよ。
複数の箇所を見つける:re.findall()
![]()
【Hiroki】
さっきの re.search() は、最初に見つかったものだけですよね?文章の中にいくつかある郵便番号を全部見つけたい時はどうすればいいですか?
![]()
【Yuki】
そういう時は re.findall() を使いましょう。これは、一致した部分をリストとして返してくれます。
import re
text = "会場Aの番号は111-1111、会場Bは222-2222です。"
pattern = r"\d{3}-\d{4}"
zip_codes = re.findall(pattern, text)
print(zip_codes) # ['111-1111', '222-2222']
![]()
【Hiroki】
これなら一気にデータを抽出できそうですね!
文字列を置換する:re.sub()
![]()
【Yuki】
検索だけでなく、「置換」も正規表現の得意分野です。re.sub() を使うと、パターンに一致した部分を別の文字列に書き換えることができます。
![]()
【Hiroki】
置換って、普通の str.replace() よりもすごいんですか?
![]()
【Yuki】
ええ。例えば、「複数の異なる表記ゆれを1つにまとめたい」時などに便利です。
import re
text = "りんご、リンゴ、林檎を食べました。"
# 「りんご」か「リンゴ」か「林檎」なら「Apple」に置き換える
pattern = r"りんご|リンゴ|林檎"
new_text = re.sub(pattern, "Apple", text)
print(new_text) # Apple、Apple、Appleを食べました。
![]()
【Hiroki】
|(パイプ)を使うと「または」という意味になるんですね。これは便利そうです。
グループ化:()の活用
![]()
【Yuki】
さらに一歩進んで、パターンの特定の部分だけを取り出したい時は、丸括弧 () を使って「グループ化」を行います。
![]()
【Hiroki】
グループ化...?
![]()
【Yuki】
例えば、2023-12-25 という日付から、「年」「月」「日」を別々に取り出したいとします。
import re
text = "今日は2023-12-25です。"
# () で囲った部分がグループになります
pattern = r"(\d{4})-(\d{2})-(\d{2})"
result = re.search(pattern, text)
if result:
print(f"年: {result.group(1)}")
print(f"月: {result.group(2)}")
print(f"日: {result.group(3)}")
![]()
【Hiroki】
なるほど、括弧でくくった順番に1, 2, 3と番号が振られるんですね。これ、Webスクレイピングとかデータの整理でめちゃくちゃ使えそうです。
![]()
【Yuki】
そうなんです...。わたしも、Webサイトの情報を整理する時にはよくお世話になっています。少し複雑なパターンを組んで、それがピタッとはまった瞬間は、まるで美しいパズルのピースが揃ったような、静かな喜びがあるんですよ。
欲張りなマッチと控えめなマッチ
![]()
【Hiroki】
Yukiさん、ちょっと試してみたんですけど、HTMLのタグみたいに < と > で囲まれた部分を抽出したくて r"<.*>" って書いたら、最初から最後まで全部繋がって取得されちゃいました...。
![]()
【Yuki】
あ、それは正規表現の「最長一致(欲張りなマッチ)」という性質のせいですね。デフォルトでは、できるだけ長くマッチしようとするんです。
![]()
【Hiroki】
どうすれば、最短の部分で止まってくれるんですか?
![]()
【Yuki】
量指定子の後ろに ? を付けてみてください。 r"<.*?>" と書くことで、「最短一致(控えめなマッチ)」になります。
![]()
【Hiroki】
あ、できました! ? を付けるだけで挙動が変わるなんて、面白いですね。
実践:メールアドレスのバリデーション(簡易版)
![]()
【Yuki】
最後に、もう少しだけ複雑なパターンに挑戦してみましょうか。メールアドレスの形式をチェックするパターンを考えてみましょう。
![]()
【Hiroki】
うわぁ、一気に難易度が上がりそうですね...。
![]()
【Yuki】
完璧なものを作ろうとすると非常に難しいですが、簡易的なものなら今までの知識で作れますよ。
「英数字などの塊」 + @ + 「英数字などの塊」 + . + 「英数字などの塊」 という構成で考えてみます。
import re
def check_email(email):
# 文字、数字、ドット、プラス、ハイフンなどの繰り返し
# @ があって、その後にドメイン名
pattern = r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$"
if re.match(pattern, email):
return True
else:
return False
test_emails = ["yuki@example.com", "hiroki.python@school.jp", "invalid-email@"]
for e in test_emails:
result = "正しい" if check_email(e) else "正しくない"
print(f"{e}: {result}形式です")
![]()
【Hiroki】
re.match() というのが出てきましたね。
![]()
【Yuki】
re.match() は、文字列の先頭からパターンに一致するかをチェックする関数です。メールアドレス全体の形式をチェックしたい時には、途中で部分一致するのではなく、先頭から末尾まで(^ と $ を使って)しっかり確認するのが一般的ですね。
![]()
【Hiroki】
なるほど。記号の組み合わせで、こんなに細かいルールが作れるなんて驚きです。
![]()
【Yuki】
ふふ。最初は戸惑うかもしれませんけれど、少しずつ触れていけば、きっと自然に読み書きできるようになります。わたしも、最初はあの呪文のような見た目に、頭が熱暴走しそうになりましたから...。
![]()
【Hiroki】
Yukiさんでもそうだったんですね。なんだか勇気が湧いてきました!
![]()
【Yuki】
もし、もっと詳しく知りたくなったら、Pythonの公式ドキュメントを見てみるといいですよ。とても詳しく、正確な情報が載っていますから。
![]()
【Hiroki】
ありがとうございます!まずは簡単なものから、自分のコードに取り入れてみます。
![]()
【Yuki】
ええ、その調子です。ゆっくり、一歩ずつ進んでいきましょう。もし分からないことがあれば、いつでも聞いてくださいね...。わたし、Hirokiくんが成長していく姿を見守るの、結構好きなんです。
![]()
【Hiroki】
はい!これからもよろしくお願いします!
「正規表現」のサンプルコードを見る
この記事では基礎を解説しましたが、実務においては「もっと複雑なデータを扱いたい」「独自のシステムに組み込みたい」といった、個別の課題に直面することも多いはずです。
「自分で書く時間は最小限に抑え、プロの品質でツールを完成させたい」という方は、ぜひ一度ご相談ください。
- 専門家の知見に基づいた、保守性の高いコード設計
- AIの専門家による、Gemini API等の最新AIを組み合わせた高度な自動化
- ChatGPT等が生成したコードのデバッグ・最適化
「教わる」だけでなく「形にする」パートナーとして、フリーランスエンジニアのmei_13が最短ルートでの解決をサポートします。


