YutaKaのPython教室

Python の文法やライブラリ、API、環境構築について画像・動画・ソースコード付きで徹底解説!

Python 正規表現の検索を本当にわかりやすく解説

正規表現を使うと、「文字列の中から自分で設定したパターンの文字列を探すこと」ができます。

正規表現が便利なことはわかっていても、いざ使おうとすると次のような問題に直面してしまうことも・・・。

  • 検索したいとき.search().match()どっち使うの?
  • 文字列だけほしいのに、Matchオブジェクトとかいう変な結果が出てきた!
  • マッチ箇所全部抽出したいのに、最初の一個しか抽出できない!

そこで、この記事ではPythonの正規表現ライブラリreについて、次の内容をわかりやすく解説しています。

  • 検索メソッド(関数)の実行方法
  • 各メソッドの紹介
  • 検索結果の出力であるMatchオブジェクト

正規表現の検索メソッド(関数)一覧

正規表現パターンを使用した検索メソッド(関数)は主に次の2つに分類できます。

  • 単独検索:文字列からマッチものを一つのみを抽出するもの(.search()など)
  • 全体検索:文字列からマッチするもの全てを抽出するもの(.findall()など)

この分類に応じて、メソッドを整理すると次のようになります。

分類 メソッド名 詳細
パターン検索(1つのみ) .search() 文字列中で最初にマッチしたもののMatchオブジェクトを返す
  .match() 文字列の先頭がパターンにマッチしたら、Matchオブジェクトを返す
  .fullmatch() 文字列全体がパターンにマッチしたら、Matchオブジェクトを返す
パターン検索(マッチするもの全て) .findall() 文字列内でパターンマッチするもの全てを[リスト]で返す
  .finditer() findall()の結果をiteratorで返す

この記事では、各検索メソッドについて詳しく解説していきます。

Pythonでの正規表現パターンの生成方法については、次の記事で詳しく解説しています。

正規表現パターンの作り方(文字の繰り返し、文字条件の指定、グループ化)がわからない方は、こちらも参考にしてください。

≫Python正規表現パターンを図解&サンプルで本当にわかりやすく解説
Pythonのreモジュールで正規表現パターンを作成する際に重要な3つのポイントを解説します。①特定の文字の繰り返しを指定 ②数値や文字、空白など条件による文字条件の指定 ③グループ化によるパターン内の特定部分の抽出
www.yutaka-note.com/entry/regex_01
 

検索メソッド実行方法|re.compileありなし

各メソッドの詳しい内容の前に、まずは検索メソッド(関数)はそもそもどうやって実行するのか確認してみましょう。

正規表現のメソッド(関数)には、次の2つの実行方法があります。

  1. 使用のたびにパターンを設定して実行するパターン
    • re.メソッド名("正規表現パターン", その他引数)
  2. 初めに正規表現パターンをコンパイルして実行するパターン
    • p = re.compile(正規表現パターン)
    • p.メソッド名()

どちらも結果は同じですが、基本次項なので、実行例を見て使い方の違いを確認しておきましょう。

まずは、re.メソッド名()で実行する例を見てみます。

import re
text = "1月1日は元旦です。"

m = re.search("\d月\d日", text) # ←実行時に正規表現パターンを与えている
print(m)
# <re.Match object; span=(0, 4), match='1月1日'>

次に正規表現パターンをコンパイルする例を見てみます。

text = "1月1日は元旦です。"

p = re.compile("\d月\d日") # 先に正規表現パターンをcompileして、正規表現オブジェクト生成
m = p.search(text) # 正規表現オブジェクトでパターンは設定済み ⇒ メソッド実行時に再指定不要
print(m)
# <re.Match object; span=(0, 4), match='1月1日'>

一度コンパイルした、正規表現オブジェクトは再利用可能です。

そのため、プログラム内で同じ正規表現パターン使用する場合は、コンパイルした方が良い場合が多いです。

text = "1月1日は元旦です。"
text2 = "5月5日は子供の日です。"

texts = [text, text2]

p = re.compile("\d月\d日") # パターンをcompileして、正規表現オブジェクト生成
for t in texts:
    m = p.search(t) # 正規表現オブジェクトでパターンは設定済み ⇒ メソッド実行時に再指定不要
    print(m)
    
