正規表現

定形の数字形式 クレジットカード番号 _key-visual

あなたのカードは何桁?

 ここからは、いよいよ正規表現の実践編になります。

基礎編で学んだ事を活用していきましょう。

 今回は、特定の形式で並んでいる数字をマッチの対象にします。

中でも第一回は、クレジットカード番号です。

クレジットカード番号は定形の数字形式で構成されています。

 例えば、American Express(アメリカン・エキスプレス)のクレジットカード番号は、15桁です。

15桁の中でも最初の2桁3437 である事が必要です。

 このように、クレジットカード番号は特定のパターンがるので、これを正規表現に落とし込みます。

American Express

 それでは、American Express の場合を考えてみましょう。

番号は15桁なので、数字15桁 を表す正規表現を思い出します。

 これは、【[ ] 文字集合を指定する】【{ } 繰り返し】 で学習した文字クラスと量指定子を用いて、[0-9]{15} と表現すればよさそうです。

但し、最初の2桁分は 3437 であることが必要になるので、3[47] として、残りの13桁は [0-9]{13} にします。

続けて書くと、3[47][0-9]{13} の形になります ..... が、このパターンでは対象文字列により、マッチが思うようにならない場合がありますので、下の実行例でもう少し詳しく説明します。

 実行例に移る前に、VISAのクレジットカード番号も説明します。

VISA

 VISAのクレジットカード番号形式は、13桁16桁で最初の1桁4 です。

13桁のときは、先程用いた文字クラスと量指定子を使い 4[0-9]{12} とします。

16桁も同様に、4[0-9]{15} とすればよいでしょう。

この両方の場合を、選択的に構成するパターンを考えます。

|

 一つは、| を利用する方法です。

(| については【| OR(または)】で学習しました。)

13桁でも16桁も最初の1桁は 4 で共通なので、この1桁分を差し引いた 12桁 と 15桁 を選択対象にします。

すると、4(?:[0-9]{12}|[0-9]{15}) のようになります。

(( )は【( ) グループを指定】で詳しく説明しました。)

?

 別の方法としては、量指定子の ? を用います。

13桁の部分は ^4[0-9]{12} として、16桁の場合はこの次に3桁分を付け足す形で構成します。

 この際に、メタキャラクタの ?省略形を表現できるので、3桁分を省略可能にするのに (?:[0-9]{3})? のようにします。

(? については、【? 繰り返し】で学習済みです。)

これにより、3桁分が無いときは13桁になり、ある場合は16桁の形を規定出来ます。

全体では 4[0-9]{12}(?:[0-9]{3})? になりますが、やはり American Express と同様に、対象文字列の形に応じて詳細を詰める必要があります。

 では、プログラムを実行して検証してみましょう。


この記事の難度は、基礎  Cクラスです。

(A: やさしい   →   E: 難しい)

 事前知識として、pythonから正規表現を扱う方法が必要になります。
また、正規表現における文字クラスや量指定子の知識も必要です。
この他に、先頭や末尾の位置を表す ^ $ 及び、\b (単語の境界) や先読み、後読みなどのアサーションを総合的に理解している事が望ましいです。
 (不安な人でも、【Pythonから使う】【基礎1 文字クラス】【基礎5 量指定子】【基礎2 アサーション①】【基礎4 アサーション②】で詳しい解説があるので安心です。)
 最初はシンプルなパターンで構成し、徐々にアサーションを使っていきましょう。

難度       :
事前知識: Pythonの基礎文法(reモジュールを含む)。正規表現の文字クラスや繰り返し、アサーション等。
学習効果:  クレジットカード番号のパターンを掴み、正規表現で取得する事が出来るようになる。

Contens  |   目次

Chapter1 Pythonで実行

Chapter1   Pythonで実行

American Express

 American Express から確認していきましょう。

文字列パターンは前述した 3[47][0-9]{13} を用います。

対象文字列を 341234567890123 とした場合にはマッチするはずなので確認します。

    re_practical1_1.py

        import  re

        pattern = re.compile("3[47][0-9]{13}")
        st = "341234567890123"
        print("↓ 対象文字列\n"+st)
        result_iter = pattern.finditer(st)
        for  result   in  result_iter:
            print("match:",result.group())
            print("位置",result.span())        
                            


