Система обеспечения данными

DataParse — это интеллектуальная инфраструктура для сбора и структурирования публичной информации. Наш сайт работает для того, чтобы бизнес мог отказаться от ручного поиска и копирования данных.

Мы собираем информацию с открытых маркетплейсов, кадастровых баз, досок объявлений и реестров, чтобы вы получали готовые к аналитике массивы данных. Это экономит сотни часов ручного труда менеджеров и аналитиков.

Платформа работает как единый цикл: сбор данных по правилам источника, очистка дублей, нормализация полей, контроль качества и выдача в формате, который можно сразу загружать в CRM, BI или внутренние сервисы.

  • Для продаж: базы контактов, лидогенерация, сегментация компаний по нишам и регионам.
  • Для аналитики: мониторинг цен, ассортимента, динамики спроса и активности конкурентов.
  • Для автоматизации: регулярные обновления без ручной проверки сайтов и каталогов.
  • Для интеграций: API-канал с понятной структурой и технической поддержкой команды.
Цель сервиса: Трансформировать хаос неструктурированных данных в интернете в чистые таблицы профильного назначения.

Как пользоваться нашими услугами?

Процесс построен так, чтобы вы быстро получили рабочий результат и могли масштабировать выгрузки без переписывания процессов внутри компании.

Типовой запуск проекта состоит из 6 этапов:

  1. Бриф и постановка задачи Пользователь описывает, какие данные нужны (например: «все новостройки Москвы с ценами» или «контакты IT-компаний СПб»), какую задачу решает выгрузка и в какой системе данные будут использоваться.
  2. Согласование источников и полей Мы проверяем легальность публичного сбора, формируем список источников, словарь полей и правила валидации для итогового датасета.
  3. Пилотная выгрузка Предоставляем сэмпл (тестовый кусок данных) бесплатно, чтобы вы убедились в корректности форматов перед оплатой.
  4. Утверждение структуры и критериев качества Фиксируем обязательные поля, формат дат, очистку дублей, минимальные требования по заполненности и правила обновления.
  5. Поставка данных Передаем полную базу в нужном формате или подключаем API для регулярного обновления данных по расписанию.
  6. Поддержка после запуска Сопровождаем интеграцию, вносим изменения в структуру выгрузки и расширяем покрытие по новым регионам и категориям.
Практика: в большинстве проектов пилот можно получить в течение 1-2 рабочих дней после уточнения требований.

API запросы

Ниже описана полная схема API по вашей спецификации.

  • API: Parsers Service
  • Base URL: https://your-domain.tld/api/v1

Две модели авторизации:

  1. JWT access_token Получается через POST /auth/login с телом { username, password } и передается в заголовке Authorization: Bearer <access_token>.
  2. Personal API token Получается через POST /auth/api-token (с Bearer JWT), а затем используется в публичном запросе GET /parse как query-параметр api_token.

Ключевые эндпоинты:

  • POST /auth/login — получить JWT access_token.
  • POST /auth/api-token — получить личный API token.
  • GET /parse — публичный парс по api_token, parser, region.
  • GET /parsers/my — список доступных активных парсеров пользователя (через JWT).
  • PATCH /admin/users/:userId/parser-access-ids — назначить доступы к парсерам (admin).
  • PATCH /admin/parser/:parserId/active — глобально включить/выключить парсер (admin).
Важно: публичный /parse не требует JWT, но валидирует api_token, блокировку пользователя, доступ к парсеру и лимиты.

API с токеном: общая схема

Поток использования выглядит так: логин по JWT, получение личного API token, запрос списка доступных парсеров, затем публичный вызов /parse с параметром api_token.

  1. Шаг 1: JWT login POST /auth/login с { "username": "...", "password": "..." }.
  2. Шаг 2: получить personal API token POST /auth/api-token с заголовком Authorization: Bearer <access_token>.
  3. Шаг 3: узнать доступные парсеры GET /parsers/my (JWT) вернет только активные парсеры, к которым у пользователя есть доступ.
  4. Шаг 4: выполнить публичный парс GET /parse?api_token=...&parser=...&region=....

