Перевод статьи Python’s Requests Library (Guide) от замечательной команды Real Python, которая уже не в первый раз радует нас новыми интересными и полезными материалами о языке Python. И хотя в сети есть ее переводы, я немного адаптировал ее содержимое, добавив некоторые справочные данные. Так же я добавил подраздел об использовании механизма сессий при обращении к удаленным службам, которого как я считаю не хватало в оригинале статьи для понимания некоторых затрагиваемых вопросов.
Библиотека requests
в настоящее время уже давно является стандартом де-факто для реализации отправки HTTP-запросов в Python. Она элегантно абстрагирует сложность написания запросов к серверу, используя красивый и простой API, так что вы можете спокойно сосредоточиться при работе над вашим приложении на вопросах организации взаимодействия с различными удаленными службами, а также дальнейшей обработки получаемых данных.
В этой статье мы рассмотрим лишь некоторые из наиболее полезных возможностей, которые может предложить requests
, а также основные способы настройки ее инструментов и оптимизации их работы для различных ситуаций, с которыми вы можете столкнуться в дальнейшем. Вы также ознакомитесь с тем, как эффективно использовать requests
для предотвращения замедления работы (зависания) ваших приложений при взаимодействии с удаленными службами.
В этом руководстве вы узнаете, как:
- отправлять запросы, используя самые распространенные HTTP методы;
- настроить содержимое заголовков и отправляемых данных запросов, используя строку запроса и текст сообщения;
- просмотреть содержимое ваших запросов и ответов;
- отправлять на сервер аутентифицированные запросы;
- корректно настроить параметры запросов таким образом, чтобы избежать резервного копирования данных или замедления работы вашего приложения.
Хотя я попытался включить в настоящее руководство столько информации, сколько нужно, чтобы понять все примеры кода, которые я включил в эту статью, но тем не менее я надеюсь, что для начала вы обладаете общими базовыми понятиями о работе протокола HTTP.
Содержание
- Начинаем работать с requests
- Запрос GET (GET request)
- Ответ (response)
- Содержимое ответа (Content)
- Заголовки ответа (Headers)
- Параметры строки запроса (Query String Parameters)
- Заголовки запроса (Request Headers)
- Другие HTTP методы запроса
- Тело сообщения запроса
- Инспектируем отправленный запрос
- Использование механизма сессий (Session)
- Аутентификация
- Проверка подлинности SSL сертификата (SSL Certificate Verification)
- Производительность
- Заключение
Начинаем работать с requests
И так начнем с установки библиотеки requests
. Для этого выполните в терминале консоли следующую команду:
$ pip install requests
Если же вы предпочитаете использовать Pipenv для управления пакетами Python, то можете запустить на исполнение следующую команду:
$ pipenv install requests
Установка библиотеки
requests
в операционной системе Windows и Linux практически не отличаются. В сети достаточно материалов, в которых описан принцип работы с пакетами Python в обеих системах. Поэтому в этой статье касаться этих вопросов мы далее не будем.
После того, как мы установили requests
, мы можем ее использовать в своем приложении. Импорт библиотеки requests
в ваш код выглядит следующим образом:
import requests
Теперь, когда мы все подготовили, пришло время начинать наш путь по изучению основ работы с библиотекой requests
. И наша первая цель научиться отправлять HTTP-запрос GET.
Запрос GET (GET request)
Такие методы запросов, как GET и POST, определяют, действие, которое вы пытаетесь выполнить при выполнении HTTP-запроса. Помимо GET и POST, есть еще несколько других достаточно распространенных методов запросов и их мы рассмотрим в этом руководстве позже.
И так одним из самых распространенных методов HTTP-запросов является GET. Метод GET предписывает, что вы пытаетесь получить или извлечь некоторые данные из указанного ресурса. Чтобы отправить GET запрос, необходимо вызвать метод requests.get(url)
.
Проверим это на практике: отправим GET-запрос к GitHub Root REST API, вызвав метод get()
со следующим значением параметра url
:
>>> requests.get('https://api.github.com')
Отлично! Мы отправили свой первый запрос. Давайте изучим содержимое полученного ответа.
Ответ (response)
Объект Response
является мощным средством для просмотра содержимого и обработки результатов наших запросов. Давайте пошлем, рассмотренный нами выше, запрос GET еще раз, но в этот раз сохраним принятое значение с объектом ответа в переменной, и затем поближе познакомиться с его атрибутами (свойствами), а также поведением:
response = requests.get('https://api.github.com')
В этом примере кода, мы используя возможности библиотеки requests
, перехватили содержимое ответа удаленного сервера, возвращаемое методом get()
. И далее его значение, которое находится в экземпляре объекта Response
, сохраним в переменную с именем response
. Теперь мы можем использовать response
для того, чтобы получить различную информацию о результатах нашего GET запроса .
Код состояния запроса (status codes)
Первым битом информации, которую вы можете получить от объекта ответа Response
, является код состояния запроса к серверу status codes. Код состояния информирует вас о статусе нашего запроса.
Например, статус 200 OK
означает, что ваш запрос был успешным, а статус 404 NOT FOUND
означает, что искомый ресурс не найден. Существует много других кодов состояния запроса, которые могут дать вам более детальное представление о том, что же все таки произошло с отправленным запросом.
Используя свойство объекта response.status_code
, мы можем получить доступ к коду состояния ответа, который вернул удаленный сервер:
>>> response.status_code 200
При обращении к свойству .status_code
мы получили значение 200
, что означает, что наш запрос был успешным, и сервер отправил нам данные, которые мы запрашивали.
В большинстве случаев эту информацию мы будем использовать для реализации в своем коде различной логики: управлять дальнейшей работой нашего приложения:
if response.status_code == 200: print('Success!') elif response.status_code == 404: print('Not Found.')
В соответствии с логикой этого примера кода, если сервер возвращает код состояния 200
, то наша программа напечатает «Success!». Если же — 404
, то напечатает “Not Found”.
Библиотека requests
существенно упрощает процесс взаимодействия вашего приложения с сервером. Однако необходимо знать ее некоторые довольно специфические особенности. Так например, если мы используем экземпляр объекта Response
в условном выражении, то его логическое значение приравнивается к True
, если был получен код состояния запроса в диапазоне от 200
до 400
, и только в противном случае False
.
Поэтому мы можем упростить последний пример, переписав код оператора if
следующим образом:
if response: print('Success!') else: print('An error has occurred.')
Маленькая техническая деталь: этот тест на истинность значения показал такой результат возможным, так как в объекте
Response
специальный метод класса__bool__()
переопределен.Это означает то, что поведение по умолчанию объекта
Response
при вычислении его логического значения было переопределено для процедуры проверки кода состояния запроса.
Имейте в виду, что этот способ проверки не гарантирует, что код состояния вашего запроса успешен и равен 200
. Причиной прохождения проверки на истинность является то, что запрос может получать и другие “успешные” коды состояния в диапазоне от 200 до 400. Такие, например, как 204 NO CONTENT
и 304 NOT MODIFIED
, которые также можно считать в определенном смысле “успешными”, так как они определяют некоторый успешно обработанный сервером ответ на запрос. Например, код 204
информирует нас, что запрос был успешным, но тело сообщения ответа сервера не ничего не содержит.
Поэтому используете этот способ проверки, если захотите узнать, был ли запрос в целом успешным, а только затем, при необходимости, обработать содержимое ответа соответствующим образом на основе значения его кода состояния.
Допустим, вы не хотите проверять код состояния ответа в операторе if
. Вместо этого вы можете генерировать исключение специального типа HTTPError
, если запрос был неудачен. Сделать это можно используя метод .raise_for_status()
следующим образом:
import requests from requests.exceptions import HTTPError for url in ['https://api.github.com', 'https://api.github.com/invalid']: try: response = requests.get(url) # Если запрос был успешен, то исключение Exception не возбуждается response.raise_for_status() except HTTPError as http_err: print(f'HTTP error occurred: {http_err}') # Python 3.6 except Exception as err: print(f'Other error occurred: {err}') # Python 3.6 else: print('Success!')
Таким образом, если вы вызываете метод .raise_for_status()
, то исключение типа HTTPError
будет вызываться для определенных кодов состояния (не успешных запросов к серверу). И если код состояния определяет запрос как успешный, то приложение продолжит работу без возбуждения этого исключения.
Для дальнейшего чтения: если вам не знакомы f-строки Python 3.6, то я призываю вас срочно познакомится с ими, поскольку они являются отличным способом упростить использование строковых шаблонов.
И так мы изучили некоторые приемы работы с кодами состояния ответов сервера. Однако, когда мы посылаем GET запрос, нам редко нужна информация лишь о коде состояния. Обычно мы хотим получить нечто больше. Далее мы научимся получать и обрабатывать содержимое данных, которые сервер отправляет обратно в теле ответа.
Содержимое ответа (Content)
Ответ на успешный запрос GET часто содержит в сообщении некоторую ценную информацию, известную так же, как полезное содержимое (payload). Используя атрибуты и методы объекта Response
, мы можем просматривать его содержимое в различных форматах.
Чтобы получить содержимое ответа в бинарном виде, мы можем использовать свойство Response.content
:
>>> response = requests.get('https://api.github.com') >>> response.content b'{"current_user_url":"https://api.github.com/user","current_user_authorizations_html_url":"https://github.com/settings/connections/applications{/client_id}","authorizations_url":"https://api.github.com/authorizations","code_search_url":"https://api.github.com/search/code?q={query}{&page,per_page,sort,order}","commit_search_url":"https://api.github.com/search/commits?q={query}{&page,per_page,sort,order}","emails_url":"https://api.github.com/user/emails","emojis_url":"https://api.github.com/emojis","events_url":"https://api.github.com/events","feeds_url":"https://api.github.com/feeds","followers_url":"https://api.github.com/user/followers","following_url":"https://api.github.com/user/following{/target}","gists_url":"https://api.github.com/gists{/gist_id}","hub_url":"https://api.github.com/hub","issue_search_url":"https://api.github.com/search/issues?q={query}{&page,per_page,sort,order}","issues_url":"https://api.github.com/issues","keys_url":"https://api.github.com/user/keys","notifications_url":"https://api.github.com/notifications","organization_repositories_url":"https://api.github.com/orgs/{org}/repos{?type,page,per_page,sort}","organization_url":"https://api.github.com/orgs/{org}","public_gists_url":"https://api.github.com/gists/public","rate_limit_url":"https://api.github.com/rate_limit","repository_url":"https://api.github.com/repos/{owner}/{repo}","repository_search_url":"https://api.github.com/search/repositories?q={query}{&page,per_page,sort,order}","current_user_repositories_url":"https://api.github.com/user/repos{?type,page,per_page,sort}","starred_url":"https://api.github.com/user/starred{/owner}{/repo}","starred_gists_url":"https://api.github.com/gists/starred","team_url":"https://api.github.com/teams","user_url":"https://api.github.com/users/{user}","user_organizations_url":"https://api.github.com/user/orgs","user_repositories_url":"https://api.github.com/users/{user}/repos{?type,page,per_page,sort}","user_search_url":"https://api.github.com/search/users?q={query}{&page,per_page,sort,order}"}'
Несмотря на то, что свойство Response.content
предоставляет нам доступ к “сырым” байтам полезного содержимого ответа и в большинстве случаев мы будем преобразовывать их в строку с заданной кодировкой символов, например, UTF-8. Объект Response
легко сделает это для нас, предоставляя доступ к свойству Response.text
:
>>> response.text '{"current_user_url":"https://api.github.com/user","current_user_authorizations_html_url":"https://github.com/settings/connections/applications{/client_id}","authorizations_url":"https://api.github.com/authorizations","code_search_url":"https://api.github.com/search/code?q={query}{&page,per_page,sort,order}","commit_search_url":"https://api.github.com/search/commits?q={query}{&page,per_page,sort,order}","emails_url":"https://api.github.com/user/emails","emojis_url":"https://api.github.com/emojis","events_url":"https://api.github.com/events","feeds_url":"https://api.github.com/feeds","followers_url":"https://api.github.com/user/followers","following_url":"https://api.github.com/user/following{/target}","gists_url":"https://api.github.com/gists{/gist_id}","hub_url":"https://api.github.com/hub","issue_search_url":"https://api.github.com/search/issues?q={query}{&page,per_page,sort,order}","issues_url":"https://api.github.com/issues","keys_url":"https://api.github.com/user/keys","notifications_url":"https://api.github.com/notifications","organization_repositories_url":"https://api.github.com/orgs/{org}/repos{?type,page,per_page,sort}","organization_url":"https://api.github.com/orgs/{org}","public_gists_url":"https://api.github.com/gists/public","rate_limit_url":"https://api.github.com/rate_limit","repository_url":"https://api.github.com/repos/{owner}/{repo}","repository_search_url":"https://api.github.com/search/repositories?q={query}{&page,per_page,sort,order}","current_user_repositories_url":"https://api.github.com/user/repos{?type,page,per_page,sort}","starred_url":"https://api.github.com/user/starred{/owner}{/repo}","starred_gists_url":"https://api.github.com/gists/starred","team_url":"https://api.github.com/teams","user_url":"https://api.github.com/users/{user}","user_organizations_url":"https://api.github.com/user/orgs","user_repositories_url":"https://api.github.com/users/{user}/repos{?type,page,per_page,sort}","user_search_url":"https://api.github.com/search/users?q={query}{&page,per_page,sort,order}"}'
Поскольку для декодирования байтов bytes
в строку str
требуется схема кодирования, то requests
в начале попытается угадать кодировку содержимого ответа на основе его заголовков Content-Type, в том если вы предварительно их не укажете. Вы также можете указать кодировку явно, установив значение свойства объекта Response.encoding
перед обращением к Response.text
:
>>> response.text '{"current_user_url":"https://api.github.com/user","current_user_authorizations_html_url":"https://github.com/settings/connections/applications{/client_id}","authorizations_url":"https://api.github.com/authorizations","code_search_url":"https://api.github.com/search/code?q={query}{&page,per_page,sort,order}","commit_search_url":"https://api.github.com/search/commits?q={query}{&page,per_page,sort,order}","emails_url":"https://api.github.com/user/emails","emojis_url":"https://api.github.com/emojis","events_url":"https://api.github.com/events","feeds_url":"https://api.github.com/feeds","followers_url":"https://api.github.com/user/followers","following_url":"https://api.github.com/user/following{/target}","gists_url":"https://api.github.com/gists{/gist_id}","hub_url":"https://api.github.com/hub","issue_search_url":"https://api.github.com/search/issues?q={query}{&page,per_page,sort,order}","issues_url":"https://api.github.com/issues","keys_url":"https://api.github.com/user/keys","notifications_url":"https://api.github.com/notifications","organization_repositories_url":"https://api.github.com/orgs/{org}/repos{?type,page,per_page,sort}","organization_url":"https://api.github.com/orgs/{org}","public_gists_url":"https://api.github.com/gists/public","rate_limit_url":"https://api.github.com/rate_limit","repository_url":"https://api.github.com/repos/{owner}/{repo}","repository_search_url":"https://api.github.com/search/repositories?q={query}{&page,per_page,sort,order}","current_user_repositories_url":"https://api.github.com/user/repos{?type,page,per_page,sort}","starred_url":"https://api.github.com/user/starred{/owner}{/repo}","starred_gists_url":"https://api.github.com/gists/starred","team_url":"https://api.github.com/teams","user_url":"https://api.github.com/users/{user}","user_organizations_url":"https://api.github.com/user/orgs","user_repositories_url":"https://api.github.com/users/{user}/repos{?type,page,per_page,sort}","user_search_url":"https://api.github.com/search/users?q={query}{&page,per_page,sort,order}"}'
Если вы обратите внимание на полученное содержимое ответа, то увидите, что оно представляет собой сериализованную строку в формате JSON. Поэтому для того, чтобы в результате получить словарь, вы можете взять строку str
, полученную из свойства Response.text
, и десериализовать ее с помощью метода json.loads()
. Однако более простой способ выполнить эту задачу — использовать метод нашего объекта Response.json()
:
>>> response.json() {'current_user_url': 'https://api.github.com/user', 'current_user_authorizations_html_url': 'https://github.com/settings/connections/applications{/client_id}', 'authorizations_url': 'https://api.github.com/authorizations', 'code_search_url': 'https://api.github.com/search/code?q={query}{&page,per_page,sort,order}', 'commit_search_url': 'https://api.github.com/search/commits?q={query}{&page,per_page,sort,order}', 'emails_url': 'https://api.github.com/user/emails', 'emojis_url': 'https://api.github.com/emojis', 'events_url': 'https://api.github.com/events', 'feeds_url': 'https://api.github.com/feeds', 'followers_url': 'https://api.github.com/user/followers', 'following_url': 'https://api.github.com/user/following{/target}', 'gists_url': 'https://api.github.com/gists{/gist_id}', 'hub_url': 'https://api.github.com/hub', 'issue_search_url': 'https://api.github.com/search/issues?q={query}{&page,per_page,sort,order}', 'issues_url': 'https://api.github.com/issues', 'keys_url': 'https://api.github.com/user/keys', 'notifications_url': 'https://api.github.com/notifications', 'organization_repositories_url': 'https://api.github.com/orgs/{org}/repos{?type,page,per_page,sort}', 'organization_url': 'https://api.github.com/orgs/{org}', 'public_gists_url': 'https://api.github.com/gists/public', 'rate_limit_url': 'https://api.github.com/rate_limit', 'repository_url': 'https://api.github.com/repos/{owner}/{repo}', 'repository_search_url': 'https://api.github.com/search/repositories?q={query}{&page,per_page,sort,order}', 'current_user_repositories_url': 'https://api.github.com/user/repos{?type,page,per_page,sort}', 'starred_url': 'https://api.github.com/user/starred{/owner}{/repo}', 'starred_gists_url': 'https://api.github.com/gists/starred', 'team_url': 'https://api.github.com/teams', 'user_url': 'https://api.github.com/users/{user}', 'user_organizations_url': 'https://api.github.com/user/orgs', 'user_repositories_url': 'https://api.github.com/users/{user}/repos{?type,page,per_page,sort}', 'user_search_url': 'https://api.github.com/search/users?q={query}{&page,per_page,sort,order}'}
Отлично, тип возвращаемого методом Response.json()
значения словарь, поэтому мы можем, как обычно, получить доступ к его значениям по соответствующему ключу.
Вы можете по разному использовать информацию о коде состояния и содержимое полученного ответа сервера, но если нам нужна дополнительная информация, такая, например, как метаданные о самом ответе, то вам нужно иметь дело с заголовками полученного ответа.
Заголовки ответа (Headers)
Заголовки ответа сервера могут дать много полезной информации, такой, например, как тип полезного содержимого ответа, ограничение по времени, в течение которого ответ будет кэшироваться и т.д. Чтобы просмотреть содержимое заголовков, необходимо обратиться к свойству объекта Response.headers
:
>>> response.headers {'Server': 'GitHub.com', 'Date': 'Mon, 10 Dec 2018 17:49:54 GMT', 'Content-Type': 'application/json; charset=utf-8', 'Transfer-Encoding': 'chunked', 'Status': '200 OK', 'X-RateLimit-Limit': '60', 'X-RateLimit-Remaining': '59', 'X-RateLimit-Reset': '1544467794', 'Cache-Control': 'public, max-age=60, s-maxage=60', 'Vary': 'Accept', 'ETag': 'W/"7dc470913f1fe9bb6c7355b50a0737bc"', 'X-GitHub-Media-Type': 'github.v3; format=json', 'Access-Control-Expose-Headers': 'ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type', 'Access-Control-Allow-Origin': '*', 'Strict-Transport-Security': 'max-age=31536000; includeSubdomains; preload', 'X-Frame-Options': 'deny', 'X-Content-Type-Options': 'nosniff', 'X-XSS-Protection': '1; mode=block', 'Referrer-Policy': 'origin-when-cross-origin, strict-origin-when-cross-origin', 'Content-Security-Policy': "default-src 'none'", 'Content-Encoding': 'gzip', 'X-GitHub-Request-Id': 'E439:4581:CF2351:1CA3E06:5C0EA741'}
При обращении к свойству Response.headers
будет возвращен схожий со словарем объект, позволяющий получить доступ к значениям заголовков полученного ответа по ключу. Например, чтобы определить тип полезного содержимого ответа, получаем доступ к значению заголовка Content-Type
:
>>> response.headers['Content-Type'] 'application/json; charset=utf-8'
У этого, как мы уже говорили, схожим со словарем объекте заголовков есть еще одна особенность. Спецификация HTTP определяет названия заголовков без учета регистра, это означает, что мы можем получить доступ к их значениям, совершенно не беспокоясь об регистре их наименований:
>>> response.headers['content-type'] 'application/json; charset=utf-8'
Используете ли вы ключ 'content-type'
или 'Content-type'
, вы получите одно и то же корректное значение.
Теперь когда мы ознакомились с основами работы с объектом Response
, рассмотрели его наиболее полезные свойства и методы в действии. Давайте сделаем шаг назад и посмотрим, как изменяются ответы сервера при использовании параметров запроса GET , передаваемых в строке запроса.
Параметры строки запроса (Query String Parameters)
Одним из самых распространенных способов настройки запроса GET является передача серверу данных в URL строки запроса.
Обратите внимание, что GET запрос не имеет тела сообщения. Но, это не означает, что с его помощью мы не можем передать серверу никакую информацию. Это можно делать с помощью специальных GET параметров. Чтобы добавить GET параметры к запросу, нужно в конце URL-адреса поставить знак
?
и после него начинать задавать их по следующему правилу:имя_параметра1=значение_параметра1&имя_параметра2=значение_параметра2
. Разделителем между параметрами служит знак&
.
Для передачи GET параметров запроса необходимо передать нужную информацию в именованный параметр params
метода get()
. Например, следующим способом вы можете использовать Search API GitHub для более узкого поиска репозитория библиотеки requests
:
import requests # Поиск requests в репозиториях GitHub response = requests.get( 'https://api.github.com/search/repositories', params={'q': 'requests+language:python'}, ) # Просматриваем значения атрибутов результатов поиска репозитория requests json_response = response.json() repository = json_response['items'][0] print(f'Repository name: {repository["name"]}') # Python 3.6+ print(f'Repository description: {repository["description"]}') # Python 3.6+
Передав словарь {'q': 'requests+language: python'}
в качестве значения параметра params
метода .get()
, мы таким образом скорректировали результаты, возвращаемые Search API.
Мы можем передавать значения в params
метода get()
как в виде словаря, как мы это только что сделали, так и в виде списка кортежей:
>>> requests.get( ... 'https://api.github.com/search/repositories', ... params=[('q', 'requests+language:python')], ... )
Так же можно передать данные в бинарном виде bytes
:
>>> requests.get( ... 'https://api.github.com/search/repositories', ... params=b'q=requests+language:python', ... )
Строка запроса используется для передачи параметров в GET запросах. Еще одним способом управления запросами к удаленным службам, является добавление или изменение отправляемых в них заголовков.
Заголовки запроса (Request Headers)
Для того, чтобы используя requests
, настроить содержимое заголовков запроса, необходимо передать словарь с соответствующими ключами и значениями HTTP-заголовков в метод .get()
, а точнее в его именованный параметр headers
. Например, изменим свой предыдущий поисковый запрос: добавим ключевые слова для уточнения результатов поиска, указав точно text-match
текстовое соответствие для поиска (тип контента) в заголовок Accept
:
import requests response = requests.get( 'https://api.github.com/search/repositories', params={'q': 'requests+language:python'}, headers={'Accept': 'application/vnd.github.v3.text-match+json'}, ) # выведем в консоли массив всех совпадений `text-matches`, # которые мы задали для уточнения результатов поиска json_response = response.json() repository = json_response['items'][0] print(f'Text matches: {repository["text_matches"]}')
Заголовок Accept
сообщает серверу, какие типы контента может обрабатывать ваше приложение. В нашем случае, поскольку вы хотите, что бы учитывались дополнительные уточняющие параметры поиска, вы устанавливаете для этого заголовка значение application / vnd.github.v3.text-match + json
. Это значение является проприетарным для заголовка Accept
, то есть зарезервированным GitHub для подобных случаев, и указывает на поиск содержимого в формате JSON.
Прежде чем мы ознакомимся с другими способами настройки ваших запросов, давайте слегка расширим свой кругозор, изучив другие HTTP методы, а точнее как с ними работать в requests
.
Другие HTTP методы запроса
Помимо GET существуют и другие часто используемые HTTP методы, например, POST, PUT, DELETE, HEAD, PATCH и OPTIONS. И библиотека requests
ожидаемо предоставляет методы, со схожей как у метода get()
нотацией использования, для отправки и управления настройками каждого из этих HTTP методов запросов:
>>> requests.post('https://httpbin.org/post', data={'key':'value'}) >>> requests.put('https://httpbin.org/put', data={'key':'value'}) >>> requests.delete('https://httpbin.org/delete') >>> requests.head('https://httpbin.org/get') >>> requests.patch('https://httpbin.org/patch', data={'key':'value'}) >>> requests.options('https://httpbin.org/get')
В примере ниже каждый вызов соответствующего метода посылает запрос сервису httpbin
, используя различные HTTP методы. И мы так же, как и ранее можем просмотреть содержимое их ответов:
>>> response = requests.head('https://httpbin.org/get') >>> response.headers['Content-Type'] 'application/json' >>> response = requests.delete('https://httpbin.org/delete') >>> json_response = response.json() >>> json_response['args'] {}
Содержимое заголовков, полезного содержимого ответов, коды состояния и многие другие данные возвращаются с объектом Response
для каждого вашего запроса, отправленного любым из методов.
Тело сообщения запроса
В соответствии со спецификацией HTTP протоколов, такие методы отправки запросов как POST, PUT, а так же другие менее распространенные, например PATCH, передают информацию в содержимом сообщения, а не через параметры строки запроса. Используя requests
, вы можете передать полезную информацию в сообщении запроса через именованный параметр data
соответствующего метода отправки.
Таким образом все отправляемые данные содержатся в самом теле вашего запроса. Кроме того, методом POST на удаленный сервер нередко загружаются файлы.
Параметр data
принимает словарь, список кортежей, байтов или файлоподобный объект. В каком конкретно виде посылать данные в теле запроса вы должны выбрать самостоятельно, исходя из требований удаленной службы (сервера) с которой хотите взаимодействовать.
Например, если тип содержимого вашего запроса application / x-www-form-urlencoded
, то вы можете отправить данные html формы в виде словаря:
>>> requests.post('https://httpbin.org/post', data={'key':'value'})
Вы можете отправить те же данные в виде списка кортежей следующим образом:
>>> requests.post('https://httpbin.org/post', data=[('key', 'value')])
В теле сообщения значения кодируются в виде кортежа с ключами, разделенными символом
'&'
, и'='
между ключом и значением.
Если вам необходимо отправить данные в формате JSON, то вы должны использовать для передачи соответствующий именованный параметр json
. При отправке запроса с содержимым JSON библиотека requests
сама сериализует ваши данные и добавит соответствующее значение для заголовка Content-Type
.
httpbin.org — отличный вспомогательный ресурс, созданный автором requests
Кеннетом Рейтцем. Это сервис принимает тестовые запросы и отправляет в ответ информацию о них. Например, вы можете использовать его для проверки корректности вашего POST-запроса:
>>> response = requests.post('https://httpbin.org/post', json={'key':'value'}) >>> json_response = response.json() >>> json_response['data'] '{"key": "value"}' >>> json_response['headers']['Content-Type'] 'application/json'
Из содержимого ответа видно, что сервер успешно обработал данные вашего запроса и послал ответ с соответствующими заголовками. Библиотека requests
предоставляет возможность сохранять настройки посылаемых запросов в виде специального объекта PreparedRequest
.
Инспектируем отправленный запрос
Когда вы делаете запрос, библиотека requests
предварительно готовит запрос, прежде чем отправить его на целевой сервер. Подготовка запроса включает в себя такие вещи, как проверка корректности заголовков и сериализация содержимого в формате JSON.
Вы можете просмотреть содержимое подготовленного к отправке запроса, используя объект типа PreparedRequest
, обратившись к свойству объекта Response.request
:
>>> response = requests.post('https://httpbin.org/post', json={'key':'value'}) >>> response.request.headers['Content-Type'] 'application/json' >>> response.request.url 'https://httpbin.org/post' >>> response.request.body b'{"key": "value"}'
Просмотр содержимого PreparedRequest
дает вам доступ ко всей информации о выполненном запросе, такой как полезное содержимое, URL, отправленные заголовки, данные аутентификации и многое другое.
В соответствии с официальной документацией объект типа PreparedRequest
может использоваться и до отправки запроса. Так в следующим примере кода вначале с помощью конструктора Request(method, url, data=data, headers=headers)
создается объект запроса, а затем с использованием метода Request.prepare()
создается подготовленный запрос. Объект PreparedRequest
может в дальнейшем, перед своей непосредственно отправкой, редактироваться в соответствии с логикой вашего приложения, а только затем отправлен с вызовом метода .send()
.
from requests import Request, Session s = Session() req = Request('POST', url, data=data, headers=headers) prepped = req.prepare() prepped.body = 'No, I want exactly this as the body.' del prepped.headers['Content-Type'] resp = s.send(prepped, stream=stream, verify=verify, proxies=proxies, cert=cert, timeout=timeout ) print(resp.status_code)
В этом примере кода мы можем заметить не знакомую нам строку кода: s = Session()
. Дело в том, что в этом примере подготовленный запрос использовался для работы с сессионными cookie. В оригинале статьи автор почему-то опустил эту немаловажную возможность библиотеки requests
: использование механизма сессий для работы с удаленными службами. Поэтому в следующем разделе мы с ним просто обязаны познакомиться ;).
Использование механизма сессий (Session)
Для организации сессионного взаимодействия вашего приложения с сервером используется объект Session
, который позволяет сохранять необходимые настройки для отправки данных от запроса к запросу в течение всей текущей сессии. То есть он сохраняет и задает содержимое файлов cookie во всех запросах, сформированных с использованием текущего экземпляра объекта Session
, и гарантирует использование выделенного пула соединений при дальнейшем взаимодействии с сервером. Объект Sesson
поддерживает любые методы запросов из состава API requests
.
И так давайте сохраним некоторые данные cookie в запросе:
import requests s = requests.Session() s.get('https://httpbin.org/cookies/set/sessioncookie/123456789') r = s.get('https://httpbin.org/cookies') print(r.text) # '{"cookies": {"sessioncookie": "123456789"}}'
В примере кода выше мы обратились к API httpbin.org для установки и поддержания соединения в течение сессии с указанным значением sessioncookie
.
Механизм сессий также можно применять для предварительной установки значений некоторых настроек по умолчанию и дальнейшем их использовании при отправке запросов. Это делается путем изменения соответствующих свойств объекта Session
:
import requests s = requests.Session() s.auth = ('user', 'pass') s.headers.update({'x-test': 'true'}) # both 'x-test' and 'x-test2' are sent s.get('https://httpbin.org/headers', headers={'x-test2': 'true'})
Все данные, которые вы затем будете передавать методу запроса, будут объединены с установленными значениями текущего сеанса сессии. При этом те параметры, которые вы будете передавать в метод запроса переопределят параметры сеанса сессии, которые мы задали по умолчанию.
Обратите внимание, что параметры, которые мы передаем в метод перед отправкой, не будут сохраняться между отдельными запросами, даже при работе в текущем сеансе сессии, даже если мы используем один и тот же тип метода запроса (в примере ниже это GET запрос). Как видно из результатов выполнения кода из примера ниже: мы отправляем данные cookie
, передавая их методу отправки, только с первым запросом GET, но не со вторым:
import requests s = requests.Session() r = s.get('https://httpbin.org/cookies', cookies={'from-my': 'browser'}) print(r.text) # '{"cookies": {"from-my": "browser"}}' r = s.get('https://httpbin.org/cookies') print(r.text) # '{"cookies": {}}'
Если вы хотите непосредственно управлять файлами cookie в течение текущего сеанса, используйте методы утилиты объекта Cookie, для установки соответствующих свойств объекта Session.cookies
. Следующий пример кода иллюстрирует способ посылки двух запросов в течение сессии с заданными значениями файлов cookies
:
import requests s = requests.Session() s.cookies.set('from-my', 'browser') r = s.get('https://httpbin.org/cookies') print(r.text) # '{"cookies": {"from-my": "browser"}}' r = s.get('https://httpbin.org/cookies') print(r.text) # '{"cookies": {"from-my": "browser"}}'
И так мы рассмотрели несколько разновидностей отправляемых запросов, однако у них есть одно ограничение: они не позволяют отправлять аутентифицированные запросы к публичным API. Многие удаленные службы, с которыми вы можете столкнуться, требуют, чтобы вы каким-либо образом предоставляли аутентификационную информацию о себе.
Аутентификация
Как вам уже, вероятно, известно аутентификация по сути помогает удаленному сервису понять, кто вы. Как правило, вы предоставляете серверу свои учетные данные, передавая их через содержимое заголовка авторизации Authorization
или любого другого пользовательского заголовка, заранее определенного удаленной службой. Все методы отправки запросов, с которыми мы ознакомились ранее, предоставляют пользователю возможность передавать значение именованному параметру auth
, который позволяет передавать учетные данные для авторизации.
Одним из примеров такого API, взаимодействие с которым требуется аутентификация, является GitHub Authenticated User API. Эта точка доступа сервиса GitHub предоставляет информацию о профиле зарегистрированных пользователей. Чтобы сделать запрос к API авторизованного пользователя, вы можете передать свои имя пользователя GitHub и пароль в метод get()
, предварительно упаковав их в кортеж:
>>> from getpass import getpass >>> requests.get('https://api.github.com/user', auth=('username', getpass()))
Запрос будет успешно обработан сервером, при условии что учетные данные, которые вы передали в кортеже параметру auth
, действительны. Если же вы попытаетесь передать этот запрос без учетных данных, то увидите, что результатом его выполнения будет получение ответа с кодом состояния 401 Unauthorized
:
>>> requests.get('https://api.github.com/user')
Когда вы передаете параметру auth
свои данные (имя пользователя и пароль) , при отправке запроса библиотека requests
учитывает эти учетные данные, используя основную схему аутентификации доступа Basic access authentication scheme HTTP протокола.
Следующим способом с помощью объекта HTTPBasicAuth
, вы можете послать тот же запрос, но уже явно передав свои учетные данные, используя механизм Basic authentication:
>>> from requests.auth import HTTPBasicAuth >>> from getpass import getpass >>> requests.get( ... 'https://api.github.com/user', ... auth=HTTPBasicAuth('username', getpass()) ... )
В общем случае можно конечно явно не указывать Basic authentication в качестве основного способа аутентификации при отправке вашего запроса, вы можете реализовать эту процедуру любым другим способом. requests
так же предоставляет другие методы аутентификации “из коробки”, например, дайджест-аутентификация HTTPDigestAuth
или прокси-аутентификация HTTPProxyAuth
.
Вы даже можете разработать свой собственный механизм аутентификации с удаленной службой и затем использовать его в ваших приложениях. Для этого вы должны сначала создать подкласс от основного AuthBase
, а затем реализовать в объявлении своего класса собственный метод __call __ ()
:
import requests from requests.auth import AuthBase class TokenAuth(AuthBase): """Реализуем собственный способ аутентификации""" def __init__(self, token): self.token = token def __call__(self, r): """Подключаем к токену API свой заголовок аутентификации""" r.headers['X-TokenAuth'] = f'{self.token}' # Python 3.6+ return r requests.get('https://httpbin.org/get', auth=TokenAuth('12345abcde-token'))
Здесь ваш настраиваемый механизм TokenAuth
получает токен, а затем включает этот токен в заголовок X-TokenAuth
вашего запроса.
При реализации собственных способов аутентификации помните, что простые механизмы проверки подлинности могут привести к уязвимостям безопасности, поэтому, если удаленной службе по какой-либо причине не требуется настраиваемый механизм проверки подлинности, всегда используйте проверенные схемы аутентификации, такие как Basic authentication или OAuth.
И так пока мы задумались о безопасности, давайте рассмотрим вопросы применения в ваших запросах SSL-сертификатов.
Проверка подлинности SSL сертификата (SSL Certificate Verification)
Всякий раз, когда данные, которые вы пытаетесь отправить или получить, являются конфиденциальными, вы начинаете думать о вопросах их безопасности. Вы общаетесь с защищенными сайтами используя протокол HTTP, устанавливая шифрованное соединение с использованием SSL, это означает, что проверка SSL-сертификата целевого сервера имеет решающее значение.
Хорошей новостью является то, что используя в ваших проектах библиотеку requests
, она делает это для вас по умолчанию. Тем не менее в некоторых случаях вы можете захотеть изменить или немножко подкорректировать это поведение.
И так если вы захотите отключить проверку SSL-сертификата на удаленном сервере, достаточно передать значение False
в именованный параметр verify
:
>>> requests.get('https://api.github.com', verify=False) InsecureRequestWarning: Unverified HTTPS request is being made. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings InsecureRequestWarning
Запрос успешен, и как мы видим, requests
даже предупреждают вас о том, что вы отправили небезопасный запрос, чтобы помочь вам сохранить ваши данные в безопасности!
Примечание:
requests
использует другой отдельный пакетcertifi
для предоставления сертификатов. Это позволяет библиотекеrequests
знать, каким организациям, выпускающим сертификаты, можно доверять. Поэтому вам следует как можно чаще обновлять пакетcertifi
, установленный в вашей системе, чтобы обеспечить максимальную безопасность ваших соединений.
Производительность
При использовании requests
, особенно при работе ваших приложений непосредственно в production environment среде, важно учитывать влияние производительности. Такие возможности приложения, как контроль над времени ожидания ответа сервера, сессиями и ограничение повторных попыток обращения к удаленным службам, помогут обеспечить бесперебойную и устойчивую работу вашего приложения.
Тайм-ауты Timeouts
Допустим ваше приложение отправляет запрос к некоторой удаленной службе, далее основной поток выполнения кода будет приостановлен, пока приложение будет дождаться ответа на отправленный запрос, и лишь после его получения продолжит свою работу. Конечно, если ваше приложение будет слишком долго ожидать ответ от сервера, то это может привести к следующим негативным последствиям: незавершенные запросы к удаленным службам могут автоматически сохраняться и накапливаться в памяти, пострадает отзывчивость интерфейса приложения на действия пользователя, а фоновые задания могут просто зависнуть.
Уточнение для тех, кто пока не сталкивался с этой проблемой. При выполнении синхронного кода каждая операция ожидает окончания предыдущей, то есть код выполняется строго последовательно в одном потоке. Поэтому приложение может зависнуть, если какая-то операция выполняется слишком долго.
Асинхронный код убирает блокирующую операцию из основного потока программы в отдельный, так что она продолжает выполняться, но в другой области памяти, а основной поток продолжает выполнение кода приложения.
По умолчанию requests
будет ждать ответа на запрос от сервера до бесконечности, поэтому рекомендуется практически во всех случаях указывать интервал ожидания, чтобы избежать всех, перечисленных выше, негативных последствий. Чтобы установить интервал времени ожидания запроса, используйте именованный параметр timeout
. Значение timeout
может быть целым числом или числом с плавающей запятой, задающее количество секунд ожидания ответа от сервера до истечения времени ожидания:
>>> requests.get('https://api.github.com', timeout=1)>>> requests.get('https://api.github.com', timeout=3.05)
В первом запросе время ожидания истекает через 1 секунду. Во втором — через 3,05 секунды.
Рекомендуется устанавливать
timeout
чуть больше 3 секунд, что определяется величиной кратной длительности по умолчанию окна повторной передачи TCP-пакетов.
В качестве значения параметра timeout
можно так же передать кортеж, состоящий из двух значений (connect
, read
). Его первый элемент connect
определяет тайм-аут соединения (время, которое выделяется на установление соединения с удаленным сервером), а второй read
— тайм-аут чтения (время, ожидания ответа от сервера, после того, как соединение было установлено):
>>> requests.get('https://api.github.com', timeout=(2, 5))
И так если наш запрос устанавливает соединение с сервером в течение 2 секунд и получает от него данные в течение 5 секунд после установления соединения, то содержимое ответа сервера будет возвращено, как это было и раньше. Если же время ожидания истекло, то будет генерироваться исключение типа Timeout
:
import requests from requests.exceptions import Timeout try: response = requests.get('https://api.github.com', timeout=1) except Timeout: print('The request timed out') else: print('The request did not time out')
Таким образом, при необходимости код вашего приложения может перехватить исключение Timeout
и обработать его соответствующим образом.
Объект сессии Session
До сих пор мы имели дело с сетевыми интерфейсами запросов и API высокого уровня, такими как методы get()
и post()
. Эти методы абстрагирует пользователя от того, что происходит “под капотом” библиотеки, когда мы отправляем запросы. Они скрывают детали реализации, такие как управление соединениями, так что нам не приходится об этом задумывать при написании своих приложений.
Одну из основных ролей при абстрагировании пользователя от деталей работы библиотеки, играет класс Session
. Если вы хотите осуществлять непосредственный контроль над выполнением запросов, а так же повысить их производительность, то вам необходимо использовать все возможности экземпляров класса Session
.
Как мы уже знаем, сессии используются для сохранения некоторого набора настроек от запроса к запросу при обращении к какое-либо удаленной службе. Например, если вы хотите использовать одни и те же данные аутентификации в течение определенного периода времени для отправки нескольких запросов, то можете использовать сессии следующим образом:
import requests from getpass import getpass # Используем менеджер контекста так как в случае завершении работы сессии # все использующиеся ресурсы будут автоматически освобождены with requests.Session() as session: session.auth = ('username', getpass()) # вместо requests.get(), используем session.get() response = session.get('https://api.github.com/user') # просматриваем содержимое ответа print(response.headers) print(response.json())
Каждый раз, когда вы делаете запрос в течение сессии, начиная с того момента, как он был инициализирован с вашими учетными данными аутентификации, эти данные будут сохраняться в течении всей сессии, то есть пока вы используете текущий экземпляр класса Session
.
Первичная оптимизация производительности с использованием механизма сессий происходит по причине того, что устанавливается постоянное соединение с сервером. Когда ваше приложение устанавливает соединение с сервером, используя сессии, происходит сохранение текущего соединения в общем пуле сервера. И когда ваше приложение снова захочет подключиться к тому же серверу, то будет повторно использоваться соединение из пула, а не устанавливать новое. Это позволяет сократить время обращения за данными к удаленной службе, что конечно же отражается на производительности вашего приложения.
Количество повторов запроса
В случае если ваш запрос по той или иной причине был неудачен, то вы можете указать приложению повторить тот же запрос заданное число раз. Однако requests
не будет это делать для вас по умолчанию. Чтобы использовать эту возможность, необходимо реализовать свой так называемый транспортный адаптер Transport Adapter.
Транспортные адаптеры позволяют вам определить набор конфигураций (настроек) для взаимодействия с конкретной удаленной службой. Допустим, вы хотите, чтобы все запросы к точке доступа https://api.github.com повторялись три раза, прежде чем, наконец, будет генерироваться исключение типа ConnectionError
.
import requests from requests.adapters import HTTPAdapter from requests.exceptions import ConnectionError github_adapter = HTTPAdapter(max_retries=3) session = requests.Session() # Используйте `github_adapter` для всех запросов к конечным точкам, # которые начинаются с этого URL session.mount('https://api.github.com', github_adapter) try: session.get('https://api.github.com') except ConnectionError as ce: print(ce)
В тех случаях, когда вы используете объект github_adapter
класса HTTPAdapter
в текущей сессии, в объекте session
будут сохраняться заданные вами свойства конфигурации для каждого запроса, отправляемого по адресу https://api.github.com.
Вызов метода монтирования регистрирует новый экземпляр транспортного адаптера для указанного префикса (подстроки адреса). После его подключения любой HTTP-запрос, выполненный с использованием этой сессии, URL-адрес которого начинается с указанного префикса, будет использовать транспортный адаптер github_adapter
. При создании нового объекта класса HTTPAdapter
именованному параметру max_retries
передается значение 3
, количество повторов запроса, после чего будет генерироваться исключение ConnectionError
. С другими настройками транспортных адаптеров, а так же, для каких целей могут использоваться, вы можете ознакомиться в официальной документации.
Заключение
И так мы прошли долгий путь в изучении возможностей Python библиотеки requests
.
Теперь вы умеете:
- Отправлять запросы, используя различные методы протокола HTTP, такие как GET, POST и т.д.
- Управлять отправляемыми запросами с помощью настроек, изменять их заголовки, данные аутентификации, строку запроса и полезное содержимое сообщений.
- Просматривать и обрабатывать данные, которые вы отправляете на сервер, а так же данные, которые сервер посылает вам обратно.
- Использовать процедуру проверки SSL-сертификатов в ходе взаимодействия с удаленным сервером.
- Эффективно использовать различные возможности
requests
для повышения эффективности и производительности ваших приложений, например,max_retries
,timeout
,Sessions
иTransport Adapters
.
Поскольку теперь мы познакомились, с использованием библиотеки requests
то, у вас появилась отличная возможность самостоятельно исследовать особенности взаимодействия с различными веб-сервисами и создавать потрясающие приложения, используя полезные данные, которые они предоставляют по вашим запросам.
Спасибо.