urllib.request в Python для HTTP-запросов
Оглавление
- Основные HTTP-запросы GET с urllib.request
- Основные принципы работы HTTP-сообщений
- Распространенные проблемы с urllib.request
- Аутентифицированные запросы
- ОТПРАВЛЯТЬ запросы с помощью urllib.request
- Экосистема пакета запросов
- Заключение
- Часто задаваемые вопросы
Смотрите сейчас, к этому уроку прилагается соответствующий видеокурс, созданный командой 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, являетсяHTTPResponseobject. Сам модуль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, чтобы вы могли безболезненно декодировать байты в текст.Переход от байтов к файлу
Если вы хотите преобразовать байты в текст, то можете приступать. Но что, если вы хотите записать текст ответа в файл? Что ж, у вас есть два варианта:
- Запишите байты непосредственно в файл
- Декодируйте байты в строку 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 явно останавливает процесс и генерирует исключение, но в программе, отображающей текст, такой как браузер, в котором вы просматриваете эту страницу, вы можете обнаружить печально известные символы замены :
Заменяющий символ
Черный ромб с белым знаком вопроса (�), квадрат (□) и прямоугольник (▯) часто используются в качестве замены символов, которые не удалось расшифровать.
Иногда кажется, что декодирование работает, но в результате получаются неразборчивые последовательности, такие как æ–‡å—å–å‘., что также указывает на то, что был использован неправильный набор символов. В Японии даже есть слово для обозначения текста, который искажен из-за проблем с кодировкой символов, Мохибаке, потому что эти проблемы мучили их в начале эры Интернета.
Таким образом, теперь вы должны быть готовы к записи файлов с необработанными байтами, возвращаемыми из
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, которые иногда вызывают путаницу:
Серверы должны возвращать
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:
- Данные формы:
application/x-www-form-urlencoded- JSON-файл:
application/jsonПервый формат является самым старым форматом для POST-запросов и включает в себя кодирование данных с помощью процентной кодировки, также известной как кодировка URL. Возможно, вы заметили, что URL-адрес пар ключ-значение закодирован как строка запроса. Ключи отделяются от значений знаком равенства (
=), пары ключ-значение разделяются амперсандом (&), пробелы обычно не используются, но могут быть заменены знаком плюс (+).Если вы начинаете со словаря Python, то для использования формата данных формы с вашей функцией
make_request()вам нужно будет дважды закодировать:
- Один раз для URL-кодирования словаря
- Затем снова для кодирования результирующей строки в байты
Для первого этапа кодирования 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" }предварительно> кодовый блок>В этом примере вы:
Кодировка
- Импорт
urlencode()из модуляurllib.parse- Инициализируйте свои данные POST, начиная со словаря
- Используйте функцию
urlencode()для кодирования словаря- Закодируйте полученную строку в байты, используя кодировку UTF-8
- Сделать запрос к
anythingконечной точкеhttpbin.org- Выведите текст ответа в кодировке 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аргумент ключевого слова для создания экземпляраRequestobject.Экосистема пакетов запросов
В завершение, этот последний раздел руководства посвящен разъяснению экосистемы пакетов, связанных с 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.Это сложный вопрос, и на него нет однозначного ответа. Существует множество предположений относительно того, почему это так, но, по-видимому, выделяются две причины:
requestsимеет другие сторонние зависимости, которые также необходимо интегрировать.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
