Библиотека Python HTTP Запросы Вероятно, моя любимая http-утилита на всех языках I программирует. Это просто, интуитивно понятно и повсеместно в сообществе Python. Большинство программ, которые взаимодействуют с HTTP используют либо запросы или URLLIB3 из стандартной библиотеки.
Хотя легко немедленно быть продуктивным с запросами из-за простых API, библиотека также предлагает расширяемость для расширенных случаев использования. Если вы пишете API-Heake Client или веб-скребок, вы, вероятно, понадобится допуску для сбоев сетевых, полезных отладки следов и синтаксический сахар.
Ниже приведена сводка функций, которые я нашел полезным в запросах при написании инструментов или программ Scraping Web Scraping, которые широко используют JSON API.
Оглавление
- Запросить крючки
- Настройка базовых URL-адресов
- Настройка тайм-аута по умолчанию
- Повторите попытку отказа
- Отладка HTTP-запросов
- Тестирование и издевательства запросов
- Повышение поведения браузера
Запросить крючки
Часто при использовании API третьей стороны вы хотите убедиться, что возвращенный ответ действительно действителен. Запросы предлагают помощник состязания ROING_FOR_STATUS ()
Что утверждает, что код состояния HTTP ответа не является 4xx или 5xx, то есть, что запрос не привел к клиенту или ошибку сервера.
Например
response = requests.get('https://api.github.com/user/repos?page=1') # Assert that there were no errors response.raise_for_status()
Это может получить повторение, если вам нужно ROING_FOR_STATUS ()
для каждого вызова. К счастью, библиотека запросов предлагает интерфейс «крючки», где вы можете прикрепить обратные вызовы на определенных частях процесса запроса.
Мы можем использовать крючки для обеспечения ROING_FOR_STATUS ()
вызывается для каждого объекта ответа.
# Create a custom requests object, modifying the global module throws an error http = requests.Session() assert_status_hook = lambda response, *args, **kwargs: response.raise_for_status() http.hooks["response"] = [assert_status_hook] http.get("https://api.github.com/user/repos?page=1") > HTTPError: 401 Client Error: Unauthorized for url: https://api.github.com/user/repos?page=1
Настройка базовых URL-адресов
Предположим, вы используете только один API, принимаемый на API.ORG. Вы получите повторение протокола и домена для каждого HTTP Call:
requests.get('https://api.org/list/') requests.get('https://api.org/list/3/item')
Вы можете сэкономить себе некоторые набрав, используя BaseUrlsession Отказ Это позволяет указывать базовый URL для клиента HTTP и указывать только путь ресурса во время запроса.
from requests_toolbelt import sessions http = sessions.BaseUrlSession(base_url="https://api.org") http.get("/list") http.get("/list/item")
Обратите внимание, что Запросы на панели инструментов Не включено в установку запросов по умолчанию, поэтому вам придется установить его отдельно.
Настройка тайм-аута по умолчанию
Документация запросов рекомендует Что вы устанавливаете тайм-ауты на весь производственный код. Если вы забыли установить Timeouts Timeouts, прозрачный сервер, может привести к зависанию вашего приложения, особенно с учетом большинства Python Code синхронно.
requests.get('https://github.com/', timeout=0.001)
Однако это становится повторяющимся и может привести к дальнейшему столу, когда вы понимаете, что кто-то забыл установить тайм-аут и остановил программу в производстве.
Использование Транспортные адаптеры Мы можем установить время ожидания по умолчанию для всех HTTP вызовов. Это гарантирует, что разумный тайм-аут устанавливается, даже если разработчик забывает добавить параметр к его индивидуальному вызову, но позволяет переопределить на основе за счет.
Ниже приведен пример пользовательского транспортного адаптера с тайм-аутами по умолчанию, вдохновленный Этот комментарий Github. . Мы отменяем конструктор, чтобы обеспечить время ожидания по умолчанию при построении HTTP-клиента и метода отправки (), чтобы убедиться, что время ожидания по умолчанию используется, если аргумент Timeout не предоставлен.
from requests.adapters import HTTPAdapter DEFAULT_TIMEOUT = 5 # seconds class TimeoutHTTPAdapter(HTTPAdapter): def __init__(self, *args, **kwargs): self.timeout = DEFAULT_TIMEOUT if "timeout" in kwargs: self.timeout = kwargs["timeout"] del kwargs["timeout"] super().__init__(*args, **kwargs) def send(self, request, **kwargs): timeout = kwargs.get("timeout") if timeout is None: kwargs["timeout"] = self.timeout return super().send(request, **kwargs)
Мы можем использовать это так:
import requests http = requests.Session() # Mount it for both http and https usage adapter = TimeoutHTTPAdapter(timeout=2.5) http.mount("https://", adapter) http.mount("http://", adapter) # Use the default 2.5s timeout response = http.get("https://api.twilio.com/") # Override the timeout as usual for specific requests response = http.get("https://api.twilio.com/", timeout=10)
Повторите попытку отказа
Сетевые соединения являются потерями, перегруженные и серверы проваливаются. Если мы хотим построить действительно надежную программу, нам нужно учитывать сбои и иметь стратегию повторной стратегии.
Добавьте стратегию повтора к вашему HTTP-клиенту просто. Мы создаем htttpadapter и передаем нашу стратегию к адаптеру.
from requests.adapters import HTTPAdapter from requests.packages.urllib3.util.retry import Retry retry_strategy = Retry( total=3, status_forcelist=[429, 500, 502, 503, 504], method_whitelist=["HEAD", "GET", "OPTIONS"] ) adapter = HTTPAdapter(max_retries=retry_strategy) http = requests.Session() http.mount("https://", adapter) http.mount("http://", adapter) response = http.get("https://en.wikipedia.org/w/api.php")
Класс повторного попытки по умолчанию предлагает Sane по умолчанию, но это Высоко Настраивается так вот вскрытие наиболее распространенных параметров, которые я использую.
Ниже приведены параметры, включают параметры по умолчанию, используемые библиотеки запросов.
total=10
Общее количество попыток повторной попытки сделать. Если количество неудачных запросов или перенаправления превышает этот номер, клиент бросит Urllib3.exceptions. MaxretryError
исключение. Я варьирую этот параметр, основанный на API, с которым я работаю, но я обычно устанавливаю его на более 10, обычно достаточно 3 повторных попыток.
status_forcelist=[413, 429, 503]
Коды ответов HTTP, чтобы повторить попытку. Вы, вероятно, хотите повторить попытку на Общие ошибки сервера (500, 502, 503, 504) Поскольку серверы и обратные прокси не всегда придерживаются спецификации HTTP. Всегда Повторите попытку на 429 превышен предел ставки Поскольку библиотека Urllib должна по умолчанию постепенно откажется от неудачных запросов.
method_whitelist=["HEAD", "GET", "PUT", "DELETE", "OPTIONS", "TRACE"]
Методы HTTP для повторения. По умолчанию это включает в себя все методы HTTP, кроме сообщения, потому что сообщение может привести к новой вставке. Изменить этот параметр, чтобы включить Post Поскольку большинство API я имею дело, не возвращайте код ошибки и выполните вставку в тот же вызов. И если они это сделают, вы, вероятно, должны выдать сообщение об ошибке.
backoff_factor=0
Это интересный. Это позволяет вам изменить, как долго процессы будут спать между неудачными запросами. Алгоритм выглядит следующим образом:
{backoff factor} * (2 ** ({number of total retries} - 1))
Например, если фактор отключения установлен:
- 1 секунда последовательный сон будет
0,5, 1, 2, 4, 8, 16, 32, 64, 128, 256
Отказ - 2 секунды –
1, 2, 4, 8, 16, 32, 64, 128, 256, 512
- 10 секунд –
5, 10, 20, 40, 80, 160, 320, 640, 1280, 2560
Значение экспоненциально увеличивается, что является внедрением по умолчанию Sane для Стратегии повторных попыток Отказ
Это значение по умолчанию по умолчанию 0, то это означает, что экспоненциальный откат будет установлен, и попытки будут немедленно выполняться. Обязательно установите это на 1 во избежание удара Ваши серверы! .
Полная документация по модулю повторной попытки – здесь Отказ
Объединение тайм-аутов и повторных попыток
Поскольку Htttraadapter сопоставим, мы можем комбинировать повторные попытки и тайм-ауты, которые так:
retries = Retry(total=3, backoff_factor=1, status_forcelist=[429, 500, 502, 503, 504]) http.mount("https://", TimeoutHTTPAdapter(max_retries=retries))
Отладка HTTP-запросов
Иногда запросы терпят неудачу, и вы не можете понять, почему. Регистрация запроса и ответа может дать вам представление о неудаче. Существует два способа сделать это – либо используя встроенные настройки регистрации отладки или с помощью крючков запросов.
Печать заголовков HTTP
Изменение уровня отладки лесозаготовки выше 0 будет регистрировать заголовки HTTP-отклика. Это самый простой вариант, но он не позволяет видеть HTTP-запрос или тело ответа. Это полезно, если вы имеете дело с API, который возвращает большую полезную нагрузку для тела, которая не подходит для регистрации или содержит двоичный контент.
Любое значение, которое превышает 0 0, позволит вести ведение отладки.
import requests import http http.client.HTTPConnection.debuglevel = 1 requests.get("https://www.google.com/") # Output send: b'GET / HTTP/1.1\r\nHost: www.google.com\r\nUser-Agent: python-requests/2.22.0\r\nAccept-Encoding: gzip, deflate\r\nAccept: */*\r\nConnection: keep-alive\r\n\r\n' reply: 'HTTP/1.1 200 OK\r\n' header: Date: Fri, 28 Feb 2020 12:13:26 GMT header: Expires: -1 header: Cache-Control: private, max-age=0
Печать все
Если вы хотите зарегистрировать весь HTTP Lifecycle, в том числе как текстовое представление запроса и ответа, вы можете использовать крючки запроса и дамп Utils из запросов_toolbelt.
Я предпочитаю этот вариант в любое время, когда я имею дело с API на основе покоя, который не возвращает очень большие ответы.
import requests from requests_toolbelt.utils import dump def logging_hook(response, *args, **kwargs): data = dump.dump_all(response) print(data.decode('utf-8')) http = requests.Session() http.hooks["response"] = [logging_hook] http.get("https://api.openaq.org/v1/cities", params={"country": "BA"}) # Output < GET /v1/cities?country=BA HTTP/1.1 < Host: api.openaq.org > HTTP/1.1 200 OK > Content-Type: application/json; charset=utf-8 > Transfer-Encoding: chunked > Connection: keep-alive > { "meta":{ "name":"openaq-api", "license":"CC BY 4.0", "website":"https://docs.openaq.org/", "page":1, "limit":100, "found":1 }, "results":[ { "country":"BA", "name":"Goražde", "city":"Goražde", "count":70797, "locations":1 } ] }
Посмотреть https://toolbelt.readtheDocs.io/en/latest/dumputils.html.
Тестирование и издевательства запросов
Использование сторонних API внедряет болевую точку развития – им трудно установить тест. Инженеры на Sentry Осналивая часть этой боли, написав библиотеку в просьбу о покупках во время развития.
Вместо того, чтобы отправить HTTP-ответ на сервер Getentry/Ответы Перехватывает HTTP-запрос и возвращает заранее определенный ответ, который вы добавили во время тестов.
Это лучше продемонстрировано с примером.
import unittest import requests import responses class TestAPI(unittest.TestCase): @responses.activate # intercept HTTP calls within this method def test_simple(self): response_data = { "id": "ch_1GH8so2eZvKYlo2CSMeAfRqt", "object": "charge", "customer": {"id": "cu_1GGwoc2eZvKYlo2CL2m31GRn", "object": "customer"}, } # mock the Stripe API responses.add( responses.GET, "https://api.stripe.com/v1/charges", json=response_data, ) response = requests.get("https://api.stripe.com/v1/charges") self.assertEqual(response.json(), response_data)
Если HTTP-запрос, который не совпадает с издевателями ответов, подключается ConnectionError.
class TestAPI(unittest.TestCase): @responses.activate def test_simple(self): responses.add(responses.GET, "https://api.stripe.com/v1/charges") response = requests.get("https://invalid-request.com")
Выход
requests.exceptions.ConnectionError: Connection refused by Responses - the call doesn't match any registered mock. Request: - GET https://invalid-request.com/ Available matches: - GET https://api.stripe.com/v1/charges
Повышение поведения браузера
Если вы написали достаточно веб-скребок, вы заметите, как определенные сайты возвращают разные HTML в зависимости от того, если вы используете браузер или доступ к сайту программно. Иногда это анти-соскобная мера, но обычно серверы участвуют в пользовательском агенте, чтобы узнать, какой контент лучше всего подходит для устройства (E.G Desktop или Mobile).
Если вы хотите вернуть тот же контент, что и дисплеи браузера, вы можете переопределить запросы заголовка пользователя, наборы с чем-то Firefox или Chrome.
import requests http = requests.Session() http.headers.update({ "User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0" })
Оригинал: “https://dev.to/danihodovic/advanced-usage-of-python-requests-timeouts-retries-hooks-kok”