Spec-Zone.ru › Python 3.13

HOWTO: получение интернет-ресурсов с помощью пакета urllib

Автор:

Michael Foord

Введение

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

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

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

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

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

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

В простых случаях urlopen очень легко использовать. Но как только при открытии HTTP URL-адресов возникают ошибки или нетривиальные ситуации, вам потребуется некоторое понимание протокола передачи гипертекста. Наиболее полным и авторитетным справочником по 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://python.org/')
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, если не может обработать ответ (хотя, как обычно в API Python, также могут быть вызваны встроенные исключения, например 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» (требуется аутентификация).

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

У вызываемого экземпляра HTTPError будет целочисленный атрибут «code», соответствующий ошибке, отправленной сервером.

Коды ошибок

Поскольку обработчики по умолчанию обрабатывают перенаправления (коды из диапазона 300), а коды из диапазона 100–299 обозначают успешное выполнение, обычно вы будете встречать только коды ошибок из диапазона 400–599.

http.server.BaseHTTPRequestHandler.responses — полезный словарь кодов ответов, содержащий все коды ответов, используемые в RFC 2616. Ниже приведён фрагмент этого словаря.

responses = {
    ...
    <HTTPStatus.OK: 200>: ('OK', 'Request fulfilled, document follows'),
    ...
    <HTTPStatus.FORBIDDEN: 403>: ('Forbidden',
                                  'Request forbidden -- authorization will '
                                  'not help'),
    <HTTPStatus.NOT_FOUND: 404>: ('Not Found',
                                  'Nothing matches the given URI'),
    ...
    <HTTPStatus.IM_A_TEAPOT: 418>: ("I'm a Teapot",
                                    'Server refuses to brew coffee because '
                                    'it is a teapot'),
    ...
    <HTTPStatus.SERVICE_UNAVAILABLE: 503>: ('Service Unavailable',
                                            'The server cannot process the '
                                            'request due to a high load'),
    ...
    }

При возникновении ошибки сервер возвращает HTTP-код ошибки и страницу с сообщением об ошибке. Экземпляр HTTPError можно использовать как ответ для возвращённой страницы. Это означает, что помимо атрибута code у него также есть методы 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 (или используемый объект opener) мог выполнить перенаправление. URL-адрес полученной страницы может отличаться от запрошенного.
  • info — возвращает объект, похожий на словарь, с описанием полученной страницы, в частности заголовков, отправленных сервером. В настоящее время это экземпляр http.client.HTTPMessage.

Типичные заголовки: «Content-length», «Content-type» и т. д. Список HTTP-заголовков с краткими пояснениями их значения и использования см. в кратком справочнике по HTTP-заголовкам.

Открыватели и обработчики

Для получения URL-адреса используется открыватель (экземпляр, возможно, неудачно названного класса urllib.request.OpenerDirector). Обычно мы использовали открыватель по умолчанию — с помощью urlopen, — но можно создавать собственные открыватели. Открыватели используют обработчики. Вся основная работа выполняется обработчиками. Каждый обработчик знает, как открывать URL-адреса для определённой схемы (http, ftp и т. д.) или как обрабатывать отдельные аспекты открытия URL-адресов, например перенаправления HTTP или файлы cookie HTTP.

Вам потребуется создать открыватель, если вы хотите получать 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)

Примечание

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

Сноски

Документ проверен и отредактирован John Lee.

[1]

Например, Google.

[2]

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

[3]

Строка user agent для MSIE 6: ‘Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)’

[4]

Подробнее о других заголовках HTTP-запросов см. в кратком справочнике по HTTP-заголовкам.

[5]

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

[6]

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

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

Spec-Zone.ru

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