Библиотека 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”