Документация Python неофициальный перевод
Содержание страницы

HOWTO. Получение интернет-ресурсов с помощью urllib2HOWTO Fetch Internet Resources Using urllib2

Автор

Michael Foord

Примечание

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

ВведениеIntroduction

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

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

В простых ситуациях urlopen очень прост в использовании. Но как только вы столкнётесь с ошибками или нетривиальными случаями при открытии HTTP-URL, потребуется некоторое понимание протокола HTTP. Наиболее полный и авторитетный источник по HTTP – это RFC 2616. Это технический документ, не предназначенный для лёгкого чтения. Данное руководство призвано проиллюстрировать использование urllib2 с достаточными подробностями об HTTP, чтобы помочь вам. Оно не заменяет документацию по urllib2, а дополняет её.

Загрузка URLFetching URLs

Самый простой способ использования urllib2 выглядит следующим образом:

import urllib2
response = urllib2.urlopen('http://python.org/')
html = response.read()

Многие случаи использования urllib2 будут столь же простыми (обратите внимание, что вместо URL с ‘http:’ мы могли бы использовать URL, начинающийся с ‘ftp:’, ‘file:’ и т.д.). Однако цель данного руководства – объяснить более сложные случаи, сосредоточившись на HTTP.

HTTP основан на запросах и ответах: клиент отправляет запросы, а серверы отправляют ответы. urllib2 отражает это с помощью объекта Request, который представляет выполняемый HTTP-запрос. В простейшем случае создаётся объект Request, указывающий URL для получения. Вызов urlopen с этим объектом Request возвращает объект ответа для запрошенного URL. Этот ответ является файлоподобным объектом, что означает, что вы можете, например, вызвать .read() для ответа:

import urllib2

req = urllib2.Request('http://www.voidspace.org.uk')
response = urllib2.urlopen(req)
the_page = response.read()

Обратите внимание, что urllib2 использует один и тот же интерфейс Request для обработки всех схем URL. Например, можно выполнить FTP-запрос следующим образом:

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

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

ДанныеData

Иногда требуется отправить данные на URL (часто URL будет ссылаться на CGI-скрипт 1 или другое веб-приложение). В HTTP это часто делается с помощью так называемого POST-запроса. Именно так часто поступает браузер при отправке заполненной HTML-формы. Не все POST-запросы должны исходить из форм: можно использовать POST для передачи произвольных данных в собственное приложение. В типичном случае с HTML-формами данные необходимо закодировать стандартным способом, а затем передать объекту Request в качестве аргумента data. Кодирование выполняется с помощью функции из библиотеки urllib, а не из urllib2.

import urllib
import urllib2

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

data = urllib.urlencode(values)
req = urllib2.Request(url, data)
response = urllib2.urlopen(req)
the_page = response.read()

Обратите внимание, что иногда требуются другие кодировки (например, для загрузки файлов из HTML-форм – подробнее см. Спецификацию HTML, раздел «Отправка формы»).

If you do not pass the data argument, urllib2 uses a GET request. One way in which GET and POST requests differ is that POST requests often have “side-effects”: they change the state of the system in some way (for example by placing an order with the website for a hundredweight of tinned spam to be delivered to your door). Though the HTTP standard makes it clear that POSTs are intended to always cause side-effects, and GET requests never to cause side-effects, nothing prevents a GET request from having side-effects, nor a POST requests from having no side-effects. Data can also be passed in an HTTP GET request by encoding it in the URL itself.

Это делается следующим образом:

>>> import urllib2
>>> import urllib
>>> data = {}
>>> data['name'] = 'Somebody Here'
>>> data['location'] = 'Northampton'
>>> data['language'] = 'Python'
>>> url_values = urllib.urlencode(data)
>>> print url_values  # Порядок может отличаться. 
name=Somebody+Here&language=Python&location=Northampton
>>> url = 'http://www.example.com/example.cgi'
>>> full_url = url + '?' + url_values
>>> data = urllib2.urlopen(full_url)

Обратите внимание, что полный URL создаётся добавлением ? к URL, после которого следуют закодированные значения.

ЗаголовкиHeaders

Здесь будет рассмотрен один конкретный HTTP-заголовок, чтобы проиллюстрировать, как добавлять заголовки в HTTP-запросы.

Некоторые веб-сайты 2 не любят, когда их просматривают программы, или отправляют разные версии разным браузерам 3. По умолчанию urllib2 идентифицирует себя как Python-urllib/x.y (где x и y – номера старшей и младшей версии дистрибутива Python, например Python-urllib/2.5), что может сбить с толку сайт или просто не сработать. Браузер идентифицирует себя через заголовок User-Agent 4. При создании объекта Request можно передать словарь заголовков. В следующем примере выполняется тот же запрос, что и выше, но клиент идентифицирует себя как версия Internet Explorer 5.

import urllib
import urllib2

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.urlencode(values)
req = urllib2.Request(url, data, headers)
response = urllib2.urlopen(req)
the_page = response.read()

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

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

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

HTTPError является подклассом URLError, возбуждаемым в конкретном случае HTTP URL.

URLError

Часто URLError возникает из-за отсутствия сетевого соединения (нет маршрута к указанному серверу) или потому что указанный сервер не существует. В этом случае возбуждённое исключение будет иметь атрибут 'reason', который представляет собой кортеж, содержащий код ошибки и текстовое сообщение об ошибке.

например

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

HTTPError