Контракт публичного parse-запроса:

GET /parse
Query:
  api_token: string (required)
  parser: integer (required)
  region: string (required)

Коды ответов для /parse:

  • 200: успешные данные парсера (формат зависит от parserPrefix).
  • 401/403: невалидный токен, блокировка пользователя, нет доступа, лимиты.
  • 404: парсер не найден.
  • 410: парсер отключен (isActive=false).

Админ-ручки:

  • PATCH /admin/users/:userId/parser-access-ids — выдать пользователю доступы к парсерам.
  • PATCH /admin/parser/:parserId/active — глобально включить/выключить парсер.

Подробные ответы по эндпоинтам:

  • POST /auth/api-token: 200 { token }, 401/403 — JWT истек/нет доступа.
  • GET /parsers/my: 200 список активных доступных парсеров, 401/403 — пользователь заблокирован или нет JWT.
  • PATCH /admin/users/:userId/parser-access-ids: 200 { success, user, parsers }, 400 невалидное тело, 403 нет admin-прав или попытка менять admin-пользователя, 404 user/parserId не найден.
  • PATCH /admin/parser/:parserId/active: 200 { success, parser }, 400 isActive не boolean, 401/403 нет admin-доступа, 404 parser не найден.
Замечание: все admin-ручки защищены ролями (JwtAuthGuard + RolesGuard) и требуют admin права.

API с токеном: PHP

Ниже полный PHP-flow по вашему API: логин, получение personal API token, чтение доступных парсеров и публичный parse-запрос с api_token.

<?php
$baseUrl = 'https://your-domain.tld/api/v1';
$username = 'demo_user';
$password = 'demo_password';

function postJson($url, $body, $headers = []) {
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST => true,
        CURLOPT_HTTPHEADER => array_merge(['Content-Type: application/json'], $headers),
        CURLOPT_POSTFIELDS => json_encode($body, JSON_UNESCAPED_UNICODE),
    ]);
    $response = curl_exec($ch);
    $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    return [$status, json_decode($response, true)];
}

function getJson($url, $headers = []) {
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER => $headers,
    ]);
    $response = curl_exec($ch);
    $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    return [$status, json_decode($response, true)];
}

// 1) login -> JWT access_token
[$loginStatus, $loginBody] = postJson(
    "$baseUrl/auth/login",
    ['username' => $username, 'password' => $password]
);
$accessToken = $loginBody['accessToken'] ?? null;

// 2) get personal API token
[$tokenStatus, $tokenBody] = postJson(
    "$baseUrl/auth/api-token",
    [],
    ["Authorization: Bearer {$accessToken}"]
);
$apiToken = $tokenBody['token'] ?? null;

// 3) list my parsers
[$parsersStatus, $parsers] = getJson(
    "$baseUrl/parsers/my",
    ["Authorization: Bearer {$accessToken}"]
);

// 4) parse by api_token (public)
$parserId = 1;
$region = 'moscow';
[$parseStatus, $parseData] = getJson(
    "$baseUrl/parse?api_token={$apiToken}&parser={$parserId}®ion={$region}"
);

print_r(compact(
    'loginStatus',
    'tokenStatus',
    'parsersStatus',
    'parseStatus',
    'parseData'
));
  1. Сначала всегда JWT, потом API token /auth/api-token работает только с валидным Bearer access_token.
  2. parser берите из /parsers/my Не хардкодьте ID парсеров, список доступов может отличаться у разных пользователей.
  3. Обрабатывайте коды parse-запроса 401/403 — токен/доступ/лимит, 404 — parser not found, 410 — parser выключен.

Admin пример (PHP):

// PATCH /admin/users/:userId/parser-access-ids
// PATCH /admin/parser/:parserId/active
// Требуется Bearer <admin_access_token>

API с токеном: Node.js

Node.js сценарий повторяет контракт 1:1. Сначала JWT, затем личный API token, потом публичный /parse.

const BASE_URL = 'https://your-domain.tld/api/v1';