# <re.Match object; span=(0, 4), match='1月1日'>
# <re.Match object; span=(0, 4), match='5月5日'>

まとめると次のように使い分ければOKです(個人的には、使い分けにそれほど拘る必要はないと思いますが…)。

  1. プログラムの中で繰り返し同じパターンを使う場合 ⇒ コンパイル
    • p = re.compile(正規表現パターン)
    • p.メソッド名()
  2. 繰り返し使用しない場合 ⇒使用のたびにパターン設定
    • re.メソッド名(正規表現パターン)

re.メソッド名()p.メソッド名()の使い分けがわかったところで、以下で各メソッドのより具体的な使用方法を紹介していきます。

以下では、re.メソッド名()の形式で解説しますが、p.メソッド名()でも使用方法はほとんど同じです。

パターン検索(1つのみ)

まずは、パターンに一致した箇所を「1つだけ」抽出できるメソッドを解説していきます。

分類 メソッド名 詳細
パターン検索(1つのみ) .search() 文字列中で最初にパターンにマッチした箇所のMatchオブジェクトを返す
  .match() 文字列の先頭がパターンにマッチしたら、Matchオブジェクトを返す
  .fullmatch() 文字列全体がパターンにマッチしたら、Matchオブジェクトを返す

多くの場合は、.search()メソッドを使用して、マッチ個所を検索することが多いと思います。

.match().fullmatch()も状況に応じて、使用することがあるので、内容をチェックしていきましょう。

パターン検索(1つのみ)|.search()

おそらくpythonのパターン検索の中で使用頻度が最も高いメソッドだと思います。

.search()は、文字列中で「最初に」パターンにマッチした箇所のMatchオブジェクトを返します。

まずはサンプルコードで.search()メソッドの挙動を確認してみましょう。

text = "4月1日は焼肉を食べた。"

re.search("\d月\d日", text)
# <re.Match object; span=(0, 4), match='4月1日'>

"\d月\d日"の正規表現パターンにマッチした'4月1日'に対応するMatchオブジェクトが取得できていますね。

.search()メソッドのポイントは、「最初に」パターンにマッチした箇所のMatchオブジェクトを返す点です。

マッチする箇所が2つあったとしても、最初のマッチ個所しか返してくれません。

text = "4月1日は焼肉を食べて、5月1日は寿司を食べた。"

re.search("\d月\d日", text)
# <re.Match object; span=(0, 4), match='4月1日'>

1つ目のマッチ個所'4月1日'を返しましたが、2つ目のマッチ個所'5月1日'は返してくれません。

2つ目以降のマッチ個所を探したい場合は、.findall()メソッド等を使用します。

これについては、後述のパターン検索(マッチするもの全て)で解説します。

パターン検索(先頭1つのみ)|.match()

.search()に似たパターン検索関数に、.match()があります。

これらの違いは次の通りです。

  • .match():文字列の「先頭」がパターンにマッチしなければならない
  • .search():文字列の途中でもパターンにマッチすればOK

以下で、先頭にパターンがある場合とそうでない場合の.match()の挙動の違いを確認してみましょう。

text = "4月1日は焼肉を食べた。"

re.match("\d月\d日", text)
# <re.Match object; span=(0, 4), match='4月1日'>

パターンが先頭にあるので、確かにMatchオブジェクトを返しています。

次にパターンがテキストの途中にある場合の実行例を見てみます。

text = "私は4月1日は焼肉を食べた。"

re.match("\d月\d日", text)
# None

文字列の先頭でマッチしないので、Noneが返されています。

.match()と特殊文字^の違い

.match()は、文字列の先頭でマッチするか判定する関数です。

一方、正規表現パターン自体にも文字列の先頭を指定する文字列"^"があります。

  • "^"|パターンが先頭でマッチすることを指定する正規表現の特殊文字
    • 使用方法:パターンの先頭に"^"を付ける
    • 使用例:"^\d月/d日"

例として、"^"の使い方を見てみましょう。

text = "私は4月1日は焼肉を食べた。"
re.search("^\d月\d日", text) #^でパターンがテキストの先頭にあることを指定
# None
#パターンが先頭にないのでマッチしない

