電話番号の検証は、非常に困難な作業です。
電話番号の形式は、国によって異なることがあります。
また、同じ国でも異なる場合があります。
ある国は同じ国番号を共有し、他の国は複数の国番号を使用します。
Google の libphonenumber
GitHub リポジトリにある例によると、アメリカ、カナダ、カリブ海諸島はすべて同じ国番号 (+1
) を使っています。
一方、セルビア、スロベニア、モロッコの国番号でコソボの電話番号にかけることができます。
これらは、電話番号の特定や検証における課題のほんの一部に過ぎない。
一見したところ、少なくともRegExで電話番号の国コードを検証することは可能です。
しかし、これは国コードを検証するためだけに、世界中のすべての国に対してカスタム RegEx ルールを書かなければならないことを意味します。
その上、携帯電話のキャリアによっては独自のルール (たとえば、特定の桁の番号には特定の範囲しか使用できない) を持っています。
このように、物事はすぐに手に負えなくなり、電話番号の入力を自分で検証することはほとんど不可能になることがおわかりいただけると思います。
幸いなことに、Pythonのライブラリがあり、検証プロセスを簡単かつ効率的に行うことができます。
PythonのPhonenumbersライブラリはGoogleの libphonenumber
ライブラリから派生したもので、C++、Java、JavaScriptなどの他のプログラミング言語でも利用可能になっています。
このチュートリアルでは、電話番号の解析、検証、抽出の方法と、キャリア、タイムゾーン、ジオコーダーの詳細などの追加情報を電話番号から抽出する方法について学習します。
このライブラリの使い方は非常に簡単で、通常は次のように使用します。
import phonenumbers
from phonenumbers import carrier, timezone, geocoder
my_number = phonenumbers.parse("+447986123456", "GB")
print(phonenumbers.is_valid_number(my_number))
print(carrier.name_for_number(my_number, "en"))
print(timezone.time_zones_for_number(my_number))
print(geocoder.description_for_number(my_number, 'en'))
そして、これがその出力です。
True
EE
('Europe/Guernsey', 'Europe/Isle_of_Man', 'Europe/Jersey', 'Europe/London')
United Kingdom
まずは、環境を整え、ライブラリをインストールしましょう。
phonenumbersのインストール
まずは、仮想環境の作成と起動を行います。
$ mkdir phonenumbers && cd phonenumbers
$ python3 -m venv venv
$ . venv/bin/active # venvScriptsctivate.bat on Windows
次に、PythonのPhonenumbersライブラリのインストールを行います。
$ pip3 install Phonenumbers
このチュートリアルでは、Phonenumbersライブラリのバージョンとして 8.12.19
を使用します。
これでPhonenumbersライブラリの探索を開始する準備ができました。
Pythonによる電話番号の解析 phonenumbers
Web フォームやテキストからの抽出など、ユーザーからの入力を得る場合 (これについてはこのチュートリアルの後半で説明します)、入力される電話番号はほとんどの場合文字列になります。
最初のステップとして、 phonenumbers
を使用して電話番号を解析し、 PhoneNumber
インスタンスに変換して、バリデーションやその他の機能で使用できるようにする必要があります。
電話番号のパースには parse()
メソッドを使用します。
import phonenumbers
my_string_number = "+40721234567"
my_number = phonenumbers.parse(my_string_number)
phonenumbers.parse()
メソッドは必須引数として電話番号の文字列を受け取ります。
オプションの引数として、ISO Alpha-2 フォーマットの国情報を渡すこともできます。
例えば、以下のようなコードを考えてみましょう。
my_number = phonenumbers.parse(my_string_number, "RO")
“RO” は ISO Alpha-2 フォーマットでルーマニアを表します。
他のAlpha-2や数値の国コードは、このウェブサイトから確認できます。
このチュートリアルでは、簡単のために、ほとんどの場合ISO Alpha-2国コードは省略し、厳密に必要なときだけ含めることにします。
phonenumbers.parse()
メソッドには、数字列の長さ、先頭のゼロのチェック、+
記号のチェックなどの基本的な検証ルールがすでに組み込まれています。
このメソッドは、必要なルールのいずれかが満たされない場合に例外をスローすることに注意してください。
ですから、アプリケーションの中で try/catch ブロックでこのメソッドを使用することを忘れないでください。
さて、電話番号が正しくパースされたので、バリデーションに進みましょう。
Python Phonenumbers による電話番号の検証
Phonenumbersには、電話番号の正当性をチェックするための2つの方法があります。
これらのメソッドの主な違いは、速度と正確さです。
詳しく説明するために、まず is_possible_number()
から説明します。
import phonenumbers
my_string_number = "+40021234567"
my_number = phonenumbers.parse(my_string_number)
print(phonenumbers.is_possible_number(my_number))
そして、出力は次のようになります。
True
今度は同じ数字を使って、is_valid_number()
メソッドを使ってみましょう。
import phonenumbers
my_string_number = "+40021234567"
my_number = phonenumbers.parse(my_string_number)
print(phonenumbers.is_valid_number(my_number))
入力は同じでも、結果は次のようになります。
False
これは、is_possible_number()
メソッドが解析された電話番号の長さをチェックすることで電話番号の妥当性を素早く推測するのに対し、 is_valid_number()
メソッドは長さ、電話番号のプレフィックス、地域をチェックして完全に検証を実行することに起因しています。
大きな電話番号のリストを繰り返し処理する場合、 phonenumbers.is_possible_number()
を使用すると、 phonenumbers.is_valid_number()
と比較して高速に結果を得ることができます。
しかし、ここで見るように、これらの結果は必ずしも正確ではないかもしれません。
長さに従わない電話番号を素早く排除するのに便利です。
ですから、自己責任で使ってください。
Python Phonenumbersによる電話番号の抽出と書式設定
電話番号を取得したり収集したりする方法は、ユーザー入力だけではありません。
例えば、Webサイトやドキュメントから特定のページを読み込むスパイダー/クローラーがあり、テキストブロックから電話番号を抽出することができます。
これは難しい問題のように思えますが、幸運なことに Phonenumbers ライブラリが PhoneNumberMatcher(text, region)
メソッドという、必要な機能を提供してくれているのです。
PhoneNumberMatcher
はテキストブロックとリージョンを引数として受け取り、マッチング結果を PhoneNumberMatch
オブジェクトとして返すために繰り返し処理を行います。
それでは、ランダムなテキストを指定して PhoneNumberMatcher
を使ってみましょう。
import phonenumbers
text_block = "Our services will cost about 2,200 USD and we will deliver the product by the 10.10.2021. For more information, you can call us at +44 7986 123456 or send an e-mail to demo@example.com"
for match in phonenumbers.PhoneNumberMatcher(text_block, "GB"):
print(match)
これは、マッチした電話番号と、その文字列内のインデックスを表示します。
PhoneNumberMatch [131,146) +44 7986 123456
お気づきかもしれませんが、この番号は国際標準のフォーマットで、スペースで区切られています。
これは、実際の場面では必ずしもそうとは限りません。
ダッシュで分割されたり、(国際的な形式ではなく)国内向けの形式にフォーマットされたりと、他の形式で番号を受け取ることもあります。
それでは、 PhoneNumberMatcher()
メソッドを他の電話番号のフォーマットでテストしてみましょう。
import phonenumbers
text_block = "Our services will cost about 2,200 USD and we will deliver the product by the 10.10.2021. For more information you can call us at +44-7986-123456 or 020 8366 1177 send an e-mail to demo@example.com"
for match in phonenumbers.PhoneNumberMatcher(text_block, "GB"):
print(match)
これは、次のように出力されます。
PhoneNumberMatch [130,145) +44-7986-123456
PhoneNumberMatch [149,162) 020 8366 1177
電話番号がテキストの奥深くに埋め込まれ、他の番号と様々なフォーマットがあるにもかかわらず、 PhoneNumberMatcher
は非常に正確に電話番号を返すことに成功しています。
テキストからデータを抽出する以外に、ユーザーから数字を1つずつ取得したい場合があります。
あなたのアプリのUIが最近の携帯電話と同じように動作し、入力された電話番号をフォーマットすると想像してください。
例えば、ウェブページでは onkeyup
イベントごとにデータを API に渡し、AsYouTypeFormatter()
を使用して入力された各桁ごとに電話番号をフォーマットしたいと思うかもしれません。
UI部分はこの記事の範囲外なので、 AsYouTypeFormatter
の基本的なサンプルを使用します。
その場での書式設定をシミュレートするために、Python インタープリタに飛び込んでみましょう。
>>> import phonenumbers
>>> formatter = phonenumbers.AsYouTypeFormatter("TR")
>>> formatter.input_digit("3")
'3'
>>> formatter.input_digit("9")
'39'
>>> formatter.input_digit("2")
'392'
>>> formatter.input_digit("2")
'392 2'
>>> formatter.input_digit("2")
'392 22'
>>> formatter.input_digit("1")
'392 221'
>>> formatter.input_digit("2")
'392 221 2'
>>> formatter.input_digit("3")
'392 221 23'
>>> formatter.input_digit("4")
'392 221 23 4'
>>> formatter.input_digit("5")
'392 221 23 45'
すべてのユーザー入力が、入力されたとおりに行われるわけではありません。
フォームの中には、電話番号のためのシンプルなテキスト入力フィールドがあるものもあります。
しかし、それは必ずしも標準的なフォーマットでデータが入力されることを意味するわけではありません。
Phonenumbers ライブラリは、ここでも format_number()
メソッドで私たちをカバーしてくれています。
このメソッドを使うと、電話番号を3つのよく知られた標準的な形式にフォーマットすることができます。
National、International、そしてE164です。
国内番号と国際番号のフォーマットは一目瞭然ですが、E164フォーマットは国際電話番号のフォーマットで、電話番号が15桁に制限され、{+}{国番号}{地域番号付き番号}というフォーマットになっていることが確認できます。
E164の詳細については、こちらのウィキペディアのページをご覧ください。
まず、国番号のフォーマットから説明します。
import phonenumbers
my_number = phonenumbers.parse("+40721234567")
national_f = phonenumbers.format_number(my_number, phonenumbers.PhoneNumberFormat.NATIONAL)
print(national_f)
これは、国別の書式を持つ、きれいな間隔の電話番号文字列を返します。
0721 234 567
では、国際電話番号の書式を変えてみましょう。
import phonenumbers
my_number = phonenumbers.parse("0721234567", "RO") # "RO" is ISO Alpha-2 code for Romania
international_f = phonenumbers.format_number(my_number, phonenumbers.PhoneNumberFormat.INTERNATIONAL)
print(international_f)
上記のコードは、電話番号の文字列をきれいに並べて返します。
+40 721 234 567
parse()メソッドの2番目のパラメーターとして
“RO”を渡していることに注意してください。
入力された番号は国番号なので、国を示唆する国番号プレフィックスがありません。
このような場合、正確な結果を得るためには、ISO Alpha-2 コードで国を指定する必要があります。
数字とISO Alpha-2の国コードのどちらかを除外すると、NumberParseException: (0) Missing or invalid default region.`という例外が発生します。
では、E164
フォーマットオプションを試してみましょう。
入力として国の文字列を渡します。
import phonenumbers
my_number = phonenumbers.parse("0721234567", "RO")
e164_f=phonenumbers.format_number(my_number, phonenumbers.PhoneNumberFormat.E164)
print(e164_f)
出力は、スペースを除けば PhoneNumberFormat.INTERNATIONAL
と非常によく似ています。
+40721234567
これは、バックグラウンドのAPIに電話番号を渡したい場合に非常に便利です。
APIが電話番号にスペースを入れない文字列を期待していることはよくあることです。
電話番号の追加情報を取得する
電話番号には、関心のあるユーザーに関するデータがロードされています。
電話番号のキャリアによってAPIやAPIエンドポイントが異なるのは、製品コストに影響するためです。
顧客(電話番号)のタイムゾーンに応じてプロモーション通知を送信し、夜中にメッセージを送信しないようにしたい場合があります。
あるいは、関連情報を提供できるように、電話番号の位置情報を取得したいと思うかもしれません。
Phonenumbersライブラリは、これらのニーズを満たすために必要なツールを提供しています。
まず位置情報を取得するために、 geocoder
クラスの description_for_number()
メソッドを使用します。
このメソッドでは、パースされた電話番号と短い言語名をパラメータとして受け取ります。
先ほどの偽の電話番号で試してみましょう。
import phonenumbers
from phonenumbers import geocoder
my_number = phonenumbers.parse("+447986123456")
print(geocoder.description_for_number(my_number, "en"))
これは、電話番号の発信国を出力します。
United Kingdom
短い言語名は、かなり直感的に理解できますね。
ロシア語で出力してみましょう。
import phonenumbers
from phonenumbers import geocoder
my_number = phonenumbers.parse("+447986123456")
print(geocoder.description_for_number(my_number, "ru"))
そして、ロシア語でイギリスと書いてある出力がこちらです。
Соединенное Королевство
de”, “fr”, “zh “など、お好みの言語でも試してみてください。
前述したように、ほとんどの場合、コストに影響を与えるので、電話番号をキャリア別にグループ化することをお勧めします。
はっきり言って、Phonenumbersライブラリは、おそらくほとんどのキャリア名を正確に提供しますが、100%ではありません。
今日、ほとんどの国で、あるキャリアから電話番号を取得し、後で同じ番号を別のキャリアに移動することが可能であり、電話番号は全く同じままです。
Phonenumbersは単なるオフラインのPythonライブラリなので、このような変更を検出することは不可能です。
ですから、キャリア名は事実ではなく、参考としてアプローチするのがベストです。
ここでは、 carrier
クラスの name_for_number()
メソッドを使用します。
import phonenumbers
from phonenumbers import carrier
my_number = phonenumbers.parse("+40721234567")
print(carrier.name_for_number(my_number, "en"))
これは、可能であれば電話番号の元のキャリアを表示します。
Vodafone
注意: Python Phonenumbers のオリジナルのドキュメントにあるように、キャリア情報はすべての国ではなく、いくつかの国の携帯電話番号について利用可能です。
電話番号に関するもう一つの重要な情報は、そのタイムゾーンです。
time_zones_for_number()メソッドは、その番号が属するタイムゾーンのリストを返します。
ここでは、phonenumbers.timezone` からインポートします。
import phonenumbers
from phonenumbers import timezone
my_number = phonenumbers.parse("+447986123456")
print(timezone.time_zones_for_number(my_number))
これは以下のタイムゾーンを表示します。
('Europe/Guernsey', 'Europe/Isle_of_Man', 'Europe/Jersey', 'Europe/London')
これでPython Phonenumbersのチュートリアルは終わりです。
結論
parse()メソッドで電話番号を解析し、
PhoneNumberMatcher()でテキストブロックから番号を抽出し、
AsYouTypeFormatter()で電話番号を一桁ずつ取得してフォーマットする方法を学びました。
is_possible_number() と is_possible_number()
で異なる検証方法を使用し、 NATIONAL
, INTERNATIONAL
, E164
フォーマット方法を使用して数字をフォーマットし、 geocoder
, carrier
, timezone
クラスを用いて電話番号から追加情報を取得します。
Phonenumbers ライブラリのオリジナルの GitHub リポジトリをチェックすることを忘れないでください。
また、何か質問がある場合は、お気軽に下記までコメントください。