特許庁が提供する「特許情報取得API」(`ip-data.jpo.go.jp`)をPythonから使用した際に遭遇した接続エラーと、その原因・対処法を紹介します。
背景
特許庁は2022年頃から「特許情報取得API」を試行提供しています。出願番号を指定するとJSONで登録情報・経過情報などを取得でき、J-PlatPatのスクレイピングに代わる手段として活用できます。このAPIをPythonの `requests` ライブラリから呼び出そうとしたところ、再現性のある接続エラーに遭遇しました。
症状
ConnectionResetError: [WinError 10054] 既存の接続はリモートホストに強制的に切断されました。
requests.get() / requests.post() を呼び出すと、TLS ハンドシェイクの段階で接続がリセットされます。HTTPレスポンスは一切返ってこないため、ステータスコードでエラーを判別できません。スタックトレースは以下のようなものでした。
urllib3.exceptions.ProtocolError: ('Connection aborted.', ConnectionResetError(10054, '...強制的に切断されました。', None, 10054, None))
試行錯誤しましたが、以下の原因の可能性が高そうです。
原因:IPv6 での TLS ハンドシェイク失敗(主要因)
繋がったり繋がらなかったりすることから、最初は隠しレートリミット(短時間での多重アクセスの制限)かなと思っていたのですが、上手くいくタイミングに一貫性がないことから迷宮入りしていました。ふとした時にDNSの回答が安定しているのか気になって調べたところ特許庁APIのホストである ip-data.jpo.go.jp についてIPv4とIPv6の両方が返ってきていることがわかりました。PythonのHTTPクライアント(`urllib3`)はデュアルスタック環境では IPv6を優先 するため、IPv6で接続を試みます。
そこで、意図的にIPv4を指定して接続したところ、当初発生していたような接続のリセットが発生しなくなりました。つまり、原因は、IPv6でのTLSハンドシェイクに失敗していたことでした。
TCP接続レベルでは成功している(ポート443へのTCPは通る)にもかかわらずTLSで切断されるため、最初はPython環境の問題や自分のネットワーク障害と誤解しやすいです。ましてやアクセストークン取得という認証ステップで確認するので、パスワードなどの渡し方が原因かと勘違いしやすいところです。
余談:Pythonのバージョンは関係ない
「OpenSSLが古いせいでは?」と考え、Python 3.8(OpenSSL 1.1.1k)と Python 3.14(OpenSSL 3.0.18)の両方で検証しましたが、どちらのバージョンでも結果は同じでした。
対処法
socket.getaddrinfo をパッチしてIPv4のみ返すようにします。
import socket
_orig_getaddrinfo = socket.getaddrinfo
def _ipv4_getaddrinfo(host, port, family=0, type=0, proto=0, flags=0):
return _orig_getaddrinfo(host, port, socket.AF_INET, type, proto, flags)
socket.getaddrinfo = _ipv4_getaddrinfo
socket のインポート時に一度だけ実行すれば、以降のすべてのHTTP通信(認証を含む)がIPv4で行われます。
リクエスト間のインターバル
公式の情報では見つけられませんでしたが、リクエストを連続して送ると短期レートリミットに引っかかることがあるかもしれません(このサイトの情報によると1秒程度のインターバルが目安か)。
ベストプラクティス:トークンをファイルに永続化する
IPv4固定後は認証エンドポイントも正常に動作しますが、アクセストークンの取得は最小限に抑えることを推奨します。取得したトークンをファイルに保存し、有効期限内は再利用することで、不要な認証呼び出しを避けられます。アクセストークンは1時間、リフレッシュトークンは8時間有効です。
複数プロセスから同時にトークンファイルを書き換える競合が心配な場合は filelock ライブラリ等でファイルロックを追加するといいと思います。
補足:日次アクセス上限
接続リセットとは別に、エンドポイントごとに日次上限があります(2026年3月時点)。日次上限超過はHTTP 200のまま返り、レスポンスボディのAPIステータスコードで判別します。
body = r.json()
if body['result']['statusCode'] == '203':
# 本日の上限超過
raise Exception('日次アクセス上限を超過しました')
検証環境
- Windows 11(IPv6有効)
- Python 3.8.10 / OpenSSL 1.1.1k / requests 2.28.2 / urllib3 1.26.15
- Python 3.14.3 / OpenSSL 3.0.18 / requests 2.34.2 / urllib3 2.7.0
- 特許情報取得API(ip-data.jpo.go.jp)2026年5月時点