Дата публикации: 19 августа 2026 г. | Категория: DevOps‚ Data Engineering‚ Azure Automation
Рынок эзотерических услуг в 2026 году — это не только газеты с объявлениями‚ но и тысячи API-агрегаторов‚ телеграм-ботов‚ профилей на маркетплейсах (Авито‚ Профи.ру‚ Яндекс.Услуги) и закрытых сообществ. Сбор данных о гадалках (рейтинги‚ отзывы‚ цены‚ специализация: таро‚ руны‚ нумерология‚ астрология‚ геолокация‚ доступность онлайн/офлайн) требует обработки гетерогенных‚ часто неструктурированных и защищенных источников.
Классические парсеры на Python/Node.js ломаються при смене верстки или появлении капчи. Современный подход 2026 года, использование Azure CLI в среде Bash для оркестрации инфраструктуры и ИИ-агентов для парсинга‚ отдающих чистый JSON‚ который дальше обрабатывается нативно средствами Azure (JMESPath‚ jq‚ Azure Stream Analytics).
Ключевая идея: Разделяй и властвуй. Azure CLI управляет инфраструктурой и транспортом (скачивание блобов‚ запуск контейнеров‚ управление Key Vault)‚ а специализированные инструменты (jq‚ JMESPath‚ ИИ-агенты) занимаются семантикой данных.
Архитектура решения: от источника до Data Lake
- Сбор (Ingestion): Azure CLI скрипты‚ запускаемые по расписанию (Cron в Container Apps / Timer Trigger в Functions)‚ скачивают сырые данные в
Raw Zone(ADLS Gen2 / Blob Storage). - Трансформация (Transformation): Потоковая обработка (Azure Stream Analytics / Flink) или батчевая (Azure CLI +
jq/ JMESPath) для приведения к единой модели:FortuneTellerProfile. - Хранилище и аналитика (Serving): Azure SQL / Cosmos DB / Synapse для BI-отчетов (Power BI: «Топ-10 тарологов по ЦА в Москве за неделю»).
Часть 1. Фундамент: Azure CLI в Bash для автоматизации ресурсов
Согласно документации Microsoft Learn (Практическое руководство по использованию Azure CLI в среде Bash)‚ Azure CLI оптимизирован для автоматизации и скриптинга. Главные преимущества для нашей задачи:
- Выходные форматы:
--output json(по умолчанию)‚table‚tsv‚yaml‚jsonc(с комментариями). - JMESPath Query (
--query): Мощный язык запросов для фильтрации JSON прямо в терминале без внешних зависимостей. - Идемпотентность: Команды
az storage blob download‚az container createбезопасны для перезапуска в CI/CD.
Настройка окружения (Best Practices 2026)
#!/bin/bash
set -euo pipefail
RESOURCE_GROUP="rg-esoterics-data-prod"
STORAGE_ACCOUNT="stesotericsrawdata"
CONTAINER_RAW="raw-fortune-tellers"
CONTAINER_PARSED="parsed-profiles"
AI_PARSER_IMAGE="ghcr.io/myorg/ai-fortune-parser:v2.4.1" # ИИ-агент для парсинга
az config set extension.use_dynamic_install=yes_without_prompt
Часть 2. Сбор сырых данных: Скрипты загрузки и запуска парсеров
Сценарий А: Скачивание выгрузок от партнеров (Blob Storage)
Партнеры скидывают дампы профилей гадалок в JSON/CSV в контейнер raw-fortune-tellers. Скрипт синхронизирует локальную папку (или монтированную шару) с облаком.
#!/bin/bash
echo "[$(date)] Starting sync from Blob Storage..."
BLOBS_TO_DOWNLOAD=$(az storage blob list
--account-name "$STORAGE_ACCOUNT"
--container-name "$CONTAINER_RAW"
--query "[?properties.lastModified > `$(date -d '24 hours ago' -u +%Y-%m-%dT%H:%M:%SZ)`].name"
-o tsv)
if [ -z "$BLOBS_TO_DOWNLOAD" ]; then
echo "No new blobs found."
exit 0
fi
echo "$BLOBS_TO_DOWNLOAD" | while IFS= read -r blob_name; do
local_path="./data/raw/$(basename "$blob_name")"
mkdir -p "$(dirname "$local_path")"
echo "Downloading $blob_name..."
az storage blob download
--account-name "$STORAGE_ACCOUNT"
--container-name "$CONTAINER_RAW"
--name "$blob_name"
--file "$local_path"
--overwrite true
--output none # Тихий режим для скриптов
done
echo "[$(date)] Sync complete. Triggering parsing pipeline..."
./run_parsing_batch.sh
Сценарий Б: Запуск ИИ-парсера в Azure Container Instances (ACI)
Согласно трендам 2026 года (Парсер сайтов в 2026 году — это автономный ИИ-агент)‚ мы не пишем селекторы. Мы упаковываем Playwright + LLM (например‚ Llama 3.1 / GPT-4o mini локально или через API) в Docker-образ. Azure CLI управляет жизненным циклом контейнера.
#!/bin/bash
INPUT_FILE="${1:-./data/urls_to_parse.txt}"
JOB_NAME="parser-job-$(date +%s)"
az storage blob upload
--account-name "$STORAGE_ACCOUNT"
--container-name "$CONTAINER_RAW"
--file "$INPUT_FILE"
--name "inputs/urls_${JOB_NAME}.txt"
--output none
az container create
--resource-group "$RESOURCE_GROUP"
--name "$JOB_NAME"
--image "$AI_PARSER_IMAGE"
--cpu 2 --memory 4
--restart-policy OnFailure
--environment-variables
INPUT_BLOB_URL="https://${STORAGE_ACCOUNT}.blob.core.windows.net/${CONTAINER_RAW}/inputs/urls_${JOB_NAME}.txt"
OUTPUT_CONTAINER="$CONTAINER_PARSED"
STORAGE_ACCOUNT="$STORAGE_ACCOUNT"
KEY_VAULT_URI="https://kv-esoterics.vault.azure.net/"
--secure-environment-variables
# Секреты лучше брать из Key Vault внутри контейнера‚ но можно и так для простоты
AZURE_CLIENT_SECRET="$SP_SECRET"
--output json --query "{fqdn:ipAddress.fqdn‚ state:provisioningState}"
echo "Parser job $JOB_NAME started. Monitoring logs..."
Часть 3. Парсинг JSON: от JSON.parse к отказоустойчивым конвейерам
Как отмечено в обзоре DataLopata (2026): «Парсинг данных из JSON сегодня, это не просто вызов функции JSON.parse‚ а выстраивание отказоустойчивых конвейеров‚ способных обрабатывать гигабайты структурированного текста в секунду без утечек памяти».
В нашем стеке используется три уровня парсинга:
Уровень 1: JMESPath (Нативный в Azure CLI) — Фильтрация и проекция
Идеален для «быстрых» запросов к уже структурированным данным (например‚ ответы API агрегаторов или результат работы ИИ-парсера). Не требует установки jq.
# Пример: Из массива профилей выбрать только онлайн-тарологов с рейтингом > 4.5 в Москве
az cli invoke --local-context ./data/parsed/profiles_batch_001.json
--query "[?specialization=='tarot' && city=='Moscow' && format=='online' && rating > `4.5`].{name: name‚ price: price_per_min‚ rating: rating‚ profile_url: url}"
-o table
Продвинутый JMESPath: Группировка и агрегация (Client-side)
Azure CLI не умеет делать GROUP BY нативно в --query‚ но можно подготовить данные для jq.
Уровень 2: jq — Тяжелая артиллерия для трансформации
Стандарт де-факто для сложной манипуляции JSON в Bash. Обработка стримингом (--stream) позволяет парсить файлы размером > RAM.
#!/bin/bash
def normalize_price:
# Приводим цену к рублям в минуту (источники дают за сеанс‚ за час‚ в у.е.)
if .currency == "USD" then .amount * 95 / .duration_minutes
elif .currency == "RUB" then .amount / .duration_minutes
else .amount / .duration_minutes # fallback
end;
def clean_phone:
gsub("[^0-9]"; "") | if length == 11 then .[1:] else . end; # Приводим к 10 цифр
map(
{
external_id: .id // .profile_id // .uuid‚
source: .source_system // "unknown"‚
full_name: .name // .full_name // "Anonymous"‚
specialization: (.specialization | lower | gsub(" "; "_"))‚ # tarot‚ runes‚ numerology
city: .location.city // .city // "Remote"‚
format: (if .online == true then "online" elif .offline == true then "offline" else "hybrid" end)‚
rating: (.rating // .avg_score // 0 | tonumber)‚
price_per_min: (normalize_price)‚
phone: (.contacts.phone // .whatsapp // .telegram | clean_phone)‚
skills: (.skills // .tags // [] | unique | map(lower))‚
parsed_at: now | todateiso8601‚
raw_payload: . # Сохраняем оригинал для аудита/репарсинга
}
)
Уровень 3: Парсинг «грязного» JSON и Schema Evolution
ИИ-агенты иногда возвращают невалидный JSON (пропущенные скобки‚ комментарии‚ trailing commas). В 2026 году используем jsonrepair (Rust/Node) или Python dirtyjson перед подачей в jq.
# Обертка для безопасного парсинга
parse_dirty_json {
local input_file="$1"
# python3 -c "import dirtyjson‚ sys; print(dirtyjson.loads(sys.stdin.read))" < "$input_file" | jq .
# Или утилита jsonrepair (быстрее)
jsonrepair "$input_file" | jq .
} Часть 4. Продвинутые паттерны Azure CLI для Data Engineering
Динамическое формирование JSON для команд (--body / --parameters)
Часто нужно отправить сложный JSON в Azure REST API (например‚ запуск Pipeline в Data Factory или Stream Analytics Job) из Bash-переменных. Проблема кавычек решается через jq -n.
# Не делайте так (боль в экранировании):
PIPELINE_PARAMS=$(jq -n
--arg date "$(date -u +%Y-%m-%d)"
--argjson batch_id "$BATCH_ID"
--arg storage_path "raw/fortune_tellers/$BATCH_ID/"
'{date: $date‚ batch_id: $batch_id‚ storage_path: $storage_path}')
az datafactory pipeline create-run
--resource-group "$RESOURCE_GROUP"
--factory-name "adf-esoterics-etl"
--name "pl_parse_fortune_tellers"
--parameters "$PIPELINE_PARAMS"
--output json --query "runId"
Обработка больших массивов: Постраничная загрузка (Pagination)
API агрегаторов гадалок часто отдают по 50-100 записей на страницу. Azure CLI не имеет встроенного пагинатора для произвольных REST API‚ пишем обертку:
fetch_all_pages {
local base_url="$1"
local token_var="$2" # имя переменной для next_token
local all_results="[]"
local next_token=""
while : ; do
# Формируем URL с токеном
local url="${base_url}"
if [ -n "$next_token" ]; then
url="${url}&page_token=${next_token}"
fi
# Вызов API (через az rest или curl с токеном из Key Vault)
# Допустим‚ ответ: { "data": [...]‚ "next_token": "abc" }
response=$(az rest --method get --uri "$url" --headers "Authorization=Bearer $(az account get-access-token --query accessToken -o tsv)" -o json)
# Сливаем данные
all_results=$(echo "$all_results" "$response" | jq -s '.[0] + .[1].data')
# Извлекаем следующий токен
next_token=$(echo "$response" | jq -r '.next_token // empty')
if [ -z "$next_token" ] || [ "$next_token" == "null" ]; then
break
fi
sleep 0.2 # Rate limiting courtesy
done
echo "$all_results"
}
ALL_TAROLOGISTS=$(fetch_all_pages "https://api.esoteric-aggregator.ru/v1/professionals?specialization=tarot")
echo "$ALL_TAROLOGISTS" | jq . > ./data/raw/tarologists_full_dump.json
Параллелизация через xargs -P или GNU Parallel
Скачивание профилей 10‚000 гадалок по API по одному — медленно. Azure CLI команды можно параллелить.
# Список ID в файл ids.txt
cat ./data/ids.txt | xargs -P 10 -I {} bash -c '
az rest --method get --uri "https://api.source.com/v1/profile/{}"
--headers "Authorization=Bearer $TOKEN"
-o json > "./data/raw/profiles/{}.json"
'
Часть 5. Оркестрация и мониторинг: Выход за пределы Bash
Bash-скрипты отлично подходят для адхок-задач‚ локальной разработки и простых крон-задач. Для продакшн-конвейера 2026 года необходима оркестрация.
Вариант А: Azure Data Factory (ADF) / Synapse Pipelines
- Custom Activity: Запуск нашего Docker-образа с ИИ-парсером как Custom Activity в ADF.
- Web Activity: Вызов API агрегаторов.
- Databricks Notebook / Spark Pool: Тяжелая трансформация JSON (плоское сжатие вложенных массивов отзывов‚ дедупликация по fuzzy matching имен).
- Плюсы: Визуальный мониторинг‚ ретраи‚ алерты‚ интеграция с Git.
Вариант Б: Azure Container Apps Jobs (Serverless Containers)
Идеально для наших Bash/CLI скриптов. Упаковываем скрипты в образ‚ определяем Job с триггером (Schedule / Event / Manual).
# containerapp-job.yaml (для `az containerapp job create`)
properties:
configuration:
triggerType: "Schedule"
schedule:
cronExpression: "0 /6 * *" # Каждые 6 часов
replicaTimeout: 1800 # 30 мин
replicaRetryLimit: 3
template:
containers:
─ name: etl-worker
image: ghcr.io/myorg/esoterics-etl:latest
env:
⸺ name: AZURE_CLIENT_ID
value: ""
resources:
cpu: 1.0
memory: 2Gi
Мониторинг качества данных (Data Quality Checks)
После парсинга обязательно валидация. Добавляем шаг в пайплайн:
# dq_check.sh
INPUT_FILE="./data/parsed/normalized_latest.json"
DUPES=$(jq '[.[] | .external_id] | group_by(.) | map(select(length > 1)) | length' "$INPUT_FILE")
if [ "$DUPES" -gt 0 ]; then
echo "ALERT: Found $DUPES duplicate external_ids!" | tee -a /var/log/dq.log
# Отправка в Azure Monitor / Log Analytics / Teams
fi
BAD_RATING=$(jq 'map(select(.rating < 0 or .rating > 5)) | length' "$INPUT_FILE")
COMPLETENESS=$(jq 'map(select(.phone != null and .price_per_min > 0)) | length / (length // 1) * 100' "$INPUT_FILE")
echo "DQ Report: Dupes=$DUPES‚ BadRating=$BAD_RATING‚ Completeness=${COMPLETENESS}%"
Часть 6. Типичные ловушки и Best Practices (Lessons Learned)
| Проблема | Симптом | Решение (2026) |
|---|---|---|
| Кавычки в Bash + JSON + Azure CLI | Скрипт падает на спецсимволах в отзывах (кавычки‚ бэкслеши‚ $). | Никогда не конкатенируйте строки. Используйте jq -n --arg var "$VALUE" '{key: $var}' для генерации JSON. Передавайте сложные тела через @file.json. |
| JMESPath vs jq | Пытаетесь сделать рекурсивный обход или математику в --query. | JMESPath, для проекции/фильтрации плоских структур. jq — для рекурсии‚ математики‚ группировки‚ стриминга. Не смешивайте логику. |
| Смена схемы источника (Schema Drift) | Парсер падает‚ так как API вернул новое поле или переименовал старое. | Схему валидируйте после ИИ-парсера (JSON Schema / Pydantic). В Bash используйте // (альтернатива) в JMESPath/jq: .new_field // .old_field // "default". Храните raw_payload для репарсинга. |
| Утечки памяти при больших файлах | jq съедает всю RAM на файле 5 ГБ. | Используйте jq --stream или разбивайте входной файл на чанки (split -l 10000) и обрабатывайте параллельно. Azure Stream Analytics / Flink для стриминга. |
| Управление секретами | API ключи LLM‚ прокси‚ токены агрегаторов в коде скриптов. | Только Azure Key Vault + Managed Identity. Скрипт читает секреты при старте: az keyvault secret show --vault-name $KV --name $SECRET --query value -o tsv. Никаких .env файлов в репозитории. |
| Блокировки и Капчи | ИИ-парсер банит источник. | Ротация прокси (резидентные/мобильные)‚ рандомизация User-Agent / Fingerprint (Playwright Stealth)‚ задержки‚ respeto robots.txt. Логирование блокировок в отдельную таблицу для анализа. |
Пример комплексного пайплайна (End-to-End)
Файл: daily_etl.sh — точка входа для Container Apps Job.
#!/bin/bash
set -euo pipefail
LOG_FILE="/var/log/daily_etl_$(date +%Y%m%d).log"
exec > >(tee -a "$LOG_FILE") ->&1
echo "=== START DAILY ETL $(date) ==="
source ./config.env # Переменные окружения
echo "Step 1: Generating target URLs..."
python3 ./scripts/generate_targets.py --output ./data/urls_today.txt --date "$(date -u +%Y-%m-%d)"
echo "Step 2: Launching AI Parser..."
JOB_ID=$(./run_ai_parser.sh ./data/urls_today.txt | jq -r '.name') # Парсим имя джобы из вывода az container create
echo "Step 3: Waiting for parser job $JOB_ID..."
while true; do
STATE=$(az container show -g "$RESOURCE_GROUP" -n "$JOB_ID" --query "instanceView.state" -o tsv)
if [ "$STATE" == "Succeeded" ]; then break; fi
if [ "$STATE" == "Failed" ]; then echo "Parser FAILED"; exit 1; fi
sleep 30
done
echo "Step 4: Downloading parsed JSON..."
az storage blob download-batch
-s "$CONTAINER_PARSED"
-d "./data/parsed_incoming"
--pattern "output_${JOB_ID}"
--account-name "$STORAGE_ACCOUNT"
echo "Step 5: Normalizing data..."
cat ./data/parsed_incoming/.json | jq -s -f ./transform_profiles.jq > ./data/normalized/full_$(date +%s).json
echo "Step 6: Running DQ checks..."
./dq_check.sh ./data/normalized/full_$(date +%s).json
echo "Step 7: Loading to Serving Layer..."
python3 ./scripts/load_to_sql.py --input ./data/normalized/full_*.json --table FortuneTellers_Staging
find ./data/raw -mtime +7 -delete
find ./data/parsed_incoming -mtime +1 -delete
echo "=== END DAILY ETL $(date) ==="
Автоматизация сбора данных о гадалках в 2026 году — это симбиоз инфраструктурного кода (Azure CLI/Bash)‚ семантического извлечения (ИИ-агенты) и потоковой обработки JSON (jq/JMESPath/Stream Analytics).
- Azure CLI дает единый пульт управления ресурсами Azure (Storage‚ ACI‚ Key Vault‚ ADF) из терминала‚ идеально встраиваясь в CI/CD и контейнеры.
- JMESPath (--query) — ваш первый инструмент для быстрой фильтрации ответов Azure API и простых JSON.
- jq — индустриальный стандарт для сложных трансформаций‚ стриминга и исправления "грязного" JSON.
- ИИ-парсинг снимает головную боль с поддержкой селекторов: меняется верстка — переобучаете промпт‚ а не код.
Главный принцип: Инфраструктура как код (IaC) + Данные как код (DaC). Храните схемы‚ скрипты парсинга‚ конфиги пайплайнов в Git. Версионируйте не только код‚ но и контракты данных (JSON Schema). Это позволит масштабировать сбор с десятков источников до тысяч‚ сохраняя качество данных и рассудок инженеров.
Полезные ссылки (Microsoft Learn / Docs)
- Практическое руководство по использованию Azure CLI в среде Bash
- Советы по использованию Azure CLI: JMESPath и форматы вывода
- Документация по Фабрике данных Azure (ADF)
- Анализ данных JSON и Avro в Azure Stream Analytics
- Примеры парсинга JSON для Stream Analytics (GitHub)
Статья подготовлена на основе актуальных практик 2026 года: серверлесс-контейнеры‚ ИИ-агенты для извлечения данных и нативная работа с JSON в командной строке Azure.