async function login(username, password) {
  const response = await fetch(`${BASE_URL}/auth/login`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ username, password }),
  });
  if (!response.ok) throw new Error('Login failed');
  return response.json(); // { accessToken, ... }
}

async function getApiToken(accessToken) {
  const response = await fetch(`${BASE_URL}/auth/api-token`, {
    method: 'POST',
    headers: { Authorization: `Bearer ${accessToken}` },
  });
  if (!response.ok) throw new Error('Get API token failed');
  return response.json(); // { token }
}

async function getMyParsers(accessToken) {
  const response = await fetch(`${BASE_URL}/parsers/my`, {
    headers: { Authorization: `Bearer ${accessToken}` },
  });
  if (!response.ok) throw new Error('Get parsers failed');
  return response.json();
}

async function parsePublic(apiToken, parserId, region) {
  const query = new URLSearchParams({
    api_token: apiToken,
    parser: String(parserId),
    region,
  });
  const response = await fetch(`${BASE_URL}/parse?${query.toString()}`);
  return { status: response.status, body: await response.json() };
}

const { accessToken } = await login('demo_user', 'demo_password');
const { token: apiToken } = await getApiToken(accessToken);
const parsers = await getMyParsers(accessToken);
const parserId = parsers[0]?.id ?? 1;
const parseResult = await parsePublic(apiToken, parserId, 'moscow');

console.log(parseResult);
  1. Разделите JWT и API token JWT нужен для служебных ручек (/auth/api-token, /parsers/my), а api_token — для публичного /parse.
  2. Проверяйте HTTP-коды parse Обрабатывайте 401/403/404/410 отдельными ветками с разной логикой retry.
  3. Фиксируйте request context Логируйте parserId, region, userId, чтобы быстрее разбирать инциденты доступа и лимитов.

Admin вызовы в Node.js:

// PATCH /admin/users/:userId/parser-access-ids
await fetch(`${BASE_URL}/admin/users/12/parser-access-ids`, {
  method: 'PATCH',
  headers: {
    'Content-Type': 'application/json',
    Authorization: `Bearer ${adminAccessToken}`,
  },
  body: JSON.stringify({ parserAccessIds: [1, 2, 3] }),
});

// PATCH /admin/parser/:parserId/active
await fetch(`${BASE_URL}/admin/parser/7/active`, {
  method: 'PATCH',
  headers: {
    'Content-Type': 'application/json',
    Authorization: `Bearer ${adminAccessToken}`,
  },
  body: JSON.stringify({ isActive: false }),
});

API с токеном: TypeScript

TypeScript-слой помогает зафиксировать контракт ответов и исключить ошибки интеграции еще на этапе компиляции.

const BASE_URL = 'https://your-domain.tld/api/v1';

type LoginResponse = { accessToken: string };
type ApiTokenResponse = { token: string };

type ParseErrorCode = 401 | 403 | 404 | 410;
type ParseSuccess = Record<string, unknown>;

interface ParseResult {
  status: number;
  body: ParseSuccess | { message?: string };
}

async function login(username: string, password: string): Promise<LoginResponse> {
  const response = await fetch(`${BASE_URL}/auth/login`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ username, password }),
  });
  if (!response.ok) throw new Error('Login failed');
  return response.json() as Promise<LoginResponse>;
}

async function getApiToken(accessToken: string): Promise<ApiTokenResponse> {
  const response = await fetch(`${BASE_URL}/auth/api-token`, {
    method: 'POST',
    headers: { Authorization: `Bearer ${accessToken}` },
  });
  if (!response.ok) throw new Error('API token issue failed');
  return response.json() as Promise<ApiTokenResponse>;
}