text = "4月1日は焼肉を食べた。"
re.search("^\d月\d日", text) #^でパターンがテキストの先頭にあることを指定
# <re.Match object; span=(0, 4), match='4月1日'>
# パターンがテキストの先頭にあるのでマッチ

これを見ると、.match()関数と特殊文字"^"の機能が重複しているように見えます。

しかし、この2つは改行のあるテキストでre.MULTILINEを指定した場合に挙動が異なります。

  • .match()関数|文字列の先頭のみでマッチ判定(改行後のパターン一致はマッチとしない)
  • 特殊文字"^"|各行の先頭でマッチするか判定

サンプルコードで挙動の違いを確認してみましょう。

text = """私の食事について話します。
5月1日は寿司を食べた。
6月1日はピザを食べた。
"""

re.match("\d月\d日", text, re.MULTILINE)
# None
# 文字列の先頭にマッチしないので、None
text = """私の食事について話します。
5月1日は寿司を食べた。
6月1日はピザを食べた。
"""

re.search("^\d月\d日", text, re.MULTILINE)
# <re.Match object; span=(15, 19), match='5月1日'>
# 行の先頭でマッチするか判定⇒5月1日にマッチ

re.MULTILINE``.match()関数と特殊文字"^"で挙動が異なるので、適宜使い分けましょう。

パターン検索(全体一致のみ)|.fullmatch()

.fullmatch()は、文字列全体がパターンに一致している場合にMatchオブジェクトを返します。

text = "4月1日"

re.fullmatch("\d月\d日", text)
# <re.Match object; span=(0, 4), match='4月1日'>

文字列の前後に余計な文字が入っているとマッチしません。

text = "4月1日は焼肉を食べた。"

re.fullmatch("\d月\d日", text)
# None

Matchオブジェクトの中身

.search()や.match()メソッドで、正規表現パターンがマッチする結果としてMatchオブジェクトが返されます。

以下で、Matchオブジェクトの内容について解説していきます。

Matchオブジェクトは、次の内容をまとめたオブジェクトと考えるとわかりやすいです。

  • マッチした文字列
  • マッチの位置
  • 正規表現パターン内の各グループの内容

とりあえず、マッチした文字列だけ抽出したい場合は、次のメソッドだけ覚えておけばOKです。

  • m.group() -> "マッチした文字列"を出力
text = "私は4月1日は焼肉を食べた。"
m = re.search("\d月\d日", text)

print(m) # 出力内容はMatchオブジェクトの概要
# <re.Match object; span=(2, 6), match='4月1日'>

print(m.group()) # マッチした文字列のみ出力
# '4月1日'

その他のメソッドで、使用頻度が比較的高いものについて、以下で紹介します。

Matchオブジェクトの便利なメソッド一覧

Matchオブジェクトで比較的使用頻度の高いメソッド一覧は次の通りです。

分類 出力内容 メソッド名 引数など
マッチ文字列出力 マッチ文字列全体 .group() 引数省略 or 0でマッチ文字列全体を出力
  特定グループの内容 .group() グループ番号 or "グループ名"を引数に渡すと、そのグループ内容のみ出力
  グループ内容のリスト .groups() 引数不要(パターンにグループがないと空)
マッチ位置出力 (開始,終了)形式のタプル .span() 引数省略でマッチ文字列全体の位置、引数でグループ指定も可能
  開始位置のみ .start()
  終了位置のみ .end()

マッチ文字列・グループ内容出力|.group()

Matchオブジェクト,m からマッチした文字列全体を出力する場合は、.group()メソッドを実行します。

引数を省略するか、0を与えると、マッチした文字列全体を出力します。

  • m.group()
  • m.group(0)

簡単な実行例を見てみましょう。

text = "私は4月1日は焼肉を食べた。"
m = re.search("(?P<Month>\d月)(?P<Day>\d日)", text)

m.group() # マッチした文字列のみ出力
# '4月1日'

この例では、正規表現パターン内に、<Month>グループと、<Day>グループを設定しています。

しかし、グループ関係なしにマッチ文字列全体を出力していますね。

各グループの内容を個別に参照したい場合には、.group()メソッドにグループ番号またはグループ名を渡します。

