urllibパッケージを使用してインターネットリソースを取得する方法—Pythonドキュメント

提供:Dev Guides
< PythonPython/docs/3.9/howto/urllib2
移動先:案内検索

urllibパッケージを使用してインターネットリソースを取得する方法

著者
マイケル・フォード

ノート

このHOWTOの以前のリビジョンのフランス語訳があり、 urllib2-Le Manuel manquant で入手できます。


イントロダクション

関連記事

Pythonを使用したWebリソースのフェッチに関する次の記事も役立つ場合があります。

  • 基本認証

    基本認証のチュートリアルとPythonの例。

urllib.request は、URL(Uniform Resource Locator)をフェッチするためのPythonモジュールです。 urlopen 関数の形式で非常にシンプルなインターフェイスを提供します。 これは、さまざまな異なるプロトコルを使用してURLをフェッチすることができます。 また、基本認証、Cookie、プロキシなどの一般的な状況を処理するための少し複雑なインターフェイスも提供します。 これらは、ハンドラーおよびオープナーと呼ばれるオブジェクトによって提供されます。

urllib.requestは、多くの「URLスキーム」(URLの":"の前の文字列で識別されます。たとえば、"ftp""ftp://python.org/%22のURLスキームです)のURLのフェッチをサポートしています。関連するネットワークプロトコル(例: FTP、HTTP)。 このチュートリアルでは、最も一般的なケースであるHTTPに焦点を当てます。

単純な状況では、 urlopen は非常に使いやすいです。 ただし、HTTP URLを開くときにエラーや重要なケースが発生するとすぐに、ハイパーテキスト転送プロトコルをある程度理解する必要があります。 HTTPへの最も包括的で信頼できる参照は、 RFC 2616 です。 これは技術文書であり、読みやすくすることを目的としたものではありません。 このHOWTOは、 urllib の使用方法を説明することを目的としており、HTTPについて十分に詳しく説明しています。 urllib.request ドキュメントを置き換えることを意図したものではありませんが、それらを補足するものです。


URLの取得

urllib.requestを使用する最も簡単な方法は次のとおりです。

import urllib.request
with urllib.request.urlopen('http://python.org/') as response:
   html = response.read()

URLを介してリソースを取得し、一時的な場所に保存する場合は、 shutil.copyfileobj()および tempfile.NamedTemporaryFile()関数を使用して行うことができます。

import shutil
import tempfile
import urllib.request

with urllib.request.urlopen('http://python.org/') as response:
    with tempfile.NamedTemporaryFile(delete=False) as tmp_file:
        shutil.copyfileobj(response, tmp_file)

with open(tmp_file.name) as html:
    pass

urllibの多くの使用法は非常に単純です(「http:」URLの代わりに「ftp:」、「file:」などで始まるURLを使用できた可能性があることに注意してください)。 ただし、このチュートリアルの目的は、HTTPに焦点を当てて、より複雑なケースを説明することです。

HTTPは要求と応答に基づいています。クライアントは要求を行い、サーバーは応答を送信します。 urllib.requestは、これを、作成しているHTTPリクエストを表すRequestオブジェクトとミラーリングします。 最も単純な形式では、フェッチするURLを指定するRequestオブジェクトを作成します。 このRequestオブジェクトを使用してurlopenを呼び出すと、要求されたURLの応答オブジェクトが返されます。 この応答はファイルのようなオブジェクトです。つまり、たとえば、応答に対して.read()を呼び出すことができます。

import urllib.request

req = urllib.request.Request('http://www.voidspace.org.uk')
with urllib.request.urlopen(req) as response:
   the_page = response.read()

urllib.requestは、すべてのURLスキームを処理するために同じRequestインターフェースを使用することに注意してください。 たとえば、次のようにFTPリクエストを行うことができます。

req = urllib.request.Request('ftp://example.com/')

HTTPの場合、Requestオブジェクトで実行できる追加の機能が2つあります。1つは、サーバーに送信するデータを渡すことができることです。 次に、データまたはリクエスト自体に関する追加情報(「メタデータ」) about をサーバーに渡すことができます。この情報はHTTP「ヘッダー」として送信されます。 これらのそれぞれを順番に見ていきましょう。

データ

URLにデータを送信したい場合があります(多くの場合、URLはCGI(Common Gateway Interface)スクリプトまたは他のWebアプリケーションを参照します)。 HTTPでは、これは POST リクエストと呼ばれるものを使用して行われることがよくあります。 これは、Webで入力したHTMLフォームを送信するときにブラウザが行うことです。 すべてのPOSTがフォームからのものである必要はありません。POSTを使用して、任意のデータを独自のアプリケーションに送信できます。 HTMLフォームの一般的なケースでは、データを標準的な方法でエンコードしてから、data引数としてRequestオブジェクトに渡す必要があります。 エンコードは、 urllib.parse ライブラリの関数を使用して行われます。

import urllib.parse
import urllib.request

url = 'http://www.someserver.com/cgi-bin/register.cgi'
values = {'name' : 'Michael Foord',
          'location' : 'Northampton',
          'language' : 'Python' }

data = urllib.parse.urlencode(values)
data = data.encode('ascii') # data should be bytes
req = urllib.request.Request(url, data)
with urllib.request.urlopen(req) as response:
   the_page = response.read()

他のエンコーディングが必要になる場合があることに注意してください(例: HTMLフォームからのファイルアップロードの場合-詳細については、 HTML仕様、フォーム送信を参照してください)。

data引数を渡さない場合、urllibは GET 要求を使用します。 GETリクエストとPOSTリクエストが異なる1つの方法は、POSTリクエストにはしばしば「副作用」があることです。つまり、何らかの方法でシステムの状態を変更します(たとえば、ハンドレッドウェイトの缶詰スパムを配信するようにWebサイトに注文するなど)。あなたのドアに)。 HTTP標準では、POSTは常に副作用を引き起こすことを意図しており、GET要求は決して副作用を引き起こさないことを明確にしていますが、GET要求が副作用を起こすのを妨げるものは何もありません。 、またはPOSTは、副作用がないことを要求します。 データは、URL自体にエンコードすることで、HTTPGETリクエストで渡すこともできます。

