- Home »

HTTP-клиент в Python — запросы GET и POST
Если вы администрируете сервера, автоматизируете задачи или интегрируете различные системы, без HTTP-клиента в Python никуда. Это ваш швейцарский нож для работы с API, мониторинга сервисов, автоматизации деплоя и кучи других задач. Разберём requests — самую популярную библиотеку для HTTP-запросов в Python, которая превращает веб-интеграцию из мучения в удовольствие.
Почему это важно? Любой админ рано или поздно сталкивается с необходимостью забирать данные с внешних API, отправлять уведомления в слак, проверять состояние сервисов или автоматизировать работу с панелями управления. Вместо curl в bash-скриптах (который, честно говоря, для сложных задач превращается в ад) лучше использовать Python — читабельно, гибко и мощно.
Как это работает под капотом
HTTP-клиент в Python — это обёртка над стандартными сетевыми библиотеками, которая берёт на себя всю рутину: парсинг URL, формирование заголовков, обработку cookies, SSL-верификацию и декодирование ответов. Requests построен поверх urllib3 и значительно упрощает жизнь.
Основные компоненты:
- Session — позволяет переиспользовать соединения и сохранять состояние между запросами
- Adapters — отвечают за транспорт (HTTP/HTTPS)
- Hooks — позволяют встраивать кастомную логику в процесс запроса
- Auth — система аутентификации (Basic, Digest, OAuth)
Установка и первоначальная настройка
Устанавливаем requests (если ещё не установлен):
pip install requests
Для работы с JSON и более продвинутыми сценариями пригодятся:
pip install requests[security] urllib3 certifi
Базовая структура скрипта:
import requests
import json
from urllib.parse import urljoin
import sys
# Настройки по умолчанию
DEFAULT_TIMEOUT = 30
DEFAULT_HEADERS = {
'User-Agent': 'ServerAdmin/1.0',
'Accept': 'application/json',
'Content-Type': 'application/json'
}
GET-запросы: забираем данные
Самый простой GET-запрос:
import requests
response = requests.get('https://api.github.com/users/octocat')
print(response.status_code)
print(response.json())
Но в реальной жизни нужно больше контроля. Правильный подход:
import requests
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry
def create_session():
session = requests.Session()
# Настройка retry-стратегии
retry_strategy = Retry(
total=3,
backoff_factor=1,
status_forcelist=[429, 500, 502, 503, 504],
)
adapter = HTTPAdapter(max_retries=retry_strategy)
session.mount("http://", adapter)
session.mount("https://", adapter)
return session
def get_server_status(url, timeout=30):
session = create_session()
try:
response = session.get(
url,
timeout=timeout,
headers={'User-Agent': 'ServerMonitor/1.0'}
)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f"Ошибка запроса: {e}")
return None
finally:
session.close()
# Использование
status = get_server_status('https://api.example.com/health')
if status:
print(f"Сервер работает: {status}")
POST-запросы: отправляем данные
POST используется для отправки данных на сервер. Типичные сценарии: аутентификация, создание ресурсов, отправка логов.
# Отправка JSON
def send_alert(webhook_url, message, level='info'):
payload = {
'text': message,
'level': level,
'timestamp': int(time.time()),
'hostname': socket.gethostname()
}
try:
response = requests.post(
webhook_url,
json=payload,
timeout=10,
headers={'Content-Type': 'application/json'}
)
response.raise_for_status()
return True
except requests.exceptions.RequestException as e:
print(f"Не удалось отправить уведомление: {e}")
return False
# Отправка форм (application/x-www-form-urlencoded)
def auth_legacy_api(username, password):
data = {
'username': username,
'password': password,
'grant_type': 'password'
}
response = requests.post(
'https://legacy-api.example.com/auth',
data=data,
timeout=15
)
if response.status_code == 200:
return response.json().get('access_token')
return None
Продвинутые возможности
Работа с сессиями
Сессии позволяют переиспользовать соединения и сохранять состояние. Это критически важно для производительности:
class APIClient:
def __init__(self, base_url, api_key):
self.base_url = base_url
self.session = requests.Session()
self.session.headers.update({
'Authorization': f'Bearer {api_key}',
'User-Agent': 'ServerAdmin/1.0'
})
# Настройка пула соединений
adapter = HTTPAdapter(
pool_connections=100,
pool_maxsize=100,
max_retries=3
)
self.session.mount('http://', adapter)
self.session.mount('https://', adapter)
def get(self, endpoint, **kwargs):
url = urljoin(self.base_url, endpoint)
return self.session.get(url, **kwargs)
def post(self, endpoint, **kwargs):
url = urljoin(self.base_url, endpoint)
return self.session.post(url, **kwargs)
def close(self):
self.session.close()
# Использование
client = APIClient('https://api.example.com', 'your-api-key')
try:
servers = client.get('/servers').json()
for server in servers:
status = client.get(f'/servers/{server["id"]}/status').json()
print(f"Сервер {server['name']}: {status['state']}")
finally:
client.close()
Аутентификация
Различные типы аутентификации:
# Basic Auth
response = requests.get(
'https://api.example.com/protected',
auth=('username', 'password')
)
# Bearer Token
headers = {'Authorization': 'Bearer your-jwt-token'}
response = requests.get('https://api.example.com/user', headers=headers)
# Custom Auth Class
class APIKeyAuth:
def __init__(self, api_key):
self.api_key = api_key
def __call__(self, r):
r.headers['X-API-Key'] = self.api_key
return r
# Использование
auth = APIKeyAuth('your-api-key')
response = requests.get('https://api.example.com/data', auth=auth)
Практические примеры и кейсы
Мониторинг серверов
import requests
import time
import json
from datetime import datetime
class ServerMonitor:
def __init__(self, servers):
self.servers = servers
self.session = requests.Session()
self.session.timeout = 10
def check_server(self, server):
start_time = time.time()
try:
response = self.session.get(f"https://{server}/health")
response_time = time.time() - start_time
return {
'server': server,
'status': 'UP' if response.status_code == 200 else 'DOWN',
'response_time': round(response_time * 1000, 2),
'status_code': response.status_code,
'timestamp': datetime.utcnow().isoformat()
}
except requests.exceptions.RequestException as e:
return {
'server': server,
'status': 'DOWN',
'error': str(e),
'timestamp': datetime.utcnow().isoformat()
}
def monitor_all(self):
results = []
for server in self.servers:
result = self.check_server(server)
results.append(result)
print(f"{result['server']}: {result['status']} ({result.get('response_time', 'N/A')}ms)")
return results
# Использование
servers = ['server1.example.com', 'server2.example.com', 'server3.example.com']
monitor = ServerMonitor(servers)
status_report = monitor.monitor_all()
Автоматизация деплоя
class DeploymentClient:
def __init__(self, ci_url, token):
self.ci_url = ci_url
self.session = requests.Session()
self.session.headers.update({
'Authorization': f'token {token}',
'Accept': 'application/vnd.github.v3+json'
})
def trigger_deployment(self, repo, branch='main', environment='production'):
payload = {
'ref': branch,
'environment': environment,
'description': f'Deploy {branch} to {environment}'
}
response = self.session.post(
f'{self.ci_url}/repos/{repo}/deployments',
json=payload
)
if response.status_code == 201:
deployment = response.json()
print(f"Деплой запущен: {deployment['id']}")
return deployment
else:
print(f"Ошибка деплоя: {response.status_code}")
return None
def get_deployment_status(self, repo, deployment_id):
response = self.session.get(
f'{self.ci_url}/repos/{repo}/deployments/{deployment_id}/statuses'
)
if response.status_code == 200:
statuses = response.json()
return statuses[0] if statuses else None
return None
# Использование
deploy_client = DeploymentClient('https://api.github.com', 'your-token')
deployment = deploy_client.trigger_deployment('myorg/myapp')
if deployment:
time.sleep(5) # Ждём немного
status = deploy_client.get_deployment_status('myorg/myapp', deployment['id'])
print(f"Статус деплоя: {status['state']}")
Обработка ошибок и исключений
Правильная обработка ошибок — это половина успеха:
import requests
from requests.exceptions import (
RequestException,
ConnectionError,
HTTPError,
Timeout,
TooManyRedirects
)
def robust_request(url, method='GET', **kwargs):
try:
response = requests.request(method, url, **kwargs)
response.raise_for_status()
return response
except ConnectionError:
print(f"Не удалось подключиться к {url}")
return None
except Timeout:
print(f"Таймаут при запросе к {url}")
return None
except HTTPError as e:
print(f"HTTP ошибка {e.response.status_code}: {e.response.text}")
return None
except TooManyRedirects:
print(f"Слишком много редиректов для {url}")
return None
except RequestException as e:
print(f"Общая ошибка запроса: {e}")
return None
# Использование с контекстом
def api_call_with_retry(url, max_retries=3, delay=1):
for attempt in range(max_retries):
response = robust_request(url, timeout=30)
if response:
return response
if attempt < max_retries - 1:
print(f"Повторная попытка через {delay} секунд...")
time.sleep(delay)
delay *= 2 # Экспоненциальная задержка
return None
Сравнение с альтернативами
Библиотека | Производительность | Простота использования | Async поддержка | Размер |
---|---|---|---|---|
requests | Хорошая | Отличная | Нет | ~500KB |
httpx | Отличная | Отличная | Да | ~400KB |
urllib3 | Отличная | Средняя | Нет | ~300KB |
aiohttp | Отличная | Хорошая | Да | ~1MB |
Интеграция с другими инструментами
Логирование запросов
import logging
import requests
# Настройка логирования HTTP
logging.basicConfig(level=logging.DEBUG)
logging.getLogger("requests.packages.urllib3").setLevel(logging.DEBUG)
logging.getLogger("requests.packages.urllib3.connectionpool").setLevel(logging.DEBUG)
# Или кастомный логгер
class RequestLogger:
def __init__(self):
self.logger = logging.getLogger('api_client')
def log_request(self, method, url, **kwargs):
self.logger.info(f"→ {method} {url}")
if 'json' in kwargs:
self.logger.debug(f"Payload: {kwargs['json']}")
def log_response(self, response):
self.logger.info(f"← {response.status_code} {response.url}")
if response.headers.get('content-type', '').startswith('application/json'):
self.logger.debug(f"Response: {response.text[:200]}...")
# Использование с декоратором
def logged_request(func):
def wrapper(*args, **kwargs):
logger = RequestLogger()
logger.log_request(func.__name__.upper(), args[0] if args else 'Unknown', **kwargs)
response = func(*args, **kwargs)
if response:
logger.log_response(response)
return response
return wrapper
@logged_request
def get(url, **kwargs):
return requests.get(url, **kwargs)
Интеграция с Redis для кеширования
import redis
import hashlib
import json
import requests
class CachedHTTPClient:
def __init__(self, redis_host='localhost', redis_port=6379, ttl=300):
self.redis_client = redis.Redis(host=redis_host, port=redis_port, decode_responses=True)
self.ttl = ttl
def _get_cache_key(self, method, url, **kwargs):
# Создаём уникальный ключ для кеша
key_data = f"{method}:{url}:{json.dumps(kwargs, sort_keys=True)}"
return hashlib.md5(key_data.encode()).hexdigest()
def request(self, method, url, use_cache=True, **kwargs):
cache_key = self._get_cache_key(method, url, **kwargs)
# Проверяем кеш
if use_cache and method.upper() == 'GET':
cached = self.redis_client.get(cache_key)
if cached:
print(f"Cache hit for {url}")
return json.loads(cached)
# Делаем запрос
response = requests.request(method, url, **kwargs)
if response.status_code == 200:
result = {
'status_code': response.status_code,
'headers': dict(response.headers),
'data': response.json() if response.headers.get('content-type', '').startswith('application/json') else response.text
}
# Сохраняем в кеш
if use_cache and method.upper() == 'GET':
self.redis_client.setex(cache_key, self.ttl, json.dumps(result))
return result
return None
# Использование
client = CachedHTTPClient(ttl=600) # 10 минут кеш
result = client.request('GET', 'https://api.example.com/slow-endpoint')
Оптимизация производительности
Несколько важных моментов для production-окружения:
- Переиспользование соединений — всегда используйте Session
- Пул соединений — настраивайте pool_connections и pool_maxsize
- Таймауты — всегда устанавливайте timeout
- Retry-стратегии — используйте urllib3.util.retry
- Сжатие — requests автоматически поддерживает gzip
# Оптимизированная конфигурация
import requests
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry
class OptimizedHTTPClient:
def __init__(self):
self.session = requests.Session()
# Retry стратегия
retry_strategy = Retry(
total=3,
backoff_factor=0.3,
status_forcelist=[429, 500, 502, 503, 504],
method_whitelist=["HEAD", "GET", "OPTIONS", "POST"]
)
# Адаптер с пулом соединений
adapter = HTTPAdapter(
pool_connections=100,
pool_maxsize=100,
max_retries=retry_strategy,
pool_block=False
)
self.session.mount("http://", adapter)
self.session.mount("https://", adapter)
# Заголовки по умолчанию
self.session.headers.update({
'User-Agent': 'OptimizedClient/1.0',
'Accept-Encoding': 'gzip, deflate',
'Connection': 'keep-alive'
})
def get(self, url, **kwargs):
kwargs.setdefault('timeout', 30)
return self.session.get(url, **kwargs)
def post(self, url, **kwargs):
kwargs.setdefault('timeout', 30)
return self.session.post(url, **kwargs)
Интересные факты и нестандартные применения
Вот несколько необычных способов использования HTTP-клиента:
- Проксирование запросов — можно создать простой HTTP-прокси
- Веб-скрейпинг — парсинг веб-страниц с сохранением сессий
- Тестирование API — автоматизированные тесты для ваших сервисов
- Мониторинг SSL-сертификатов — проверка срока действия сертификатов
# Проверка SSL-сертификатов
import ssl
import socket
from datetime import datetime
def check_ssl_expiry(hostname, port=443):
context = ssl.create_default_context()
with socket.create_connection((hostname, port), timeout=10) as sock:
with context.wrap_socket(sock, server_hostname=hostname) as ssock:
cert = ssock.getpeercert()
# Парсим дату истечения
expiry_date = datetime.strptime(cert['notAfter'], '%b %d %H:%M:%S %Y %Z')
days_until_expiry = (expiry_date - datetime.now()).days
return {
'hostname': hostname,
'expiry_date': expiry_date.isoformat(),
'days_until_expiry': days_until_expiry,
'issuer': dict(x[0] for x in cert['issuer']),
'subject': dict(x[0] for x in cert['subject'])
}
# Интеграция с requests для полной проверки
def check_site_health(url):
# Проверяем HTTP
response = requests.get(url, timeout=10)
# Проверяем SSL
from urllib.parse import urlparse
parsed_url = urlparse(url)
if parsed_url.scheme == 'https':
ssl_info = check_ssl_expiry(parsed_url.hostname)
else:
ssl_info = None
return {
'url': url,
'status_code': response.status_code,
'response_time': response.elapsed.total_seconds(),
'ssl_info': ssl_info,
'headers': dict(response.headers)
}
Автоматизация и скрипты
HTTP-клиент открывает массу возможностей для автоматизации:
- Интеграция с CI/CD — автоматический деплой и уведомления
- Мониторинг инфраструктуры — проверка состояния сервисов
- Автоматическое резервное копирование — через API облачных провайдеров
- Синхронизация данных — между различными системами
- Генерация отчётов — сбор метрик и отправка в системы мониторинга
# Универсальный скрипт для webhook-уведомлений
#!/usr/bin/env python3
import requests
import argparse
import sys
import json
from datetime import datetime
def send_webhook(url, message, level='info', service='unknown'):
payload = {
'text': message,
'level': level,
'service': service,
'timestamp': datetime.utcnow().isoformat(),
'hostname': socket.gethostname()
}
try:
response = requests.post(url, json=payload, timeout=10)
response.raise_for_status()
print(f"Уведомление отправлено: {message}")
return True
except Exception as e:
print(f"Ошибка отправки: {e}")
return False
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Отправка webhook-уведомлений')
parser.add_argument('--url', required=True, help='URL webhook')
parser.add_argument('--message', required=True, help='Сообщение')
parser.add_argument('--level', default='info', choices=['info', 'warning', 'error'])
parser.add_argument('--service', default='system', help='Имя сервиса')
args = parser.parse_args()
success = send_webhook(args.url, args.message, args.level, args.service)
sys.exit(0 if success else 1)
Работа с различными форматами данных
Не только JSON живёт в мире API:
# XML API
import xml.etree.ElementTree as ET
def parse_xml_response(response):
root = ET.fromstring(response.text)
return {child.tag: child.text for child in root}
# Multipart form data (загрузка файлов)
def upload_file(url, file_path, additional_data=None):
files = {'file': open(file_path, 'rb')}
data = additional_data or {}
response = requests.post(url, files=files, data=data)
files['file'].close()
return response
# Бинарные данные
def download_file(url, local_path):
response = requests.get(url, stream=True)
response.raise_for_status()
with open(local_path, 'wb') as f:
for chunk in response.iter_content(chunk_size=8192):
f.write(chunk)
return local_path
Если вам нужно развернуть сервер для тестирования ваших HTTP-клиентов или создать VPS для автоматизации, это поможет быстро настроить окружение для экспериментов.
Заключение и рекомендации
HTTP-клиент в Python — это мощный инструмент, который должен быть в арсенале каждого системного администратора. Requests остаётся золотым стандартом благодаря простоте использования и богатой функциональности.
Когда использовать:
- Интеграция с внешними API
- Автоматизация деплоя и CI/CD
- Мониторинг сервисов и инфраструктуры
- Синхронизация данных между системами
- Веб-скрейпинг и парсинг контента
Основные принципы:
- Всегда используйте Session для множественных запросов
- Обязательно устанавливайте timeout
- Правильно обрабатывайте исключения
- Используйте retry-стратегии для критических запросов
- Логируйте запросы для отладки
Для высоконагруженных систем рассмотрите асинхронные альтернативы (httpx, aiohttp), но для большинства административных задач requests более чем достаточно. Если планируете развернуть серьёзную инфраструктуру, возможно, стоит подумать о выделенном сервере для максимальной производительности.
Помните: хороший HTTP-клиент — это не только про отправку запросов, но и про грамотную обработку ответов, ошибок и граничных случаев. Пишите код так, чтобы он работал стабильно в production, а не только на вашем localhost.
В этой статье собрана информация и материалы из различных интернет-источников. Мы признаем и ценим работу всех оригинальных авторов, издателей и веб-сайтов. Несмотря на то, что были приложены все усилия для надлежащего указания исходного материала, любая непреднамеренная оплошность или упущение не являются нарушением авторских прав. Все упомянутые товарные знаки, логотипы и изображения являются собственностью соответствующих владельцев. Если вы считаете, что какой-либо контент, использованный в этой статье, нарушает ваши авторские права, немедленно свяжитесь с нами для рассмотрения и принятия оперативных мер.
Данная статья предназначена исключительно для ознакомительных и образовательных целей и не ущемляет права правообладателей. Если какой-либо материал, защищенный авторским правом, был использован без должного упоминания или с нарушением законов об авторском праве, это непреднамеренно, и мы исправим это незамедлительно после уведомления. Обратите внимание, что переиздание, распространение или воспроизведение части или всего содержимого в любой форме запрещено без письменного разрешения автора и владельца веб-сайта. Для получения разрешений или дополнительных запросов, пожалуйста, свяжитесь с нами.