urllib.request в Python для HTTP-запросов

Оглавление

Смотрите сейчас, к этому уроку прилагается соответствующий видеокурс, созданный командой Real Python. Просмотрите его вместе с письменным руководством, чтобы углубить свое понимание: HTTP-запросы с помощью urllib.request в Python

Если вы хотите выполнять HTTP-запросы на Python с помощью встроенного модуля urllib.request, то это руководство для вас. urllib.request позволяет выполнять HTTP-операции без добавления внешних зависимостей.

В этом руководстве рассказывается о том, как выполнять запросы GET и POST, обрабатывать HTTP-ответы и даже управлять кодировками символов. Вы также узнаете, как справляться с распространенными ошибками и отличать библиотеку urllib.request от библиотеки requests.

К концу этого урока вы поймете, что:

  • urllib является частью стандартной библиотеки Python .
  • urllib используется для выполнения HTTP-запросов.
  • Вы можете открыть URL-адрес с помощью urllib, импортировав urlopen и вызвав его с целевым URL-адресом.
  • Чтобы отправить POST-запрос с помощью urllib, вы передаете данные в urlopen() или в Request объект.
  • Пакет requests предлагает высокоуровневый интерфейс с интуитивно понятным синтаксисом.
  • urllib3 отличается от встроенного urllib модуля.

В этом руководстве вы узнаете, как выполнять основные HTTP-запросы, как работать с кодировками символов в HTTP-сообщениях и как устранить некоторые распространенные ошибки при использовании urllib.request. Наконец, вы узнаете, почему существуют библиотеки urllib и requests и когда следует использовать ту или иную.

Если вы слышали о HTTP-запросах, включая GET и POST, то вы, вероятно, готовы к прочтению этого руководства. Кроме того, вы уже должны были использовать Python для чтения и записи в файлы, в идеале с помощью контекстного менеджера, по крайней мере, один раз.

Узнайте больше: Нажмите здесь, чтобы присоединиться к более чем 290 000 разработчикам Python в новостной рассылке Real Python и получать новые руководства по Python и новости, которые помогут сделает вас более эффективным специалистом по питону.

Основные HTTP-запросы GET С urllib.request

Перед тем, как углубиться в то, что такое HTTP-запрос и как он работает, вам нужно немного промочить ноги, отправив базовый GET-запрос на примерный URL-адрес. Вы также сделаете запрос GET к макету REST API для получения некоторых данных в формате JSON. В случае, если вам интересно узнать о запросах на публикацию, вы расскажете о них позже в руководстве, как только у вас появятся дополнительные знания о urllib.request.

Будьте осторожны: В зависимости от ваших точных настроек вы можете обнаружить, что некоторые из этих примеров не работают. Если это так, перейдите к разделу, посвященному распространенным ошибкам urllib.request для устранения неполадок.

Если вы столкнулись с проблемой, которая здесь не описана, обязательно прокомментируйте ее ниже с точным и воспроизводимым примером.

Для начала вы отправите запрос на www.example.com, и сервер вернет HTTP-сообщение. Убедитесь, что вы используете Python 3 или выше, а затем используйте функцию urlopen() из urllib.request:

>>> from urllib.request import urlopen
>>> with urlopen("https://www.example.com") as response:
...     body = response.read()
...
>>> body[:15]
b'<!doctype html>'


В этом примере вы импортируете urlopen() из urllib.request. Используя контекстный менеджер with, вы отправляете запрос и получаете ответ с помощью urlopen(). Затем вы читаете текст ответа и закрываете объект response. После этого вы выводите на экран первые пятнадцать позиций основного текста, отмечая, что он выглядит как HTML-документ.

Вот и вы! Вы успешно выполнили запрос и получили ответ. Изучив содержимое, вы можете сказать, что это, скорее всего, HTML-документ. Обратите внимание, что печатному тексту основного текста предшествует b. Это указывает на литерал байт, который может потребоваться расшифровать. Далее в руководстве вы узнаете, как преобразовать байты в строку, записать их в файл или преобразовать их в словарь.

Процесс лишь немного отличается, если вы хотите выполнять вызовы REST API для получения данных в формате JSON. В следующем примере вы отправите запрос в {JSON}-заполнитель для получения некоторых поддельных данных о текущих задачах:

>>> from urllib.request import urlopen
>>> import json
>>> url = "https://jsonplaceholder.typicode.com/todos/1"
>>> with urlopen(url) as response:
...     body = response.read()
...
>>> todo_item = json.loads(body)
>>> todo_item
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}


В этом примере вы делаете практически то же самое, что и в предыдущем примере. Но в этом случае вы импортируете urllib.request и json, используя функцию json.loads() с body для декодирования и синтаксического анализа вернул байты JSON в Словарь Python. Вуаля!

Если вам посчастливилось использовать безошибочные конечные точки, такие как в этих примерах, то, возможно, вышеописанное - это все, что вам нужно от urllib.request. С другой стороны, вы можете обнаружить, что этого недостаточно.

Теперь, прежде чем приступить к urllib.request устранению неполадок, вы сначала получите представление о базовой структуре HTTP-сообщений и узнаете, как urllib.request с ними обращаться. Это понимание заложит прочную основу для устранения множества различных проблем.

Основные принципы HTTP-сообщений

Чтобы понять некоторые проблемы, с которыми вы можете столкнуться при использовании urllib.request, вам нужно изучить, как ответ представлен urllib.request. Чтобы сделать это, вам пригодится подробный обзор того, что такое HTTP-сообщение, который вы получите в этом разделе.

Перед обзором на высоком уровне, краткое примечание по справочным источникам. Если вы хотите разобраться в технических тонкостях, Рабочая группа по разработке Интернета (IETF) располагает обширным набором документов для запроса комментариев (RFC). Эти документы в конечном итоге становятся фактическими спецификациями для таких вещей, как HTTP-сообщения. RFC 7230, часть 1: синтаксис сообщений и маршрутизация, например, полностью посвящен HTTP-сообщениям.

Если вы ищете какой-нибудь справочный материал, который было бы немного легче усвоить, чем RFC, то в Сети разработчиков Mozilla (MDN) есть большой выбор справочных статей. Например, их статья о HTTP-сообщениях, хотя и остается технической, гораздо более удобоварима.

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

Понимание того, что такое HTTP-сообщение

В двух словах, HTTP-сообщение можно понимать как текст, передаваемый в виде потока байт, структурированного в соответствии с рекомендациями, указанными в RFC 7230. Декодированное HTTP-сообщение может состоять всего из двух строк:

GET / HTTP/1.1
Host: www.google.com


Это указывает на запрос GET в корневом каталоге (/) с использованием протокола HTTP/1.1. Один-единственный заголовок, который требуется указать, - это хост, www.google.com. Целевой сервер располагает достаточной информацией, чтобы отправить ответ с этой информацией.

Ответ похож по структуре на запрос. HTTP-сообщения состоят из двух основных частей: метаданных и основного текста. В приведенном выше примере запроса сообщение полностью состоит из метаданных без текста. С другой стороны, ответ состоит из двух частей:

HTTP/1.1 200 OK
Content-Type: text/html; charset=ISO-8859-1
Server: gws
(... other headers ...)

<!doctype html><html itemscope="" itemtype="http://schema.org/WebPage"
...


Ответ начинается со строки состояния , в которой указывается протокол HTTP HTTP/1.1 и статус 200 OK. После строки состояния вы получаете множество пар ключ-значение, таких как Server: gws, представляющих все заголовки ответа . Это метаданные ответа.