実行結果 定形の数字形式 クレジットカード番号_1

 これは特に問題無いでしょう。

次は対象文字列の2桁目を 4 から 9 に変更します。

マッチが起こらないのを確かめます。

    re_practical1_2.py

        import  re

        pattern = re.compile("3[47][0-9]{13}")
        st = "391234567890123"
        print("↓ 対象文字列\n"+st)
        result_iter = pattern.finditer(st)
        for  result   in  result_iter:
            print("match:",result.group())
            print("位置",result.span())        
                            


実行結果 定形の数字形式 クレジットカード番号_2

 これも問題は無いです。

では、対象文字列を1桁分余計に増やして、3412345678901234 にします。

この場合、16桁の番号になるので元来であれば弾いて欲しいところです。

    re_practical1_3.py

        import  re

        pattern = re.compile("3[47][0-9]{13}")
        st = "3412345678901234"
        print("↓ 対象文字列\n"+st)
        result_iter = pattern.finditer(st)
        for  result   in  result_iter:
            print("match:",result.group())
            print("位置",result.span())        
                            


実行結果 定形の数字形式 クレジットカード番号_3

 結果は16桁の番号(3412345678901234)の中で、15桁分(341234567890123)までマッチしてしまいました。

これはあまり望ましい結果とは言えないので、16桁の番号は弾くようにします。

^3[47][0-9]{13}$

 15桁分だけをマッチの対象にするには、^ $ を使います。

^ $ は、それぞれ行の先頭、文字列の末尾を表します。

ゆえに、3[47][0-9]{13} の両端を ^ と $ で挟んで ^3[47][0-9]{13}$ にすれば、先頭から末尾までで15桁の数字を指定できます。

(^ や $ については【^ 行の先頭】及び、【$ 文字列の末尾】で詳しく説明しています。)

 先程の re_practical1_3.py の文字列パターンを変更して再度実行します。

    re_practical1_4.py

        import  re

        pattern = re.compile("^3[47][0-9]{13}$")
        st = "3412345678901234"
        print("↓ 対象文字列\n"+st)
        result_iter = pattern.finditer(st)
        for  result   in  result_iter:
            print("match:",result.group())
            print("位置",result.span())        
                            


実行結果 定形の数字形式 クレジットカード番号_4

 今度はしっかり弾きました。

\b3[47][0-9]{13}\b

 しかし、このパターンにするとカード番号が、複数まとめて書いてある場合に一致しなくなります。

対象文字列が 371234567890123 341234567890129 371234567890128 のようなケースです。

    re_practical1_5.py

        import  re

        pattern = re.compile("^3[47][0-9]{13}$")
        st = "371234567890123 341234567890129 371234567890128"
        print("↓ 対象文字列\n"+st)
        result_iter = pattern.finditer(st)
        for  result   in  result_iter:
            print("match:",result.group())
            print("位置",result.span())        
                            


実行結果 定形の数字形式 クレジットカード番号_5

 3つの番号は各々パターンを満たしていますが、どれもマッチしていません。

^ と $ の制約が影響するからです。

 そこで、^ と $ の代わりに \b を使う事でこの結果を回避できそうです。

\b は、単語の境目にマッチする正規表現であり、\w と \W との間、あるいは \w と文字列の先頭・末尾との間でのマッチを引き起こします。

(\b については、【\b 単語の境界】で詳しく説明しています。)

パターンである 3[47][0-9]{13} を \b で挟んで、\b3[47][0-9]{13}\b のようにしましょう。

 これで番号が複数個ある場合でも拾えるはずです。

    re_practical1_6.py

        import  re

        pattern = re.compile(r"\b3[47][0-9]{13}\b")
        st = "371234567890123 3419 341234567890128"
        print("↓ 対象文字列\n"+st)
        result_iter = pattern.finditer(st)
        for  result   in  result_iter:
            print("match:",result.group())
            print("位置",result.span())        
                            


実行結果 定形の数字形式 クレジットカード番号_6

 狙い通り、パターンの構成要件を満たしている番号を拾えました。