これは次のように行われます。

>>> import urllib.request
>>> import urllib.parse
>>> data = {}
>>> data['name'] = 'Somebody Here'
>>> data['location'] = 'Northampton'
>>> data['language'] = 'Python'
>>> url_values = urllib.parse.urlencode(data)
>>> print(url_values)  # The order may differ from below.  
name=Somebody+Here&language=Python&location=Northampton
>>> url = 'http://www.example.com/example.cgi'
>>> full_url = url + '?' + url_values
>>> data = urllib.request.urlopen(full_url)

?をURLに追加し、その後にエンコードされた値を追加することで、完全なURLが作成されることに注意してください。


ヘッダー

ここでは、HTTPリクエストにヘッダーを追加する方法を説明するために、特定のHTTPヘッダーについて説明します。

一部のWebサイト 1 は、プログラムによる閲覧を嫌うか、異なるバージョンを異なるブラウザーに送信します 2 。 デフォルトでは、urllibはそれ自体をPython-urllib/x.yとして識別します(xおよびyはPythonリリースのメジャーバージョン番号とマイナーバージョン番号です。例: Python-urllib/2.5)、これはサイトを混乱させるか、単に機能しない可能性があります。 ブラウザがそれ自体を識別する方法は、User-Agentヘッダー 3 を介して行われます。 Requestオブジェクトを作成するときに、ヘッダーのディクショナリをに渡すことができます。 次の例では、上記と同じ要求を行いますが、それ自体をInternet Explorer 4 のバージョンとして識別します。

import urllib.parse
import urllib.request

url = 'http://www.someserver.com/cgi-bin/register.cgi'
user_agent = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64)'
values = {'name': 'Michael Foord',
          'location': 'Northampton',
          'language': 'Python' }
headers = {'User-Agent': user_agent}

data = urllib.parse.urlencode(values)
data = data.encode('ascii')
req = urllib.request.Request(url, data, headers)
with urllib.request.urlopen(req) as response:
   the_page = response.read()

応答には、2つの便利な方法もあります。 infoとgeturl のセクションを参照してください。これは、問題が発生したときに何が起こるかを確認した後に表示されます。


例外の処理

urlopen は、応答を処理できない場合にURLErrorを発生させます(ただし、Python APIでは通常どおり、 ValueErrorTypeError などの組み込み例外) 。 発生する可能性もあります)。

HTTPErrorは、HTTPURLの特定のケースで発生するURLErrorのサブクラスです。

例外クラスは、 urllib.error モジュールからエクスポートされます。

URLError

多くの場合、URLErrorは、ネットワーク接続がない(指定されたサーバーへのルートがない)か、指定されたサーバーが存在しないために発生します。 この場合、発生した例外には「reason」属性があります。これは、エラーコードとテキストエラーメッセージを含むタプルです。

例えば

>>> req = urllib.request.Request('http://www.pretend_server.org')
>>> try: urllib.request.urlopen(req)
... except urllib.error.URLError as e:
...     print(e.reason)      
...
(4, 'getaddrinfo failed')

HTTPError

サーバーからのすべてのHTTP応答には、数値の「ステータスコード」が含まれています。 ステータスコードは、サーバーが要求を実行できないことを示している場合があります。 デフォルトのハンドラーは、これらの応答の一部を処理します(たとえば、応答が、クライアントに別のURLからドキュメントをフェッチするように要求する「リダイレクト」である場合、urllibがそれを処理します)。 処理できない場合、urlopenはHTTPErrorを発生させます。 一般的なエラーには、「404」(ページが見つかりません)、「403」(リクエストは禁止されています)、「401」(認証が必要)などがあります。

すべてのHTTPエラーコードのリファレンスについては、 RFC 2616 のセクション10を参照してください。

発生したHTTPErrorインスタンスには、サーバーから送信されたエラーに対応する整数の「コード」属性があります。

エラーコード

デフォルトのハンドラーはリダイレクト(300の範囲のコード)を処理し、100〜299の範囲のコードは成功を示すため、通常は400〜599の範囲のエラーコードのみが表示されます。

http.server.BaseHTTPRequestHandler.responses は、 RFC 2616 で使用されるすべての応答コードを表示する応答コードの便利な辞書です。 辞書は便宜上ここに複製されています

# Table mapping response codes to messages; entries have the
# form {code: (shortmessage, longmessage)}.
responses = {
    100: ('Continue', 'Request received, please continue'),
    101: ('Switching Protocols',
          'Switching to new protocol; obey Upgrade header'),

    200: ('OK', 'Request fulfilled, document follows'),
    201: ('Created', 'Document created, URL follows'),
    202: ('Accepted',
          'Request accepted, processing continues off-line'),
    203: ('Non-Authoritative Information', 'Request fulfilled from cache'),
    204: ('No Content', 'Request fulfilled, nothing follows'),
    205: ('Reset Content', 'Clear input form for further input.'),
    206: ('Partial Content', 'Partial content follows.'),

    300: ('Multiple Choices',
          'Object has several resources -- see URI list'),
    301: ('Moved Permanently', 'Object moved permanently -- see URI list'),
    302: ('Found', 'Object moved temporarily -- see URI list'),
    303: ('See Other', 'Object moved -- see Method and URL list'),
    304: ('Not Modified',
          'Document has not changed since given time'),
    305: ('Use Proxy',
          'You must use proxy specified in Location to access this '
          'resource.'),
    307: ('Temporary Redirect',
          'Object moved temporarily -- see URI list'),

    400: ('Bad Request',
          'Bad request syntax or unsupported method'),
    401: ('Unauthorized',
          'No permission -- see authorization schemes'),
    402: ('Payment Required',
          'No payment -- see charging schemes'),
    403: ('Forbidden',
          'Request forbidden -- authorization will not help'),
    404: ('Not Found', 'Nothing matches the given URI'),
    405: ('Method Not Allowed',
          'Specified method is invalid for this server.'),
    406: ('Not Acceptable', 'URI not available in preferred format.'),
    407: ('Proxy Authentication Required', 'You must authenticate with '
          'this proxy before proceeding.'),
    408: ('Request Timeout', 'Request timed out; try again later.'),
    409: ('Conflict', 'Request conflict.'),
    410: ('Gone',
          'URI no longer exists and has been permanently removed.'),
    411: ('Length Required', 'Client must specify Content-Length.'),
    412: ('Precondition Failed', 'Precondition in headers is false.'),
    413: ('Request Entity Too Large', 'Entity is too large.'),
    414: ('Request-URI Too Long', 'URI is too long.'),
    415: ('Unsupported Media Type', 'Entity body in unsupported format.'),
    416: ('Requested Range Not Satisfiable',
          'Cannot satisfy request range.'),
    417: ('Expectation Failed',
          'Expect condition could not be satisfied.'),

    500: ('Internal Server Error', 'Server got itself in trouble'),
    501: ('Not Implemented',
          'Server does not support this operation'),
    502: ('Bad Gateway', 'Invalid responses from another server/proxy.'),
    503: ('Service Unavailable',
          'The server cannot process the request due to a high load'),
    504: ('Gateway Timeout',
          'The gateway server did not receive a timely response'),
    505: ('HTTP Version Not Supported', 'Cannot fulfill request.'),
    }

エラーが発生すると、サーバーはHTTPエラーコードおよびエラーページを返すことで応答します。 返されたページの応答として、HTTPErrorインスタンスを使用できます。 これは、コード属性だけでなく、urllib.responseモジュールによって返されるread、geturl、info、メソッドも持っていることを意味します。

>>> req = urllib.request.Request('http://www.python.org/fish.html')
>>> try:
...     urllib.request.urlopen(req)
... except urllib.error.HTTPError as e:
...     print(e.code)
...     print(e.read())  
...
404
b'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n\n\n<html
  ...
  <title>Page Not Found</title>\n
  ...

まとめ

したがって、HTTPError または URLErrorの準備をしたい場合は、2つの基本的なアプローチがあります。 私は2番目のアプローチを好みます。

ナンバー1

from urllib.request import Request, urlopen
from urllib.error import URLError, HTTPError
req = Request(someurl)
try:
    response = urlopen(req)
except HTTPError as e:
    print('The server couldn\'t fulfill the request.')
    print('Error code: ', e.code)
except URLError as e:
    print('We failed to reach a server.')
    print('Reason: ', e.reason)
else:
    # everything is fine

ノート

except HTTPError が最初に来る必要があります。そうでない場合、except URLErrorHTTPErrorをキャッチします。


2番

from urllib.request import Request, urlopen
from urllib.error import URLError
req = Request(someurl)
try:
    response = urlopen(req)
except URLError as e:
    if hasattr(e, 'reason'):
        print('We failed to reach a server.')
        print('Reason: ', e.reason)
    elif hasattr(e, 'code'):
        print('The server couldn\'t fulfill the request.')
        print('Error code: ', e.code)
else:
    # everything is fine

情報とgeturl

urlopen(またはHTTPErrorインスタンス)によって返される応答には、2つの便利なメソッドinfo()geturl()があり、モジュール urllib.response で定義されています。

geturl -これは、フェッチされたページの実際のURLを返します。 urlopen(または使用されるオープナーオブジェクト)がリダイレクトに続いている可能性があるため、これは便利です。 フェッチされたページのURLは、要求されたURLと同じでない場合があります。

info -これは、フェッチされたページ、特にサーバーによって送信されたヘッダーを説明する辞書のようなオブジェクトを返します。 現在、http.client.HTTPMessageインスタンスです。

一般的なヘッダーには、「Content-length」、「Content-type」などがあります。 HTTPヘッダーの便利なリストとその意味と使用法の簡単な説明については、 HTTPヘッダーのクイックリファレンスを参照してください。


オープナーとハンドラー

URLをフェッチするときは、オープナー(おそらく紛らわしい名前の urllib.request.OpenerDirector のインスタンス)を使用します。 通常、urlopenを介してデフォルトのオープナーを使用していますが、カスタムオープナーを作成することもできます。 オープナーはハンドラーを使用します。 すべての「重い持ち上げ」はハンドラーによって行われます。 各ハンドラーは、特定のURLスキーム(http、ftpなど)のURLを開く方法、またはHTTPリダイレクトやHTTPCookieなどのURL開く側面を処理する方法を知っています。

たとえば、Cookieを処理するオープナーを取得したり、リダイレクトを処理しないオープナーを取得したりするために、特定のハンドラーがインストールされたURLをフェッチする場合は、オープナーを作成する必要があります。

オープナーを作成するには、OpenerDirectorをインスタンス化してから、.add_handler(some_handler_instance)を繰り返し呼び出します。

または、build_openerを使用することもできます。これは、1回の関数呼び出しでオープナーオブジェクトを作成するための便利な関数です。 build_openerはデフォルトでいくつかのハンドラーを追加しますが、さらに追加したり、デフォルトのハンドラーをオーバーライドしたりするための簡単な方法を提供します。

プロキシ、認証、およびその他の一般的ではあるがわずかに特殊な状況を処理できる、他の種類のハンドラー。

install_openerを使用して、openerオブジェクトを(グローバル)デフォルトオープナーにすることができます。 これは、urlopenの呼び出しで、インストールしたオープナーが使用されることを意味します。

オープナーオブジェクトにはopenメソッドがあり、urlopen関数と同じ方法でURLをフェッチするために直接呼び出すことができます。利便性を除いて、install_openerを呼び出す必要はありません。 。


基本認証

ハンドラーの作成とインストールを説明するために、HTTPBasicAuthHandlerを使用します。 基本認証の仕組みの説明を含む、この主題の詳細については、基本認証チュートリアルを参照してください。

認証が必要な場合、サーバーは認証を要求するヘッダー(および401エラーコード)を送信します。 これは、認証スキームと「レルム」を指定します。 ヘッダーは次のようになります:WWW-Authenticate: SCHEME realm="REALM"

例えば

WWW-Authenticate: Basic realm="cPanel Users"

次に、クライアントは、リクエストのヘッダーとして含まれているレルムの適切な名前とパスワードを使用して、リクエストを再試行する必要があります。 これが「基本認証」です。 このプロセスを簡素化するために、HTTPBasicAuthHandlerのインスタンスと、このハンドラーを使用するためのオープナーを作成できます。

HTTPBasicAuthHandlerは、パスワードマネージャーと呼ばれるオブジェクトを使用して、URLとレルムのパスワードとユーザー名へのマッピングを処理します。 (サーバーから送信された認証ヘッダーから)レルムが何であるかがわかっている場合は、HTTPPasswordMgrを使用できます。 多くの場合、レルムが何であるかを気にしません。 その場合は、HTTPPasswordMgrWithDefaultRealmを使用すると便利です。 これにより、URLのデフォルトのユーザー名とパスワードを指定できます。 これは、特定のレルムに代替の組み合わせを提供する必要がない場合に提供されます。 add_passwordメソッドのレルム引数としてNoneを提供することにより、これを示します。

トップレベルURLは、認証を必要とする最初のURLです。 .add_password()に渡すURLよりも「深い」URLも一致します。

# create a password manager
password_mgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()

# Add the username and password.
# If we knew the realm, we could use it instead of None.
top_level_url = "http://example.com/foo/"
password_mgr.add_password(None, top_level_url, username, password)

handler = urllib.request.HTTPBasicAuthHandler(password_mgr)

# create "opener" (OpenerDirector instance)
opener = urllib.request.build_opener(handler)

# use the opener to fetch a URL
opener.open(a_url)

# Install the opener.
# Now all calls to urllib.request.urlopen use our opener.
urllib.request.install_opener(opener)