После метаданных есть пустая строка, которая служит разделителем между заголовками и основной частью. Все, что следует за пустой строкой, составляет основную часть. Это та часть, которая читается, когда вы используете urllib.request.

Примечание: Пустые строки часто технически называются новыми строками. Новая строка в HTTP-сообщении должна быть в виде , возвращающей каретку, в стиле Windows (\r) вместе с в конце строки (\n). В Unix-подобных системах новые строки обычно просто заканчиваются (\n).

Вы можете предположить, что все HTTP-сообщения соответствуют этим спецификациям, но возможно, что некоторые из них могут нарушать эти правила или соответствовать более старым спецификациям. Однако, это исключительно редко вызывает какие-либо проблемы. Так что просто держите это в голове на случай, если вы столкнетесь со странной ошибкой!

В следующем разделе вы увидите, как urllib.request работает с необработанными HTTP-сообщениями.

Понимание того, как urllib.request Представляет собой HTTP-сообщение

Основным представлением HTTP-сообщения, с которым вы будете взаимодействовать при использовании urllib.request, является HTTPResponse object. Сам модуль urllib.request зависит от низкоуровневого модуля http, с которым вам не нужно взаимодействовать напрямую. Однако в конечном итоге вы используете некоторые структуры данных, которые предоставляет http, такие как HTTPResponse и HTTPMessage.

Примечание: Внутреннее именование объектов, представляющих HTTP-ответы и сообщения в Python, может немного сбивать с толку. Как правило, вы взаимодействуете только с экземплярами HTTPResponse, в то время как запрос выполняется внутри системы.

Вы можете подумать, что HTTPMessage - это своего рода базовый класс, который HTTPResponse наследуется от, но это не так. HTTPResponse наследуется непосредственно от io.BufferedIOBase, в то время как класс HTTPMessage наследуется от email.message.EmailMessage.

EmailMessage определен в исходном коде как объект, содержащий набор заголовков и полезную нагрузку, поэтому это не обязательно должно быть электронное письмо. HTTPResponse просто используется HTTPMessage как контейнер для своих заголовков.

Однако, если вы говорите о самом протоколе HTTP, а не о его реализации на Python, то вы были бы правы, рассматривая HTTP-ответ как своего рода HTTP-сообщение.

Когда вы отправляете запрос с помощью urllib.request.urlopen(), вы получаете в ответ объект HTTPResponse. Потратьте некоторое время на изучение объекта HTTPResponse с помощью pprint() и dir(), чтобы увидеть все различные методы и свойства, которые ему принадлежат:

>>> from urllib.request import urlopen
>>> from pprint import pprint
>>> with urlopen("https://www.example.com") as response:
...     pprint(dir(response))
...


Чтобы просмотреть выходные данные этого фрагмента кода, нажмите, чтобы развернуть раздел, который можно сворачивать, ниже:

>>> with urlopen("https://www.example.com") as response:
...     pprint(dir(response))
...
['__abstractmethods__',
 '__class__',
 '__del__',
 '__delattr__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__enter__',
 '__eq__',
 '__exit__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__iter__',
 '__le__',
 '__lt__',
 '__module__',
 '__ne__',
 '__new__',
 '__next__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '_abc_impl',
 '_checkClosed',
 '_checkReadable',
 '_checkSeekable',
 '_checkWritable',
 '_check_close',
 '_close_conn',
 '_get_chunk_left',
 '_method',
 '_peek_chunked',
 '_read1_chunked',
 '_read_and_discard_trailer',
 '_read_next_chunk_size',
 '_read_status',
 '_readall_chunked',
 '_readinto_chunked',
 '_safe_read',
 '_safe_readinto',
 'begin',
 'chunk_left',
 'chunked',
 'close',
 'closed',
 'code',
 'debuglevel',
 'detach',
 'fileno',
 'flush',
 'fp',
 'getcode',
 'getheader',
 'getheaders',
 'geturl',
 'headers',
 'info',
 'isatty',
 'isclosed',
 'length',
 'msg',
 'peek',
 'read',
 'read1',
 'readable',
 'readinto',
 'readinto1',
 'readline',
 'readlines',
 'reason',
 'seek',
 'seekable',
 'status',
 'tell',
 'truncate',
 'url',
 'version',
 'will_close',
 'writable',
 'write',
 'writelines']


Это множество методов и свойств, но в конечном итоге вы будете использовать лишь некоторые из них. Помимо .read(), важные из них обычно связаны с получением информации о заголовках .

Один из способов проверить все заголовки - это получить доступ к атрибуту .headers объекта HTTPResponse. Это вернет объект HTTPMessage. Удобно, что вы можете использовать HTTPMessage как словарь, вызвав для него .items(), чтобы получить все заголовки в виде кортежей:

>>> with urlopen("https://www.example.com") as response:
...     pass
...
>>> response.headers
<http.client.HTTPMessage object at 0x000001E029D9F4F0>
>>> pprint(response.headers.items())
[('Accept-Ranges', 'bytes'),
 ('Age', '398424'),
 ('Cache-Control', 'max-age=604800'),
 ('Content-Type', 'text/html; charset=UTF-8'),
 ('Date', 'Tue, 25 Jan 2022 12:18:53 GMT'),
 ('Etag', '"3147526947"'),
 ('Expires', 'Tue, 01 Feb 2022 12:18:53 GMT'),
 ('Last-Modified', 'Thu, 17 Oct 2019 07:18:26 GMT'),
 ('Server', 'ECS (nyb/1D16)'),
 ('Vary', 'Accept-Encoding'),
 ('X-Cache', 'HIT'),
 ('Content-Length', '1256'),
 ('Connection', 'close')]


Теперь у вас есть доступ ко всем заголовкам ответов! Большая часть этой информации вам, вероятно, не понадобится, но будьте уверены, что некоторые приложения ее используют. Например, ваш браузер может использовать заголовки для чтения ответа, установки файлов cookie и определения соответствующего срока службы кэша.

Существуют удобные методы для получения заголовков из объекта HTTPResponse, поскольку это довольно распространенная операция. Вы можете вызвать .getheaders() непосредственно для объекта HTTPResponse, который вернет точно такой же список кортежей, как указано выше. Если вас интересует только один заголовок, скажем, заголовок Server, то вы можете использовать единственное число .getheader("Server") в HTTPResponse или использовать квадратные скобки ([]) в синтаксисе .headers из HTTPMessage:

>>> response.getheader("Server")
'ECS (nyb/1D16)'
>>> response.headers["Server"]
'ECS (nyb/1D16)'


По правде говоря, вам, вероятно, не понадобится напрямую взаимодействовать с заголовками, как это. Информация, которая вам, скорее всего, понадобится, вероятно, уже содержит некоторые встроенные вспомогательные методы, но теперь вы знаете, на случай, если вам когда-нибудь понадобится копнуть глубже!

Закрытие HTTPResponse

Объект HTTPResponse имеет много общего с файловым объектом. Класс HTTPResponse наследуется от класса IOBase, как и файловые объекты, что означает, что вы должны быть внимательны при открытии и закрытии.

В простых программах вы вряд ли заметите какие-либо проблемы, если забудете закрыть объекты HTTPResponse. Однако в более сложных проектах это может значительно замедлить выполнение и привести к трудноопределимым ошибкам.

Проблемы возникают из-за того, что потоки ввода-вывода ограничены. Для каждого HTTPResponse потока требуется, чтобы он оставался открытым во время чтения. Если вы никогда не закроете свои потоки, это в конечном итоге предотвратит открытие любого другого потока и может привести к сбоям в работе других программ или даже вашей операционной системы.

