Парсинг API

Парсинг через API вместо HTML: как находить скрытые эндпоинты сайтов, работать с авторизацией, лимитами и пагинацией.

КP
Команда Parsing.agency
Сбор данных под задачи бизнеса
Опубликовано: 22 февраля 2025

Что такое API и парсинг при работе с ним

API (Application Programming Interface) — это интерфейс, через который одна программа обращается к другой и получает данные или запускает действия, не зная её внутреннего устройства. На практике под «работой с API» чаще всего понимают обращение к веб-API по протоколу HTTP: клиент отправляет запрос на определённый адрес (эндпоинт), а сервер возвращает ответ.

Под «парсингом API» обычно понимают весь цикл получения и разбора данных от внешнего сервиса:

  1. Сформировать и отправить корректный HTTP-запрос.
  2. Получить ответ сервера.
  3. Проверить статус ответа и заголовки.
  4. Разобрать тело ответа (чаще всего JSON) в объекты программы.
  5. Обработать ошибки, повторы, постраничную выдачу.

См. также: материал «Парсинг JSON» посвящён узкому шагу разбора самих данных — превращению JSON-строки в объекты. Настоящая статья охватывает весь процесс работы с API, частью которого этот разбор и является.

В отличие от «парсинга сайтов» (скрапинга HTML), работа с API опирается на структурированные ответы в машинно-читаемом формате, поэтому она надёжнее, стабильнее и почти всегда предпочтительнее, когда у сервиса есть официальный API.

Анатомия HTTP-запроса

Любой запрос к веб-API состоит из нескольких частей.

Метод (HTTP-глагол)

Метод описывает намерение запроса:

  • GET — получить данные (не изменяет состояние).
  • POST — создать новый ресурс или отправить данные.
  • PUT / PATCH — обновить ресурс (целиком / частично).
  • DELETE — удалить ресурс.

URL и параметры запроса

Адрес эндпоинта может содержать query-параметры для фильтрации, сортировки и пагинации:

code
https://api.example.com/users?role=admin&page=2&limit=50

Заголовки (headers)

Заголовки передают метаданные запроса. Самые важные при работе с API:

  • Authorization — данные аутентификации (токен, ключ).
  • Content-Type — формат отправляемого тела (например, application/json).
  • Accept — формат, в котором клиент хочет получить ответ.
  • User-Agent — идентификатор клиента.

Тело запроса (body)

Для POST/PUT/PATCH в теле передаются данные — как правило, в виде JSON-строки, которую нужно предварительно сериализовать из объектов программы.

Анатомия ответа и его разбор

Ответ сервера состоит из статус-кода, заголовков и тела.

Статус-коды

Прежде чем разбирать тело, нужно проверить статус-код:

  • 2xx — успех (200 OK, 201 Created, 204 No Content).
  • 3xx — перенаправление.
  • 4xx — ошибка на стороне клиента (400 неверный запрос, 401 не авторизован, 403 запрещено, 404 не найдено, 429 слишком много запросов).
  • 5xx — ошибка на стороне сервера.

Разбирать тело как успешные данные имеет смысл только при кодах 2xx. Тело ошибки тоже часто содержит полезный JSON с описанием проблемы.

Заголовки ответа

Из заголовков ответа берут важную информацию: Content-Type (формат тела), параметры пагинации, лимиты запросов (X-RateLimit-Remaining), управление кешем.

Тело ответа

Тело — это сами данные. В большинстве API это JSON, который нужно распарсить (см. «Парсинг JSON»). Реже встречаются другие текстовые форматы — XML (см. «Парсинг XML») и CSV (см. «Парсинг CSV»), — а также бинарные форматы.

Аутентификация и авторизация

Большинство API требуют подтвердить, что клиент имеет право на доступ. Распространённые способы:

  • API-ключ — простая строка, передаётся в заголовке или query-параметре.
  • Bearer-токен / OAuth 2.0 — токен в заголовке Authorization: Bearer <token>; самый распространённый подход в современных API.
  • Basic Auth — логин и пароль в закодированном виде.
  • HMAC / подпись запроса — запрос подписывается секретом, применяется в платёжных и облачных API.

Важно: ключи и токены — это секреты. Их нельзя хранить в коде и коммитить в репозиторий; используйте переменные окружения или защищённые хранилища.

