Spec-Zone.ru › Python 3.11

Как использовать пакет urllib для получения ресурсов из Интернета

Автор

Михаил Форд

Введение

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

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

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

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

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

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

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

Для создания открывателя создайте экземпляр 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.11/howto/urllib2.html

Spec-Zone.ru

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