nikkie-ftnextの日記

イベントレポートや読書メモを発信

Python標準ライブラリのurllibでだけ403 Forbiddenとなる

現時点の理解

  • Pythonの標準ライブラリurllib.request.urlopenを使ってHTTP通信すると、403 Forbiddenが返る
    • urllib.error.HTTPError: HTTP Error 403: Forbidden

    • 同じURLにrequestsを使うと、403は発生しない(正常に扱える)
  • CloudflareがurllibのデフォルトのUser-Agentヘッダのリクエストを弾いているらしい
  • urllibでヘッダのUser-Agentを指定すると、403は発生せず正常に扱えた

目次

Webhookを使ってDiscordにPythonで投稿する裏話

Pythonの標準ライブラリurllibを使って、DiscordのWebhookにリクエストを送り、Discordに投稿する実装が手元にあります1
からあげさんがまとめてくださっています2

この実装では、HTTPリクエストヘッダを以下のように書いています。

headers = {
    "Content-Type": "application/json",
    "User-Agent": "DiscordBot (private use) Python-urllib/3.10",
}

User-Agentがポイントです3
これを指定しないと、403 Forbiddenが返ります。
ただしurllibを使わなければ403は送出されません。

urllibのデフォルトのUser-Agentヘッダ

HOW TOドキュメント「urllib パッケージを使ってインターネット上のリソースを取得するには」より。
https://docs.python.org/ja/3/howto/urllib2.html#headers

デフォルトでは urllib は自身の情報を Python-urllib/x.y として扱います( x と y は Python のリリースバージョンのメジャーバージョンとマイナーバージョンです、例えば Python-urllib/2.5 など)。

このドキュメントには、Internet Explorerとして扱う例が記載されています(例がだいぶ古いですね)。

headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64)'}

Discord以外でも再現。共通点はCloudflare

urllibで403 Forbiddenという事象は、DiscordのWebhookだけではなく、arXiv vanityへのリクエストでも経験しました。
urllibの代わりにrequestsを使ったときは再現しません。

検索したところ、原因はCloudflareのようです4

The specific site in question, wallpapercrafter.com, is protected by cloudflare which will block web scrapers.

たしかにScrape Shieldの中にUser-Agentヘッダでブロックする機能があります。

おそらくこれでurllibのデフォルトのUser-Agentヘッダが弾かれているのだと理解しました。

終わりに

過去に経験した、urllibでだけHTTPリクエストが403になる事象を共有しました。
私の経験ではCloudflareが共通していて、urllibのデフォルトのUser-Agentヘッダがブロックされています。
User-Agentを指定する、もしくはurllib以外を使うことで解決しました。


  1. https://github.com/ftnext/meetup-host-ops/blob/ffc50f8f8a97868d8beddf0c9a6cda556cdcef33/discord.py
  2. 手元の実装を参考例としてお伝えしました。
  3. この指定も当時検索して見つけたのだと思うのですが、ソースを見つけられませんでした。
  4. DiscordはCloudflareを使っている認識です。