数弱の文系大学生によるブログです。

Python、Webデザイン関連の記事を投稿していけたらなと思っています。

備忘録的なブログなので、読みにくい文章だと思います。ご了承ください。

Python3の復習(正規表現)

こんにちは。

かにたまごです。
昨日は久しぶりに学校へ行ってきました。
懐かしさに浸りたい気持ちもありましたが、人混みに当てられて直ぐに帰ってきました(;^ω^)
これから就活だってのに大丈夫か・・・?


まあまあそれは置いておいて、今回は

について、復習していきたいと思います。

スクレイピングとかで主に使うやつですよね。
特殊な書き方で複雑なイメージ。


今までのPython3 復習履歴は以下の通り。

  1. 数値・文字列
  2. リスト・タプル
  3. 辞書・集合
  4. if文・while文・for文
  5. 内包表記
  6. 関数
  7. ジェネレータ
  8. デコレータ
  9. 名前空間とスコープ
  10. アンダーバー・アンダースコアの意味
  11. エラー処理:try・except
  12. モジュール・パッケージ
  13. 標準ライブラリ
  14. オブジェクトとクラス①-クラスの定義-
  15. オブジェクトとクラス②-継承-
  16. プロパティ
  17. ポリモーフィズムとダックタイピング
  18. 特殊メソッドとその他諸々
  19. Unicodeとエンコード・デコード
  20. 書式指定

正規表現

困ったときはまず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
                  
                
              
              
                見出し
                テキストテキストテキスト
              
            
          
        
  

などがある。


うーん。今までで一番モヤモヤが残る・・・。
何か良い参考書かサイトを見つけてもっと勉強しよう。


以上です。


参考にさせていただいたサイト:

書きながら覚えよう!Pythonで正規表現を使う方法【初心者向け】

Pythonでの正規表現の使い方

正規表現:文字列を「含まない」否定の表現まとめ

正規表現のマッチング方法の解説