Python3の復習(正規表現)
こんにちは。
かにたまごです。
昨日は久しぶりに学校へ行ってきました。
懐かしさに浸りたい気持ちもありましたが、人混みに当てられて直ぐに帰ってきました(;^ω^)
これから就活だってのに大丈夫か・・・?
まあまあそれは置いておいて、今回は
について、復習していきたいと思います。
スクレイピングとかで主に使うやつですよね。
特殊な書き方で複雑なイメージ。
今までのPython3 復習履歴は以下の通り。
- 数値・文字列
- リスト・タプル
- 辞書・集合
- if文・while文・for文
- 内包表記
- 関数
- ジェネレータ
- デコレータ
- 名前空間とスコープ
- アンダーバー・アンダースコアの意味
- エラー処理:try・except
- モジュール・パッケージ
- 標準ライブラリ
- オブジェクトとクラス①-クラスの定義-
- オブジェクトとクラス②-継承-
- プロパティ
- ポリモーフィズムとダックタイピング
- 特殊メソッドとその他諸々
- Unicodeとエンコード・デコード
- 書式指定
正規表現
困ったときはまずwikipedia。
正規表現とは、文字列の集合を一つの文字列で表現する方法の一つである。
毎度毎度、噛ませ犬的な立ち位置にさせてしまって申し訳ないけど、この一文だと分からない・・・。
普段ならここから数分はWebサイト巡りに使うのですが、一発目に引いたこちらのサイトの以下の文で何となく意味は掴めました。
正規表現とは、文字列を1つのパターン化した文字列で表現する表記法です。
Wikipediaさんの一文とほぼ変わらない気がするけど、パターン化した文字列で表現ってところで何かを掴めたみたい。
"8578284"と"6748583"は違う文字列だけど、パターン化した文字列で表現するとどちらも「数値で構成されている文字列」になる、みたいな?
このパターンを利用して文字列の中から特定の値を抽出し、様々な変更を加える。
- 正規表現のパターン
正規表現のパターンをメタ文字と呼ぶらしい。なんかカッコいい。
記述方法については公式ドキュメントへ。
- . = 任意の一文字
- ^ = 行の先頭
- $ = 行の末尾
- * = (直前の文字が)0回以上の繰り返し
- + = (直前の文字が)1回以上の繰り返し
- ? = (直前の文字が)0回または1回
- {m} = m回の繰り返し
- {m,n} = m〜n回の繰り返し
- [〇-△] = 〇-△のどれか1文字
- 〇|△ = 〇か△のどれか
- 使い方
正規表現によるパターンマッチングを行うには、reモジュールを使用する。
match()は文字列の先頭がパターンとマッチするかを調べる。
こちらのサイトで練習用のダミー電話番号を生成した。
#20個のダミー電話番号の中から047を抽出する import re dummy_txt = """ 電話番号 082-837-5750 046-477-1932 043-440-7198 059-736-5981 039-552-5877 072-784-6823 054-678-7596 0 5- 54- 107 037-414- 452 073-281-4586 0 7-257-5560 036-635-8790 062-863-1554 043-803-3040 089-533-1633 071-373-6861 047-929-4458 047-209-4303 066-306-2679 0 -669-2244 """ #先頭の空白除去、その他の空白除去、改行でsplit dummy_list = dummy_txt.strip().replace(" ", "").split("\n") pattern = "047" results = [] for phone_num in dummy_list: result = re.match(pattern, phone_num) if result: #マッチした部分全体を抜き出す results.append(result.group()) print(results)
['047', '047']
パターンとマッチしない場合はNoneが返ってくるため、if文でチェックしている。
ここで得られた結果は、「047で始まる電話番号は2つある」ということ。
さらにこれを発展させて、「047で始まる電話番号を全て抜き出す」という結果を得るには正規表現を用いると良い。
047に続く任意の文字を全て抽出したいので、. と * というメタ文字を使って 047.* というパターンを定義する。
#20個のダミー電話番号の中から047を抽出する import re dummy_txt = """ 電話番号 082-837-5750 046-477-1932 043-440-7198 059-736-5981 039-552-5877 072-784-6823 054-678-7596 0 5- 54- 107 037-414- 452 073-281-4586 0 7-257-5560 036-635-8790 062-863-1554 043-803-3040 089-533-1633 071-373-6861 047-929-4458 047-209-4303 066-306-2679 0 -669-2244 """ #先頭の空白除去、その他の空白除去、改行でsplit dummy_list = dummy_txt.strip().replace(" ", "").split("\n") pattern = "047.*" results = [] for phone_num in dummy_list: result = re.match(pattern, phone_num) if result: #マッチした部分全体を抜き出す results.append(result.group()) print(results)
すると、結果は
['047-929-4458', '047-209-4303']
けど、これって
pattern = "047" results = [] for phone_num in dummy_list: if pattern in phone_num: results.append(phone_num) print(results)
と変わんなくない( ^ω^)・・・?書いてる途中に気付いた。アフォか。
この例で正規表現を正しく使うなら、扱いやすいようにリスト化なんてせずに
dummy_txt = """ 電話番号 082-837-5750 046-477-1932 043-440-7198 059-736-5981 039-552-5877 072-784-6823 054-678-7596 0 5- 54- 107 037-414- 452 073-281-4586 0 7-257-5560 036-635-8790 062-863-1554 043-803-3040 089-533-1633 071-373-6861 047-929-4458 047-209-4303 066-306-2679 0 -669-2244 """
を用いるべきだった・・・。
これだとfor文で回したところで047から始まる番号は抽出できない。
さて、もう一度。
#20個のダミー電話番号の中から047を抽出する import re dummy_txt = """ 電話番号 082-837-5750 046-477-1932 043-440-7198 059-736-5981 039-552-5877 072-784-6823 054-678-7596 0 5- 54- 107 037-414- 452 073-281-4586 0 7-257-5560 036-635-8790 062-863-1554 043-803-3040 089-533-1633 071-373-6861 047-929-4458 047-209-4303 066-306-2679 0 -669-2244 """ #先頭の空白除去、その他の空白除去、改行でsplit #dummy_list = dummy_txt.strip().replace(" ", "").split("\n") pattern = "047.*" result = re.match(pattern, dummy_txt) print(result)
None
そうだった・・・。matchは先頭文字のみを調べるからダメなんだ。二度目のアフォ。
文字列全体を調べるには、search()かfindall()を使用する。
search()は、最初にマッチしたものを返す。
#20個のダミー電話番号の中から047を抽出する import re dummy_txt = """ 電話番号 082-837-5750 046-477-1932 043-440-7198 059-736-5981 039-552-5877 072-784-6823 054-678-7596 0 5- 54- 107 037-414- 452 073-281-4586 0 7-257-5560 036-635-8790 062-863-1554 043-803-3040 089-533-1633 071-373-6861 047-929-4458 047-209-4303 066-306-2679 0 -669-2244 """ #先頭の空白除去、その他の空白除去、改行でsplit #dummy_list = dummy_txt.strip().replace(" ", "").split("\n") pattern = "047.*" result = re.search(pattern, dummy_txt) print(result.group())
047-929-4458
findall()は、重なり合わない全てのマッチしたものをリストとして返す。
今回の例だと、こちらの方が適切かな。
#20個のダミー電話番号の中から047を抽出する import re dummy_txt = """ 電話番号 082-837-5750 046-477-1932 043-440-7198 059-736-5981 039-552-5877 072-784-6823 054-678-7596 0 5- 54- 107 037-414- 452 073-281-4586 0 7-257-5560 036-635-8790 062-863-1554 043-803-3040 089-533-1633 071-373-6861 047-929-4458 047-209-4303 066-306-2679 0 -669-2244 """ #先頭の空白除去、その他の空白除去、改行でsplit #dummy_list = dummy_txt.strip().replace(" ", "").split("\n") pattern = "047.*" result = re.findall(pattern, dummy_txt) print(result)
['047-929-4458', '047-209-4303']
なんとかクリア。
ちなみに、今回のような簡単な例だとあまり実感できないけど、先にコンパイルをしておくと複雑なマッチのスピードが上がるらしい。
num_pattern = re.compile("047.*") result = re.findall(num_pattern, dummy_txt) print(result)
['047-929-4458', '047-209-4303']
この他にも、パターンで文字列を分割するsplit()や
text = "fjdosigoisodosgoteng890gfod34ongos0gosngn344nfdnon0ngdosngiondio438957932nodsnfions98ddsh3532ndsno9u09" pattern = re.compile("[^0-9]+") result = pattern.split(text) print(result)
['', '890', '34', '0', '344', '0', '438957932', '98', '3532', '9', '09']
マッチした部分を置換するsub()
pattern = '<.+?>' replace = "" html = """ <!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <title>タイトル</title> </head> <body> <div class="wrapper"> <header> <h1>メインタイトル</h1> <nav id="global-nav"> <ul> <li>リンク1</li> <li>リンク2</li> <li>リンク3</li> <li>リンク4</li> <li>リンク5</li> </ul> </nav> </header> <div class="main-contents"> <h2>見出し</h2> <p>テキストテキストテキスト</p> </div> </div> </body> </html> """ result = re.sub(pattern, replace, html) print(result)
タイトル メインタイトル リンク1 リンク2 リンク3 リンク4 リンク5 見出し テキストテキストテキスト
などがある。
うーん。今までで一番モヤモヤが残る・・・。
何か良い参考書かサイトを見つけてもっと勉強しよう。
以上です。
参考にさせていただいたサイト: