Spec-Zone.ru › Python 3.10

Как получить ресурсы из интернета с помощью пакета urllib

Автор

Майкл Форд

Примечание

Существует французский перевод более ранней версии данного руководства, доступный по адресу urllib2 - Le Manuel manquant.

Введение

Связанные статьи

Вам также может быть полезной следующая статья о получении веб-ресурсов с помощью Python:

  • Базовая аутентификация

    Учебное пособие по базовой аутентификации с примерами на Python.

urllib.request — это модуль Python для получения URL (Uniform Resource Locators). Он предлагает очень простой интерфейс в виде функции urlopen. Это позволяет получать URL с использованием различных протоколов. Он также предоставляет несколько более сложный интерфейс для обработки распространённых ситуаций — таких как базовая аутентификация, куки, прокси и так далее. Они предоставляются объектами, называемыми обработчиками и открывателями.

urllib.request поддерживает получение URL для многих «схем URL» (определяемых строкой перед ":" в URL — например, "ftp" — это схема URL "ftp://python.org/") с использованием соответствующих сетевых протоколов (например, FTP, HTTP). В этом руководстве основное внимание уделяется наиболее распространённому случаю — HTTP.

В простых ситуациях urlopen очень легко использовать. Но как только вы столкнетесь с ошибками или нетривиальными случаями при открытии HTTP-URL, вам потребуется некоторое понимание протокола HyperText Transfer Protocol. Наиболее полная и авторитетная ссылка на HTTP — RFC 2616. Это технический документ, и его нелегко читать. Это руководство по практическому применению 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 будут столь же просты (обратите внимание, что вместо URL «http:» мы могли бы использовать URL, начинающиеся с «ftp:», «file:» и т. д.). Однако цель данного руководства — объяснить более сложные случаи, сосредоточившись на HTTP.

HTTP основан на запросах и ответах — клиент отправляет запросы, а серверы возвращают ответы. urllib.request отражает это с помощью объекта Request, который представляет собой HTTP-запрос, который вы отправляете. В простейшем случае вы создаёте объект Request, который указывает URL, который вы хотите получить. Вызов urlopen с этим объектом Request возвращает объект ответа для запрошенного 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 использует тот же интерфейс Request для обработки всех схем URL. Например, вы можете отправить FTP-запрос следующим образом:

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

В случае HTTP существует две дополнительные возможности, которые предоставляют объекты Request: Во-первых, вы можете передать данные, которые нужно отправить на сервер. Во-вторых, вы можете передать дополнительную информацию («метаданные») о данных или о самом запросе на сервер — эта информация отправляется в виде HTTP-«заголовков». Давайте рассмотрим каждый из этих аспектов.

Данные

Иногда вы хотите отправить данные по URL (часто URL будет ссылаться на скрипт CGI (Common Gateway Interface) или другое веб-приложение). В HTTP это часто делается с помощью так называемого запроса POST. Так часто поступает ваш браузер, когда вы отправляете заполненную вами HTML-форму в веб. Не все POST'ы должны происходить из форм: вы можете использовать POST для передачи произвольных данных в своё приложение. В общем случае HTML-форм данные должны быть закодированы стандартным способом, а затем переданы в объект Request в качестве аргумента data. Кодирование выполняется с помощью функции из библиотеки 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 заключается в том, что запросы POST часто имеют «побочные эффекты»: они каким-либо образом изменяют состояние системы (например, размещая заказ на веб-сайте на центнер консервированной ветчины для доставки к вашей двери). Хотя стандарт HTTP чётко указывает, что POST-запросы всегда должны иметь побочные эффекты, а запросы GET никогда не должны иметь побочных эффектов, ничего не мешает запросу GET иметь побочные эффекты, ни запросу POST — не иметь побочных эффектов. Данные также могут передаваться в HTTP-запросе GET, кодируясь в самом URL.

Это делается так:

>>> 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-запросу.

Некоторые веб-сайты 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()

Ответ также имеет две полезные функции. См. раздел о info и geturl, который следует за рассмотрением того, что происходит при возникновении ошибок.

Обработка исключений

urlopen поднимает URLError когда не может обработать ответ (хотя, как обычно в Python API, могут также возникать встроенные исключения, такие как ValueError, TypeError и т. д.).

HTTPError — это подкласс URLError , возникающий в конкретном случае HTTP-URL.

Классы исключений экспортируются из модуля 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» (требуется аутентификация).

См. раздел 10 RFC 2616 для справки по всем кодам ошибок HTTP.

Возбуждённый экземпляр 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 в качестве ответа на возвращённую страницу. Это означает, что помимо атрибута «код», он также имеет методы read, geturl и info, возвращаемые модулем urllib.response:

>>> 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 , существуют два основных подхода. Я предпочитаю второй подход.

Вариант 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 URLError также поймает HTTPError.

Вариант 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

info и geturl

Ответ, возвращаемый urlopen (или экземпляр HTTPError), имеет два полезных метода 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 для определенной схемы URL (http, ftp и т. д.) или как обрабатывать аспект открытия URL, например, HTTP-переадресации или HTTP-cookie.

Вам захочется создать открыватели, если вы хотите получать URL с определенными установленными обработчиками, например, чтобы получить открыватель, обрабатывающий cookie, или чтобы получить открыватель, не обрабатывающий переадресации.

Для создания открывателя инициализируйте OpenerDirector, а затем вызывайте .add_handler(some_handler_instance) многократно.

В качестве альтернативы, вы можете использовать build_opener, что является удобной функцией для создания объектов открывателя с помощью одного вызова функции. build_opener добавляет несколько обработчиков по умолчанию, но предоставляет быстрый способ добавить больше и/или переопределить обработчики по умолчанию.

Другие типы обработчиков, которые вам могут потребоваться, могут обрабатывать прокси, аутентификацию и другие распространенные, но несколько специализированные ситуации.

install_opener может быть использован для назначения объекта opener в качестве (глобального) стандартного открывателя. Это означает, что вызовы urlopen будут использовать установленный вами открыватель.

Объекты открывателей имеют метод open, который можно вызывать напрямую для получения URL таким же образом, как и функция urlopen: нет необходимости вызывать install_opener, за исключением удобства.

Базовая аутентификация

Чтобы проиллюстрировать создание и установку обработчика, мы будем использовать HTTPBasicAuthHandler. Для более подробного обсуждения этого вопроса — включая объяснение работы базовой аутентификации — см. Инструкции по базовой аутентификации.

Когда требуется аутентификация, сервер отправляет заголовок (а также код ошибки 401) с запросом аутентификации. Это указывает на схему аутентификации и «область». Заголовок выглядит следующим образом: WWW-Authenticate: SCHEME realm="REALM".

Например:

WWW-Authenticate: Basic realm="cPanel Users"

Затем клиент должен повторно отправить запрос с соответствующим именем пользователя и паролем для области, включенными в заголовок запроса. Это «базовая аутентификация». Для упрощения этого процесса мы можем создать экземпляр HTTPBasicAuthHandler и открыватель для использования этого обработчика.

HTTPBasicAuthHandler использует объект, называемый менеджером паролей, для обработки сопоставления URL и областей с паролями и именами пользователей. Если вы знаете область (из заголовка аутентификации, отправленного сервером), то можете использовать HTTPPasswordMgr. Часто неважно, какая область. В этом случае удобно использовать HTTPPasswordMgrWithDefaultRealm. Это позволяет указать имя пользователя и пароль по умолчанию для URL. Это будет предоставлено в отсутствие предоставления вами альтернативной комбинации для конкретной области. Мы указываем это, предоставляя None в качестве аргумента области методу add_password.

URL верхнего уровня — это первый URL, требующий аутентификации. URL «ниже», чем URL, который вы передаете в .add_password(), также будут соответствовать.

# 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)

Примечание

В приведенном выше примере мы передали наш HTTPBasicAuthHandler только в build_opener. По умолчанию открыватели имеют обработчики для обычных ситуаций — ProxyHandler (если установлены настройки прокси, такие как переменная среды http_proxy), UnknownHandler, HTTPHandler, HTTPDefaultErrorHandler, HTTPRedirectHandler, FTPHandler, FileHandler, DataHandler, HTTPErrorProcessor.

top_level_url фактически является либо полным URL (включая компонент схемы «http:», имя хоста и необязательно номер порта), например "http://example.com/", либо «авторитетом» (т. е. именем хоста, необязательно включая номер порта), например "example.com" или "example.com:8080" (в последнем примере указан номер порта). Авторитет, если он присутствует, НЕ должен содержать компонент «userinfo» — например, "joe:password@example.com" неверно.

Прокси

urllib автоматически обнаружит ваши настройки прокси и будет использовать их. Это происходит через ProxyHandler, который является частью обычной цепочки обработчиков при обнаружении настройки прокси. Обычно это хорошо, но бывают случаи, когда это может быть не полезно 5. Один из способов сделать это — настроить свой собственный ProxyHandler, без указанных прокси. Это делается аналогично настройке обработчика базовой аутентификации:

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

Примечание

В настоящее время urllib.request не поддерживает получение ресурсов https через прокси. Однако это можно включить, расширив urllib.request, как показано в рецепте 6.

Примечание

HTTP_PROXY будет проигнорировано, если установлена переменная REQUEST_METHOD; см. документацию по getproxies().

Сокеты и слои

Поддержка Python для получения ресурсов из веб-сети многослойная. urllib использует библиотеку http.client, которая в свою очередь использует библиотеку сокетов.

Начиная с Python 2.3, вы можете указать, сколько времени сокет должен ждать ответа перед истечением времени ожидания. Это может быть полезно в приложениях, которые должны получать веб-страницы. По умолчанию модуль сокета не имеет таймаута и может зависнуть. В настоящее время таймаут сокета не доступен на уровнях 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)

Примечания

Этот документ был пересмотрен и изменен Джоном Ли.

1

Например, Google.

2

Обнаружение браузера — очень плохая практика для разработки веб-сайтов — создание сайтов с использованием веб-стандартов гораздо разумнее. К сожалению, многие сайты по-прежнему отправляют разные версии разным браузерам.

3

Пользовательский агент для MSIE 6 — ‘Mozilla/4.0 (совместимый; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)’

4

Для получения более подробной информации о других HTTP-заголовках запросов см. Быстрое справочное руководство по HTTP-заголовкам.

5

В моем случае мне нужно использовать прокси для доступа к интернету на работе. Если вы пытаетесь получить URL localhost через этот прокси, он блокирует их. IE настроен на использование прокси, что urllib улавливает. Чтобы протестировать скрипты с локальным сервером, мне нужно предотвратить использование urllib прокси.

6

Открыватель urllib для SSL-прокси (метод CONNECT): Рецепт ASPN Cookbook.

© 2001–2023 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.10/howto/urllib2.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API