ノート

上記の例では、HTTPBasicAuthHandlerbuild_openerにのみ提供しました。 デフォルトでは、オープナーには通常の状況のハンドラーがあります– ProxyHandler http_proxy環境変数などのプロキシ設定が設定されている場合)、UnknownHandler、[ X162X] 、HTTPDefaultErrorHandlerHTTPRedirectHandlerFTPHandlerFileHandlerDataHandlerHTTPErrorProcessor


top_level_urlは、実際にはいずれか完全なURL(「http:」スキームコンポーネントとホスト名、およびオプションでポート番号を含む)です。 "http://example.com/%22 または「権限」(つまり ホスト名、オプションでポート番号を含む)例: "example.com"または"example.com:8080"(後者の例にはポート番号が含まれています)。 権限が存在する場合、「userinfo」コンポーネントを含めることはできません。たとえば、"joe:[email protected]"は正しくありません。


プロキシ

urllib はプロキシ設定を自動検出し、それらを使用します。 これは、プロキシ設定が検出されたときの通常のハンドラチェーンの一部であるProxyHandlerを介して行われます。 通常はそれは良いことですが、役に立たない場合もあります 5 。 これを行う1つの方法は、プロキシを定義せずに、独自のProxyHandlerをセットアップすることです。 これは、基本認証ハンドラーの設定と同様の手順を使用して行われます。

>>> proxy_support = urllib.request.ProxyHandler({})
>>> opener = urllib.request.build_opener(proxy_support)
>>> urllib.request.install_opener(opener)

ノート

現在、urllib.request は、プロキシを介したhttpsロケーションのフェッチをサポートしていません。 ただし、これは、レシピ 6 に示されているようにurllib.requestを拡張することで有効にできます。


ノート

変数REQUEST_METHODが設定されている場合、HTTP_PROXYは無視されます。 getproxies()のドキュメントを参照してください。


ソケットとレイヤー

WebからリソースをフェッチするためのPythonサポートは階層化されています。 urllibは http.client ライブラリを使用し、ライブラリはソケットライブラリを使用します。

Python 2.3以降、タイムアウトする前にソケットが応答を待機する時間を指定できます。 これは、Webページをフェッチする必要があるアプリケーションで役立ちます。 デフォルトでは、ソケットモジュールにはタイムアウトなしがあり、ハングする可能性があります。 現在、ソケットタイムアウトはhttp.clientまたはurllib.requestレベルでは公開されていません。 ただし、を使用して、すべてのソケットに対してデフォルトのタイムアウトをグローバルに設定できます。

import socket
import urllib.request

# timeout in seconds
timeout = 10
socket.setdefaulttimeout(timeout)

# this call to urllib.request.urlopen now uses the default timeout
# we have set in the socket module
req = urllib.request.Request('http://www.voidspace.org.uk')
response = urllib.request.urlopen(req)

脚注

このドキュメントは、JohnLeeによってレビューおよび改訂されました。

1
たとえばグーグル。
2
ブラウザのスニッフィングは、Webサイトの設計にとって非常に悪い習慣です。Web標準を使用してサイトを構築する方がはるかに賢明です。 残念ながら、多くのサイトはまだ異なるバージョンを異なるブラウザに送信しています。
3
MSIE6のユーザーエージェントは Mozilla / 4.0(互換、MSIE 6.0、Windows NT 5.1、SV1、.NET CLR 1.1.4322)
4
その他のHTTPリクエストヘッダーの詳細については、 HTTPヘッダーのクイックリファレンスを参照してください。
5
私の場合、職場のインターネットにアクセスするにはプロキシを使用する必要があります。 このプロキシを介して localhost URLをフェッチしようとすると、それらがブロックされます。 IEは、urllibが取得するプロキシを使用するように設定されています。 ローカルホストサーバーでスクリプトをテストするには、urllibがプロキシを使用しないようにする必要があります。
6
SSLプロキシ用のurllibオープナー(CONNECTメソッド): ASPNクックブックレシピ