PHPしか知らない僕がPythonを少し触ってみたよ 〜正規表現〜

この記事はだいぶ前に書かれたものなので情報が古いかもしれません
マッチ売りの少女

この記事を三行にまとめると

取れそうな文字はだいたい同じ
preg_match
preg_match_all
今日は正規表現を使ってみたいと思います。

使う関数名が違うくらいで考え方はほとんど一緒ですね。悪そうなやつはだいたい友達、取れそうな文字はだいたい同じ。そんな感じです。



preg_match

正規表現を使ってaタグで書かれたリンクからurlだけを抜き取ってみたいと思います。説明がちょい長くなるんで、まずはPHPでこんな感じの動きをさせるぞーってのを見てください。

$link = '<a href="https://norm-nois.com">あかつきのお宿</a>';
preg_match('/href="(.*?)"/', $link, $match);
echo match[1];

「https://norm-nois.com」だけを抜き取って表示しています。

上記のように書くと「$match」の中身はこんな感じになります。

Array
(
 [0] => href="https://norm-nois.com"
 [1] => https://norm-nois.com
)

0番目にはパターン全体にマッチした文字列、1番目にはサブパターンにマッチした文字列が入ります。サブパターンってのは今回だと「(.*?)」の部分。なので「$match[1]」でURLだけを取ることができます。



では同じことをPythonでやってみます。

import re
link = '<a href="https://norm-nois.com">あかつきのお宿</a>'
match = re.search('href="(.*?)"', link)
print(match.group(1))

Pythonの場合、正規表現を使うには「re」というモジュールが必要になります。そのモジュールの中にある「search()」という関数を使えばpreg_match()と同じ動きを実現できます。

preg_match()と違うのは、パターンをスラッシュで囲うが必要ないとこですかね。

変数matchの中身はこんな感じです。

<_sre.SRE_Match object; span=(3, 31), match='href="https://norm-nois.com"'>

何かよく分かんないですね。PHPの「$match」みたいにマッチした部分だけが配列で返ってくるわけではなく、もうちょいいろんな情報が入ってます。

詳細な説明は僕もできないんでここでは無視することにして……とりあえず今は、マッチした文字列を使用するには「group()」を使えば良いってことだけ把握しときましょう。

#href="https://norm-nois.com"
match.group(0)

#https://norm-nois.com
match.group(1)

「group(0)」がパターン全体にマッチした文字列、「group(1)」がサブパターンにマッチした文字列です。ちょうどPHPの「$match[0]」「$match[1]」がそれぞれ「group(0)」「group(1)」に対応しているような感じですね。数字を省略して「match.group()」と書いた場合は「match.group(0)」と同じです。

Pythonの方ではgroup()以外にも「start()」「end()」「span()」などで、パターンにマッチした文字列の開始位置や終了位置を取得することもできます。

match.start(0) #3
match.start(1) #9

match.end(0) #31
match.end(1) #30

match.span(0) #(3,31)
match.span(1) #(9,30)

「start()」は開始位置、「end()」は終了位置、「span()」は開始位置と終了位置の両方を取ることができます。中の数字は「group()」と同じ考え方です。例えば「match.start(1)」ならサブパターンにマッチした文字列の開始位置ってことですね。

つまりこれらを使えば、substring的に文字列の中からURLを切り取って表示することも可能ってことですね。

#startとendを使う
s = match.start(1)
e = match.end(1)
link[s:e]

#spanを使う
s = match.span(1)
link[s[0]:s[1]]

「group(1)」の方が手軽なんで、わざわざこんなことやる必要はなさそうですけどね。



パターンに一致する文字列がなかった場合は「None」という結果が返ってきます。

import re
link = '<a href="https://norm-nois.com">あかつきのお宿</a>'
match = re.search('abc', link)
print(match)#None

この場合「match.group(0)」とかを使おうとするとエラーになってしまうので、引っかからない可能性がある時はif文で回避しておく必要があります。

import re
link = '<a href="https://norm-nois.com">あかつきのお宿</a>'
match = re.search('abc', link)