ですが、さらに意地悪くカード番号に、単語構成文字以外である -+ などが付いているケースを検証してみましょう。

(?<!-)\b3[47][0-9]{13}\b

 パターンは \b3[47][0-9]{13}\b のままで、カード番号が -371234567890129 を含むような場合について実行してみます。

    re_practical1_7.py

        import  re

        pattern = re.compile(r"\b3[47][0-9]{13}\b")
        st = "371234567890123 3419 -371234567890129"
        print("↓ 対象文字列\n"+st)
        result_iter = pattern.finditer(st)
        for  result   in  result_iter:
            print("match:",result.group())
            print("位置",result.span())        
                            


実行結果 定形の数字形式 クレジットカード番号_7

 - が付いている番号までマッチしました。

元来であれば、何も付いていない番号に一致させたいので、これは否定したい結果です。

 この問題に対処するには、否定後読みを用います。

パターンに (?<!-) を付け足して (?<!-)\b3[47][0-9]{13}\b にします。

こうする事で - の後に番号がある形式は不一致になるはずです。

(否定後読みについては、【(?<! ) 否定後読み】で詳しく説明しています。)

    re_practical1_8.py

        import  re

        pattern = re.compile(r"(?<!-)\b3[47][0-9]{13}\b")
        st = "371234567890123 3419 -371234567890129"
        print("↓ 対象文字列\n"+st)
        result_iter = pattern.finditer(st)
        for  result   in  result_iter:
            print("match:",result.group())
            print("位置",result.span())        
                            


実行結果 定形の数字形式 クレジットカード番号_8

 きちんと、適切な形式の番号だけに一致しました。

なお、番号の後に : などの余計な記号が付いている場合には、否定先読みで対応します。

(否定先読みについては、【(?! ) 否定先読み】で詳しく説明しています。)

(?<!-)\b3[47][0-9]{13}\b(?!:)

 番号の後ろに : の付いた 349994567890128: を弾くために、先程の文字列パターンにさらに (?!:) を加えます。

全体の構成は、(?<!-)\b3[47][0-9]{13}\b(?!:) になります。

    re_practical1_9.py

        import  re

        pattern = re.compile(r"(?<!-)\b3[47][0-9]{13}\b(?!:)")
        st = "371234567890123 3419 -371234567890129"\
                "\n" "349994567890128:"
        print("↓ 対象文字列\n"+st+"\n↑ 対象文字列")
        result_iter = pattern.finditer(st)
        for  result   in  result_iter:
            print("match:",result.group())
            print("位置",result.span())        
                            


実行結果 定形の数字形式 クレジットカード番号_9

 意図した結果になった事が確認できました。