text = "私は4月1日は焼肉を食べた。"
m = re.search("(?P<Month>\d月)(?P<Day>\d日)", text)

# グループ番号を指定する例
m.group(1)
# '4月'

m.group(2)
# '1日'

# グループ名を指定する例
m.group("Month")
# '4月'

m.group("Day")
# '1日'

引数に複数グループの番号、名前を渡して、複数同時に出力することも可能です。

text = "私は4月1日は焼肉を食べた。"
m = re.search("(?P<Month>\d月)(?P<Day>\d日)", text)

# グループ番号を指定する例
m.group(1, 2)
# ('4月', '1日')

# グループ名を指定する例
m.group("Month", "Day")
# ('4月', '1日')

ただし、全てのグループ内容を出力する場合には、次に紹介する.groups()の方が便利です。

グループ内容を全て出力|.groups()

正規表現パターンがグループを持つ場合に、グループの内容を一覧で取得したい場合は.groups()メソッドを使用します。

text = "私は4月1日は焼肉を食べた。"
m = re.search("(?P<Month>\d月)(?P<Day>\d日)", text)

m.groups()
# ('4月', '1日')

グループが設定されていない場合には、空のタプルが返されます。

text = "私は4月1日は焼肉を食べた。"
m = re.search("\d月\d日", text)

m.groups()
# ()

マッチ位置の出力|.span()

正規表現パターンが、文字列のどの位置にマッチしたかを出力する際には、次のメソッドを使用します。

分類 出力内容 メソッド名
マッチ位置出力 (開始,終了)形式のタプル .span()
  開始位置のみ .start()
  終了位置のみ .end()

引数を省略すると、マッチ文字列全体の開始位置、終了位置を取得できます。

text = "私は4月1日は焼肉を食べた。"
m = re.search("(?P<Month>\d月)(?P<Day>\d日)", text)

# 開始位置、終了位置のタプル取得
m.span()
# (2, 6)

# 開始位置のみ
m.start()
# 2

# 終了位置のみ
m.end()
# 6

グループ番号またはグループ名を指定すれば、特定のグループの開始・終了位置を出力することもできます。

text = "私は4月1日は焼肉を食べた。"
m = re.search("(?P<Month>\d月)(?P<Day>\d日)", text)

# 開始位置、終了位置のタプル取得
m.span(2)
# (4, 6)

# 開始位置のみ
m.start(2)
# 4

# 終了位置のみ(グループ名で指定)
m.end("Day")
# 6

パターン検索(マッチするもの全て)

.search()は基本的なパターン検索の関数ですが、文字列の中で一番最初にマッチした箇所しか返してくれません。

マッチするもの全てを見つけたい場合には、次の3つの方法があります。

分類 メソッド名/方法 詳細
パターン検索(マッチするもの全て) .findall() 文字列内でマッチするもの全てを[リスト]で返す
  .finditer() Matchオブジェクトを順番に生成するiteratorで返す
  .search()while文の組み合わせ while文内で検索範囲を調整

マッチ箇所全て抽出|findall()

.findall()は、文字列内でパターンにマッチした部分を[リスト]にして返します。

text = """私は4月1日は焼肉を食べて、
5月1日は寿司を食べて、
6月1日はピザを食べた。
"""

re.findall("\d月\d日", text)
# ['4月1日', '5月1日', '6月1日']

一致箇所を全て抽出したいときには、かなり便利な関数ですね。

.findall()で覚えておきたいものとして、次の挙動があります。

  • 正規表現パターンでグループを指定すると、グループの内容をタプルにしたリストを返す

サンプルコードで、正規表現パターン内にグループを指定した場合の挙動を確認してみましょう。

text = """私は4月1日は焼肉を食べて、
5月1日は寿司を食べて、
6月1日はピザを食べた。
"""

re.findall("(\d月\d日)は(\w+)を", text)
# [('4月1日', '焼肉'), ('5月1日', '寿司'), ('6月1日', 'ピザ')]

必要な箇所を抜き出すのに非常に便利ですね。

マッチ箇所イテレータで抽出|finditer()