Сложности при работе с API

Парсинг API редко сводится к одному запросу. Ниже — типичные сложности, к которым стоит готовиться.

Пагинация

API почти никогда не отдаёт большие коллекции целиком — данные разбиваются на страницы. Основные модели:

  • Offset/limit?page=2&limit=50 или ?offset=100&limit=50.
  • Курсорная (cursor) — в ответе приходит указатель на следующую страницу (next_cursor), который передаётся в следующий запрос.
  • Keyset — следующая страница запрашивается по значению последнего элемента (например, по id или дате).

Чтобы собрать все данные, нужен цикл, который идёт по страницам, пока они не закончатся.

Ограничение частоты запросов (rate limiting)

Сервисы ограничивают число запросов за период. При превышении приходит код 429. Корректное решение — следить за заголовками лимитов и применять экспоненциальную задержку (backoff) с повторами.

Ненадёжность сети и повторы

Сетевые запросы могут падать по таймауту или из-за временных ошибок 5xx. Для устойчивости применяют:

  • разумные таймауты;
  • повторы (retry) для идемпотентных запросов с нарастающей задержкой;
  • паттерн circuit breaker при систематических сбоях.

Изменчивость и валидация данных

Ответ API может прийти неполным, с null-полями или с изменённой структурой после обновления версии API. Никогда не стоит слепо доверять структуре ответа — поля нужно проверять, а данные валидировать.

Версионирование

API развиваются, и их версии меняются (/v1/, /v2/). Стоит фиксировать конкретную версию и отслеживать объявления об устаревании (deprecation).

Вложенность и разные форматы

Полезные данные нередко спрятаны глубоко во вложенной структуре (data.items[0].attributes.name). Иногда вместо JSON приходит XML (см. «Парсинг XML») или CSV (см. «Парсинг CSV»), что требует другого парсера.

Примеры реализации на разных языках

Ниже — минимальные примеры полного цикла: запрос к API, проверка статуса и разбор JSON-ответа. Для единообразия используется условный эндпоинт https://api.example.com/users/42.

Python

Популярная библиотека requests берёт на себя и запрос, и разбор JSON.

python
import requests

url = "https://api.example.com/users/42"
headers = {"Authorization": "Bearer YOUR_TOKEN"}

response = requests.get(url, headers=headers, timeout=10)

# Проверка статуса
response.raise_for_status()  # выбросит исключение при 4xx/5xx

# Разбор JSON-ответа в словарь
user = response.json()
print(user["name"])

Пример сбора всех страниц (offset/limit):

python
def fetch_all_users():
    users, page = [], 1
    while True:
        resp = requests.get(
            "https://api.example.com/users",
            params={"page": page, "limit": 50},
            timeout=10,
        )
        resp.raise_for_status()
        batch = resp.json()["data"]
        if not batch:
            break
        users.extend(batch)
        page += 1
    return users

JavaScript (Node.js / браузер)

Встроенный fetch возвращает промис; разбор JSON — метод .json().

javascript
const url = "https://api.example.com/users/42";

const response = await fetch(url, {
  headers: { Authorization: "Bearer YOUR_TOKEN" },
});

// Проверка статуса
if (!response.ok) {
  throw new Error(`Ошибка API: ${response.status}`);
}

// Разбор JSON
const user = await response.json();
console.log(user.name);

Пример отправки данных (POST):

javascript
const response = await fetch("https://api.example.com/users", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: "Bearer YOUR_TOKEN",
  },
  body: JSON.stringify({ name: "Анна", role: "admin" }), // сериализация
});

const created = await response.json();

Java

В современной Java есть встроенный HttpClient, а для разбора JSON используют библиотеку (здесь — Jackson).

java
import java.net.URI;
import java.net.http.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.JsonNode;

public class ApiExample {
    public static void main(String[] args) throws Exception {
        HttpClient client = HttpClient.newHttpClient();

        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://api.example.com/users/42"))
                .header("Authorization", "Bearer YOUR_TOKEN")
                .GET()
                .build();

        HttpResponse<String> response =
                client.send(request, HttpResponse.BodyHandlers.ofString());

        if (response.statusCode() != 200) {
            throw new RuntimeException("Ошибка API: " + response.statusCode());
        }

        ObjectMapper mapper = new ObjectMapper();
        JsonNode user = mapper.readTree(response.body());
        System.out.println(user.get("name").asText());
    }
}