(?<![+#-])\b3[47][0-9]{13}\b(?![:*@])

 先程は、番号の前に - しか余計な記号はありませんでしたが、もう少し不要な記号がある場合にも対応してみましょう。

 それには、先読みや後読みと文字クラスを組み合わせます。

例えば、+ # - の付いた番号はマッチの対象から外したいなら、+ # - を集合にして [+#-] とし、後は否定後読みなどに付加して (?<![+#-]) の形にします。

 下の re_practical1_10.py では、番号の前後に様々な不必要な記号がありますが、これらを弾くパターンを構成しています。

    re_practical1_10.py

        import  re

        pattern = re.compile(r"(?<![+#-])\b3[47][0-9]{13}\b(?![:*@])")
        st = "371234567890123 3419 -371234567890129"\
                "\n" "349994567890128: +349994567890127@"\
                "\n" "#349994567890126* 341234567890124"
        print("↓ 対象文字列\n"+st+"\n↑ 対象文字列")
        result_iter = pattern.finditer(st)
        for  result   in  result_iter:
            print("match:",result.group())
            print("位置",result.span())        
                            


実行結果 定形の数字形式 クレジットカード番号_10

実行結果(続き) 定形の数字形式 クレジットカード番号_10_2

 無事に 371234567890123341234567890124 のみマッチさせる事が出来ました。

sub()

 ところで、今まで番号は連続して15桁並んでいました。

これを -(ハイフン) が間にあり、4桁 - 6桁 - 5桁のときも一旦マッチさせ、その後 -(ハイフン) を取り除き、15桁の番号をつくる処理も実行してみます。

-(ハイフン) を取り除くには、 sub() を用いて置換で対応します。

(置換については、【正規表現による置換と分割】で詳しく説明しています。)

    re_practical1_11.py

        import  re

        pattern = re.compile("3[47][0-9]{2}-[0-9]{6}-[0-9]{5}")
        st = "3734-123456-12345"
        print("↓ 対象文字列\n"+st)
        result_iter = pattern.finditer(st)
        for  result   in  result_iter:
            s = result.group()
            print("match:",s)
            pat = re.compile("-")
            repl = ""
            result = pat.sub(repl,s)
            print("置換後:",result)        
                            


実行結果 定形の数字形式 クレジットカード番号_11

 sub() により -(ハイフン) が除去されている事を確認出来ました。

VISA

 VISA についても忘れずにやっておきましょう。

基本的な構成パターンは冒頭で述べたように、4[0-9]{12}(?:[0-9]{3})? です。

    re_practical1_12.py

        import  re

        pattern = re.compile("4[0-9]{12}(?:[0-9]{3})?")
        st = "4123456789012"
        print("↓ 対象文字列\n"+st)
        result_iter = pattern.finditer(st)
        for  result   in  result_iter:
            print("match:",result.group())
            print("位置",result.span())        
                            


実行結果 定形の数字形式 クレジットカード番号_12

 特に問題はありません。

後は、American Express と同様にパターンを修正します。

修正後の一つの例としては、(?<![#%-])\b4[0-9]{12}(?:[0-9]{3})?\b(?![+*@]) などが挙げられます。

    re_practical1_13.py

        import  re

        pattern = re.compile(r"(?<![#%-])\b4[0-9]{12}(?:[0-9]{3})?\b(?![+*@])")
        st = "4123456789012 4123456789012345 41333"\
                "\n" "-4123456789019 4123456789018+ 4123456789017*"\
                "\n" "#4123456789016 %4123456789015@"
        print("↓ 対象文字列\n"+st+"\n↑ 対象文字列")
        result_iter = pattern.finditer(st)
        for  result   in  result_iter:
            print("match:",result.group())
            print("位置",result.span())        
                            


実行結果 定形の数字形式 クレジットカード番号_10

実行結果(続き) 定形の数字形式 クレジットカード番号_13_2

 13桁か16桁の、前後に余計な記号が付いていない番号のみマッチしました。



 以上でクレジットカード番号編の解説は終了です。

クレジットカード番号のパターンを掴み、正規表現で取得する事が出来るようになりました。

 American Express や VISA 以外にもクレジットカードはあります。

今回習得した技術でこの他にも対応できる力が、あなたには備わっています。

是非他のカードにも挑戦してみて下さい。

関連記事

実践1 定形の数字形式 クレジットカード番号_key-visual

クレジットカード番号

正規表現: 定形の数字形式
難度       : 基礎
事前知識: Pythonと正規表現の基礎。文字クラス等。
学習効果: クレジットカードのパターンを掴み、正規表現で取得する事が出来るようになる。
実践1 定形の数字形式 電話番号_key-visual

電話番号

正規表現: 定形の数字形式
難度       : 基礎
事前知識: Pythonと正規表現の基礎。文字クラス等。
学習効果: 電話番号のパターンを掴み、正規表現で取得する事が出来るようになる。
Pythonで正規表現を使う1

正規表現をPythonから使うには ?

正規表現: Pythonから使う
難度       : 入門
事前知識: Pythonの基礎文法
学習効果: pythonから正規表現を使う一連の流れを掴む
メタキャラクタに馴染む_key-visual

ハロー ! メタキャラクタ

正規表現: メタキャラクタの概要
難度       : 入門
事前知識: 不要
学習効果: メタキャラクタの概要を掴む
正規表現の概要_key-visual

正規表現とは?

正規表現: 概要
難度       : 入門
事前知識: 不要
学習効果: 正規表現の概要を知る
正規表現の概要

PR