.findall()は、一致箇所を全て[リスト]で抽出する際に便利ですが、次のようなときに不便です。

  • Matchオブジェクトとして取得したい
  • 全てを[リスト]で取得したいわけではない
    • 特定の回数だけでいい場合や、文字列の途中までの検索で良い場合など

そういった場合は、.finditer()関数を使用する便利です。

.finditer()関数は、Matchオブジェクトを一つずつ出力するイテレータを取得できます。

イテレータを取得した後は、for文で回すなりして必要な処理をしましょう。

text = """私は4月1日は焼肉を食べて、
5月1日は寿司を食べて、
6月1日はピザを食べた。
"""

match_iter = re.finditer("\d月\d日", text)
for t in match_iter:
    print(t)
    
# <re.Match object; span=(2, 6), match='4月1日'>
# <re.Match object; span=(15, 19), match='5月1日'>
# <re.Match object; span=(28, 32), match='6月1日'>

リストとして、Matchオブジェクトをまとめたい場合にはlist()を適用すればOKです。

text = """私は4月1日は焼肉を食べて、
5月1日は寿司を食べて、
6月1日はピザを食べた。
"""

match_iter = re.finditer("\d月\d日", text)
match_list = list(match_iter)
    
match_list
# [<re.Match object; span=(2, 6), match='4月1日'>,
#  <re.Match object; span=(15, 19), match='5月1日'>,
#  <re.Match object; span=(28, 32), match='6月1日'>]

.search()とfor文の組み合わせ

.search()for文を組み合わせて、マッチ個所全てを抽出することも可能です。

Matchオブジェクトの位置を利用して、文字列内の検索範囲をずらしていきます。

text = """私は4月1日は焼肉を食べて、
5月1日は寿司を食べて、
6月1日はピザを食べた。
"""

pos = 0 # 検索範囲の初期値(0は文字列の先頭)
match_list = [] # 結果のリストの準備

while m := re.search("\d月\d日", text[pos:]): # 文字列内のpos以降でマッチする限りループ   
    match_list.append(m) # Matchオブジェクトを結果のリストに格納
    pos+=m.end() # マッチした文字列の後尾にposの値を変更
    
match_list
# [<re.Match object; span=(2, 6), match='4月1日'>,
#  <re.Match object; span=(9, 13), match='5月1日'>,
#  <re.Match object; span=(9, 13), match='6月1日'>]

何らかの理由で.finditer()を使わずに処理したい場合には、この方法もありですね。

正規表現の関連記事

今回は正規表現での検索方法について解説しました。

正規表現関連のその他の記事は次の通りです。

正規表現パターンの生成方法

Pythonでの正規表現のパターンの生成方法については、次の記事で詳しく解説しています。

文字の繰り返し、文字条件の指定、グループ化がよくわからないという方は、こちらも参考にしてください。

≫Python正規表現パターンを図解&サンプルで本当にわかりやすく解説
Pythonのreモジュールで正規表現パターンを作成する際に重要な3つのポイントを解説します。①特定の文字の繰り返しを指定 ②数値や文字、空白など条件による文字条件の指定 ③グループ化によるパターン内の特定部分の抽出
www.yutaka-note.com/entry/regex_01
 

正規表現応用例

正規表現は実際に使ってみないとなかなか身につかないものです。

正規表現の応用例は、次の記事で使用例を紹介しているので、参考にしてみてください!

≫Amazonの購入履歴をスクレイピングなしで抽出
簡易的にAmazonの注文履歴を整理したい場合のPythonスクリプトです。「Amazonの注文履歴から金額と商品名と日付を抽出したいな」、「とはいえ、わざわざSeleniumを使ってスクレイピングするほどでもないな」、「でも、手作業で集計するのはめんどくさいな・・・」。こういったときに、さらっとAmazonの注文履歴から金額、商品名、日付を抽出するPythonスクリプトを紹介します。
www.yutaka-note.com/entry/ama2df
 
≫aタグのリンクを新規タブで開くようにするPythonスクリプト
htmlのaタグのリンクを外部リンクで開くようにaタグを更新するpythonスクリプトを紹介します。テキスト形式でhtmlを用意して、pythonスクリプトを実行すると自動でaタグの属性を更新してくれます。
www.yutaka-note.com/entry/a_tag_jido