--- title: "Eksporty i zadania asynchroniczne" source: https://docs.senuto.com/exports-and-tasks --- # Eksporty i zadania asynchroniczne Trzy różne sposoby wyciągania danych z API — mylenie ich to najczęstsza przyczyna timeoutów przy „dużych pobraniach". | Tryb | Kiedy | Co dostajesz | | -------------------------- | -------------------------------------------------- | ---------------------------------------------------------------- | | **Raport synchroniczny** | domyślnie, większość endpointów | JSON w odpowiedzi, stronicowany ([paginacja](/types/pagination)) | | **Zadanie asynchroniczne** | narzędzia SERP (crawl SERP-a, TOP100, listy URL-i) | `task_id` od razu, dane po zakończeniu zadania | | **Eksport plikowy** | gdy chcesz plik zamiast JSON-a | CSV/plik do pobrania, bez koperty `{success, data}` | > **Ostrzeżenie:** > **Nie ma ogólnego mechanizmu „zleć raport i odbierz później".** Zwykłe raporty > (np. `positions/getData`, `dashboard/getDomainStatistics`) liczą się w trakcie żądania — > nie zwracają identyfikatora zadania i nie da się ich zakolejkować. Duże pobranie dzielisz > **paginacją i zakresem dat**, a nie zleceniem w tle. Kolejkowanie istnieje wyłącznie > w rodzinach `tasks/management/*` opisanych niżej. ## Jak rozpoznać, w którym trybie działa Twój endpoint Tryb rozpoznasz po ścieżce i wymaganych parametrach: | Sygnał w endpoincie | Tryb | Co to znaczy | | ------------------------------------------------ | -------------------------- | ----------------------------------------------------------------- | | ścieżka zaczyna się od `/api/tasks/management/…` | **zadanie asynchroniczne** | `create` zwraca `task_id`, wyniki odbierasz po `check` | | endpoint **wymaga `task_id`** w parametrach | odbiór wyników zadania | zadanie musi być już zakończone | | w ścieżce jest segment **`exports`** | **eksport plikowy** | odpowiedzią jest plik (CSV), nie JSON | | nic z powyższych | **raport synchroniczny** | dane liczą się w trakcie żądania; brak job ID i brak kolejkowania | Innymi słowy: **job ID istnieje wyłącznie w trzech rodzinach `tasks/management/*`** wymienionych niżej. Jeśli Twojego endpointu tam nie ma i nie przyjmuje `task_id`, to nie da się go „zlecić i odebrać później" — zostaje paginacja, zawężenie zakresu albo eksport plikowy. --- ## 1. Duże pobranie z raportu synchronicznego Gdy raport przerywa się timeoutem (`data.error.type = "timeout"` — patrz [Błędy](/types/errors)), zawężaj żądanie, zamiast je ponawiać w tej samej postaci: - **Stronicuj.** `limit` + `page`; łączną liczbę wierszy podaje `pagination.count`, a `pagination.has_next_page` mówi, kiedy przestać. Maksymalny `limit` zależy od endpointu (często 100) — wyższa wartość to `418`, nie szybsze pobranie. - **Tnij zakres dat.** Raporty historyczne (`date_min` / `date_max`) kosztują tym więcej, im szerszy zakres i im większa domena. Miesiąc po miesiącu przechodzi tam, gdzie rok naraz się wywraca. - **Ogranicz zbiór na wejściu.** `filtering` po stronie API jest tańsze niż pobranie wszystkiego i filtrowanie u siebie. - **Rozważ eksport plikowy** (sekcja 3) — dla tych samych danych zwraca CSV jednym żądaniem, bez składania stron. > **Informacja:** > Odpowiedź `200` z pustym `data: []` to nie timeout ani błąd — dla tej domeny i tego zakresu > po prostu nie ma danych. Sprawdź `fetch_mode`, zakres dat i pisownię domeny. --- ## 2. Zadania asynchroniczne (`tasks/management/*`) Narzędzia, które muszą najpierw zebrać dane z Google, działają w cyklu **`create` → `check` (polling) → odbiór wyników**. `create` zwraca `task_id` natychmiast; dane pojawiają się dopiero, gdy zadanie się zakończy. | Rodzina | `create` | `check` | Wyniki | | ---------------- | --------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | | **Analiza SERP** | [`POST /api/tasks/management/serp_analysis/create`](/modules/serp_analysis/serp-task-create) | [`GET …/serp_analysis/check`](/modules/serp_analysis/serp-task-check) | raporty [`serp_analysis/reports/*`](/modules/serp_analysis) | | **TOP100 crawl** | [`POST /api/tasks/management/top100_crawl/create`](/modules/serp_analysis/serp-tool-top100-create) | [`GET …/top100_crawl/check`](/modules/serp_analysis/serp-tool-top100-check) | [`top100_crawl/getData`](/modules/serp_analysis/serp-tool-top100-getData) lub [eksport CSV](/modules/serp_analysis/serp-export-top100) | | **URLs crawl** | [`POST /api/tasks/management/urls_crawl/create`](/modules/serp_analysis/serp-tool-urlscrawl-create) | [`GET …/urls_crawl/check`](/modules/serp_analysis/serp-tool-urlscrawl-check) | [`urls_crawl/getList`](/modules/serp_analysis/serp-tool-urlscrawl-getList) lub [eksport CSV](/modules/serp_analysis/serp-export-urlscrawl) | ### Skąd wiadomo, że zadanie jest gotowe `check` przyjmuje `task_id` **w query stringu** i zwraca obiekt zadania. Sygnał gotowości różni się między rodzinami — dlatego czytaj konkretne pole, a nie „jakikolwiek status": - **Analiza SERP** — `progress.has_serp_data = true` (crawl SERP gotowy) oraz `progress.has_keywords_analysis_data = true` (dane Bazy słów gotowe). Odpytanie raportu wcześniej zwraca błąd walidacji `Unfinished task`. - **TOP100 / URLs crawl** — `status = "completed"` (świeżo utworzone zadanie ma `waiting`) oraz `progress = {all, finished, unfinished}`, po którym widać postęp. Czas realizacji zależy od rodzaju i wielkości zadania — od minut po godziny. **Nie zakładaj stałej wartości**: odpytuj `check` w odstępach (np. co 30–60 s) i przerwij po własnym limicie czasu. ### Przechowuj `task_id` `create` zwraca identyfikator tylko raz — **zapisz go po swojej stronie**. Zadanie nie wygasa: raporty zakończonego zadania odpytasz również następnego dnia, bez tworzenia nowego i bez konsumpcji limitu. Zgubiony `task_id` oznacza zlecenie analizy od nowa, czyli kolejną jednostkę limitu. ### Szkic pętli **Python** ```python filename="polling.py" import time, requests H = {"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"} BASE = "https://api.senuto.com" # 1. zlecenie zadania — zużywa limit narzędzi (tools_daily_limit) task = requests.post(f"{BASE}/api/tasks/management/top100_crawl/create", headers=H, json={"keywords": ["buty"], "country_id": 1}).json() task_id = task["data"]["id"] # 2. polling — task_id w QUERY STRINGU, nie w ciele deadline = time.time() + 3600 while time.time() < deadline: state = requests.get(f"{BASE}/api/tasks/management/top100_crawl/check", headers=H, params={"task_id": task_id}).json()["data"] if state["status"] == "completed": break time.sleep(60) else: raise TimeoutError(f"zadanie {task_id} nie zdążyło — sprawdź je później przez /list") # 3. odbiór wyników rows = requests.post(f"{BASE}/api/serp_analysis/tools/top100_crawl/getData", headers=H, json={"task_id": task_id, "limit": 100, "page": 1}).json() ``` **Pseudokod** ```text filename="cykl zadania" task_id = create(parametry) → status "waiting" powtarzaj: stan = check(task_id) → task_id w query stringu jeśli gotowe (patrz wyżej) → przerwij jeśli przekroczono własny limit czasu → przerwij i wróć później przez list() czekaj 30–60 s wyniki = getData/getList(task_id) → albo eksport CSV ``` > **Ostrzeżenie:** > **`create` zużywa limit** — `serp_analysis_daily_limit` dla Analizy SERP, > `tools_daily_limit` (plus limit wejściowy narzędzia) dla crawlów. `check`, `list` i odczyt > wyników limitu nie zużywają, więc polling jest bezpieczny — podobnie jak ponowienie `create` > po timeoucie ([Ponowienia po timeoucie](#ponowienia-po-timeoucie)). Stan liczników: > [`GET /api/users/getLimits`](/rate-limits). --- ## 3. Eksporty plikowe (CSV) Endpointy z segmentem `exports` **zwracają plik**, nie kopertę JSON — nie mają playgroundu i nie parsuj ich jak zwykłej odpowiedzi. Przydają się tam, gdzie alternatywą jest przejście przez kilkadziesiąt stron paginacji. | Moduł | Endpoint | Zawartość | | -------------------- | ------------------------------------------------------------------ | --------------------------- | | Analiza SERP | `POST /api/serp_analysis/tools/exports/top100_crawl/getData` | wyniki zadania TOP100 crawl | | Analiza SERP | `POST /api/serp_analysis/tools/exports/urls_crawl/getList` | wyniki zadania URLs crawl | | Baza słów kluczowych | `POST /api/keywords_analysis/tools/exports/statistics/getKeywords` | frazy z raportu statystyk | > **Ostrzeżenie:** > Eksporty narzędziowe (`tools/exports/*`) **zużywają `tools_daily_limit`** — tak samo jak > wywołanie samego narzędzia. Powtórzenie **tego samego** eksportu w ciągu 24 h już nie nalicza > kolejnej jednostki (patrz [Ponowienia po timeoucie](#ponowienia-po-timeoucie)), ale eksport > z innym zestawem parametrów to nowa jednostka. --- ## Ponowienia po timeoucie Timeout po stronie klienta nie mówi, czy operacja wykonała się po stronie Senuto. **Nie ma nagłówka `Idempotency-Key`** — i nie jest potrzebny, bo każdy z trzech trybów ma własny sposób bezpiecznego ponowienia. Wybierz właściwy dla swojego endpointu: | Tryb | Jak sprawdzić, czy operacja przeszła | Czy ponowienie kosztuje | | ---------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Raport synchroniczny** | `usage` w [`GET /api/users/getLimits`](/rate-limits#podgląd-limitów-przez-api); dla domeny w Analizie widoczności — `checkQuery` | **Nie** — identyczne zapytanie w oknie 24 h trafia w Grace Window ([szczegóły](/rate-limits#ponowienia-i-grace-window)) | | **Zadanie async** (`tasks/management/*`) | `…/list` — zwraca wszystkie zadania konta z ich `task_id` i statusami | Analiza SERP: **nie** (`create` zwraca istniejące zadanie dla tej samej frazy i kraju). Crawle: powstanie **nowe** zadanie, ale `tools_daily_limit` nie naliczy się drugi raz dla identycznego payloadu w 24 h | | **Narzędzie z `public_id`** (KA `statistics/create`) | brak listy — `public_id` odzyskasz tylko z odpowiedzi, `checkData` sprawdza znany `public_id` | **Nie** dla identycznego payloadu w oknie 24 h, ale wynik dostaniesz pod **nowym** `public_id` | | **Eksport plikowy** | brak — to zwykłe pobranie pliku | **Nie** dla powtórzenia tego samego eksportu w 24 h | Zasady, które warto zaszyć w kliencie: - **Retry wysyłaj bajt w bajt.** Klucz Grace Window liczy się z parametrów żądania, więc zmieniona kolejność fraz w `keywords` albo dorzucony opcjonalny parametr tworzy nową jednostkę limitu. - **Zanim ponowisz `create` zadania — zajrzyj do `list`.** To najtańszy sposób ustalenia, czy zadanie już istnieje, i jedyny sposób odzyskania zgubionego `task_id`. - **`public_id` zapisuj natychmiast po odebraniu odpowiedzi.** Jest generowany losowo i nie ma endpointu, który wylistuje wyniki narzędzi konta — utracony `public_id` oznacza ponowne wywołanie `create` (kosztowo bezpieczne w oknie 24 h, ale wynik dostaniesz pod nowym identyfikatorem). - **Podnieś timeout, zamiast ponawiać agresywnie.** Ciężkie wywołania (`create` z kilkuset frazami, szerokie raporty historyczne) potrafią liczyć się minutami — ustaw timeout klienta rzędu 120 s. - **Dwie operacje naliczają się przy każdym wywołaniu** i nie mają Grace Window: `refresh` w Analizie SERP oraz ręczne odświeżanie pozycji w Monitoringu. Tutaj retry realnie kosztuje. ## Co dalej - [Paginacja](/types/pagination) — `limit`, `page` i pole `pagination`. - [Limity zapytań](/rate-limits) — które liczniki zużywają zadania i eksporty. - [Błędy](/types/errors) — `timeout`, `418` i pozostałe kategorie. - [Analiza SERP](/modules/serp_analysis) — pełny opis rodzin zadań i raportów.