Go

Стандартная библиотека предоставляет и HTTP-клиент (net/http), и парсер (encoding/json).

go
package main

import (
    "encoding/json"
    "fmt"
    "net/http"
)

type User struct {
    Name string `json:"name"`
}

func main() {
    req, _ := http.NewRequest("GET", "https://api.example.com/users/42", nil)
    req.Header.Set("Authorization", "Bearer YOUR_TOKEN")

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        panic(fmt.Sprintf("Ошибка API: %d", resp.StatusCode))
    }

    var user User
    json.NewDecoder(resp.Body).Decode(&user) // потоковый разбор тела
    fmt.Println(user.Name)
}

C

В .NET используют HttpClient и встроенный System.Text.Json.

c#
using System;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;

record User(string Name);

class Program
{
    static async Task Main()
    {
        using var client = new HttpClient();
        client.DefaultRequestHeaders.Add("Authorization", "Bearer YOUR_TOKEN");

        var response = await client.GetAsync("https://api.example.com/users/42");
        response.EnsureSuccessStatusCode();

        // Разбор JSON прямо в типизированный объект
        var user = await response.Content.ReadFromJsonAsync<User>();
        Console.WriteLine(user?.Name);
    }
}

PHP

С помощью cURL делается запрос, а json_decode разбирает ответ.

php
<?php
$ch = curl_init("https://api.example.com/users/42");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer YOUR_TOKEN"]);

$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($status !== 200) {
    throw new Exception("Ошибка API: $status");
}

// Разбор JSON в ассоциативный массив
$user = json_decode($body, true);
echo $user["name"];

Rust

Популярная связка — асинхронный клиент reqwest и serde для разбора.

rust
use serde::Deserialize;

#[derive(Deserialize)]
struct User {
    name: String,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = reqwest::Client::new();

    let response = client
        .get("https://api.example.com/users/42")
        .header("Authorization", "Bearer YOUR_TOKEN")
        .send()
        .await?;

    if !response.status().is_success() {
        return Err(format!("Ошибка API: {}", response.status()).into());
    }

    // Разбор JSON в структуру
    let user: User = response.json().await?;
    println!("{}", user.name);
    Ok(())
}

Сравнение инструментов

Язык HTTP-клиент Разбор JSON
Python requests / httpx .json() (json)
JavaScript fetch / axios .json() (JSON)
Java HttpClient Jackson / Gson
Go net/http encoding/json
C# HttpClient System.Text.Json
PHP cURL / Guzzle json_decode
Rust reqwest serde / serde_json

Хорошие практики

При построении надёжной работы с API стоит придерживаться нескольких принципов:

  • Всегда проверяйте статус-код до разбора тела ответа.
  • Оборачивайте разбор в обработку ошибок — внешние данные ненадёжны.
  • Не храните секреты в коде — используйте переменные окружения.
  • Уважайте лимиты запросов — применяйте задержки и backoff при 429.
  • Ставьте таймауты на каждый запрос, чтобы не зависать.
  • Логируйте запросы и ошибки — это упрощает отладку интеграций.
  • Кешируйте редко меняющиеся данные, чтобы снизить нагрузку и не упираться в лимиты.
  • Фиксируйте версию API и следите за объявлениями об устаревании.
  • Валидируйте структуру ответа перед использованием полей.

Итог

Парсинг API — это полный цикл взаимодействия с внешним сервисом: формирование запроса, аутентификация, проверка статуса и заголовков, разбор тела ответа и обработка краевых случаев — пагинации, лимитов, сетевых сбоев. Технически шаг разбора данных почти всегда сводится к парсингу JSON, но устойчивая интеграция требует учитывать и всё, что его окружает.

Инструменты есть в каждом распространённом языке: где-то HTTP-клиент и парсер встроены, где-то применяются популярные библиотеки. Принцип везде один — превратить ответ удалённого сервиса в надёжные данные, с которыми безопасно работать в коде.

Подробнее о самом этапе разбора данных см. материал «Парсинг JSON», а о разборе других форматов ответа — материалы «Парсинг XML» и «Парсинг CSV».