正規表現実践
クレジットカード番号
カテゴリー:定形の数字形式
あなたのカードは何桁?
ここからは、いよいよ正規表現の実践編になります。
基礎編で学んだ事を活用していきましょう。
今回は、特定の形式で並んでいる数字をマッチの対象にします。
中でも第一回は、クレジットカード番号です。
クレジットカード番号は定形の数字形式で構成されています。
例えば、American Express(アメリカン・エキスプレス)のクレジットカード番号は、15桁です。
15桁の中でも最初の2桁は 34 か 37 である事が必要です。
このように、クレジットカード番号は特定のパターンがるので、これを正規表現に落とし込みます。
American Express
それでは、American Express の場合を考えてみましょう。
番号は15桁なので、数字 と 15桁 を表す正規表現を思い出します。
これは、【[ ] 文字集合を指定する】 と 【{ } 繰り返し】 で学習した文字クラスと量指定子を用いて、[0-9]{15} と表現すればよさそうです。
但し、最初の2桁分は 34 か 37 であることが必要になるので、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())
実行結果
これは特に問題無いでしょう。
次は対象文字列の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())
実行結果
これも問題は無いです。
では、対象文字列を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())
実行結果
結果は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())
実行結果
今度はしっかり弾きました。
\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())
実行結果
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())
実行結果
狙い通り、パターンの構成要件を満たしている番号を拾えました。
ですが、さらに意地悪くカード番号に、単語構成文字以外である - や + などが付いているケースを検証してみましょう。
(?<!-)\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())
実行結果
- が付いている番号までマッチしました。
元来であれば、何も付いていない番号に一致させたいので、これは否定したい結果です。
この問題に対処するには、否定後読みを用います。
パターンに (?<!-) を付け足して (?<!-)\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())
実行結果
きちんと、適切な形式の番号だけに一致しました。
なお、番号の後に : などの余計な記号が付いている場合には、否定先読みで対応します。
(否定先読みについては、【(?! ) 否定先読み】で詳しく説明しています。)
(?<!-)\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())
実行結果
意図した結果になった事が確認できました。
(?<![+#-])\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())
実行結果
実行結果(続き)
無事に 371234567890123 と 341234567890124 のみマッチさせる事が出来ました。
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)
実行結果
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())
実行結果
特に問題はありません。
後は、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())
実行結果
実行結果(続き)
13桁か16桁の、前後に余計な記号が付いていない番号のみマッチしました。
以上でクレジットカード番号編の解説は終了です。
クレジットカード番号のパターンを掴み、正規表現で取得する事が出来るようになりました。
American Express や VISA 以外にもクレジットカードはあります。
今回習得した技術でこの他にも対応できる力が、あなたには備わっています。
是非他のカードにも挑戦してみて下さい。
関連記事
クレジットカード番号
正規表現: | 定形の数字形式 |
難度 : | 基礎 |
事前知識: | Pythonと正規表現の基礎。文字クラス等。 |
学習効果: | クレジットカードのパターンを掴み、正規表現で取得する事が出来るようになる。 |
電話番号
正規表現: | 定形の数字形式 |
難度 : | 基礎 |
事前知識: | Pythonと正規表現の基礎。文字クラス等。 |
学習効果: | 電話番号のパターンを掴み、正規表現で取得する事が出来るようになる。 |
正規表現をPythonから使うには ?
正規表現: | Pythonから使う |
難度 : | 入門 |
事前知識: | Pythonの基礎文法 |
学習効果: | pythonから正規表現を使う一連の流れを掴む |
ハロー ! メタキャラクタ
正規表現: | メタキャラクタの概要 |
難度 : | 入門 |
事前知識: | 不要 |
学習効果: | メタキャラクタの概要を掴む |
正規表現とは?
正規表現: | 概要 |
難度 : | 入門 |
事前知識: | 不要 |
学習効果: | 正規表現の概要を知る |
PR