5. Инструменты — сбор и поиск данных
🔵 Движки для получения данных извне: HTTP-скрап, локальный архив, JSON, RSS, PDF, геокодинг. Сводную таблицу всех движков см. в «Справочник инструментов».
5.4 site_tool
Универсальный скрапер: HTTP-запрос → извлечение полей → формат вывода.
kind: site_tool
name: my_scraper
description: Парсит страницу товара.
parameters:
type: object
properties:
url: { type: string }
required: [url]
fetch:
url: "{{ input.url }}" # шаблон MiniJinja
method: POST # GET (по умолч.) | POST | PUT | PATCH | DELETE
json: { query: "{{ input.q }}" } # JSON-тело (листья — шаблоны)
# body: "q={{ input.q | urlencode }}" # сырое тело (шаблон)
# form: { search: "{{ input.q }}" } # x-www-form-urlencoded
# multipart: { fields: {...}, files: {...} }
timeout_secs: 15
cache_ttl_secs: 300
follow_redirects: true
cookies: true
headers:
User-Agent: "MyBot/1.0"
Authorization: "Bearer {{ env_var('API_KEY') }}"
source:
type: script_json # JSON внутри <script>
contains: productData
# type: json # или тело само — JSON
extract: { ... }
output: { ... }
fetch — URL
Два стиля:
- Прямой URL —
url: "{{ input.url }}". - Построитель пути —
base+path_opt+path+query(пример —drom_search.yaml):
fetch:
base: "https://auto.drom.ru/"
path_opt:
- when: { has: region }
value: "region{{ region | region_code }}"
- when: { equals: [condition, "new"] }
value: "new"
path:
- "{{ brand }}"
- "{{ model | default('') | slugify }}"
- "{% if page is defined and page > 1 %}page{{ page }}{% endif %}"
query:
unsold: "1"
minprice: "{{ price_from }}"
filters:
slugify: _builtin_
region_code:
moscow: "77"
nsk: "54"
region_code_ci:
case_insensitive: true
default: "77"
"Москва": "77"
filters сопоставляет имена фильтров с _builtin_ (например slugify) или
таблицей ключ→значение. Lookup-таблица — обычная карта, либо объект с
case_insensitive: true и default (резерв при отсутствии значения).
env_var('NAME') читает переменную окружения — используй для ключей в заголовках.
query применяется и при прямом url (добавляется с URL-кодированием).
fetch — общий профиль (extends)
Чтобы не повторять base_url и Authorization, вынеси их в профиль и укажи
extends (путь относительно YAML инструмента):
# tools/_shared.yaml
base_url: "https://api.example.com"
headers:
Authorization: "Bearer {{ env_var('API_KEY') }}"
# tools/list.yaml
fetch:
extends: _shared.yaml
path: ["issues"]
headers:
Accept: "text/plain" # перекрывает Accept из профиля
Собственные url/base/headers инструмента выигрывают у профиля.
fetch — тело запроса
Ровно одно из body / json / form / multipart (неоднозначный конфиг
отвергается при загрузке). json: — JSON-тело, каждый строковый лист — шаблон.
form: — urlencoded. multipart: { fields, files } — files это локальные пути.
Булевы/null в MiniJinja рендерятся Python-стилем (True/False/None), что
невалидный JSON — в сыром body прогоняй значения через tojson:
body: '{"completed": {{ input.completed | tojson }}}'
Для json-тела omit_empty: true рекурсивно выбрасывает ключи с пустым
значением ("", null, [], {}) — удобно для PATCH.
fetch — ретраи / backoff
Временные сбои ретраятся автоматически: HTTP 429, 5xx, сетевые/таймаут-ошибки.
retries — число попыток (по умолчанию 3, 0 отключает); backoff_ms — базовая
задержка с удвоением (по умолчанию 3000 → 3с, 6с, 12с).
fetch — пагинация (token-стиль)
Для списков с токеном следующей страницы добавь pagination:
pagination:
token_field: /next_page_token
token_param: page_token
items_field: /issues
max_items: 100
max_pages: 10
Инструмент сам следует по страницам (агрегируя элементы) и сообщает, где
продолжить. Результат: { items, page_info: { returned, total, has_more, next_token } }.
LLM продолжает вызовом с page_token: <next_token>.
source — где лежат структурированные данные
| Поле | Значения |
|---|---|
type |
script_json (JSON-блоб в <script>) или json (тело — JSON) |
contains |
подстрока, которую должен содержать скрипт (для script_json) |
min_length |
мин. длина скрипта (по умолчанию 5000) |
Другие типы source:
json_ld— schema.org JSON-LD; выбор блока по@type:source: type: json_ld type_selector: Product # совпадает с "Product" или "https://schema.org/Product" graph: true # искать и во вложенных @graph (по умолчанию true)file_json— JSON из локального файла (без HTTP):source: { type: file_json, file: data/input.json }devalue— payload Nuxt 3 (__NUXT_DATA__): резолвит целочисленные ссылки и маркеры.source: { type: devalue, contains: __NUXT_DATA__, min_length: 1000 }html— извлечение CSS-селекторами (JSON не нужен):
Возвращаетsource: type: html selector: ".product-card" # повторяющийся элемент-запись fields: title: { selector: ".product-card__name", attr: text } price: { selector: ".product-card__price-current", attr: text } url: { selector: "a.product-card__name", attr: href }{ "items": [ {field: value, …}, …] }.
fetch — двухшаговый токен (auth)
Когда сначала нужен токен (анонимная авторизация), добавь auth:
fetch:
auth:
token_url: "https://…/api/v1/auth/anonymous"
method: POST
json: { app: "mobile" } # или body / form / headers
field: token.accessToken # JSON-указатель /a/b или точка a.b
header: Authorization
prefix: "Bearer "
fetch — OAuth2 (oauth2)
Для OAuth2-сервисов (Bitrix24, amoCRM, Google) — oauth2. В отличие от
auth, движок кэширует access-токен до истечения срока, ротирует
refresh-токен и при HTTP 401 сам обновляет токен и повторяет запрос. Секреты
берутся из env (никогда в YAML):
fetch:
oauth2:
token_url: "https://…/oauth2/token"
grant: refresh_token # refresh_token (по умолч.) | client_credentials
client_id_env: OAUTH_CLIENT_ID
client_secret_env: OAUTH_CLIENT_SECRET
refresh_token_env: OAUTH_REFRESH_TOKEN # для grant: refresh_token
# scope: "..." # для grant: client_credentials
header: Authorization # по умолчанию
prefix: "Bearer " # по умолчанию
Ответ парсится по стандартным полям OAuth2: access_token, expires_in
(секунды) и опциональный refresh_token. Возвращённый refresh_token
заменяет кэшированный для следующих обновлений. oauth2 и auth
взаимоисключающие.
fetch — подпись запроса (sign)
HMAC-SHA256 для API, требующих подписи (ЮKassa, Тинькофф, Сбер). Каноническая
строка — MiniJinja-шаблон с переменными method, url, body (каноническое
тело) и input:
fetch:
sign:
secret_env: SIGN_SECRET # ключ подписи из env
template: "{{ method }}{{ url }}{{ body }}" # по умолчанию
header: X-Signature # либо header, либо query
# query: sign
encoding: hex # hex (по умолч.) | base64
header и query взаимоисключающие: подпись кладётся либо в заголовок, либо
в query-параметр.
extract — типы полей
Три вида, различаются по ключам.
Простое поле — навигация по JSON-указателю:
extract:
location:
pointer: /geoInfo/0/text
value: /sub/path # доп. навигация вглубь
fallback: "unknown"
transform: int
Указатели могут быть MiniJinja-шаблонами от input; сегмент * (или ~first)
перешагивает динамический ключ.
Поле-массив — фильтр массива по дискриминатору:
year:
pointer: /fields
array_match: { type: year } # первый элемент с .type == "year"
value: /payload
transform: int
photos:
pointer: /gallery/images
collect_all: true # все элементы → массив
value: /image/src
transform: [ { skip: 5 }, { take: 20 } ]
Вычисляемое поле — условная логика:
status:
compute:
- when: { op: not_null, pointer: /soldNotification } then: sold
- when: { op: equals, pointer: /shouldShowDeleted, value: true } then: deleted
default: active
op в поставках: not_null, equals.
Трансформации
Применяются по порядку (можно цепочкой списком):
| Трансформация | YAML | Описание |
|---|---|---|
| Lookup | { lookup: { "1": foo, "2": bar } } |
значение по таблице (неизвестно → "?") |
| Truncate | { truncate: 2000 } |
обрезать до N символов |
| Take | { take: 20 } |
взять первые N |
| Skip | { skip: 5 } |
отбросить первые N |
| Len | len |
длина массива |
| NotNull | not_null |
true, если не null |
| Bool | bool |
к булевому |
| Int | int |
к целому |
| Filter | { filter: "not {{ item._sold }}" } |
отбросить элементы (массивы) |
| Transliterate | transliterate |
транслитерация в slug |
| StripHtml | strip_html |
убрать HTML/XML-теги (<noindex>…</noindex> → текст) |
output — формат
Опциональный MiniJinja-шаблон. Листья рендерятся от input и extracted.
{{ extracted._warnings }} доступен, когда поля не разрешились. Без output
возвращается извлечённая карта как есть.
Обработка ошибок
Движок не паникует: ошибки возвращают success: false с сообщением (HTTP
403/404/429/5xx, сетевые ошибки, «структурированные данные не найдены»).
5.5 archive_tool
Поиск по локальному архиву.
kind: archive_tool
name: ngu_search
description: Поиск по архиву НГУ.
parameters:
type: object
properties:
query: { type: string }
required: [query]
mode: search_grouped
archive:
path: data/ngu
default_limit: 10
filters:
- { field: players, op: players_min, param: players_min }
- { field: price, op: gte, param: price_min }
sort:
field: price
order: asc
mode
| Режим | Назначение |
|---|---|
search |
поиск по ключевым словам |
search_grouped |
результаты, сгруппированные по странице (matched_chunks) |
lookup |
полный документ по URL |
read_chunks |
диапазон чанков (start_chunk, end_chunk) |
outline |
структура страницы: заголовок, число символов/чанков, превью чанков |
Архив — каталог с meta.json, docs.bin (документы по чанкам) и terms.bin
(индекс терминов); собирается scraper-ом.
5.6 json_tool
Поиск по предзагруженному JSON.
kind: json_tool
name: find_brands
description: Поиск брендов по имени.
parameters:
type: object
properties:
query: { type: string }
section: { type: string, enum: [auto, moto, truck] }
required: [query]
data_file: data/drom_brands.json
search:
paths: ["{{ input.section | default('*') }}.top", "*.all"]
field: name
query_param: query
mode: contains
limit: 20
dedupe_by: id
source_field: section
computed:
slug: "{{ item.url | path_segment(0) }}"
| Поле | Описание |
|---|---|
data_file |
загружаемый JSON-файл |
search.paths |
JSON-пути поиска; * = wildcard-ключ |
search.field |
поле для сопоставления |
search.query_param |
входной параметр с запросом |
search.mode |
contains (частичное совпадение) |
search.limit |
максимум результатов |
search.dedupe_by |
дедупликация по полю |
computed |
MiniJinja-шаблоны на каждый результат (item в скоупе) |
5.7 rss_tool
Поиск по RSS-лентам.
kind: rss_tool
name: rss_search
description: Поиск новостей по ключевому слову.
parameters:
type: object
properties:
site: { type: string }
query: { type: string }
required: [site, query]
feeds:
lenta: "https://lenta.ru/rss"
ria: "https://ria.ru/export/rss2/archive/index.xml"
rbc: "https://rssexport.rbc.ru/rbcnews/news/30/full.rss"
site — ключ в feeds. Результат несёт count, заголовки, сниппеты, URL.
5.8 pdf_tool
Чтение PDF постранично.
kind: pdf_tool
name: pdf_read
description: Скачать и прочитать PDF.
parameters:
type: object
properties:
url: { type: string }
page: { type: integer, default: 1 }
required: [url]
fetch:
url: "{{ input.url }}"
timeout_secs: 30
cache_ttl_secs: 3600
pdf:
chars_per_page: 3000
output:
page: "{{ extracted.page }}"
total_pages: "{{ extracted.total_pages }}"
text: "{{ extracted.text }}"
next_page: "{{ extracted.next_page }}"
5.15 geo_tool
Геокодинг и места (OpenStreetMap). Бесплатный, без ключей, геокодинг и поиск POI (Nominatim + Overpass API) — агент не выдумывает адреса.
kind: geo_tool
name: geo_places
description: Найти места категории рядом.
operation: places
| operation | Назначение | Параметры |
|---|---|---|
locate |
геокодинг места → координаты + bbox | query |
places |
POI категории рядом / внутри bbox | category, place/bbox, limit |
distance |
сортировка адресов по расстоянию | place, addresses |
Категории (RU/EN синонимы): сто/автосервис/car_repair, шиномонтаж,
аптека/pharmacy, банк/атм, кафе/ресторан, школа, магазин/
продукты/супермаркет, заправка/азс, парковка. Неизвестная категория →
поиск Overpass shop=<value>.
Опционально: nominatim_url / overpass_url, user_agent, ui.
5.23 browser_tool
Headless-браузер. Для сайтов, которые рендерятся через JavaScript (SPA,
бесконечный скролл, логины) — обычного HTTP-фетча site_tool мало. browser_tool
грузит страницу в Chromium, выполняет взаимодействия и возвращает результат —
либо через JS-скрипт, выполненный внутри страницы («внутренний скриптинг»),
либо через отрендеренный текст.
Браузер ищется на системе, а если его нет — скачивается автоматически
(chrome-headless-shell из Chrome-for-Testing; та же логика, что у PDF-экспорта
OfficeCLI).
Процессы браузера переиспользуются (пул): каждый вызов открывает свежую
изолированную вкладку (CDP browser context) в живом Chromium и по завершении
возвращает браузер в пул — повторные вызовы не платят ~1–2 с за запуск. Число
процессов ограничено переменной AGENT_OS_MAX_BROWSERS (по умолчанию 4);
лишние вызовы ждут свободного слота (до 60 с, затем ошибка).
kind: browser_tool
name: scrape_spa
description: Извлечь товары из JS-листинга.
parameters:
type: object
properties:
url: { type: string }
required: [url]
steps: # необязательные действия, по порядку
- { type: goto, url: "{{ input.url }}" }
- { type: wait, selector: ".product-card" }
- { type: click, selector: "button.load-more" }
- { type: type, selector: "#q", text: "{{ input.q }}" }
- { type: check, selector: "input[name=agree]" }
- { type: eval, script: "return document.title" }
- { type: screenshot, name: "page.png" }
- { type: pdf, name: "page.pdf" }
script: | # JS внутри страницы → итоговый результат (JSON)
[...document.querySelectorAll('.product-card')].map(c => ({
title: c.querySelector('h2')?.innerText,
price: c.querySelector('.price')?.innerText,
}))
timeout_secs: 30
Шаги
type |
Поля | Действие |
|---|---|---|
goto |
url |
перейти и дождаться readyState |
wait |
ms или selector |
пауза или ожидание селектора |
click |
selector |
клик по первому совпавшему элементу |
type |
selector, text |
заполнить <input>/<textarea>/<select> и вызвать события |
check |
selector, checked? |
установить checked у чекбокса/radio (по умолч. true) |
eval |
script |
выполнить JS в странице (результат сохраняется) |
screenshot |
name |
PNG-скриншот → файл |
pdf |
name |
печать в PDF → файл |
Скриптинг и результат
Строковые поля шагов (url, selector, text) — шаблоны MiniJinja над
input. JS в script / eval.script выполняется в контексте страницы, аргументы
вызова доступны как глобальная переменная input (например input.url).
Результат: если script (или последний eval) вернул значение — оно попадает в
data и content (строка или pretty-JSON); иначе возвращается текст страницы.
screenshot/pdf формируют скачиваемые блоки file в visual; массив объектов
дополнительно рендерится таблицей.