Выполнение HTTP-запросов в Python: подробное руководство
Основные подходы к запросу веб-страницы в Python
Наиболее эффективное решение: библиотека requests
Библиотека requests предоставляет интуитивный интерфейс для HTTP-запросов, автоматически обрабатывает кодировки, поддерживает сессии, куки, таймауты и многие другие возможности. Она является стандартом для синхронных запросов в Python.
import requests
url = 'https://httpbin.org/get'
try:
response = requests.get(url, timeout=10)
response.raise_for_status() # проверка на HTTP-ошибки
data = response.text
print(data[:200])
except requests.exceptions.RequestException as e:
print(f"Ошибка при запросе: {e}")
запрос страницы python (запрос веб-страницы в python)
Пояснение: requests.get() отправляет GET-запрос. Параметр timeout задаёт максимальное время ожидания. raise_for_status() генерирует исключение при кодах 4xx/5xx. Ответ содержит текст страницы в .text.
Как избежать зависания программы при медленном ответе сервера?
Проблема: запрос может зависнуть на неопределённое время. Решение: всегда указывать timeout и обрабатывать исключение requests.exceptions.Timeout.
response = requests.get(url, timeout=(3, 5)) # (connect, read)
Python requests (библиотека requests в python)
Также возможны ошибки SSL-сертификатов. Их можно отключить (не рекомендуется) параметром verify=False или указать путь к сертификату.
Как получить страницу без установки дополнительных пакетов?
Использование urllib.request из стандартной библиотеки.
import urllib.request
import urllib.error
url = 'https://httpbin.org/get'
try:
with urllib.request.urlopen(url, timeout=10) as response:
data = response.read().decode('utf-8')
print(data[:200])
except urllib.error.URLError as e:
print(f"Ошибка: {e.reason}")
Python request download (скачивание файла через requests)
Пояснение: urlopen() открывает соединение, возвращает объект ответа. Содержимое читается методом .read() (в байтах), затем декодируется. Ошибки обрабатываются через urllib.error.URLError.
Почему при запросе появляется ошибка http.client.IncompleteRead?
Проблема: сервер может передать неполный ответ. Решение: использовать urllib.request.urlopen с частью context для указания протокола или повторить запрос с обработкой исключения.
from http.client import IncompleteRead
try:
response = urllib.request.urlopen(url)
data = response.read()
except IncompleteRead as e:
data = e.partial # получить частичные данные
Как выполнить HTTP/2 запросы или асинхронные запросы синхронным способом?
Библиотека httpx поддерживает и синхронный, и асинхронный режимы, а также HTTP/1.1 и HTTP/2.
import httpx
url = 'https://httpbin.org/get'
try:
with httpx.Client(http2=True, timeout=10) as client:
response = client.get(url)
response.raise_for_status()
print(response.text[:200])
except httpx.HTTPError as e:
print(f"Ошибка: {e}")
Пояснение: httpx.Client управляет сессией. Параметр http2=True включает HTTP/2. Ответ аналогичен requests.
Как обработать ошибку подключения при использовании httpx?
Проблема: сервер может быть недоступен. Решение: использовать try-except с httpx.ConnectError и httpx.ConnectTimeout.
from httpx import ConnectError, ConnectTimeout
try:
response = client.get(url, timeout=5)
except ConnectError:
print("Сервер не отвечает")
except ConnectTimeout:
print("Превышено время ожидания соединения")
Как выполнять множество запросов одновременно?
Библиотека aiohttp для асинхронных запросов с использованием asyncio.
import asyncio
import aiohttp
async def fetch(session, url):
async with session.get(url, timeout=10) as response:
return await response.text()
async def main():
urls = ['https://httpbin.org/get', 'https://httpbin.org/get?q=1']
async with aiohttp.ClientSession() as session:
tasks = [fetch(session, url) for url in urls]
results = await asyncio.gather(*tasks)
for result in results:
print(result[:100])
asyncio.run(main())
Пояснение: aiohttp.ClientSession управляет пулом соединений. Каждый запрос выполняется в корутине fetch. asyncio.gather запускает их параллельно.
Как избежать ошибки RuntimeError: cannot reuse already awaited coroutine?
Проблема: попытка повторно использовать корутину. Решение: создавать новый таск для каждого запроса через asyncio.create_task или использовать gather с новыми корутинами.
tasks = [asyncio.create_task(fetch(session, url)) for url in urls]
results = await asyncio.gather(*tasks)
Как использовать утилиту curl из Python?
Через subprocess с командой curl (если curl установлен в системе).
import subprocess
url = 'https://httpbin.org/get'
result = subprocess.run(['curl', '-s', url], capture_output=True, text=True, timeout=10)
if result.returncode == 0:
print(result.stdout[:200])
else:
print(f"Ошибка curl: {result.stderr}")
Пояснение: subprocess.run выполняет команду curl -s (тихий режим). Флаг capture_output=True захватывает вывод. text=True возвращает строку.
Почему curl может не вернуть данные, хотя в терминале всё работает?
Проблема: различия в окружении (путь к curl, переменные окружения). Решение: указать полный путь к curl и передать env при необходимости.
import shutil
curl_path = shutil.which('curl')
subprocess.run([curl_path, ...])
Расширенные примеры запросов веб-страниц
Ниже приведены подробные примеры с кодом и выводом, демонстрирующие разные сценарии.
1. Библиотека requests: сессия, заголовки, куки, прокси
import requests
# Создание сессии (сохраняет куки, заголовки, пул соединений)
session = requests.Session()
session.headers.update({'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'})
session.cookies.set('sessionid', 'abc123', domain='httpbin.org')
# GET-запрос с прокси (если требуется через HTTPS прокси)
proxies = {'http': 'http://user:pass@proxy:8080', 'https': 'http://user:pass@proxy:8080'}
try:
response = session.get('https://httpbin.org/cookies', proxies=proxies, timeout=10)
response.raise_for_status()
print(response.json())
except requests.exceptions.ProxyError as e:
print(f"Ошибка прокси: {e}")
{"cookies": {"sessionid": "abc123"}}
Пояснение: сессия автоматически отправляет установленные куки. Прокси задаются словарём. Если прокси не нужны, параметр опускается.
2. urllib.request: кастомные заголовки и обработка перенаправлений
import urllib.request
url = 'https://httpbin.org/redirect/3' # сервер делает 3 редиректа
req = urllib.request.Request(url, headers={'User-Agent': 'CustomAgent'})
# Отключить автоматическое следование редиректам (по умолчанию включено)
# Для этого создаём свой обработчик (opener)
opener = urllib.request.build_opener()
opener.add_handler = urllib.request.HTTPRedirectHandler()
opener.add_handler.max_redirections = 0 # не следовать
try:
with opener.open(req, timeout=5) as response:
print(f"Код ответа: {response.status}")
print(response.read().decode()[:100])
except urllib.error.HTTPError as e:
print(f"Ошибка: {e.code} {e.reason}")
Код ответа: 302 Ошибка: 302 Found
Пояснение: HTTPRedirectHandler управляет редиректами. Установив max_redirections=0, мы не следуем перенаправлениям и получаем ответ с кодом 302.
3. httpx: асинхронные запросы с повторными попытками
import asyncio
import httpx
async def fetch_with_retry(url, retries=3):
async with httpx.AsyncClient(http2=True, timeout=10) as client:
for attempt in range(retries):
try:
resp = await client.get(url)
resp.raise_for_status()
return resp.text[:150]
except httpx.HTTPStatusError as e:
if attempt == retries - 1:
raise
await asyncio.sleep(2 ** attempt) # экспоненциальная задержка
async def main():
result = await fetch_with_retry('https://httpbin.org/status/503')
print(result)
asyncio.run(main())
(При успехе вернётся текст, при трёх неудачах - исключение)
Пояснение: httpx.AsyncClient используется в асинхронном контексте. Механизм повторных попыток с задержкой помогает при временных ошибках сервера.
4. aiohttp: параллельный сбор данных с ограничением числа одновременных соединений
import asyncio
import aiohttp
from aiohttp import ClientTimeout
async def fetch(session, url, semaphore):
async with semaphore:
try:
async with session.get(url, timeout=ClientTimeout(total=10)) as resp:
return await resp.text()
except (aiohttp.ClientError, asyncio.TimeoutError) as e:
return f"Ошибка: {e}"
async def main():
urls = [f'https://httpbin.org/delay/{i}' for i in range(1, 6)]
semaphore = asyncio.Semaphore(3) # не более 3 одновременных запросов
async with aiohttp.ClientSession() as session:
tasks = [fetch(session, url, semaphore) for url in urls]
results = await asyncio.gather(*tasks, return_exceptions=False)
for r in results:
print(len(r))
asyncio.run(main())
(Выводятся длины ответов, от 200 до 2000 символов)
Пояснение: семафор (asyncio.Semaphore) ограничивает количество одновременных запросов для предотвращения перегрузки сети или сервера.
5. subprocess + curl: передача тела POST-запроса и обработка заголовков
import subprocess
import json
url = 'https://httpbin.org/post'
data = json.dumps({'key': 'value'})
cmd = ['curl', '-s', '-X', 'POST', url, '-H', 'Content-Type: application/json', '-d', data]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
if result.returncode == 0:
resp = json.loads(result.stdout)
print(resp['json'])
else:
print(f"Ошибка: {result.stderr}")
{'key': 'value'}
Пояснение: аргументы curl передаются в виде списка. Флаг -d отправляет тело POST-запроса. Вывод разбирается как JSON.