PythonでWhois情報を構造的に取得したい
そう、やりたいのはこれだけです。
RDAPを使ってもいいのですが、JPドメインのRDAPとかみつからないし、まだ時期尚早かな、ということでWhoisで頑張ります。
なぜ難しいのか
whoisは太古のインターネットのプロトコルのひとつです。
仕組み自体は、45番portにアクセスして、情報の欲しいドメイン名を入れる、というシンプルなものなのですが、まず、どこに問い合わせをしたらいいのかはっきりしません。次に、どんな情報をどんな形式で返すかは、それぞれのwhoisサーバー次第なのです。
基本的に人間が読みやすいように工夫されてるので、TLD毎にParserを書いてあげる必要があります。いや、JPの場合、.jpと.co.jpと.ne.jpとでは異なるので、TLD毎でさえないんですよね。
探してみる
いくつかググってPythonで使えそうなwhoisのライブラリを探してみました。
とりあえず、pipでインストールできるものとして、whois
、python-whois
、pythonwhois
を見つけました。
まず、簡単な説明と、どのくらい人気があるのか、最近メンテされてるのかを調べてみます。
whois 0.9.13
PyPiで確認してみます。ここです。
このパッケージの作者による説明はありません。あぁ、そうですか。
しかし、最終更新日は今年の4月なので、そんなに悪くはなさそうです。
githubの方も確認してみます。DannyCork/python-whois です。
えっ?!
さっき、python-whois
って別のパッケージありましたよね。
ま、それはおいといて、実装内容としては、Linuxのwhoisコマンドを呼んで結果を成形するようです。
Able to extract data for all the popular TLDs (com, org, net, biz, info, pl, jp, uk, nz, ...)
とか書いてあるので、いいんじゃないでしょうか。
Starも160あります。
python-whois 0.7.3
次は、python-whois
を見てみます。ここです。
こちらは、whoisコマンド経由ではなく、直接whoisサーバーにアクセスしてデータを取得するようです。
githubも確認してみます。richardpenman/whois です。
おっと、こちらはpython-whois
ではなく、whois
なのですね。
カオスですね・・・。
先月更新があったようですし、Starも149個あります。
pythonwhois 2.4.3
こちらは、pythonwhois
です。ここです。
ほぅ、UNKNOWNですと・・・。
最終リリースは2014年ですね。
IPアドレスのwhoisライブラリのipwhoisがオススメしてたので期待したのですが、新しいgTLDとかはないかもしれませんね。
githubは、joepie91/python-whois です。
もう、パッケージ名の突っ込みはよしましょう・・・。
READMEを見ると、お、なにやら立派なゴールが書いてあります。
- 100% coverage of WHOIS formats.
- Accurate and complete data.
- Consistently functional parsing; constant tests to ensure the parser isn't accidentally broken.
すげ。
無理でしょ。
メンテが7年前で止まっていますが・・・。
ま、Starが367個のすばらしいプロジェクトだったようです。
あと、ライセンスが、WTFPL(Do What the Fuck You Want to Public License)です。聞いたことないのですが、何ですかね、これ。勝手にしやがれってことですかね。
ま、いやならCC0を使え、と。
使ってみる
whois 0.9.13
まずは、今年4月にメンテされているwhoisです。
import whois
import pprint
pprint.pprint(whois.query("qiita.com").__dict__)
実行してみます。
{'creation_date': datetime.datetime(2011, 7, 18, 11, 47, 4),
'dnssec': False,
'expiration_date': datetime.datetime(2022, 7, 18, 11, 47, 4),
'last_updated': datetime.datetime(2021, 6, 13, 23, 21, 43),
'name': 'qiita.com',
'name_servers': {'ns-1049.awsdns-03.org',
'ns-171.awsdns-21.com',
'ns-1956.awsdns-52.co.uk',
'ns-772.awsdns-32.net'},
'registrant': '',
'registrant_country': '',
'registrar': 'Amazon Registrar, Inc.',
'status': 'clientTransferProhibited '
'https://icann.org/epp#clientTransferProhibited',
'statuses': ['clientTransferProhibited '
'https://icann.org/epp#clientTransferProhibited']}
あれ。registrantがないですね。
whoisコマンドで直接見てみるとわかるのですが、
Registrar WHOIS Server: whois.registrar.amazon.com
のように、実はもう一段階whoisサーバを辿る必要があります。
ここまで、追いかけてはくれないようです。
汎用JPドメインも見てみましょう。hiragana.jpです。
{'creation_date': datetime.datetime(2002, 8, 4, 0, 0),
'dnssec': False,
'expiration_date': datetime.datetime(2022, 8, 31, 0, 0),
'last_updated': datetime.datetime(2021, 9, 1, 1, 5, 10),
'name': 'hiragana.jp',
'name_servers': {'ns1.qt-dns.jp', 'ns1.qt-dns.com', 'ns1.qt-dns.net'},
'registrant': 'Yoshitaka Hirano',
'registrant_country': '',
'registrar': '',
'status': 'Active',
'statuses': ['Active']}
いい感じのようですが、dnssecは登録してあるので、間違いですね。
レジストラはwhoisコマンドで見えないので、出なくても仕方ありません。
co.jpはどうでしょうか。interlingua.co.jpです。
{'creation_date': None,
'dnssec': False,
'expiration_date': datetime.datetime(2022, 6, 30, 0, 0),
'last_updated': datetime.datetime(2021, 7, 1, 1, 8, 4),
'name': 'interlingua.co.jp',
'name_servers': None,
'registrant': '',
'registrant_country': '',
'registrar': '',
'status': 'Connected (2022/06/30)',
'statuses': ['Connected (2022/06/30)']}
registrantもcreation_dateがないですね。
whoisコマンドの出力にはあるので、うまくparseできていないようですね。残念です。
ne.jpはどうでしょうか。imail.ne.jpです。
Traceback (most recent call last):
File "/home/hirano/test/a.py", line 6, in <module>
pprint.pprint(whois.query("imail.ne.jp").__dict__)
AttributeError: 'NoneType' object has no attribute '__dict__'
あらら。死んじゃいました。
うーん。なんか、全体的に残念な感じです。
whoisコマンドの出力にあるのに、結果に出ないものについては、whois/tld_regexpr.py
を修正すればいけそうです。
例えば、co.jpのcreation_dateが出ないのは、r'\[登録年月\]\s?(.+)'
をr'\[登録年月日\]\s?(.+)'
にすれば直ります。
ne.jpはそもそもコードが対応していないようです。
では、新しめので、xyzドメインとかはどうでしょうか。aaa.xyzで試してみます。
No whois server is known for this kind of object.
Traceback (most recent call last):
File "/root/jpaawg/tsuritai/tsuritai-scanner/a.py", line 6, in <module>
pprint.pprint(whois.query("aaa.xyz").__dict__)
File "/root/jpaawg/tsuritai/tsuritai-scanner/.venv/lib/python3.9/site-packages/whois/__init__.py", line 82, in query
pd = do_parse(do_query(d, force, cache_file, slow_down, ignore_returncode), tld)
File "/root/jpaawg/tsuritai/tsuritai-scanner/.venv/lib/python3.9/site-packages/whois/_2_parse.py", line 45, in do_parse
raise FailedParsingWhoisOutput(whois_str)
whois.exceptions.FailedParsingWhoisOutput: No whois server is known for this kind of object.
これも、死んじゃいました。
OSのwhoisコマンドでも出力されませんでした。ま、whoisコマンドのラッパーなので仕方がないですね。
python-whois 0.7.3
最近でもメンテされているもう一つのwhoisライブラリです。
import whois
import pprint
pprint.pprint(whois.whois("qiita.com"))
実行してみます。
{'address': 'P.O. Box 81226',
'city': 'Seattle',
'country': 'US',
'creation_date': datetime.datetime(2011, 7, 18, 11, 47, 4),
'dnssec': 'unsigned',
'domain_name': ['QIITA.COM', 'qiita.com'],
'emails': ['abuse@amazonaws.com',
'owner-10669316@qiita.com.whoisprivacyservice.org',
'admin-10669316@qiita.com.whoisprivacyservice.org',
'tech-10669316@qiita.com.whoisprivacyservice.org'],
'expiration_date': datetime.datetime(2022, 7, 18, 11, 47, 4),
'name': 'On behalf of qiita.com owner',
'name_servers': ['NS-1049.AWSDNS-03.ORG',
'NS-171.AWSDNS-21.COM',
'NS-1956.AWSDNS-52.CO.UK',
'NS-772.AWSDNS-32.NET',
'ns-1049.awsdns-03.org',
'ns-171.awsdns-21.com',
'ns-1956.awsdns-52.co.uk',
'ns-772.awsdns-32.net'],
'org': 'Whois Privacy Service',
'referral_url': None,
'registrar': 'Amazon Registrar, Inc.',
'state': 'WA',
'status': ['clientTransferProhibited '
'https://icann.org/epp#clientTransferProhibited',
'renewPeriod https://icann.org/epp#renewPeriod'],
'updated_date': [datetime.datetime(2021, 6, 13, 23, 21, 43),
datetime.datetime(2021, 6, 13, 23, 21, 44, 397000)],
'whois_server': 'whois.registrar.amazon.com',
'zipcode': '98108-1226'}
whois
と違い、python-whois
はレジストラのwhois情報も辿って取得しているようで、org
のところに登録者情報も表示されています。「Whois Privacy Service」ですがね。
name server情報は、なぜか、大文字小文字2つあって残念な感じです。
では、汎用JPドメインも見てみましょう。hiragana.jpです。
{'creation_date': None,
'domain_name': None,
'name_servers': None,
'registrant_org': None,
'status': None,
'updated_date': None}
おっと、これは。やる気が感じられません。
co.jpはどうでしょうか。interlingua.co.jpです。
{'creation_date': None,
'domain_name': None,
'name_servers': None,
'registrant_org': 'Interlingua, Inc.',
'status': None,
'updated_date': None}
こちらも残念な感じですね。かろうじて登録者情報だけ取得できています。
ne.jpも見てみましょう。imail.ne.jpです。
{'creation_date': None,
'domain_name': None,
'name_servers': None,
'registrant_org': None,
'status': None,
'updated_date': None}
あらら。全滅ですね。
whois/parser.py
を見てみると、動きそうなのですが、記述が英語表記になっています。
実は、JPのwhoisサーバーはデフォルトで日本語で情報を返すので、期待した動作をしなかったようです。
もしかしたら、昔はJPドメインのwhoisサーバーも英語で返してその頃は動いていたのかも知れません。
JPRSのWhoisサーバーはドメイン名の後ろに/e
を付けると英語で返すので、ソースをちょっと修正すれば取得できました。
が、対応しているのはco.jpのみで、汎用JPドメインやne.jpドメインは動作しません。
うーん。こちらも残念な感じです。
xyzドメインも見てみます。
{'address': None,
'city': None,
'country': None,
'creation_date': None,
'dnssec': None,
'domain_name': None,
'emails': None,
'expiration_date': None,
'name': None,
'name_servers': None,
'org': None,
'referral_url': None,
'registrar': None,
'state': None,
'status': None,
'updated_date': None,
'whois_server': None,
'zipcode': None}
ほぅ。なぜか項目は増えましたが、Noneですね。
残念です。
pythonwhois 2.4.3
次は、7年前で開発が止まってるpythonwhoisです。
from pythonwhois import get_whois
import pprint
pprint.pprint(get_whois("qiita.com"))
実行してみます。
Traceback (most recent call last):
File "/usr/local/lib/python3.9/sre_parse.py", line 1039, in parse_template
this = chr(ESCAPES[this][1])
KeyError: '\\s'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/home/hirano/test/a.py", line 3, in <module>
from pythonwhois import get_whois
File "/home/hirano/test/.venv/lib/python3.9/site-packages/pythonwhois/__init__.py", line 1, in <module>
from . import net, parse
File "/home/hirano/test/.venv/lib/python3.9/site-packages/pythonwhois/parse.py", line 422, in <module>
registrant_regexes = [preprocess_regex(regex) for regex in registrant_regexes]
File "/home/hirano/test/.venv/lib/python3.9/site-packages/pythonwhois/parse.py", line 422, in <listcomp>
registrant_regexes = [preprocess_regex(regex) for regex in registrant_regexes]
File "/home/hirano/test/.venv/lib/python3.9/site-packages/pythonwhois/parse.py", line 215, in preprocess_regex
regex = re.sub(r"\\s\*\(\?P<([^>]+)>\.\+\)", r"\s*(?P<\1>\S.*)", regex)
File "/usr/local/lib/python3.9/re.py", line 210, in sub
return _compile(pattern, flags).sub(repl, string, count)
File "/usr/local/lib/python3.9/re.py", line 327, in _subx
template = _compile_repl(template, pattern)
File "/usr/local/lib/python3.9/re.py", line 318, in _compile_repl
return sre_parse.parse_template(repl, pattern)
File "/usr/local/lib/python3.9/sre_parse.py", line 1042, in parse_template
raise s.error('bad escape %s' % this, len(this))
re.error: bad escape \s at position 0
うわ。いきなりimportで死んだよ・・・。
ま、7年前のコードなので仕方ないでしょう。
修正してみます。pythonwhois/parse.pyの215行目です。
regex = re.sub(r"\\s\*\(\?P<([^>]+)>\.\+\)", r"\s*(?P<\1>\S.*)", regex)
↓
regex = re.sub(r"\\s\*\(\?P<([^>]+)>\.\+\)", r"\\s*(?P<\1>\\S.*)", regex)
実行してみます。
長いので折りたたみ
{'contacts': {'admin': None, 'billing': None, 'registrant': None, 'tech': None},
'creation_date': [datetime.datetime(2011, 7, 18, 11, 47, 4)],
'emails': ['abuse@amazonaws.com'],
'expiration_date': [datetime.datetime(2022, 7, 18, 11, 47, 4)],
'id': ['1667512845_DOMAIN_COM-VRSN'],
'nameservers': ['NS-1049.AWSDNS-03.ORG',
'NS-171.AWSDNS-21.COM',
'NS-1956.AWSDNS-52.CO.UK',
'NS-772.AWSDNS-32.NET'],
'raw': [' Domain Name: QIITA.COM\n'
' Registry Domain ID: 1667512845_DOMAIN_COM-VRSN\n'
' Registrar WHOIS Server: whois.registrar.amazon.com\n'
' Registrar URL: http://registrar.amazon.com\n'
' Updated Date: 2021-06-13T23:21:43Z\n'
' Creation Date: 2011-07-18T11:47:04Z\n'
' Registry Expiry Date: 2022-07-18T11:47:04Z\n'
' Registrar: Amazon Registrar, Inc.\n'
' Registrar IANA ID: 468\n'
' Registrar Abuse Contact Email: abuse@amazonaws.com\n'
' Registrar Abuse Contact Phone: +1.2067406200\n'
' Domain Status: clientTransferProhibited '
'https://icann.org/epp#clientTransferProhibited\n'
' Name Server: NS-1049.AWSDNS-03.ORG\n'
' Name Server: NS-171.AWSDNS-21.COM\n'
' Name Server: NS-1956.AWSDNS-52.CO.UK\n'
' Name Server: NS-772.AWSDNS-32.NET\n'
' DNSSEC: unsigned\n'
' URL of the ICANN Whois Inaccuracy Complaint Form: '
'https://www.icann.org/wicf/\n'
'>>> Last update of whois database: 2021-09-08T11:12:17Z <<<\n'
'\n'
'For more information on Whois status codes, please visit '
'https://icann.org/epp\n'
'\n'
'NOTICE: The expiration date displayed in this record is the date '
'the\n'
"registrar's sponsorship of the domain name registration in the "
'registry is\n'
'currently set to expire. This date does not necessarily reflect the '
'expiration\n'
"date of the domain name registrant's agreement with the sponsoring\n"
"registrar. Users may consult the sponsoring registrar's Whois "
'database to\n'
"view the registrar's reported date of expiration for this "
'registration.\n'
'\n'
'TERMS OF USE: You are not authorized to access or query our Whois\n'
'database through the use of electronic processes that are '
'high-volume and\n'
'automated except as reasonably necessary to register domain names '
'or\n'
'modify existing registrations; the Data in VeriSign Global Registry\n'
'Services\' ("VeriSign") Whois database is provided by VeriSign for\n'
'information purposes only, and to assist persons in obtaining '
'information\n'
'about or related to a domain name registration record. VeriSign does '
'not\n'
'guarantee its accuracy. By submitting a Whois query, you agree to '
'abide\n'
'by the following terms of use: You agree that you may use this Data '
'only\n'
'for lawful purposes and that under no circumstances will you use '
'this Data\n'
'to: (1) allow, enable, or otherwise support the transmission of '
'mass\n'
'unsolicited, commercial advertising or solicitations via e-mail, '
'telephone,\n'
'or facsimile; or (2) enable high volume, automated, electronic '
'processes\n'
'that apply to VeriSign (or its computer systems). The compilation,\n'
'repackaging, dissemination or other use of this Data is expressly\n'
'prohibited without the prior written consent of VeriSign. You agree '
'not to\n'
'use electronic processes that are automated and high-volume to '
'access or\n'
'query the Whois database except as reasonably necessary to register\n'
'domain names or modify existing registrations. VeriSign reserves the '
'right\n'
'to restrict your access to the Whois database in its sole discretion '
'to ensure\n'
'operational stability. VeriSign may restrict or terminate your '
'access to the\n'
'Whois database for failure to abide by these terms of use. VeriSign\n'
'reserves the right to modify these terms at any time.\n'
'\n'
'The Registry database contains ONLY .COM, .NET, .EDU domains and\n'
'Registrars.\n'],
'registrar': ['Amazon Registrar, Inc.'],
'status': ['clientTransferProhibited '
'https://icann.org/epp#clientTransferProhibited'],
'updated_date': [datetime.datetime(2021, 6, 13, 23, 21, 43)],
'whois_server': ['whois.registrar.amazon.com']}
お。動きました。
生データはうるさいので、静かにしてもらいましょう。
{'contacts': {'admin': None, 'billing': None, 'registrant': None, 'tech': None},
'creation_date': [datetime.datetime(2011, 7, 18, 11, 47, 4)],
'emails': ['abuse@amazonaws.com'],
'expiration_date': [datetime.datetime(2022, 7, 18, 11, 47, 4)],
'id': ['1667512845_DOMAIN_COM-VRSN'],
'nameservers': ['NS-1049.AWSDNS-03.ORG',
'NS-171.AWSDNS-21.COM',
'NS-1956.AWSDNS-52.CO.UK',
'NS-772.AWSDNS-32.NET'],
'registrar': ['Amazon Registrar, Inc.'],
'status': ['clientTransferProhibited '
'https://icann.org/epp#clientTransferProhibited'],
'updated_date': [datetime.datetime(2021, 6, 13, 23, 21, 43)],
'whois_server': ['whois.registrar.amazon.com']}
こんな感じです。
whois
と同じようにレジストラのwhoisまでは追いかけてないですが、whois_server
に書いてあるので、頑張ったら取れそうです。
汎用JPはどうでしょうか。hiragana.jpで試します。
{'contacts': {'admin': None,
'billing': None,
'registrant': {'email': 'yo@hirano.cc',
'name': 'Interlingua. Inc.',
'phone': '03-3904-2501',
'postalcode': '177-0033',
'street': '2-7-13-201 Takanodai\nNerima-ku'},
'tech': None},
'creation_date': [datetime.datetime(2002, 8, 4, 0, 0),
datetime.datetime(2002, 8, 4, 0, 0)],
'expiration_date': [datetime.datetime(2022, 8, 31, 0, 0),
datetime.datetime(2022, 8, 31, 0, 0)],
'nameservers': ['ns1.qt-dns.com', 'ns1.qt-dns.jp', 'ns1.qt-dns.net'],
'status': ['Active'],
'updated_date': [datetime.datetime(2021, 9, 1, 1, 5, 10)]}
取れてますが、公開窓口情報がregistrantに入ってますね。本物のRegistrantは取れていません。
あと、creation_date
とexpiration_data
がなぜか2つありますね。
co.jpはどうでしょうか。interlingua.co.jpで試します。
{'contacts': {'admin': {'changedate': datetime.datetime(2004, 6, 14, 4, 31, 1),
'email': 'domain@interlingua.co.jp',
'firstname': 'Yoshitaka',
'handle': 'YH5591JP',
'lastname': 'Hirano',
'name': 'Yoshitaka Hirano',
'organization': 'Interlingua, Inc.'},
'billing': None,
'registrant': {'organization': 'Interlingua, Inc.'},
'tech': {'changedate': datetime.datetime(2004, 11, 17, 9, 10, 48),
'email': 'yo@hirano.cc',
'fax': '03-3904-2501',
'firstname': 'Yoshitaka',
'handle': 'YH5927JP',
'lastname': 'Hirano',
'name': 'Yoshitaka Hirano',
'organization': 'Interlingua, Inc.',
'phone': '03-3904-2501'}},
'creation_date': [datetime.datetime(2004, 6, 14, 0, 0)],
'nameservers': ['ns1.qt-dns.com', 'ns1.qt-dns.jp', 'ns1.qt-dns.net'],
'status': ['Connected (2022/06/30)'],
'updated_date': [datetime.datetime(2021, 7, 1, 1, 8, 4)]}
こちらはRegistrantは正しく取れていますね。
こりゃ、ne.jpもいけるかもしれません。imail.ne.jpで試してみます。
{'contacts': {'admin': {'changedate': datetime.datetime(2000, 7, 22, 4, 33, 1),
'firstname': 'Yoshitaka',
'handle': 'YH2576JP',
'lastname': 'Hirano',
'name': 'Yoshitaka Hirano',
'organization': 'Hirano, Yoshitaka'},
'billing': None,
'registrant': None,
'tech': {'changedate': datetime.datetime(2000, 4, 7, 17, 9, 44),
'email': 'hirano@girigiri.ne.jp',
'firstname': 'Yoshitaka',
'handle': 'YH1048JP',
'lastname': 'Hirano',
'name': 'Yoshitaka Hirano',
'organization': 'Girigiri Group Company',
'phone': '03-5731-6270'}},
'creation_date': [datetime.datetime(2000, 7, 24, 0, 0)],
'nameservers': ['ns1.hirano.cc', 'ukulele.orcaland.gr.jp'],
'status': ['Connected (2022/07/31)'],
'updated_date': [datetime.datetime(2021, 8, 1, 1, 4, 58)]}
残念。registrantは取れませんでした。
co.jpは「g. [Organization]
」なんですが、ne.jpは「d. [Network Service Name]
」なんですよね。
どうやら20年くらい更新を忘れている情報があるようです。直さないと。
では、xyzドメインはどうでしょうか。
{'contacts': {'admin': None, 'billing': None, 'registrant': None, 'tech': None},
'creation_date': [datetime.datetime(2016, 3, 8, 21, 38, 58),
datetime.datetime(2016, 3, 8, 21, 38, 58)],
'emails': ['domainabuse@service.aliyun.com'],
'expiration_date': [datetime.datetime(2022, 3, 8, 23, 59, 59)],
'id': ['D18453057-CNIC'],
'nameservers': ['DNS9.HICHINA.COM', 'DNS10.HICHINA.COM'],
'registrar': ['Alibaba Cloud Computing Ltd. d/b/a HiChina (www.net.cn)'],
'status': ['ok https://icann.org/epp#ok'],
'updated_date': [datetime.datetime(2021, 2, 8, 15, 24, 45)],
'whois_server': ['grs-whois.hichina.com']}
おーーーーっ!
取れましたね。
7年前から開発が止まっているけど、もしかして、7年間メンテしなくていいくらいに完成してるのか?!
まとめ
whois, python-whois, pythonwhois 3つのライブラリを見てみました。
結局、これは、というものはなく、全部残念な感じでした。
日本で使うには、JPドメインが取れないと話にならないので、whoisやpython-whoisはそのままでは使えなさそうです。
pythonwhoisはJPドメインの取れますが、登録者情報が窓口情報になってしまっているので、そのままでは使えなさそうです。
新しめのドメインもxyzの例を見る限りでは、7年前のpythonwhoisが唯一結果を出したので、新しいものがいい、とも言えなさそうです。
結論
なんか、踏み入れてはいけない闇に1歩足を踏み入れてしまった気がします。
追記 2021-09-09
闇に足を踏み入れてしまったので、pythonwhoisをForkして問題あるところ修正しておきました。よかったら使ってみてください。
本家へのPRはあまり取り込まれていないのと、developブランチがなんか闇が深そうなので、見合わせます。
- Python3.7以降でもimportで死なないようにしました
- JPドメインの窓口情報がRegistrantに入ってたのをadminに、Registrantは登録者名を入れるようにしました
- .ne.jpのサービス名をRegistrantに入れるようにしました
- 属性JPでTitleなど省略可のものを省略した場合Handleが取れてなかったので修正しました