Каждый HTTP-ответ от сервера содержит числовой «код состояния». Иногда код состояния указывает на то, что сервер не может выполнить запрос. Обработчики по умолчанию обработают некоторые из таких ответов (например, если ответ является «перенаправлением», требующим от клиента получить документ по другому URL, urllib2 обработает это автоматически). Для тех случаев, которые не могут быть обработаны, urlopen возбуждает исключение HTTPError. Типичные ошибки: «404» (страница не найдена), «403» (запрос запрещён) и «401» (требуется аутентификация).

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

Возбуждённый экземпляр HTTPError будет иметь целочисленный атрибут 'code', который соответствует ошибке, отправленной сервером.

Коды ошибокError Codes

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

BaseHTTPServer.BaseHTTPRequestHandler.responses – это полезный словарь кодов ответов, который содержит все коды ответов, используемые в RFC 2616. Для удобства словарь приведён здесь.

# Таблица, сопоставляющая коды ответов с сообщениями; записи имеют
# вид {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 можно использовать в качестве ответа на возвращённой странице. Это означает, что помимо атрибута ⟦code⟧, у него также есть методы ⟦read⟧, ⟦geturl⟧ и ⟦info⟧.

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

РезюмеWrapping it Up

Итак, если вы хотите быть готовым к HTTPError или URLError, есть два основных подхода. Второй подход предпочтительнее.

Способ 1Number 1

from urllib2 import Request, urlopen, 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:
    # всё в порядке

Примечание

except HTTPError должен идти первым, иначе except URLError также перехватит HTTPError.

Способ 2Number 2

from urllib2 import Request, urlopen, 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:
    # всё в порядке

info и geturlinfo and geturl

Ответ, возвращаемый urlopen (или экземпляром HTTPError), имеет два полезных метода: info() и geturl().

geturl – возвращает реальный URL полученной страницы. Это полезно, поскольку urlopen (или используемый объект opener) мог выполнить перенаправление. URL полученной страницы может не совпадать с запрошенным URL.

info – возвращает объект, подобный словарю, который описывает полученную страницу, в частности заголовки, отправленные сервером. В настоящее время это экземпляр httplib.HTTPMessage.

Типичные заголовки включают ‘Content-length’, ‘Content-type’ и т.д. Смотрите Краткий справочник HTTP-заголовков для полезного списка HTTP-заголовков с краткими пояснениями их значения и использования.

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

При получении URL используется открыватель – экземпляр urllib2.OpenerDirector, название которого, возможно, сбивает с толку. Обычно используется открыватель по умолчанию – через urlopen, – но можно создавать собственные открыватели. Открыватели используют обработчики. Вся «тяжёлая работа» выполняется обработчиками. Каждый обработчик знает, как открывать URL для определённой схемы (http, ftp и т.д.) или как обрабатывать аспект открытия URL, например перенаправления HTTP или HTTP-куки.

Стоит создавать собственные opener'ы, если нужно получать URL с установленными определёнными обработчиками, например чтобы получить opener, обрабатывающий cookies, или opener, не обрабатывающий перенаправления.

Чтобы создать opener, создайте экземпляр OpenerDirector, а затем вызывайте .add_handler(some_handler_instance) несколько раз.

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

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

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

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

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

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

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

например

WWW-Authenticate: Basic realm="cPanel Users"

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

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

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

# создать менеджер паролей
password_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()

# Добавить имя пользователя и пароль.
# Если бы мы знали realm, мы могли бы использовать его вместо None.
top_level_url = "http://example.com/foo/"
password_mgr.add_password(None, top_level_url, username, password)

handler = urllib2.HTTPBasicAuthHandler(password_mgr)

# создать "opener" (экземпляр OpenerDirector)
opener = urllib2.build_opener(handler)

# использовать opener для получения URL
opener.open(a_url)

# Установить opener.
# Теперь все вызовы urllib2.urlopen используют наш opener.
urllib2.install_opener(opener)

Примечание

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

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

ПроксиProxies

urllib2 автоматически определяет настройки прокси и использует их. Это происходит через ProxyHandler, который входит в стандартную цепочку обработчиков, когда обнаружена настройка прокси. Обычно это хорошо, но бывают случаи, когда это может быть нежелательно 6. Один из способов – настроить собственный ProxyHandler без определения прокси. Это делается похожими шагами, что и настройка обработчика Basic Authentication:

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

Примечание

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

Примечание

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

Сокеты и уровниSockets and Layers

Поддержка Python для получения ресурсов из Интернета является многоуровневой. urllib2 использует библиотеку httplib, которая, в свою очередь, использует библиотеку socket.

Начиная с Python 2.3 можно указать, как долго сокет должен ждать ответа до истечения времени ожидания. Это может быть полезно в приложениях, которым необходимо получать веб-страницы. По умолчанию модуль socket не имеет тайм-аута и может зависнуть. В настоящее время тайм-аут сокета не раскрыт на уровнях httplib или urllib2. Однако можно глобально задать тайм-аут по умолчанию для всех сокетов с помощью

import socket
import urllib2

# тайм-аут в секундах
timeout = 10
socket.setdefaulttimeout(timeout)

# этот вызов urllib2.urlopen теперь использует таймаут по умолчанию
# который мы установили в модуле socket
req = urllib2.Request('http://www.voidspace.org.uk')
response = urllib2.urlopen(req)

ПримечанияFootnotes

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

1

Введение в протокол CGI см. в Writing Web Applications in Python.

2

Google, например.

3

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

4

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

5

Подробнее о HTTP-заголовках запросов см. Quick Reference to HTTP Headers.

6

В моём случае для доступа в интернет на работе приходится использовать прокси. При попытке получить URL-адреса localhost через этот прокси они блокируются. Браузер Internet Explorer настроен на использование прокси, и urllib2 использует эту настройку. Чтобы тестировать скрипты с локальным сервером, приходится запрещать urllib2 использовать прокси.

7

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