Pythonでスクレイピングを書いていて、requests.exceptions.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failedが突然出て止まった経験はないでしょうか。特にmacOSでPython公式インストーラーを使った直後や、社内プロキシ環境で頻発します。本記事では原因を切り分けたうえで、macOS・Windows・Linuxそれぞれの正しい対処法と、絶対にやってはいけない対処法を解説します。

macOSでcertificate verify failedが起きる3つの原因

このエラーは「Pythonがサーバー証明書を検証しようとしたが、信頼できるルート証明書が見つからなかった」ことを意味します。原因は環境によって異なり、対処法も変わるため、まず切り分けが必要です。

原因1: macOS版Pythonにルート証明書がインストールされていない

python.orgの公式インストーラーでPythonを入れた場合、macOSのシステム証明書ストアを参照せず、Python独自のcertifiパッケージの証明書束を使う仕様になっています。インストール直後はcertifi自体は入っていても、macOS用の追加設定スクリプトを実行し忘れているケースが大半です。

原因2: 企業プロキシ・セキュリティソフトによるSSL傍受

会社支給のMacでZscalerやウイルス対策ソフトが導入されていると、通信を一度復号して検査する「SSLインスペクション」が働き、サーバー本来の証明書ではなく社内発行の中間証明書がPythonに提示されます。この場合、certifiを再インストールしても直りません。

原因3: requestsとurllibで参照する証明書ストアが異なる

requestsは内部でcertifiのCA束を使いますが、urllib.requestは標準ではOSの証明書ストア(macOSならKeychain)を参照しようとして失敗することがあります。同じPCで「requestsは通るがurllibは通らない」という現象が起きるのはこのためです。

原因別の対処法をコードで解決する

対処1: macOS標準スクリプトでルート証明書を登録する

python.org版Pythonには証明書インストール用のスクリプトが同梱されています。ターミナルで実行するだけで、certifiの証明書束をPythonが参照できるようになります。

open "/Applications/Python 3.12/Install Certificates.command"

アプリ版フォルダが無い場合(pyenvやHomebrew経由の場合)は、certifiを最新化したうえで環境変数でパスを明示します。

pip install --upgrade certifi
python3 -c "import certifi; print(certifi.where())"
# 出力されたパスを環境変数に設定
export SSL_CERT_FILE=$(python3 -c "import certifi; print(certifi.where())")
export REQUESTS_CA_BUNDLE=$SSL_CERT_FILE

対処2: 社内プロキシ環境ではカスタムCA証明書を明示的に読み込む

SSLインスペクションが原因の場合は、IT部門から配布された社内CA証明書(.pem/.crt)をrequestsに教える必要があります。

import requests

response = requests.get(
    "https://example.com",
    verify="/path/to/corporate_ca_bundle.pem"
)

複数サイトをまたいで使う場合は、certifiの証明書束に社内CAを追記した独自バンドルを作り、環境変数で常時読み込ませると管理が楽になります。

対処3: urllibでも証明書ストアを明示指定する

import ssl
import certifi
import urllib.request

context = ssl.create_default_context(cafile=certifi.where())
req = urllib.request.Request("https://example.com")
with urllib.request.urlopen(req, context=context) as res:
    html = res.read()

原因

有効な対処

再発リスク

証明書未インストール

Install Certificates.command実行

低(OS更新時に再確認)

社内プロキシのSSL傍受

社内CA証明書をverifyに指定

中(PC入れ替え時に再設定要)

certifiが古い

pip install --upgrade certifi

低(定期更新で対応)

urllibとrequestsで挙動差

ssl.create_default_context(cafile=...)を明示

verify=Falseで「解決」してはいけない理由

検索するとよく出てくるrequests.get(url, verify=False)は、証明書検証そのものを無効化する応急処置であり、恒久対処にしてはいけません。証明書検証を切ると、通信内容が第三者に改ざん・盗聴される中間者攻撃(MITM)を検知できなくなります。ログイン情報やAPIキーを扱うスクレイピングでこれをやると、社内プロキシとは別の悪意ある経路が割り込んでいても気づけません。どうしても一時的に切り分けで使う場合も、urllib3.disable_warnings()で警告を握りつぶさず、原因特定後は必ず正規の証明書指定に戻すことを徹底してください。

大量取得の設計では証明書エラーを個別に潰さない

数十〜数百サイトを巡回するスクレイピング基盤を作ると、サイトごと・環境ごとに証明書まわりの例外が散発します。1件ずつtry-exceptで潰すのではなく、セッションレベルで証明書設定を一元管理するのが実務的です。

import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
import certifi

session = requests.Session()
session.verify = certifi.where()
retry = Retry(total=3, backoff_factor=1, status_forcelist=[500, 502, 503, 429])
session.mount("https://", HTTPAdapter(max_retries=retry))

証明書エラーとタイムアウト・429エラーは別レイヤーの問題なので、リトライ設計は分けて考える必要があります。429対策の実装例はYahoo!ショッピングAPI ItemSearch 429対策8月版でも扱っているので、あわせて参照してください。動的サイトやログイン必須サイトを含む複合的な取得設計はスクレイピング実践テク|動的・ログイン攻略【26年8月】で詳しく解説しています。

スクレイピング実施時の規約・法令面の注意

  • 対象サイトの利用規約でスクレイピング・自動アクセスが禁止されていないか必ず確認する
  • robots.txtのDisallow指定を尊重する(法的拘束力はないが業界慣行として遵守が原則)
  • 取得したデータの著作物性がある場合、著作権法上の私的利用の範囲を超えた再配布・商用利用は権利者の許諾が必要
  • SSL証明書検証を無効化してまで通信を強行する行為は、対象サーバーが意図的にブロックしている場合に不正アクセス禁止法上の論点になりうるため、認証回避を目的とした証明書検証の無効化は行わない
  • 個人情報を含むデータを取得・保存する場合は個人情報保護法の取得目的の明示・安全管理措置が必要

証明書エラー自体は技術的な設定ミスであることがほとんどですが、「エラーが出たから検証を切って強行突破する」という発想は、規約違反や不正アクセスの論点に直結しやすい点は覚えておいてください。

この記事に関連するアイテム

実際に使うものを選ぶ際の参考にどうぞ。

※ 本サイトはAmazonアソシエイト・プログラムの参加者です。紹介リンクを経由してご購入いただいた場合、手数料を受け取ることがあります。

まとめ

macOSのcertificate verify failedは、証明書未インストール・社内プロキシによるSSL傍受・requestsとurllibのストア差という3パターンに切り分ければ、ほぼ機械的に解決できます。ただし本番の大量スクレイピング基盤では、環境ごとに証明書設定が食い違い、verify=Falseのような危険な回避策が現場に残りがちです。規約確認・リトライ設計・証明書管理まで含めた堅牢な仕組みづくりは片手間では時間がかかるため、業務自動化・スクレイピングの導入をご検討の方はnashiまでお問い合わせください。https://nashi-portfolio.netlify.app