Итак, убедитесь, что вы закрыли свои объекты HTTPResponse! Для вашего удобства вы можете использовать контекстный менеджер, как вы видели в примерах. Вы также можете добиться того же результата, явно вызвав .close() для объекта response:

>>> from urllib.request import urlopen
>>> response = urlopen("https://www.example.com")
>>> body = response.read()
>>> response.close()


В этом примере вы не используете контекстный менеджер, а вместо этого явно закрываете поток ответов. Однако в приведенном выше примере все еще есть проблема, поскольку перед вызовом .close() может возникнуть исключение, препятствующее правильному завершению работы. Чтобы сделать этот вызов безусловным, как и должно быть, вы можете использовать try ... except блок как с else, так и с finally предложением:

>>> from urllib.request import urlopen
>>> response = None
>>> try:
...     response = urlopen("https://www.example.com")
... except Exception as ex:
...     print(ex)
... else:
...     body = response.read()
... finally:
...     if response is not None:
...         response.close()


В этом примере вы выполняете безусловный вызов .close() с помощью блока finally, который всегда будет выполняться независимо от возникающих исключений. Код в блоке finally сначала проверяет, существует ли объект response с is not None, а затем закрывает его.

Тем не менее, это именно то, что делает контекстный менеджер, и синтаксис with обычно предпочтительнее. Синтаксис with не только менее подробен и удобочитаем, но и защищает вас от досадных ошибок, связанных с пропусками. Иными словами, это гораздо лучшая защита от случайного забывания закрыть объект:

>>> from urllib.request import urlopen
>>> with urlopen("https://www.example.com") as response:
...     response.read(50)
...     response.read(50)
...
b'<!doctype html>\n<html>\n<head>\n    <title>Example D'
b'omain</title>\n\n    <meta charset="utf-8" />\n    <m'


В этом примере вы импортируете urlopen() из модуля urllib.request. Вы используете ключевое слово with с .urlopen(), чтобы присвоить объект HTTPResponse переменной response. Затем вы считываете первые пятьдесят байт ответа, а затем считываете следующие пятьдесят байт, все в пределах блока with. Наконец, вы закрываете блок with, который выполняет запрос и запускает строки кода внутри своего блока.

С помощью этого кода вы вызываете отображение двух наборов данных по пятьдесят байт каждый. Объект HTTPResponse закроется, как только вы выйдете из области действия блока with, что означает, что метод .read() вернет только объекты с пустыми байтами:

>>> import urllib.request
>>> with urllib.request.urlopen("https://www.example.com") as response:
...     response.read(50)
...
b'<!doctype html>\n<html>\n<head>\n    <title>Example D'
>>> response.read(50)
b''


В этом примере второй вызов для чтения пятидесяти байт находится за пределами области with. Нахождение за пределами блока with означает, что HTTPResponse закрыт, хотя вы все еще можете получить доступ к переменной. Если вы попытаетесь выполнить чтение из HTTPResponse, когда он закрыт, он вернет пустой объект bytes.

Еще один момент, на который следует обратить внимание, заключается в том, что вы не сможете перечитать ответ, прочитав его до конца:

>>> import urllib.request
>>> with urllib.request.urlopen("https://www.example.com") as response:
...     first_read = response.read()
...     second_read = response.read()
...
>>> len(first_read)
1256
>>> len(second_read)
0


Этот пример показывает, что, прочитав ответ один раз, вы не сможете прочитать его снова. Если вы полностью прочитали ответ, последующая попытка просто вернет пустой объект bytes, даже если ответ не закрыт. Вам придется сделать запрос еще раз.

В этом отношении ответ отличается от файлового объекта, потому что с файловым объектом вы можете прочитать его несколько раз, используя метод .seek(), который HTTPResponse не поддерживается. Однако даже после закрытия ответа вы все равно можете получить доступ к заголовкам и другим метаданным.

Изучение текста, октетов и битов

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

Необработанное HTTP-сообщение, отправляемое по проводу, разбивается на последовательность байт, иногда называемую октетами. Байты - это 8- разрядные фрагменты. Например, 01010101 - это байт. Чтобы узнать больше о двоичных файлах, битах и байтах, ознакомьтесь с Побитовыми операторами в Python..

Итак, как вы представляете буквы в байтах? Байт содержит 256 возможных комбинаций, и каждой комбинации можно присвоить букву. Вы можете присвоить 00000001 значению A, 00000010 значение B и так далее. Кодировка символов ASCII, которая довольно проста. common, использует этот тип системы для кодирования 128 символов, чего достаточно для такого языка, как английский. Это особенно удобно, потому что все символы могут быть представлены всего в одном байте, с запасом места.

Все стандартные английские символы, включая заглавные, знаки препинания и цифры, соответствуют стандарту ASCII. С другой стороны, считается, что в японском языке около пятидесяти тысяч логографических знаков, так что 128 символов не хватит! Даже 256 символов, которые теоретически доступны в пределах одного байта, было бы недостаточно для японского языка. Таким образом, чтобы использовать все языки мира, существует множество различных систем кодирования символов.

Несмотря на то, что существует множество систем, на одну вещь вы можете положиться, так это на то, что они всегда будут разбиты на байт. Большинство серверов, если они не могут разрешить MIME-тип и кодировку символов, по умолчанию используют application/octet-stream, что буквально означает поток байтов. Тогда тот, кто получает сообщение, может определить кодировку символов.

Работа с кодировками Символов

Проблемы часто возникают из-за того, что, как вы, наверное, уже догадались, существует очень много различных возможных кодировок символов. Доминирующей кодировкой символов на сегодняшний день является UTF-8,, которая является реализацией Unicode. К счастью, девяносто восемь процентов веб-страниц сегодня закодированы в UTF-8!

UTF-8 является доминирующим, поскольку он может эффективно обрабатывать ошеломляющее количество символов. Он обрабатывает все 1 112 064 потенциальных символа, определенных Unicode, включая китайский, японский, арабский (с написанием справа налево), русский и многие другие наборы символов, включая эмодзи !

UTF-8 остается эффективным, поскольку использует переменное количество байт для кодирования символов, что означает, что для многих символов требуется только один байт, в то время как для других может потребоваться до четырех байт.

Примечание: Чтобы узнать больше о кодировках в Python, ознакомьтесь с Кодировкой Unicode и символов в Python: руководство по безболезненному использованию.

Хотя UTF-8 является доминирующим, и вы обычно не ошибетесь, если будете использовать кодировки UTF-8, вы все равно будете постоянно сталкиваться с разными кодировками. Хорошей новостью является то, что вам не нужно быть экспертом в кодировках, чтобы справиться с ними при использовании urllib.request.

Переход от байтов к строкам

Когда вы используете urllib.request.urlopen(), текст ответа представляет собой объект bytes. Первое, что вам может понадобиться сделать, это преобразовать объект bytes в строку. Возможно, вы захотите выполнить некоторую очистку веб-страниц. Для этого вам нужно декодировать байт. Чтобы расшифровать байты с помощью Python, все, что вам нужно выяснить, - это используемая кодировка символов . Кодировку, особенно когда речь идет о кодировке символов, часто называют набором символов .

Как уже упоминалось, в девяноста восьми процентах случаев вы, вероятно, будете в безопасности, если по умолчанию будете использовать UTF-8:

>>> from urllib.request import urlopen
>>> with urlopen("https://www.example.com") as response:
...     body = response.read()
...
>>> decoded_body = body.decode("utf-8")
>>> print(decoded_body[:30])
<!doctype html>
<html>
<head>