async function parseByApiToken(
  apiToken: string,
  parser: number,
  region: string,
): Promise<ParseResult> {
  const query = new URLSearchParams({
    api_token: apiToken,
    parser: String(parser),
    region,
  });
  const response = await fetch(`${BASE_URL}/parse?${query.toString()}`);
  return { status: response.status, body: await response.json() };
}
  1. Описывайте отдельные типы для auth и parse Контракты у /auth/* и /parse разные, лучше разделять интерфейсы.
  2. Нормализуйте обработку 401/403/404/410 Для parse лучше делать отдельный mapper в доменные ошибки приложения.
  3. Комбинируйте compile-time и runtime валидацию Внешний API может меняться, поэтому полезно проверять ключевые поля ответа на рантайме.
Практика: храните HTTP-код parse-ответа вместе с телом и передавайте его дальше в слой UI.

API с токеном: NestJS

Для NestJS оптимально вынести внешние вызовы в сервис и использовать его в use-case слое: логин, выдача API token, список парсеров, публичный parse и admin-операции.

import { Injectable } from '@nestjs/common';
import { HttpService } from '@nestjs/axios';
import { firstValueFrom } from 'rxjs';

@Injectable()
export class ParsersServiceClient {
  private readonly baseUrl = 'https://your-domain.tld/api/v1';

  constructor(private readonly http: HttpService) {}

  async login(username: string, password: string) {
    const { data } = await firstValueFrom(
      this.http.post(`${this.baseUrl}/auth/login`, { username, password }),
    );
    return data; // { accessToken }
  }

  async getApiToken(accessToken: string) {
    const { data } = await firstValueFrom(
      this.http.post(
        `${this.baseUrl}/auth/api-token`,
        {},
        { headers: { Authorization: `Bearer ${accessToken}` } },
      ),
    );
    return data; // { token }
  }

  async getMyParsers(accessToken: string) {
    const { data } = await firstValueFrom(
      this.http.get(`${this.baseUrl}/parsers/my`, {
        headers: { Authorization: `Bearer ${accessToken}` },
      }),
    );
    return data;
  }

  async parseByApiToken(apiToken: string, parser: number, region: string) {
    const { data } = await firstValueFrom(
      this.http.get(`${this.baseUrl}/parse`, {
        params: { api_token: apiToken, parser, region },
      }),
    );
    return data;
  }

  async setUserParserAccess(
    adminAccessToken: string,
    userId: number,
    parserAccessIds: number[],
  ) {
    const { data } = await firstValueFrom(
      this.http.patch(
        `${this.baseUrl}/admin/users/${userId}/parser-access-ids`,
        { parserAccessIds },
        { headers: { Authorization: `Bearer ${adminAccessToken}` } },
      ),
    );
    return data;
  }

  async setParserActive(
    adminAccessToken: string,
    parserId: number,
    isActive: boolean,
  ) {
    const { data } = await firstValueFrom(
      this.http.patch(
        `${this.baseUrl}/admin/parser/${parserId}/active`,
        { isActive },
        { headers: { Authorization: `Bearer ${adminAccessToken}` } },
      ),
    );
    return data;
  }
}
  1. Разделяйте user и admin потоки Пользовательские операции и admin-операции должны идти разными use-case и guard-проверками.
  2. Проверяйте права до вызова admin-ручек До PATCH запросов валидируйте, что роль действительно admin.
  3. Стандартизируйте исключения Переводите внешние 401/403/404/410 в понятные domain exceptions внутри Nest-приложения.
Практика для NestJS: добавьте timeout, retry и structured logging c requestId/userId.

API с токеном: React

React-сценарий лучше строить через custom hook: логин, выдача API token, загрузка списка парсеров, запуск parse и отображение результата/ошибок в UI.

import { useCallback, useState } from 'react';

const BASE_URL = 'https://your-domain.tld/api/v1';

async function login(username, password) {
  const response = await fetch(`${BASE_URL}/auth/login`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ username, password }),
  });
  if (!response.ok) throw new Error('Login failed');
  return response.json();
}

export function useParsersApi() {
  const [accessToken, setAccessToken] = useState(null);
  const [apiToken, setApiToken] = useState(null);
  const [parsers, setParsers] = useState([]);
  const [state, setState] = useState('idle');
  const [result, setResult] = useState(null);

  const authorize = useCallback(async (username, password) => {
    const auth = await login(username, password);
    setAccessToken(auth.accessToken);

    const tokenResp = await fetch(`${BASE_URL}/auth/api-token`, {
      method: 'POST',
      headers: { Authorization: `Bearer ${auth.accessToken}` },
    });
    const tokenData = await tokenResp.json();
    setApiToken(tokenData.token);

    const parsersResp = await fetch(`${BASE_URL}/parsers/my`, {
      headers: { Authorization: `Bearer ${auth.accessToken}` },
    });
    setParsers(await parsersResp.json());
    setState('authorized');
  }, []);

  const parse = useCallback(async (parserId, region) => {
    setState('loading');
    const query = new URLSearchParams({
      api_token: apiToken,
      parser: String(parserId),
      region,
    });
    const response = await fetch(`${BASE_URL}/parse?${query.toString()}`);
    const body = await response.json();
    setResult({ status: response.status, body });
    setState(response.ok ? 'done' : 'error');
  }, [apiToken]);

  return { accessToken, apiToken, parsers, state, result, authorize, parse };
}
  1. Разделите auth и parse UI На первом экране получите JWT/API token, на втором дайте выбрать parser+region.
  2. Показывайте пользователю код ответа Для parse отображайте статусы 401/403/404/410 с понятным текстом.
  3. Не храните admin JWT в клиенте Admin-операции выполняйте только на backend.

API с токеном: Vue 3

Для Vue 3 используйте composable с двумя этапами: авторизация (JWT + api_token) и публичный parse по выбранному парсеру и региону.

import { ref } from 'vue';

const BASE_URL = 'https://your-domain.tld/api/v1';

export function useParsersApi() {
  const accessToken = ref(null);
  const apiToken = ref(null);
  const parsers = ref([]);
  const state = ref('idle');
  const result = ref(null);

  const authorize = async (username, password) => {
    const loginResp = await fetch(`${BASE_URL}/auth/login`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ username, password }),
    });
    const loginData = await loginResp.json();
    accessToken.value = loginData.accessToken;

    const tokenResp = await fetch(`${BASE_URL}/auth/api-token`, {
      method: 'POST',
      headers: { Authorization: `Bearer ${accessToken.value}` },
    });
    const tokenData = await tokenResp.json();
    apiToken.value = tokenData.token;

    const parsersResp = await fetch(`${BASE_URL}/parsers/my`, {
      headers: { Authorization: `Bearer ${accessToken.value}` },
    });
    parsers.value = await parsersResp.json();
    state.value = 'authorized';
  };

  const parse = async (parserId, region) => {
    const query = new URLSearchParams({
      api_token: apiToken.value,
      parser: String(parserId),
      region,
    });
    const response = await fetch(`${BASE_URL}/parse?${query.toString()}`);
    const body = await response.json();
    result.value = { status: response.status, body };
    state.value = response.ok ? 'done' : 'error';
  };

  return { accessToken, apiToken, parsers, state, result, authorize, parse };
}
  1. Храните auth-state в composable Это позволяет переиспользовать логику между страницами без дублирования.
  2. Делайте явный mapping ошибок Для 401/403/404/410 выводите разные сообщения и действия для пользователя.
  3. Admin операции держите на сервере Не отправляйте admin access token в браузер.
Практика для Vue: в шаблоне показывайте селект парсера из parsers и отдельный селект региона перед вызовом parse.

Форматы и API

В вашей архитектуре публичный parse возвращает данные в формате конкретного парсера. Поэтому интеграция должна опираться на parserPrefix, который связан с выбранным parserId.

Что именно означает "форматы" в этом API:

  • Транспорт API: HTTP + JSON (UTF-8).
  • Публичный parse: формат параметров через query-строку URL.
  • Служебные и admin ручки: JSON body + Bearer JWT в заголовке.
  • Отдельного параметра format=csv/xlsx в API нет: базовый ответ приходит в JSON, при необходимости конвертируйте у себя в CSV/XLSX.

1) Формат запроса (public parse):

Базовый публичный запрос:

GET /api/v1/parse?api_token=...&parser=1&region=moscow

Обязательные параметры:

  • api_token — personal API token пользователя.
  • parser — ID парсера (integer).
  • region — регион в формате, который поддерживает выбранный parser.

2) Формат служебных auth-запросов:

POST /api/v1/auth/login
Content-Type: application/json

{
  "username": "string",
  "password": "string"
}
POST /api/v1/auth/api-token
Authorization: Bearer <access_token>
Content-Type: application/json

{}

3) Формат admin-запросов (JSON body):

PATCH /api/v1/admin/users/:userId/parser-access-ids
Authorization: Bearer <admin_access_token>
Content-Type: application/json

{
  "parserAccessIds": [1, 2, 3]
}
PATCH /api/v1/admin/parser/:parserId/active
Authorization: Bearer <admin_access_token>
Content-Type: application/json

{
  "isActive": true
}

Служебные ручки, которые обеспечивают стабильный flow:

  • POST /auth/login — получить JWT access_token.
  • POST /auth/api-token — получить personal API token по JWT.
  • GET /parsers/my — получить список активных парсеров, доступных пользователю.

Admin ручки для управления доступами:

  • PATCH /admin/users/:userId/parser-access-ids
  • PATCH /admin/parser/:parserId/active

Коды ответов и конкретные действия:

  • 200: вернуть данные в приложение и зафиксировать лог вызова.
  • 401/403: обновить JWT/API token, проверить блокировку и доступ к parser.
  • 404: перечитать /parsers/my и заново выбрать parser.
  • 410: parser отключен глобально, выбрать другой parser и уведомить пользователя.

4) Формат успешного ответа (пример структуры):

{
  "status": 200,
  "data": {
    "parserId": 1,
    "region": "moscow",
    "items": [
      { "title": "Example item", "url": "https://example.com/item/1" }
    ]
  }
}

5) Формат ошибок (пример):

{
  "status": 403,
  "message": "Нет доступа к выбранному парсеру"
}
Практическая конкретика: в логах храните userId, parser, region, HTTP-код, время и requestId для быстрой диагностики.

Принципы легального парсинга

Деятельность сервиса строго регламентирована законодательством РФ в сфере открытой информации.

Мы никогда не занимаемся сбором:
• Закрытой персональной информации
• Коммерческой тайны
• Данных за авторизацией (взлом аккаунтов)

Вся работа идет исключительно с публично представленной информацией (OSINT подход), что делает покупку и хранение таких баз абсолютно законным для вашего бизнеса.

Для технической безопасности мы применяем разграничение доступов, журналирование действий в API и контроль ролей пользователей. Ключи и токены доступа должны храниться в защищенных переменных окружения, а не в открытом клиентском коде.

  • Минимизация доступа: каждый токен имеет предсказуемую область применения и сроки ротации.
  • Шифрование трафика: обмен данными между сервисами идет по защищенным каналам.
  • Аудит операций: сохраняется история запуска задач, изменения статусов и выгрузок.
  • Управление инцидентами: при подозрительной активности доступ может быть временно ограничен.

Политика конфиденциальности

Настоящая политика описывает, какие данные мы получаем, как обрабатываем и как защищаем информацию пользователей сервиса DataParse.

Используя сайт и API, вы соглашаетесь с обработкой данных в рамках этой политики и действующего законодательства.

  • Какие данные собираются: контактные данные из формы, технические логи запросов, служебные метаданные задач, история обращений в поддержку.
  • Цели обработки: предоставление услуг парсинга, техническая поддержка, улучшение качества сервиса, обеспечение безопасности и предотвращение злоупотреблений.
  • Срок хранения: данные хранятся в объеме и сроках, необходимых для оказания услуг и выполнения юридических обязательств.
  • Передача третьим лицам: не передаем данные третьим лицам, кроме случаев, предусмотренных договором или законодательством.
  • Права пользователя: запросить уточнение, обновление или удаление данных, если это не противоречит обязательным требованиям закона.
  • Cookies и аналитика: используем технические cookies и обезличенную аналитику для стабильной работы сайта и интерфейса.

По вопросам конфиденциальности и удалению данных вы можете обратиться через форму обратной связи на сайте или по контактам, указанным в вашем договоре обслуживания.