if match:
  print(match.group())



preg_match_all

「google.co.jp amazon.co.jp yahoo.co.jp」という文字列から、「google」「amazon」「yahoo」を抜き出してみたいと思います。

例によってまずはPHPから。「preg_match_all()」を使って抜き出します。

$domain = 'google.co.jp amazon.co.jp yahoo.co.jp';
preg_match_all('/\s*(.*?).co.jp/', $domain, $match);

foreach($match[1] as $m) {
  echo $m;
}

ざっくり書くとこんな感じですね。「$match」の中身はこんな風になります。

Array
(
  [0] => google.co.jp
  [1] =>  amazon.co.jp
  [2] =>  yahoo.co.jp
)

Array
(
  [0] => google
  [1] => amazon
  [2] => yahoo
)

「$match[0]」の「amazon.co.jp」や「yahoo.co.jp」は先頭にスペースがついちゃってますね。パターンが「(.*?).co.jp」だけだと「$match[1]」の方でもamazonやyahooの前にスペースが残ってしまうので、「\s*」(スペースの0回以上繰り返し)をパターンの先頭に足してます。



ほんじゃあこの動きをPythonで実現してみませう。

import re
domain = 'google.co.jp amazon.co.jp yahoo.co.jp'
match = re.findall('\s*(.*?).co.jp', domain)

for m in match:
  print(m)

Pythonには「findall()」という「preg_match_all()」的な関数があります。なのでこれを使えば同じような動きが実現できます。

ただし「findall()」は、上記のようにサブパターンがある場合はそっちしか結果を返してくれません。実際にやってみれば分かるのですが、上記の場合、matchの中身は以下のようになる。

['google', 'amazon', 'yahoo'] 

サブパターンに引っかかった部分のリストです。

preg_match_all()だと0番目にパターン全体、1番目にサブパターンの結果が入るので、ちょっとだけ動きが違いますね。

もしパターン全体の方も合わせて取りたい場合は、「finditer()」という関数を使います。

import re
domain = 'google.co.jp amazon.co.jp yahoo.co.jp'
match = re.finditer('\s*(.*?).co.jp', domain)

for m in match:
  print(m.group(1))

「finditer()」を使った場合、「search()」と同じように「group()」「start()」「end()」「span()」が使えます。なので「group(0)」でパターン全体の文字列を取ったり、「span(1)」でサブパターンの開始位置と終了位置を取ったりできます。



match()

Pythonで正規表現を使う場合、「search()」の他に「match()」というやつもあります。

「match()」は文字列の先頭からパターンが一致する文字を取得します。パターンと一致する文字列が先頭にない場合は値が返ってきません。

import re
url = 'https://norm-nois.com'

match = re.match('https', url)
match.group() #https

match = re.match('com', url)
match.group() #値がないのでエラー



修飾子

「preg_match()」を使う時、修飾子ってやつを使うことがあると思います。例えば「u」という修飾子を使うと、文字列をUTF-8として扱うみたいな。

Pythonにも修飾子はあります。

//PHP
preg_match('/https/u', $url, $match)

#Python
match = re.search(u'https', url)

Pythonの場合はパターンの前に修飾子をつけます。うっかりコーテーションの中に書かないように注意です。






正規表現っていろいろなパターンの書き方があって、PHPでもしょっちゅう調べながら書いてますわ。どーにも覚えらんないのよねぇ。このパターンはよく使いそうだから覚えとこうって思っても、たいてい次の日には忘れてます。今回もわりと調べた。$match[1]にスペースを残さない方法とか実は結構調べた(汗)

正規表現にはいろんなパターンの書き方がありますが、基本的にはPHPでもPythonでも同じ書き方で大丈夫なはずです。全部のパターンを試したわけじゃないから、中にはちょこっと変えなきゃいけないのもあるかもしれませんが……そこはすまん、各自調べてくれぃ。



その他のPythonを少し触ってみたの記事はこちら
前書きと索引的な
 もしかしたら何か関連しているかも? 
 質問や感想などお気軽にコメントしてください