В этом примере вы берете объект bytes, возвращаемый из response.read(), и декодируете его с помощью метода .decode() объекта bytes, передавая utf-8 в качестве аргумента. Когда вы печатаете decoded_body,, вы можете видеть, что теперь это строка.

Тем не менее, полагаться на волю случая редко бывает хорошей стратегией. К счастью, заголовки - отличное место для получения информации о наборе символов:

>>> from urllib.request import urlopen
>>> with urlopen("https://www.example.com") as response:
...     body = response.read()
...
>>> character_set = response.headers.get_content_charset()
>>> character_set
'utf-8'
>>> decoded_body = body.decode(character_set)
>>> print(decoded_body[:30])
<!doctype html>
<html>
<head>


В этом примере вы вызываете .get_content_charset() для .headers объекта response и используете его для декодирования. Это удобный метод, который анализирует заголовок Content-Type, чтобы вы могли безболезненно декодировать байты в текст.

Переход от байтов к файлу

Если вы хотите преобразовать байты в текст, то можете приступать. Но что, если вы хотите записать текст ответа в файл? Что ж, у вас есть два варианта:

  1. Запишите байты непосредственно в файл
  2. Декодируйте байты в строку Python, а затем закодируйте строку обратно в файл

Первый метод является наиболее простым, но второй метод позволяет вам изменить кодировку, если вы захотите. Чтобы узнать о манипулировании файлами более подробно, ознакомьтесь с Real Python для чтения и записи файлов на Python (Руководство).

Чтобы записать байты непосредственно в файл без необходимости декодирования, вам понадобится встроенная функция open(), и вам нужно убедиться, что вы используете функцию записи двоичный режим:

>>> from urllib.request import urlopen
>>> with urlopen("https://www.example.com") as response:
...     body = response.read()
...
>>> with open("example.html", mode="wb") as html_file:
...     html_file.write(body)
...
1256


Использование open() в режиме wb позволяет избежать необходимости декодирования или кодирования и помещает байты тела HTTP-сообщения в файл example.html. Число, которое выводится после операции записи, указывает на количество записанных байт. Вот и все! Вы записали байты непосредственно в файл, ничего не кодируя и не декодируя.

Теперь предположим, что у вас есть URL-адрес, который не использует UTF-8, но вы хотите записать содержимое в файл с UTF-8. Для этого вам нужно сначала расшифровать байты в строку, а затем закодировать строку в файл, указав кодировку символов.

Похоже, что домашняя страница Google использует разные кодировки в зависимости от вашего местоположения. В большинстве стран Европы и США используется ISO-8859-1 кодировка:

>>> from urllib.request import urlopen
>>> with urlopen("https://www.google.com") as response:
...     body = response.read()
...
>>> character_set = response.headers.get_content_charset()
>>> character_set
'ISO-8859-1'
>>> content = body.decode(character_set)
>>> with open("google.html", encoding="utf-8", mode="w") as file:
...     file.write(content)
...
14066


В этом коде вы получили набор символов ответа и использовали его для декодирования объекта bytes в строку. Затем вы записали строку в файл, закодировав ее с помощью UTF-8.

Примечание: Интересно, что у Google, похоже, есть несколько уровней проверок, которые используются для определения того, на каком языке и в какой кодировке будет отображаться веб-страница. Это означает, что вы можете указать Accept-Language заголовок, который, по-видимому, переопределяет ваш IP-адрес. Попробуйте использовать различные Языковые идентификаторы, чтобы увидеть, какие кодировки вы можете получить!

После того, как вы выполнили запись в файл, вы сможете открыть полученный файл в своем браузере или текстовом редакторе. Большинство современных текстовых процессоров могут автоматически определять кодировку символов.

Если есть ошибки кодирования и вы используете Python для чтения файла, то, скорее всего, вы получите сообщение об ошибке:

>>> with open("encoding-error.html", mode="r", encoding="utf-8") as file:
...     lines = file.readlines()
...
UnicodeDecodeError:
    'utf-8' codec can't decode byte


Python явно останавливает процесс и генерирует исключение, но в программе, отображающей текст, такой как браузер, в котором вы просматриваете эту страницу, вы можете обнаружить печально известные символы замены :

Unicode Replacement Character Заменяющий символ

Черный ромб с белым знаком вопроса (�), квадрат (□) и прямоугольник (▯) часто используются в качестве замены символов, которые не удалось расшифровать.

Иногда кажется, что декодирование работает, но в результате получаются неразборчивые последовательности, такие как æ–‡å—å–偑., что также указывает на то, что был использован неправильный набор символов. В Японии даже есть слово для обозначения текста, который искажен из-за проблем с кодировкой символов, Мохибаке, потому что эти проблемы мучили их в начале эры Интернета.

Таким образом, теперь вы должны быть готовы к записи файлов с необработанными байтами, возвращаемыми из urlopen(). В следующем разделе вы узнаете, как анализировать байты в словаре Python с помощью модуля json.

Переход от байтов к словарю

Для ответов application/json вы часто обнаружите, что они не содержат никакой информации о кодировке:

>>> from urllib.request import urlopen
>>> with urlopen("https://httpbin.org/json") as response:
...     body = response.read()
...
>>> character_set = response.headers.get_content_charset()
>>> print(character_set)
None


В этом примере вы используете json конечную точку httpbin, службы, которая позволяет вам экспериментировать с различными типами запросов и ответов. Конечная точка json имитирует типичный API, который возвращает данные в формате JSON. Обратите внимание, что метод .get_content_charset() ничего не возвращает в своем ответе.

Несмотря на отсутствие информации о кодировке символов, не все потеряно. Согласно RFC 4627, кодировка UTF-8 по умолчанию является абсолютным требованием спецификации application/json. Это не значит, что каждый отдельный сервер играет по правилам, но в целом вы можете предположить, что если передается JSON, то он почти всегда будет закодирован с использованием UTF-8.

К счастью, json.loads() декодирует байтовые объекты скрытно и даже имеет некоторую свободу действий с точки зрения различных кодировок, с которыми он может работать. Итак, json.loads() должен быть в состоянии справиться с большинством байтовых объектов, которые вы ему добавляете, при условии, что они являются допустимыми JSON:

>>> import json
>>> json.loads(body)
{'slideshow': {'author': 'Yours Truly', 'date': 'date of publication', 'slides'
: [{'title': 'Wake up to WonderWidgets!', 'type': 'all'}, {'items': ['Why <em>W
onderWidgets</em> are great', 'Who <em>buys</em> WonderWidgets'], 'title': 'Ove
rview', 'type': 'all'}], 'title': 'Sample Slide Show'}}


Как вы можете видеть, модуль json автоматически выполняет декодирование и создает словарь Python. Почти все API возвращают информацию о ключе-значении в формате JSON, хотя вы можете столкнуться с некоторыми более старыми API, которые работают с XML. Для этого вам, возможно, захочется ознакомиться с Дорожной картой для синтаксических анализаторов XML в Python.

При этом вы должны знать достаточно о байтах и кодировках, чтобы быть опасным! В следующем разделе вы узнаете, как устранить неполадки и устранить несколько распространенных ошибок, с которыми вы можете столкнуться при использовании urllib.request.

Распространенные urllib.request Проблемы

Существует множество проблем, с которыми вы можете столкнуться в мировой дикой сети, независимо от того, используете вы urllib.request или нет. В этом разделе вы узнаете, как справиться с несколькими наиболее распространенными ошибками при запуске работы: 403 ошибки и Ошибки сертификатов TLS/SSL. Однако, прежде чем рассматривать эти конкретные ошибки, вы сначала узнаете, как реализовать обработку ошибок в более общем плане при использовании urllib.request.

Реализация обработки ошибок

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

Коды состояния HTTP сопровождают каждый ответ в строке состояния. Если вы можете прочитать код состояния в ответе, значит, запрос достиг своей цели. Хотя это и хорошо, вы можете считать запрос полностью выполненным, только если код ответа начинается с 2. Например, 200 и 201 представляют собой успешные запросы. Например, если код состояния 404 или 500, что-то пошло не так, и urllib.request вызовет ошибку HTTPError.

Иногда случаются ошибки, и указанный URL-адрес неверен, или соединение не может быть установлено по другой причине. В этих случаях urllib.request вызовет URLError.

Наконец, иногда серверы просто не отвечают. Возможно, у вас медленное сетевое подключение, сервер не работает или запрограммирован на игнорирование определенных запросов. Чтобы справиться с этим, вы можете передать timeout аргумент в urlopen(), чтобы вызвать TimeoutError через определенный промежуток времени.

Первым шагом в обработке этих исключений является их перехват. Вы можете перехватывать ошибки, возникающие в пределах urlopen(), с помощью блока try ... except, используя классы HTTPError, URLError, и TimeoutError :

# request.py

from urllib.error import HTTPError, URLError
from urllib.request import urlopen

def make_request(url):
    try:
        with urlopen(url, timeout=10) as response:
            print(response.status)
            return response.read(), response
    except HTTPError as error:
        print(error.status, error.reason)
    except URLError as error:
        print(error.reason)
    except TimeoutError:
        print("Request timed out")


Функция make_request() принимает строку URL в качестве аргумента, пытается получить ответ от этого URL с помощью urllib.request и перехватывает объект HTTPError, который генерируется при возникновении ошибки. Если URL неверный, он перехватит URLError. Если он пройдет без каких-либо ошибок, он просто напечатает статус и вернет кортеж, содержащий текст и ответ. Ответ закроется после return.

Функция также вызывает urlopen() с аргументом timeout, который вызовет TimeoutError по истечении заданных секунд. Десять секунд - это, как правило, достаточный срок для ожидания ответа, хотя, как всегда, многое зависит от сервера, на который вам нужно отправить запрос.

Теперь вы можете корректно обрабатывать различные ошибки, включая, но не ограничиваясь ими, ошибки, о которых мы расскажем далее.

Устранение ошибок 403

Теперь вы будете использовать функцию make_request() для выполнения некоторых запросов к httpstat.us , который является макетом сервера, используемого для тестирования. Этот фиктивный сервер будет возвращать ответы с запрошенным вами кодом состояния. Например, если вы отправляете запрос на адрес https://httpstat.us/200, вам следует ожидать ответа 200.

API, подобные httpstat.us, используются для обеспечения того, чтобы ваше приложение могло обрабатывать все различные коды состояния, с которыми оно может столкнуться. httpbin также обладает этой функциональностью, но httpstat.us имеет более широкий выбор кодов состояния. В нем даже есть печально известный и полуофициальный 418 код статуса, который возвращает сообщение Я чайник!

Чтобы взаимодействовать с функцией make_request(), которую вы описали в предыдущем разделе, запустите скрипт в интерактивном режиме:

$ python3 -i request.py


С флагом -i эта команда запустит скрипт в интерактивном режиме. Это означает, что он выполнит скрипт, а затем откроет Python REPL после этого, так что теперь вы можете вызвать функцию, которую вы только что определили:

>>> make_request("https://httpstat.us/200")
200
(b'200 OK', <http.client.HTTPResponse object at 0x0000023D612660B0>)
>>> make_request("https://httpstat.us/403")
403 Forbidden


Здесь вы попробовали использовать конечные точки 200 и 403 из httpstat.us. Конечная точка 200 выполняется, как и ожидалось, и возвращает текст ответа и объект ответа. Конечная точка 403 просто напечатала сообщение об ошибке и ничего не вернула, как и ожидалось.

403 Статус означает, что сервер понял запрос, но не выполняет его. Это распространенная ошибка, с которой вы можете столкнуться, особенно при очистке веб-страниц. Во многих случаях вы можете решить эту проблему, передав заголовок User-Agent.

Примечание: Существуют два тесно связанных кода 4xx, которые иногда вызывают путаницу:

  1. 401 Несанкционированный
  2. 403 Запрещенный

Серверы должны возвращать 401, если пользователь не идентифицирован или не вошел в систему и должен что-то сделать, чтобы получить доступ, например, войти в систему или зарегистрироваться.

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

Тем не менее, не стоит полностью доверять кодам состояния. Ошибки существуют и часто встречаются в сложных распределенных сервисах. Некоторые серверы просто не являются образцовыми гражданами!

Один из основных способов, с помощью которого серверы определяют, кто или что отправляет запрос, - это проверка заголовка User-Agent. Исходный запрос по умолчанию, отправляемый urllib.request, выглядит следующим образом:

GET https://httpstat.us/403 HTTP/1.1
Accept-Encoding: identity
Host: httpstat.us
User-Agent: Python-urllib/3.10
Connection: close


Обратите внимание, что User-Agent указан как Python-urllib/3.10. Возможно, вы обнаружите, что некоторые сайты пытаются заблокировать веб-скрейперы, и это User-Agent выдает вас с головой. С учетом сказанного, вы можете установить свой собственный User-Agent с помощью urllib.request, хотя вам нужно будет немного изменить свою функцию:

 # request.py

 from urllib.error import HTTPError, URLError
-from urllib.request import urlopen
+from urllib.request import urlopen, Request

-def make_request(url):
+def make_request(url, headers=None):
+    request = Request(url, headers=headers or {})
     try:
-        with urlopen(url, timeout=10) as response:
+        with urlopen(request, timeout=10) as response:
             print(response.status)
             return response.read(), response
     except HTTPError as error:
         print(error.status, error.reason)
     except URLError as error:
         print(error.reason)
     except TimeoutError:
         print("Request timed out")


Чтобы настроить заголовки, которые вы отправляете с вашим запросом, вам сначала нужно создать экземпляр объекта Request с URL-адресом. Кроме того, вы можете передать аргумент ключевого слова из headers, который принимает стандартный словарь, представляющий любые заголовки, которые вы хотите включить. Таким образом, вместо передачи строки URL непосредственно в urlopen(), вы передаете этот объект Request, который был создан с URL и заголовками.

Примечание: В приведенном выше примере, когда создается экземпляр Request, вам нужно передать ему заголовки, если они были определены. В противном случае передайте пустой объект, например, {}. Вы не можете передать None, так как это приведет к ошибке.

Чтобы использовать эту обновленную функцию, перезапустите интерактивный сеанс, затем вызовите make_request() со словарем, представляющим заголовки в качестве аргумента:

>>> body, response = make_request(
...     "https://www.httpbin.org/user-agent",
...     {"User-Agent": "Real Python"}
... )
200
>>> body
b'{\n  "user-agent": "Real Python"\n}\n'


В этом примере вы отправляете запрос в httpbin. Здесь вы используете конечную точку user-agent, чтобы вернуть значение запроса User-Agent. Поскольку вы отправили запрос с помощью пользовательского агента пользователя Real Python, это то, что будет возвращено.

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

Исправление ошибки SSL CERTIFICATE_VERIFY_FAILED

Другая распространенная ошибка возникает из-за того, что Python не может получить доступ к требуемому сертификату безопасности. Для имитации этой ошибки вы можете использовать несколько фиктивных сайтов с заведомо неверными SSL-сертификатами, предоставленными badssl.com . Вы можете сделать запрос к одному из них, например, к superfish.badssl.com, и на собственном опыте убедиться в ошибке:

>>> from urllib.request import urlopen
>>> urlopen("https://superfish.badssl.com/")
Traceback (most recent call last):
  (...)
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED]
certificate verify failed: unable to get local issuer certificate (_ssl.c:997)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  (...)
urllib.error.URLError: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED]
certificate verify failed: unable to get local issuer certificate (_ssl.c:997)>


В данном случае отправка запроса на адрес с заведомо неверным SSL-сертификатом приведет к CERTIFICATE_VERIFY_FAILED, что является типом URLError.

SSL расшифровывается как Secure Sockets Layer. Это неправильное название, поскольку SSL устарел в пользу TLS, Безопасность транспортного уровня. Иногда старая терминология просто прилипает! Это способ зашифровать сетевой трафик таким образом, чтобы гипотетический слушатель не мог подслушать информацию, передаваемую по проводам.

В наши дни адресам большинства веб-сайтов предшествует не http://, а https://, а и, что означает безопасный. HTTPS соединения должны быть зашифрованы с помощью TLS. urllib.request может обрабатывать как HTTP, так и HTTPS соединения.

Подробности о HTTPS выходят далеко за рамки данного руководства, но вы можете представить себе HTTPS-соединение, состоящее из двух этапов: подтверждение связи и передача информации. Подтверждение связи гарантирует безопасность соединения. Для получения дополнительной информации о Python и HTTPS ознакомьтесь с Изучение HTTPS с помощью Python..

Чтобы убедиться в безопасности конкретного сервера, программы, отправляющие запросы, полагаются на хранилище доверенных сертификатов. Сертификат сервера проверяется на этапе подтверждения связи. Python использует хранилище сертификатов операционной системы. Если Python не может найти системное хранилище сертификатов или если хранилище устарело, то вы столкнетесь с этой ошибкой.

Примечание: В предыдущих версиях Python поведение по умолчанию для urllib.request было , а не для проверки сертификатов, что привело к включению проверки сертификата по умолчанию в PEP 476. Значение по умолчанию изменено в Python 3.4.3.

Иногда хранилище сертификатов, к которому может получить доступ Python, устарело или Python не может получить к нему доступ по какой-либо причине. Это расстраивает, потому что иногда вы можете перейти по URL-адресу из своего браузера, который считает его безопасным, но urllib.request все равно выдает эту ошибку.

У вас может возникнуть соблазн отказаться от проверки сертификата, но это сделает ваше соединение небезопасным и определенно не рекомендуется:

>>> import ssl
>>> from urllib.request import urlopen
>>> unverified_context = ssl._create_unverified_context()
>>> urlopen("https://superfish.badssl.com/", context=unverified_context)
<http.client.HTTPResponse object at 0x00000209CBE8F220>


Здесь вы импортируете модуль ssl, который позволяет вам создавать непроверенный контекст. Затем вы можете передать этот контекст в urlopen() и перейти к заведомо неверному SSL-сертификату. Соединение установлено успешно, поскольку SSL-сертификат не проверен.

Прежде чем прибегать к этим отчаянным мерам, попробуйте обновить свою операционную систему или версию Python. Если это не сработает, вы можете взять страницу из библиотеки requests и установить ее certifi:

PS> python -m venv venv
PS> .\venv\Scripts\activate
(venv) PS> python -m pip install certifi



$ python3 -m venv venv
$ source venv/bin/activate.sh
(venv) $ python3 -m pip install certifi


certifi это набор сертификатов, который вы можете использовать вместо набора вашей системы. Вы делаете это, создавая контекст SSL с пакетом сертификатов certifi вместо пакета операционной системы:

>>> import ssl
>>> from urllib.request import urlopen
>>> import certifi
>>> certifi_context = ssl.create_default_context(cafile=certifi.where())
>>> urlopen("https://sha384.badssl.com/", context=certifi_context)
<http.client.HTTPResponse object at 0x000001C7407C3490>


В этом примере вы использовали certifi в качестве хранилища SSL-сертификатов и успешно подключились к сайту с заведомо исправным SSL-сертификатом. Обратите внимание, что вместо ._create_unverified_context() вы используете .create_default_context().

Таким образом, вы сможете оставаться в безопасности без особых проблем! В следующем разделе вы познакомитесь с миром аутентификации.

Аутентифицированные запросы

Аутентификация - обширная тема, и если вы имеете дело с гораздо более сложной аутентификацией, чем то, что описано здесь, это может стать хорошей отправной точкой для ознакомления с пакетом requests.

В этом руководстве вы рассмотрите только один метод аутентификации, который служит примером типа настроек, которые вам необходимо внести для аутентификации ваших запросов. urllib.request имеет множество других функций, которые помогают в аутентификации, но которые это не будет рассмотрено в данном руководстве.

Одним из наиболее распространенных средств аутентификации является токен на предъявителя, указанный в RFC 6750. Он часто используется как часть OAuth, но также может использоваться изолированно. Его также чаще всего можно увидеть в виде заголовка, который вы можете использовать с вашей текущей функцией make_request():

>>> token = "abcdefghijklmnopqrstuvwxyz"
>>> headers = {
...     "Authorization": f"Bearer {token}"
... }
>>> make_request("https://httpbin.org/bearer", headers)
200
(b'{\n  "authenticated": true, \n  "token": "abcdefghijklmnopqrstuvwxyz"\n}\n',
<http.client.HTTPResponse object at 0x0000023D612642E0>)


В этом примере вы отправляете запрос к конечной точке httpbin /bearer, которая имитирует аутентификацию на предъявителя. В качестве токена она принимает любую строку. Для этого требуется только правильный формат, указанный в RFC 6750. Имя должно быть Authorization или иногда в нижнем регистре authorization, а значение должно быть Bearer, с единственным пробелом между этим и токеном.

Примечание: Если вы используете какие-либо токены или секретную информацию, обязательно защитите эти токены надлежащим образом. Например, не размещайте их в репозитории GitHub, а вместо этого сохраняйте как временные переменные среды.

Поздравляем, вы успешно прошли аутентификацию, используя токен на предъявителя!

Называется другая форма аутентификации Базовая проверка подлинности доступа, это очень простой метод аутентификации, который лишь немного лучше, чем отправка сообщения имя пользователя и пароль в заголовке. Это очень небезопасно!

Одним из наиболее распространенных протоколов, используемых на сегодняшний день, является OAuth (Открытая авторизация). Если вы когда-либо использовали Google, GitHub или Facebook для входа на другой веб-сайт, значит, вы использовали OAuth. Процесс OAuth обычно включает в себя несколько запросов между сервисом, с которым вы хотите взаимодействовать, и сервером идентификации, в результате чего токен на предъявителя выдается на короткий срок. Этот токен на предъявителя затем может быть использован в течение определенного периода времени с аутентификацией на предъявителя.

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

ОТПРАВЛЯТЬ запросы с urllib.request

Вы сделали много запросов GET, но иногда вам хочется отправить информацию. Вот тут-то и возникают запросы POST. Чтобы отправлять POST-запросы с помощью urllib.request, вам не нужно явно изменять метод. Вы можете просто передать объект data в новый объект Request или непосредственно в объект urlopen(). Однако объект data должен быть в специальном формате. Вы немного адаптируете свою функцию make_request() для поддержки запросов POST, добавив параметр data:

 # request.py

 from urllib.error import HTTPError, URLError
 from urllib.request import urlopen, Request

-def make_request(url, headers=None):
+def make_request(url, headers=None, data=None):

-    request = Request(url, headers=headers or {})
+    request = Request(url, headers=headers or {}, data=data)
     try:
         with urlopen(request, timeout=10) as response:
             print(response.status)
             return response.read(), response
     except HTTPError as error:
         print(error.status, error.reason)
     except URLError as error:
         print(error.reason)
     except TimeoutError:
         print("Request timed out")


Здесь вы просто изменили функцию, чтобы она принимала аргумент data со значением по умолчанию None, и передали это прямо в экземпляр Request. Однако это еще не все, что нужно сделать. Вы можете использовать один из двух различных форматов для выполнения запроса POST:

  1. Данные формы: application/x-www-form-urlencoded
  2. JSON-файл: application/json

Первый формат является самым старым форматом для POST-запросов и включает в себя кодирование данных с помощью процентной кодировки, также известной как кодировка URL. Возможно, вы заметили, что URL-адрес пар ключ-значение закодирован как строка запроса. Ключи отделяются от значений знаком равенства (=), пары ключ-значение разделяются амперсандом (&), пробелы обычно не используются, но могут быть заменены знаком плюс (+).

Если вы начинаете со словаря Python, то для использования формата данных формы с вашей функцией make_request() вам нужно будет дважды закодировать:

  1. Один раз для URL-кодирования словаря
  2. Затем снова для кодирования результирующей строки в байты

Для первого этапа кодирования URL-адреса вы будете использовать другой модуль urllib, urllib.parse. Не забудьте запустить свой скрипт в интерактивном режиме, чтобы вы могли использовать функцию make_request() и поиграть с ней в REPL:

>>> from urllib.parse import urlencode
>>> post_dict = {"Title": "Hello World", "Name": "Real Python"}
>>> url_encoded_data = urlencode(post_dict)
>>> url_encoded_data
'Title=Hello+World&Name=Real+Python'
>>> post_data = url_encoded_data.encode("utf-8")
>>> body, response = make_request(
...     "https://httpbin.org/anything", data=post_data
... )
200
>>> print(body.decode("utf-8"))
{
  "args": {},
  "data": "",
  "files": {},
  "form": {
    "Name": "Real Python",
    "Title": "Hello World"
  },
  "headers": {
    "Accept-Encoding": "identity",
    "Content-Length": "34",
    "Content-Type": "application/x-www-form-urlencoded",
    "Host": "httpbin.org",
    "User-Agent": "Python-urllib/3.10",
    "X-Amzn-Trace-Id": "Root=1-61f25a81-03d2d4377f0abae95ff34096"
  },
  "json": null,
  "method": "POST",
  "origin": "86.159.145.119",
  "url": "https://httpbin.org/anything"
}


В этом примере вы:

  1. Импорт urlencode() из модуля urllib.parse
  2. Инициализируйте свои данные POST, начиная со словаря
  3. Используйте функцию urlencode() для кодирования словаря
  4. Закодируйте полученную строку в байты, используя кодировку UTF-8
  5. Сделать запрос к anything конечной точке httpbin.org
  6. Выведите текст ответа в кодировке UTF-8
Кодировка

UTF-8 является частью спецификации для типа application/x-www-form-urlencoded. UTF-8 используется преимущественно для декодирования текста, потому что вы уже знаете, что httpbin.org надежно использует UTF-8.

Конечная точка anything из httpbin действует как своего рода эхо-сигнал, возвращая всю полученную информацию, чтобы вы могли просмотреть детали сделанного вами запроса. В этом случае вы можете подтвердить, что method действительно является POST, и вы можете увидеть, что отправленные вами данные указаны в разделе form.

Чтобы выполнить тот же запрос с помощью JSON, вы преобразуете словарь Python в строку JSON с помощью json.dumps(), закодируете ее с помощью UTF-8, передадите в качестве аргумента data и, наконец, добавите специальный заголовок чтобы указать, что тип данных - JSON:

>>> post_dict = {"Title": "Hello World", "Name": "Real Python"}
>>> import json
>>> json_string = json.dumps(post_dict)
>>> json_string
'{"Title": "Hello World", "Name": "Real Python"}'
>>> post_data = json_string.encode("utf-8")
>>> body, response = make_request(
...     "https://httpbin.org/anything",
...     data=post_data,
...     headers={"Content-Type": "application/json"},
... )
200
>>> print(body.decode("utf-8"))
{
  "args": {},
  "data": "{\"Title\": \"Hello World\", \"Name\": \"Real Python\"}",
  "files": {},
  "form": {},
  "headers": {
    "Accept-Encoding": "identity",
    "Content-Length": "47",
    "Content-Type": "application/json",
    "Host": "httpbin.org",
    "User-Agent": "Python-urllib/3.10",
    "X-Amzn-Trace-Id": "Root=1-61f25a81-3e35d1c219c6b5944e2d8a52"
  },
  "json": {
    "Name": "Real Python",
    "Title": "Hello World"
  },
  "method": "POST",
  "origin": "86.159.145.119",
  "url": "https://httpbin.org/anything"
}


Чтобы сериализовать словарь, на этот раз вы используете json.dumps() вместо urlencode(). Вы также явно добавляете заголовок Content-Type со значением application/json. Используя эту информацию, сервер httpbin может десериализовать JSON на принимающей стороне. В его ответе вы можете увидеть данные, перечисленные под ключом json.

Примечание: Иногда необходимо отправить данные JSON в виде обычного текста, и в этом случае шаги аналогичны описанным выше, за исключением того, что вы задаете Content-Type как text/plain; charset=UTF-8. Многое из этого зависит от сервера или API, на который вы отправляете данные, поэтому обязательно ознакомьтесь с документацией и поэкспериментируйте!

После этого вы можете приступать к отправке POST-запросов. В этом руководстве не будут подробно рассмотрены другие методы запроса, такие как PUT. Достаточно сказать, что вы также можете явно задать метод, передав method аргумент ключевого слова для создания экземпляра Request object.

Экосистема пакетов запросов

В завершение, этот последний раздел руководства посвящен разъяснению экосистемы пакетов, связанных с HTTP-запросами с помощью Python. Поскольку существует множество пакетов без четкого стандарта, это может привести к путанице. Тем не менее, для каждого пакета есть свои варианты использования, что просто расширяет ваш выбор!

Что такое urllib2 и urllib3?

Чтобы ответить на этот вопрос, вам нужно вернуться к раннему Python, вплоть до версии 1.2, когда был представлен оригинальный urllib. Примерно в версии 1.6 был добавлен обновленный urllib2, который работал параллельно с оригинальным urllib. Когда появился Python 3, исходный urllib был признан устаревшим, и из urllib2 был удален 2, взяв оригинальное название urllib. Он также разделен на части:

Так что насчет urllib3? Это сторонняя библиотека, разработанная еще при urllib2. Это не связано со стандартной библиотекой, потому что это независимо поддерживаемая библиотека. Интересно, что библиотека requests на самом деле использует urllib3 под капотом, и то же самое делает pip!

Когда я должен использовать requests Поверх urllib.request?

Основным ответом является простота использования и безопасность. urllib.request считается библиотекой низкого уровня, которая предоставляет множество подробных сведений о работе HTTP-запросов. Документация на Python для urllib.request без обиняков рекомендует requests в качестве клиентского интерфейса HTTP более высокого уровня.

Если вы изо дня в день взаимодействуете с множеством различных REST API, то настоятельно рекомендуется использовать requests. Библиотека requests позиционирует себя как “созданная для людей” и успешно создала интуитивно понятный, безопасный и простой API на основе HTTP. Обычно ее считают самой популярной библиотекой! Если вы хотите узнать больше о библиотеке requests, ознакомьтесь с руководством Real Python по requests.

Примером того, как requests упрощает задачу, является кодировка символов. Вы должны помнить, что при использовании urllib.request вы должны быть осведомлены о кодировках и предпринять несколько шагов для обеспечения безошибочной работы. Пакет requests устраняет это и разрешает кодировку с помощью chardet, универсального детектора кодировки символов, на всякий случай, если возникнет какая-нибудь проблема.

Если ваша цель - узнать больше о стандарте Python и деталях того, как он обрабатывает HTTP-запросы, то urllib.request - отличный способ разобраться в этом. Вы могли бы пойти еще дальше и использовать модули самого низкого уровня http. С другой стороны, вы можете просто захотеть свести зависимости к минимуму, на что urllib.request более чем способны.

Почему requests Не является частью стандартной библиотеки?

Возможно, вам интересно, почему requests на данный момент не является частью ядра Python.

Это сложный вопрос, и на него нет однозначного ответа. Существует множество предположений относительно того, почему это так, но, по-видимому, выделяются две причины:

  1. requests имеет другие сторонние зависимости, которые также необходимо интегрировать.
  2. requests должен оставаться гибким и может делать это лучше за пределами стандартной библиотеки.

Библиотека requests имеет сторонние зависимости. Интеграция requests в стандартную библиотеку означала бы также интеграцию chardet, certifi, и urllib3, среди прочего. Альтернативой было бы коренным образом изменить requests и использовать только существующую стандартную библиотеку Python. Это нетривиальная задача!

Интеграция requests также означала бы, что существующая команда, разрабатывающая эту библиотеку, должна была бы отказаться от полного контроля над проектированием и внедрением, уступив место PEP принятию решений процесс.

Спецификации и рекомендации HTTP постоянно меняются, и библиотека высокого уровня должна быть достаточно гибкой, чтобы не отставать от них. Если требуется исправить уязвимость системы безопасности или добавить новый рабочий процесс, команда requests может создать и выпустить продукт гораздо быстрее, чем в рамках процесса выпуска Python. Предположительно, бывали случаи, когда они выпускали исправление для системы безопасности через двенадцать часов после обнаружения уязвимости!

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

Поскольку эта гибкость так необходима для requests и лежащего в ее основе urllib3, часто используется парадоксальное утверждение, что requests слишком важен для стандартной библиотеки. Это связано с тем, что сообщество Python так сильно зависит от requests и его гибкости, что интеграция его в ядро Python, вероятно, повредит ему и сообществу Python.

На доске объявлений по вопросам репозитория GitHub для requests была опубликована проблема с просьбой о включении requests в стандартную библиотеку. Разработчики requests и urllib3 вмешались, в основном заявив, что они, скорее всего, потеряют интерес к его поддержке самостоятельно. Некоторые даже заявили, что они будут разветвлять репозитории и продолжать разрабатывать их для своих собственных случаев использования.

С учетом сказанного, обратите внимание, что репозиторий библиотеки requests на GitHub размещен под учетной записью Python Software Foundation. То, что что-то не является частью стандартной библиотеки Python, не означает, что это не является неотъемлемой частью экосистемы!

Похоже, что текущая ситуация устраивает как основную команду Python, так и разработчиков requests. Хотя это может немного сбить с толку новичков, существующая структура обеспечивает наиболее стабильную работу с HTTP-запросами.

Также важно отметить, что HTTP-запросы по своей сути сложны. urllib.request не пытайтесь приукрасить это слишком сильно. Он раскрывает большую часть внутренней работы HTTP-запросов, поэтому его называют низкоуровневым модулем. Ваш выбор между requests и urllib.request на самом деле зависит от вашего конкретного варианта использования, соображений безопасности и предпочтений.

Заключение

Теперь вы можете использовать urllib.request для выполнения HTTP-запросов. Теперь вы можете использовать этот встроенный модуль в своих проектах, что позволит им дольше оставаться свободными от зависимостей. Вы также получили более глубокое представление о протоколе HTTP, благодаря использованию модуля более низкого уровня, такого как urllib.request.

В этом руководстве вы должны:

  • Узнал, как создавать базовые HTTP-запросы с помощью urllib.request
  • Изучил все тонкости HTTP-сообщения и изучил, как оно представлено с помощью urllib.request
  • Разобрался, как работать с кодировками HTTP-сообщений
  • Изучил некоторые распространенные ошибки при использовании urllib.request и узнал, как их устранить
  • Окунитесь с головой в мир аутентифицированных запросов с помощью urllib.request
  • Понял, почему существуют и urllib, и requests библиотеки, и когда использовать ту или иную

Теперь вы можете выполнять базовые HTTP-запросы с помощью urllib.request, и у вас также есть инструменты для более глубокого погружения в низкоуровневую среду HTTP с помощью стандартной библиотеки. Наконец, вы можете выбрать, использовать ли requests или urllib.request, в зависимости от того, чего вы хотите или в чем нуждаетесь. Приятного просмотра в Интернете!

Часто задаваемые вопросы

Теперь, когда у вас есть некоторый опыт работы с urllib.request в Python, вы можете использовать вопросы и ответы, приведенные ниже, чтобы проверить свое понимание и резюмировать то, что вы узнали.

Эти часто задаваемые вопросы относятся к наиболее важным понятиям, которые вы рассмотрели в этом руководстве. Нажмите на переключатель Показывать/скрывать рядом с каждым вопросом, чтобы открыть ответ.

urllib используется для работы с URL-адресами в Python. urlib позволяет выполнять такие задачи, как отправка HTTP-запросов, синтаксический анализ URL-адресов и управление кодированием и декодированием данных.

A urllib.request позволяет отправлять HTTP-запросы и взаимодействовать с веб-ресурсами, извлекая данные из URL-адресов или отправляя им данные.

Вы открываете URL-адрес с помощью urllib, импортируя urlopen из urllib.request и используя его в контекстном менеджере для обработки HTTP-ответа.

Вы отправляете запрос POST с помощью urllib, создавая объект Request с вашим URL-адресом и данными, а затем выполняете запрос с помощью urlopen().

Библиотека requests - это сторонняя библиотека более высокого уровня, которая предлагает более простой API и лучшую обработку HTTP-запросов, в то время как urllib.request - это встроенный модуль более низкого уровня, который обеспечивает больший контроль и меньше зависимостей.

Узнайте больше: Нажмите здесь, чтобы присоединиться к более чем 290 000 разработчикам Python в новостной рассылке Real Python и получать новые руководства по Python и новости, которые помогут сделает вас более эффективным специалистом по питону.

<статус завершения article-slug="urllib-запрос" class="btn-group mb-0" data-api-article-bookmark-url="/api/v1/articles/urllib-запрос/закладка/" data-api-article-статус завершения-url="/api/v1/articles/urllib-запрос/завершение_статуса/"> <кнопка поделиться bluesky-text="Интересная статья на #Python от @realpython.com :" email-body="Ознакомьтесь с этой статьей о Python:%0A%0apython's urllib.request для HTTP-запросов" email-subject="Статья о Python для вас" twitter-text="Интересная статья о #Python от @realpython:" url="https://realpython.com/urllib-request /" url-title="urllib.request в Python для HTTP-запросов">

Смотрите сейчас, к этому уроку прилагается соответствующий видеокурс, созданный командой Real Python. Просмотрите его вместе с письменным руководством, чтобы углубить свое понимание: HTTP-запросы с помощью urllib.request в Python

Back to Top