Выполнение HTTP-запросов в Python: подробное руководство

Раздел: Веб-разработка -> HTTP-запросы

Основные подходы к запросу веб-страницы в 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.

Запрос веб-страницы в Python - comments

En
запрос страницы python (python)