Init
This commit is contained in:
13
.env.example
Normal file
13
.env.example
Normal file
@@ -0,0 +1,13 @@
|
||||
# Яндекс OAuth-токен (работает для Трекера и Вики)
|
||||
YT=y0_...
|
||||
ORG_ID=7405124
|
||||
|
||||
# Supabase
|
||||
SUPABASE_HOST=aws-1-eu-north-1.pooler.supabase.com
|
||||
SUPABASE_PORT=5432
|
||||
SUPABASE_USER=postgres.xeakxxnriopsmaxdioke
|
||||
SUPABASE_PASSWORD=YmyO2rmahTkfdV97
|
||||
SUPABASE_DB=postgres
|
||||
|
||||
# OpenAI
|
||||
OPENAI_API_KEY=sk-...
|
||||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
.env
|
||||
__pycache__/
|
||||
141
README.md
Normal file
141
README.md
Normal file
@@ -0,0 +1,141 @@
|
||||
# wiki_embedding
|
||||
|
||||
Семантический поиск по страницам Яндекс Вики через pgvector + OpenAI embeddings.
|
||||
|
||||
## Что делает
|
||||
|
||||
- Обходит раздел «Управление Аналитики» Яндекс Вики через API и Playwright
|
||||
- Сохраняет содержимое страниц в Supabase (PostgreSQL)
|
||||
- Генерирует векторные embeddings через OpenAI `text-embedding-3-small`
|
||||
- Позволяет делать семантический поиск по ~700 страницам на русском и английском
|
||||
|
||||
## Стек
|
||||
|
||||
| Компонент | Технология |
|
||||
|-----------|-----------|
|
||||
| Хранилище | Supabase (PostgreSQL 17) |
|
||||
| Векторный поиск | pgvector, cosine similarity (`<=>`) |
|
||||
| Embeddings | OpenAI `text-embedding-3-small` (1536 dims) |
|
||||
| Wiki API | Яндекс Вики API v1 (`api.wiki.yandex.net/v1`) |
|
||||
| Браузерный краулер | Playwright + Chromium headless |
|
||||
| Язык | Python 3.12 |
|
||||
|
||||
## Структура файлов
|
||||
|
||||
```
|
||||
wiki_embedding/
|
||||
├── wiki_sync.py # Главный скрипт синхронизации (API → Supabase → embeddings)
|
||||
├── wiki_embeddings.py # Генерация embeddings и семантический поиск
|
||||
├── yandex_wiki.py # Краулер через Яндекс Вики API v1
|
||||
├── wiki_tree_crawler.py # Playwright-краулер для страниц с {% tree %}
|
||||
├── wiki_check_slugs.py # Проверка покрытия ROOT_SLUGS
|
||||
├── wiki_auth.py # Сохранение браузерной сессии для Playwright
|
||||
├── supabase.py # SupabaseManager — подключение и операции с БД
|
||||
├── requirements.txt # Зависимости
|
||||
└── .env # Credentials (не коммитить)
|
||||
```
|
||||
|
||||
## Настройка
|
||||
|
||||
### 1. Установить зависимости
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
playwright install chromium
|
||||
```
|
||||
|
||||
### 2. Создать .env
|
||||
|
||||
```
|
||||
# Яндекс OAuth-токен (работает для Трекера и Вики)
|
||||
YT=y0_...
|
||||
ORG_ID=7405124
|
||||
|
||||
# Supabase
|
||||
SUPABASE_HOST=aws-1-eu-north-1.pooler.supabase.com
|
||||
SUPABASE_PORT=5432
|
||||
SUPABASE_USER=postgres.xeakxxnriopsmaxdioke
|
||||
SUPABASE_PASSWORD=...
|
||||
SUPABASE_DB=postgres
|
||||
|
||||
# OpenAI
|
||||
OPENAI_API_KEY=sk-...
|
||||
```
|
||||
|
||||
### 3. Сохранить браузерную сессию (один раз)
|
||||
|
||||
```bash
|
||||
python wiki_auth.py
|
||||
```
|
||||
|
||||
Откроется браузер — залогинься в wiki.yandex.ru, нажми Enter.
|
||||
|
||||
## Запуск
|
||||
|
||||
### Полная синхронизация
|
||||
|
||||
```bash
|
||||
python wiki_sync.py
|
||||
```
|
||||
|
||||
Обходит все 642 slug-а из `ROOT_SLUGS`, обновляет `wiki_pages`, генерирует embeddings.
|
||||
|
||||
### Обнаружить новые страницы через Playwright
|
||||
|
||||
```bash
|
||||
python wiki_tree_crawler.py
|
||||
```
|
||||
|
||||
Открывает страницы с `{% tree %}` в headless-браузере, находит дочерние slug-и которых нет в базе.
|
||||
|
||||
### Проверить покрытие
|
||||
|
||||
```bash
|
||||
python wiki_check_slugs.py
|
||||
```
|
||||
|
||||
Показывает какие `{% tree %}` страницы не покрыты ROOT_SLUGS.
|
||||
|
||||
### Семантический поиск (из кода)
|
||||
|
||||
```python
|
||||
import sys
|
||||
sys.path.insert(0, '/Users/at/code/wiki_embedding')
|
||||
from supabase import SupabaseManager
|
||||
from wiki_embeddings import search
|
||||
|
||||
db = SupabaseManager()
|
||||
db.connect()
|
||||
results = search(db, 'твой запрос', limit=5)
|
||||
for r in results:
|
||||
print(r['similarity'], r['title'])
|
||||
print(r['content_text'][:500])
|
||||
db.close()
|
||||
```
|
||||
|
||||
Similarity > 0.5 — хорошее совпадение.
|
||||
|
||||
## Cron (ежедневная синхронизация)
|
||||
|
||||
```
|
||||
0 3 * * * cd /Users/at/code/wiki_embedding && /usr/bin/python3 wiki_sync.py >> logs/wiki_sync.log 2>&1
|
||||
```
|
||||
|
||||
## Как устроен поиск
|
||||
|
||||
1. При индексировании каждая страница → вектор через OpenAI (title + content)
|
||||
2. При поиске запрос → вектор тем же способом
|
||||
3. pgvector находит страницы с минимальным косинусным расстоянием (`<=>`)
|
||||
4. Возвращается `1 - distance` как similarity (0..1)
|
||||
|
||||
Точный поиск без индекса (IVFFlat не используется — при < 10k векторов даёт плохие результаты).
|
||||
|
||||
## Схема БД
|
||||
|
||||
```sql
|
||||
-- Содержимое страниц
|
||||
wiki_pages (id, slug, title, page_type, modified_at, content_hash, value JSONB)
|
||||
|
||||
-- Векторные embeddings
|
||||
wiki_embeddings (id, slug, title, content_text, content_hash, embedding vector(1536))
|
||||
```
|
||||
6
requirements.txt
Normal file
6
requirements.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
openai==2.17.0
|
||||
playwright==1.58.0
|
||||
psycopg2-binary==2.9.11
|
||||
python-dotenv==1.2.1
|
||||
requests==2.32.5
|
||||
pendulum==3.1.0
|
||||
443
supabase.py
Normal file
443
supabase.py
Normal file
@@ -0,0 +1,443 @@
|
||||
"""
|
||||
Модуль для работы с Supabase (PostgreSQL)
|
||||
"""
|
||||
import os
|
||||
from pathlib import Path
|
||||
import psycopg2
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv(Path(__file__).parent / '.env')
|
||||
|
||||
|
||||
class SupabaseManager:
|
||||
"""Менеджер для работы с Supabase"""
|
||||
|
||||
def __init__(self):
|
||||
"""Инициализация подключения к Supabase"""
|
||||
self.host = os.getenv('SUPABASE_HOST')
|
||||
self.port = os.getenv('SUPABASE_PORT')
|
||||
self.user = os.getenv('SUPABASE_USER')
|
||||
self.password = os.getenv('SUPABASE_PASSWORD')
|
||||
self.database = os.getenv('SUPABASE_DB')
|
||||
|
||||
self.conn = None
|
||||
self.cursor = None
|
||||
|
||||
def connect(self):
|
||||
"""Подключение к базе данных"""
|
||||
try:
|
||||
self.conn = psycopg2.connect(
|
||||
host=self.host,
|
||||
port=self.port,
|
||||
user=self.user,
|
||||
password=self.password,
|
||||
database=self.database,
|
||||
sslmode='require',
|
||||
)
|
||||
self.cursor = self.conn.cursor()
|
||||
|
||||
# Проверка версии
|
||||
self.cursor.execute('SELECT version();')
|
||||
db_version = self.cursor.fetchone()
|
||||
print(f"Подключение к Supabase успешно!\nВерсия PostgreSQL: {db_version[0]}")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"✗ Ошибка подключения к Supabase: {e}")
|
||||
return False
|
||||
|
||||
def close(self):
|
||||
"""Закрытие соединения"""
|
||||
if self.cursor:
|
||||
self.cursor.close()
|
||||
if self.conn:
|
||||
self.conn.close()
|
||||
print("✓ Соединение с базой данных закрыто")
|
||||
|
||||
# ============================================================================
|
||||
# ПРОВЕРКА ТАБЛИЦ
|
||||
# ============================================================================
|
||||
|
||||
def table_exists(self, table_name):
|
||||
"""Проверка существования таблицы"""
|
||||
self.cursor.execute("""
|
||||
SELECT EXISTS (
|
||||
SELECT FROM information_schema.tables
|
||||
WHERE table_schema = 'public'
|
||||
AND table_name = %s
|
||||
);
|
||||
""", (table_name,))
|
||||
return self.cursor.fetchone()[0]
|
||||
|
||||
def table_has_data(self, table_name):
|
||||
"""Проверка, что таблица не пустая"""
|
||||
self.cursor.execute(f"SELECT EXISTS (SELECT 1 FROM {table_name} LIMIT 1);")
|
||||
return self.cursor.fetchone()[0]
|
||||
|
||||
# ============================================================================
|
||||
# СОЗДАНИЕ ТАБЛИЦ
|
||||
# ============================================================================
|
||||
|
||||
def create_employee_table(self):
|
||||
"""Создание таблицы employee"""
|
||||
self.cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS employee (
|
||||
id SERIAL PRIMARY KEY,
|
||||
pg_load_dttm TIMESTAMPTZ NOT NULL,
|
||||
yatracker_employee_id TEXT NOT NULL,
|
||||
value JSONB NOT NULL
|
||||
);
|
||||
""")
|
||||
self.conn.commit()
|
||||
print("Таблица 'employee' создана")
|
||||
|
||||
def create_tasks_table(self):
|
||||
"""Создание таблицы tasks"""
|
||||
self.cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS tasks (
|
||||
id SERIAL PRIMARY KEY,
|
||||
pg_load_dttm TIMESTAMPTZ NOT NULL,
|
||||
queue TEXT,
|
||||
task_key TEXT NOT NULL,
|
||||
task_title TEXT,
|
||||
status_name TEXT,
|
||||
created_at_dttm TIMESTAMPTZ,
|
||||
updated_at_dttm TIMESTAMPTZ,
|
||||
status_start_dttm TIMESTAMPTZ,
|
||||
value JSONB NOT NULL
|
||||
);
|
||||
""")
|
||||
self.conn.commit()
|
||||
print("Таблица 'tasks' создана")
|
||||
|
||||
def create_employee_info_table(self):
|
||||
"""Создание таблицы employee_info"""
|
||||
self.cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS employee_info (
|
||||
id SERIAL PRIMARY KEY,
|
||||
pg_load_dttm TIMESTAMPTZ NOT NULL,
|
||||
column_d TEXT,
|
||||
column_n TEXT
|
||||
);
|
||||
""")
|
||||
self.conn.commit()
|
||||
print("Таблица 'employee_info' создана")
|
||||
|
||||
# ============================================================================
|
||||
# ЗАГРУЗКА ДАННЫХ
|
||||
# ============================================================================
|
||||
|
||||
def load_employee_data(self, employee_df):
|
||||
"""Загрузка данных сотрудников в базу"""
|
||||
if len(employee_df) > 0:
|
||||
print("\nЗагружаем данные в таблицу 'employee'...")
|
||||
for _, row in employee_df.iterrows():
|
||||
self.cursor.execute("""
|
||||
INSERT INTO employee (pg_load_dttm, yatracker_employee_id, value)
|
||||
VALUES (%s, %s, %s::jsonb)
|
||||
""", (row['pg_load_dttm'], row['yatracker_employee_id'], row['value']))
|
||||
self.conn.commit()
|
||||
print(f"✓ Загружено {len(employee_df)} записей в таблицу 'employee'")
|
||||
else:
|
||||
print("\nНет данных для загрузки в таблицу 'employee'")
|
||||
|
||||
def load_tasks_data(self, tasks_df):
|
||||
"""Загрузка данных задач в базу"""
|
||||
if len(tasks_df) > 0:
|
||||
print("\nЗагружаем данные в таблицу 'tasks'...")
|
||||
for _, row in tasks_df.iterrows():
|
||||
self.cursor.execute("""
|
||||
INSERT INTO tasks (pg_load_dttm, queue, task_key, task_title, status_name,
|
||||
created_at_dttm, updated_at_dttm, status_start_dttm, value)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s::jsonb)
|
||||
""", (
|
||||
row['pg_load_dttm'],
|
||||
row['queue'],
|
||||
row['task_key'],
|
||||
row['task_title'],
|
||||
row['status_name'],
|
||||
row['created_at_dttm'],
|
||||
row['updated_at_dttm'],
|
||||
row['status_start_dttm'],
|
||||
row['value']
|
||||
))
|
||||
self.conn.commit()
|
||||
print(f"✓ Загружено {len(tasks_df)} записей в таблицу 'tasks'")
|
||||
else:
|
||||
print("\nНет данных для загрузки в таблицу 'tasks'")
|
||||
|
||||
def load_employee_info_data(self, employee_info_df):
|
||||
"""Загрузка данных employee_info в базу"""
|
||||
if len(employee_info_df) > 0:
|
||||
print("\nЗагружаем данные в таблицу 'employee_info'...")
|
||||
for _, row in employee_info_df.iterrows():
|
||||
self.cursor.execute("""
|
||||
INSERT INTO employee_info (pg_load_dttm, column_d, column_n)
|
||||
VALUES (%s, %s, %s)
|
||||
""", (
|
||||
row['pg_load_dttm'],
|
||||
row['column_d'],
|
||||
row['column_n']
|
||||
))
|
||||
self.conn.commit()
|
||||
print(f"✓ Загружено {len(employee_info_df)} записей в таблицу 'employee_info'")
|
||||
else:
|
||||
print("\nНет данных для загрузки в таблицу 'employee_info'")
|
||||
|
||||
# ============================================================================
|
||||
# ОСНОВНАЯ ЛОГИКА ПРОВЕРКИ И ЗАГРУЗКИ
|
||||
# ============================================================================
|
||||
|
||||
def check_and_prepare_tables(self):
|
||||
"""
|
||||
Проверка существования таблиц и создание при необходимости
|
||||
|
||||
Returns:
|
||||
dict: Словарь с флагами наличия данных для каждой таблицы
|
||||
"""
|
||||
tables_status = {}
|
||||
|
||||
# Проверяем таблицу employee
|
||||
employee_exists = self.table_exists('employee')
|
||||
employee_has_data = False
|
||||
|
||||
if employee_exists:
|
||||
employee_has_data = self.table_has_data('employee')
|
||||
if employee_has_data:
|
||||
print("Таблица 'employee' существует и содержит данные. Пропускаем загрузку.")
|
||||
else:
|
||||
print("Таблица 'employee' существует, но пустая.")
|
||||
else:
|
||||
self.create_employee_table()
|
||||
|
||||
tables_status['employee'] = employee_has_data
|
||||
|
||||
# Проверяем таблицу tasks
|
||||
tasks_exists = self.table_exists('tasks')
|
||||
tasks_has_data = False
|
||||
|
||||
if tasks_exists:
|
||||
tasks_has_data = self.table_has_data('tasks')
|
||||
if tasks_has_data:
|
||||
print("Таблица 'tasks' существует и содержит данные. Пропускаем загрузку.")
|
||||
else:
|
||||
print("Таблица 'tasks' существует, но пустая.")
|
||||
else:
|
||||
self.create_tasks_table()
|
||||
|
||||
tables_status['tasks'] = tasks_has_data
|
||||
|
||||
# Проверяем таблицу employee_info
|
||||
employee_info_exists = self.table_exists('employee_info')
|
||||
employee_info_has_data = False
|
||||
|
||||
if employee_info_exists:
|
||||
employee_info_has_data = self.table_has_data('employee_info')
|
||||
if employee_info_has_data:
|
||||
print("Таблица 'employee_info' существует и содержит данные. Пропускаем загрузку.")
|
||||
else:
|
||||
print("Таблица 'employee_info' существует, но пустая.")
|
||||
else:
|
||||
self.create_employee_info_table()
|
||||
|
||||
tables_status['employee_info'] = employee_info_has_data
|
||||
|
||||
return tables_status
|
||||
|
||||
def load_all_data(self, tables_status, employee=None, tasks=None, employee_info=None):
|
||||
"""
|
||||
Загрузка всех данных в базу
|
||||
|
||||
Args:
|
||||
tables_status (dict): Статус таблиц из check_and_prepare_tables
|
||||
employee (pd.DataFrame): Данные сотрудников
|
||||
tasks (pd.DataFrame): Данные задач
|
||||
employee_info (pd.DataFrame): Информация о сотрудниках
|
||||
"""
|
||||
if employee is not None and not tables_status['employee']:
|
||||
self.load_employee_data(employee)
|
||||
elif tables_status['employee']:
|
||||
print("\nТаблица 'employee' уже содержит данные, пропускаем загрузку")
|
||||
|
||||
if tasks is not None and not tables_status['tasks']:
|
||||
self.load_tasks_data(tasks)
|
||||
elif tables_status['tasks']:
|
||||
print("\nТаблица 'tasks' уже содержит данные, пропускаем загрузку")
|
||||
|
||||
if employee_info is not None and not tables_status['employee_info']:
|
||||
self.load_employee_info_data(employee_info)
|
||||
elif tables_status['employee_info']:
|
||||
print("\nТаблица 'employee_info' уже содержит данные, пропускаем загрузку")
|
||||
|
||||
# ============================================================================
|
||||
# ПОЛУЧЕНИЕ ДАННЫХ ДЛЯ АНАЛИЗА
|
||||
# ============================================================================
|
||||
|
||||
def get_tasks_for_analysis(self, task_keys=None):
|
||||
"""
|
||||
Получение задач для анализа с описанием и исполнителем
|
||||
|
||||
Args:
|
||||
task_keys (list): Список ключей задач для фильтрации (опционально)
|
||||
|
||||
Returns:
|
||||
list: Список словарей с данными задач
|
||||
"""
|
||||
if task_keys:
|
||||
# Формируем строку с ключами для SQL IN
|
||||
keys_str = ", ".join([f"'{key}'" for key in task_keys])
|
||||
query = f"""
|
||||
SELECT *
|
||||
FROM
|
||||
(SELECT
|
||||
t.queue,
|
||||
t.task_key,
|
||||
t.task_title,
|
||||
t.value -> 'description' as description,
|
||||
t.value -> 'assignee' ->> 'display' as assignee,
|
||||
row_number() over (partition by task_key order by updated_at_dttm desc) as rn
|
||||
FROM tasks t
|
||||
WHERE task_key IN ({keys_str})) T1
|
||||
WHERE rn = 1;
|
||||
"""
|
||||
else:
|
||||
query = """
|
||||
SELECT
|
||||
t.queue,
|
||||
t.task_key,
|
||||
t.task_title,
|
||||
t.value -> 'description' as description,
|
||||
t.value -> 'assignee' ->> 'display' as assignee
|
||||
FROM tasks t
|
||||
ORDER BY t.updated_at_dttm DESC
|
||||
LIMIT 100;
|
||||
"""
|
||||
|
||||
self.cursor.execute(query)
|
||||
columns = [desc[0] for desc in self.cursor.description]
|
||||
rows = self.cursor.fetchall()
|
||||
|
||||
tasks = []
|
||||
for row in rows:
|
||||
task_dict = dict(zip(columns, row))
|
||||
tasks.append(task_dict)
|
||||
|
||||
return tasks
|
||||
|
||||
# ============================================================================
|
||||
# WIKI PAGES
|
||||
# ============================================================================
|
||||
|
||||
def create_wiki_pages_table(self):
|
||||
"""Создание таблицы wiki_pages"""
|
||||
self.cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS wiki_pages (
|
||||
id SERIAL PRIMARY KEY,
|
||||
pg_load_dttm TIMESTAMPTZ NOT NULL,
|
||||
slug TEXT NOT NULL,
|
||||
wiki_page_id INTEGER,
|
||||
title TEXT,
|
||||
page_type TEXT,
|
||||
modified_at TIMESTAMPTZ,
|
||||
content_hash TEXT NOT NULL,
|
||||
value JSONB NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_wiki_pages_slug
|
||||
ON wiki_pages (slug);
|
||||
CREATE INDEX IF NOT EXISTS idx_wiki_pages_load
|
||||
ON wiki_pages (pg_load_dttm DESC);
|
||||
""")
|
||||
self.conn.commit()
|
||||
print("Таблица 'wiki_pages' создана")
|
||||
|
||||
def get_latest_hashes(self) -> dict[str, str]:
|
||||
"""Получить последний content_hash для каждого slug."""
|
||||
self.cursor.execute("""
|
||||
SELECT DISTINCT ON (slug) slug, content_hash
|
||||
FROM wiki_pages
|
||||
ORDER BY slug, pg_load_dttm DESC;
|
||||
""")
|
||||
return {row[0]: row[1] for row in self.cursor.fetchall()}
|
||||
|
||||
def upsert_wiki_pages(self, pages: list[dict]) -> dict:
|
||||
"""
|
||||
Вставить новые и изменённые страницы.
|
||||
|
||||
Логика: сравниваем content_hash с последним сохранённым.
|
||||
Если хеш совпадает — пропускаем. Если новый или изменился — вставляем.
|
||||
|
||||
Returns:
|
||||
dict: {'inserted': int, 'unchanged': int}
|
||||
"""
|
||||
import json
|
||||
|
||||
if not pages:
|
||||
return {'inserted': 0, 'unchanged': 0}
|
||||
|
||||
if not self.table_exists('wiki_pages'):
|
||||
self.create_wiki_pages_table()
|
||||
|
||||
latest_hashes = self.get_latest_hashes()
|
||||
|
||||
inserted = 0
|
||||
unchanged = 0
|
||||
|
||||
for page in pages:
|
||||
slug = page['slug']
|
||||
new_hash = page['content_hash']
|
||||
stored_hash = latest_hashes.get(slug)
|
||||
|
||||
if stored_hash == new_hash:
|
||||
unchanged += 1
|
||||
continue
|
||||
|
||||
value = {
|
||||
'slug': slug,
|
||||
'wiki_page_id': page.get('wiki_page_id'),
|
||||
'title': page.get('title'),
|
||||
'page_type': page.get('page_type'),
|
||||
'modified_at': str(page.get('modified_at') or ''),
|
||||
'content': page.get('content', ''),
|
||||
}
|
||||
|
||||
self.cursor.execute("""
|
||||
INSERT INTO wiki_pages
|
||||
(pg_load_dttm, slug, wiki_page_id, title, page_type, modified_at, content_hash, value)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, %s, %s::jsonb)
|
||||
""", (
|
||||
page['pg_load_dttm'],
|
||||
slug,
|
||||
page.get('wiki_page_id'),
|
||||
page.get('title'),
|
||||
page.get('page_type'),
|
||||
page.get('modified_at'),
|
||||
new_hash,
|
||||
json.dumps(value, ensure_ascii=False),
|
||||
))
|
||||
inserted += 1
|
||||
|
||||
self.conn.commit()
|
||||
return {'inserted': inserted, 'unchanged': unchanged}
|
||||
|
||||
def execute_sql_file(self, sql_file_path):
|
||||
"""
|
||||
Выполнение SQL запроса из файла
|
||||
|
||||
Args:
|
||||
sql_file_path (str): Путь к файлу с SQL запросом
|
||||
|
||||
Returns:
|
||||
list: Список словарей с результатами
|
||||
"""
|
||||
with open(sql_file_path, 'r', encoding='utf-8') as f:
|
||||
query = f.read()
|
||||
|
||||
self.cursor.execute(query)
|
||||
columns = [desc[0] for desc in self.cursor.description]
|
||||
rows = self.cursor.fetchall()
|
||||
|
||||
results = []
|
||||
for row in rows:
|
||||
result_dict = dict(zip(columns, row))
|
||||
results.append(result_dict)
|
||||
|
||||
return results
|
||||
36
wiki_auth.py
Normal file
36
wiki_auth.py
Normal file
@@ -0,0 +1,36 @@
|
||||
"""
|
||||
Сохранить сессию wiki.yandex.ru для последующего использования Playwright.
|
||||
|
||||
Запуск (один раз):
|
||||
python wiki_auth.py
|
||||
|
||||
Откроется браузер — залогинься в wiki.yandex.ru, затем нажми Enter в терминале.
|
||||
Cookies сохранятся в wiki_auth.json.
|
||||
"""
|
||||
from playwright.sync_api import sync_playwright
|
||||
from pathlib import Path
|
||||
|
||||
AUTH_FILE = Path(__file__).parent / 'wiki_auth.json'
|
||||
|
||||
|
||||
def main():
|
||||
with sync_playwright() as p:
|
||||
browser = p.chromium.launch(headless=False)
|
||||
context = browser.new_context()
|
||||
page = context.new_page()
|
||||
|
||||
print('Открываю wiki.yandex.ru...')
|
||||
page.goto('https://wiki.yandex.ru')
|
||||
|
||||
print()
|
||||
print('Залогинься в браузере, затем вернись сюда и нажми Enter.')
|
||||
input('Нажми Enter когда будешь залогинен: ')
|
||||
|
||||
context.storage_state(path=str(AUTH_FILE))
|
||||
browser.close()
|
||||
|
||||
print(f'✓ Сессия сохранена в {AUTH_FILE}')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
78
wiki_check_slugs.py
Normal file
78
wiki_check_slugs.py
Normal file
@@ -0,0 +1,78 @@
|
||||
"""
|
||||
Проверка покрытия ROOT_SLUGS: находит страницы в wiki_pages с {% tree %},
|
||||
которые не добавлены в ROOT_SLUGS — у них могут быть необнаруженные дочерние страницы.
|
||||
|
||||
Запуск:
|
||||
python wiki_check_slugs.py
|
||||
"""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv(Path(__file__).parent / '.env')
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent))
|
||||
from supabase import SupabaseManager
|
||||
|
||||
# ROOT_SLUGS из wiki_sync.py — поддерживай синхронно
|
||||
from wiki_sync import ROOT_SLUGS
|
||||
|
||||
|
||||
def main():
|
||||
db = SupabaseManager()
|
||||
if not db.connect():
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
# Все slug-и в базе
|
||||
db.cursor.execute("SELECT slug FROM wiki_pages ORDER BY slug")
|
||||
all_slugs = {row[0] for row in db.cursor.fetchall()}
|
||||
|
||||
# Страницы с {% tree %} — у них могут быть необнаруженные дети
|
||||
db.cursor.execute("""
|
||||
SELECT slug, value->>'title' AS title
|
||||
FROM wiki_pages
|
||||
WHERE value->>'content' LIKE '%{%% tree%%}%'
|
||||
ORDER BY slug
|
||||
""")
|
||||
tree_pages = db.cursor.fetchall()
|
||||
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
root_slugs_set = set(ROOT_SLUGS)
|
||||
|
||||
# Страницы с {% tree %}, которых нет в ROOT_SLUGS
|
||||
missing_from_roots = [(s, t) for s, t in tree_pages if s not in root_slugs_set]
|
||||
|
||||
# ROOT_SLUGS которых нет в базе (удалены или никогда не синхронизировались)
|
||||
missing_from_db = root_slugs_set - all_slugs
|
||||
|
||||
print(f'Всего страниц в wiki_pages: {len(all_slugs)}')
|
||||
print(f'Страниц с {{% tree %}}: {len(tree_pages)}')
|
||||
print(f'ROOT_SLUGS: {len(ROOT_SLUGS)}')
|
||||
print()
|
||||
|
||||
if missing_from_roots:
|
||||
print('⚠️ Страницы с {% tree %}, НЕ добавленные в ROOT_SLUGS:')
|
||||
print(' (у них могут быть дочерние страницы, которые краулер не найдёт)')
|
||||
print()
|
||||
for slug, title in missing_from_roots:
|
||||
print(f' + {slug}')
|
||||
print(f' «{title}»')
|
||||
print(f' https://wiki.yandex.ru/{slug}')
|
||||
print()
|
||||
else:
|
||||
print('✓ Все страницы с {% tree %} уже есть в ROOT_SLUGS')
|
||||
|
||||
if missing_from_db:
|
||||
print('⚠️ ROOT_SLUGS которых НЕТ в базе (не были синхронизированы или удалены):')
|
||||
for slug in sorted(missing_from_db):
|
||||
print(f' - {slug}')
|
||||
print()
|
||||
else:
|
||||
print('✓ Все ROOT_SLUGS присутствуют в базе')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
171
wiki_embeddings.py
Normal file
171
wiki_embeddings.py
Normal file
@@ -0,0 +1,171 @@
|
||||
"""
|
||||
Семантический поиск по Яндекс Вики через OpenAI embeddings + pgvector.
|
||||
"""
|
||||
import re
|
||||
import os
|
||||
from pathlib import Path
|
||||
from dotenv import load_dotenv
|
||||
from openai import OpenAI
|
||||
|
||||
load_dotenv(Path(__file__).parent / '.env')
|
||||
|
||||
EMBED_MODEL = 'text-embedding-3-small' # 1536 dims, быстро и дёшево
|
||||
|
||||
_client = None
|
||||
|
||||
|
||||
def _openai() -> OpenAI:
|
||||
global _client
|
||||
if _client is None:
|
||||
_client = OpenAI(api_key=os.getenv('OPENAI_API_KEY'))
|
||||
return _client
|
||||
|
||||
|
||||
def clean_wiki_markup(text: str) -> str:
|
||||
"""Убрать wiki-разметку, оставить чистый текст для embedding."""
|
||||
if not text:
|
||||
return ''
|
||||
# {% ... %} блоки
|
||||
text = re.sub(r'\{%[^%]*%\}', '', text)
|
||||
# [[ссылки]] → оставить текст после |
|
||||
text = re.sub(r'\[\[([^\]|]*)\|([^\]]*)\]\]', r'\2', text)
|
||||
text = re.sub(r'\[\[([^\]]*)\]\]', r'\1', text)
|
||||
# ((url текст)) → текст
|
||||
text = re.sub(r'\(\(https?://\S+\s+([^)]+)\)\)', r'\1', text)
|
||||
text = re.sub(r'\(\(https?://\S+\)\)', '', text)
|
||||
# Markdown-разметка
|
||||
text = re.sub(r'\*{1,3}([^*]+)\*{1,3}', r'\1', text)
|
||||
text = re.sub(r'_{1,2}([^_]+)_{1,2}', r'\1', text)
|
||||
# Таблицы и спец-символы wiki
|
||||
text = re.sub(r'[#|]{2,}', '\n', text)
|
||||
text = re.sub(r'^\s*[#>]+\s*', '', text, flags=re.MULTILINE)
|
||||
# Лишние пробелы
|
||||
text = re.sub(r'\n{3,}', '\n\n', text)
|
||||
return text.strip()
|
||||
|
||||
|
||||
def embed_text(text: str) -> list[float]:
|
||||
"""Получить embedding для текста через OpenAI API."""
|
||||
import openai as _openai_module
|
||||
# Начинаем с 15000 символов, при ошибке обрезаем вдвое
|
||||
limit = 15000
|
||||
while limit >= 1000:
|
||||
try:
|
||||
resp = _openai().embeddings.create(model=EMBED_MODEL, input=text[:limit])
|
||||
return resp.data[0].embedding
|
||||
except _openai_module.BadRequestError as e:
|
||||
if 'maximum context length' in str(e):
|
||||
limit = limit // 2
|
||||
continue
|
||||
raise
|
||||
raise ValueError(f'Не удалось уложиться в лимит токенов даже при 1000 символах')
|
||||
|
||||
|
||||
def embed_page(page: dict) -> list[float]:
|
||||
"""Сгенерировать embedding для страницы (title + content)."""
|
||||
title = page.get('title', '')
|
||||
content = clean_wiki_markup(page.get('content', '') or '')
|
||||
combined = f'{title}\n\n{content}'
|
||||
return embed_text(combined)
|
||||
|
||||
|
||||
def upsert_embeddings(db, pages: list[dict]) -> dict:
|
||||
"""
|
||||
Сгенерировать и сохранить embeddings для новых/изменённых страниц.
|
||||
|
||||
Пропускает страницы, у которых content_hash не изменился.
|
||||
|
||||
Returns:
|
||||
dict: {'embedded': int, 'skipped': int}
|
||||
"""
|
||||
import json
|
||||
import pendulum
|
||||
|
||||
if not pages:
|
||||
return {'embedded': 0, 'skipped': 0}
|
||||
|
||||
# Получить текущие хеши из wiki_embeddings
|
||||
db.cursor.execute('SELECT slug, content_hash FROM wiki_embeddings')
|
||||
stored = {row[0]: row[1] for row in db.cursor.fetchall()}
|
||||
|
||||
embedded = 0
|
||||
skipped = 0
|
||||
|
||||
for page in pages:
|
||||
slug = page['slug']
|
||||
new_hash = page['content_hash']
|
||||
content = page.get('content', '') or ''
|
||||
|
||||
if not content.strip():
|
||||
skipped += 1
|
||||
continue
|
||||
|
||||
if stored.get(slug) == new_hash:
|
||||
skipped += 1
|
||||
continue
|
||||
|
||||
print(f' ↑ embedding: {slug}')
|
||||
content_text = clean_wiki_markup(content)
|
||||
vector = embed_page({'title': page.get('title', ''), 'content': content})
|
||||
|
||||
db.cursor.execute("""
|
||||
INSERT INTO wiki_embeddings
|
||||
(pg_load_dttm, slug, title, content_text, content_hash, embedding)
|
||||
VALUES (%s, %s, %s, %s, %s, %s)
|
||||
ON CONFLICT (slug) DO UPDATE SET
|
||||
pg_load_dttm = EXCLUDED.pg_load_dttm,
|
||||
title = EXCLUDED.title,
|
||||
content_text = EXCLUDED.content_text,
|
||||
content_hash = EXCLUDED.content_hash,
|
||||
embedding = EXCLUDED.embedding
|
||||
""", (
|
||||
pendulum.now('Europe/Moscow'),
|
||||
slug,
|
||||
page.get('title', ''),
|
||||
content_text,
|
||||
new_hash,
|
||||
json.dumps(vector),
|
||||
))
|
||||
db.conn.commit()
|
||||
embedded += 1
|
||||
|
||||
return {'embedded': embedded, 'skipped': skipped}
|
||||
|
||||
|
||||
def search(db, query: str, limit: int = 5) -> list[dict]:
|
||||
"""
|
||||
Семантический поиск по wiki_embeddings.
|
||||
|
||||
Args:
|
||||
db: подключённый SupabaseManager
|
||||
query: текстовый запрос на любом языке
|
||||
limit: кол-во результатов
|
||||
|
||||
Returns:
|
||||
Список {'slug', 'title', 'similarity', 'content_text'}
|
||||
"""
|
||||
import json
|
||||
|
||||
query_vec = embed_text(query)
|
||||
|
||||
db.cursor.execute("""
|
||||
SELECT
|
||||
slug,
|
||||
title,
|
||||
content_text,
|
||||
1 - (embedding <=> %s::vector) AS similarity
|
||||
FROM wiki_embeddings
|
||||
ORDER BY embedding <=> %s::vector
|
||||
LIMIT %s
|
||||
""", (json.dumps(query_vec), json.dumps(query_vec), limit))
|
||||
|
||||
rows = db.cursor.fetchall()
|
||||
return [
|
||||
{
|
||||
'slug': row[0],
|
||||
'title': row[1],
|
||||
'content_text': row[2],
|
||||
'similarity': round(float(row[3]), 4),
|
||||
}
|
||||
for row in rows
|
||||
]
|
||||
737
wiki_sync.py
Normal file
737
wiki_sync.py
Normal file
@@ -0,0 +1,737 @@
|
||||
"""
|
||||
Ежедневная синхронизация Яндекс Вики → Supabase.
|
||||
|
||||
Запуск:
|
||||
python wiki_sync.py
|
||||
|
||||
Cron (каждый день в 03:00):
|
||||
0 3 * * * cd /Users/at/python/tracker-checker && /usr/bin/python3 wiki_sync.py >> logs/wiki_sync.log 2>&1
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from dotenv import load_dotenv
|
||||
import pendulum
|
||||
|
||||
from yandex_wiki import crawl
|
||||
from supabase import SupabaseManager
|
||||
from wiki_embeddings import upsert_embeddings
|
||||
|
||||
load_dotenv(Path(__file__).parent / '.env')
|
||||
|
||||
# ── Настройки ────────────────────────────────────────────────────────────────
|
||||
|
||||
API_KEY = os.getenv('API_KEY') or os.getenv('YT')
|
||||
ORG_ID = os.getenv('ORG_ID') or os.getenv('ORG')
|
||||
|
||||
# Корневые разделы для обхода — добавляй нужные slug-и
|
||||
ROOT_SLUGS = [
|
||||
# Корневой раздел
|
||||
'upravlenie-analitiki',
|
||||
# Процессы УА (дети рендерятся через {% tree %}, добавляй slug вручную когда откроешь в браузере)
|
||||
'upravlenie-analitiki/processy-ua',
|
||||
# Продукты УА и все найденные подразделы
|
||||
'upravlenie-analitiki/produkty-ua',
|
||||
'upravlenie-analitiki/bdd',
|
||||
'upravlenie-analitiki/analiticheskaja-podderzhka',
|
||||
'upravlenie-analitiki/onboarding',
|
||||
'upravlenie-analitiki/antifraud',
|
||||
'upravlenie-analitiki/research-analytics',
|
||||
'upravlenie-analitiki/dlja-sotrudnikov-ua',
|
||||
'upravlenie-analitiki/operacionnaja-analitika',
|
||||
'upravlenie-analitiki/biznes-analitika',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpa',
|
||||
'upravlenie-analitiki/produktovaja-analitika-lojalnosti',
|
||||
'upravlenie-analitiki/logirovanie-mp',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo',
|
||||
# Добавленные вручную по URL
|
||||
'upravlenie-analitiki/platforma-po-av-testam',
|
||||
'upravlenie-analitiki/kak-podat-zajavku-analitikam',
|
||||
'upravlenie-analitiki/put-novichka',
|
||||
'upravlenie-analitiki/dutykotiki',
|
||||
'upravlenie-analitiki/processy-ua/dq-process',
|
||||
'upravlenie-analitiki/processy-ua/emk-process',
|
||||
'upravlenie-analitiki/processy-ua/process-revju-issledovatelskix-zadach',
|
||||
'upravlenie-analitiki/platforma-po-av-testam/testirovanie',
|
||||
# Аналитическая поддержка (дети через {% tree %})
|
||||
'upravlenie-analitiki/analiticheskaja-podderzhka/ab-testy',
|
||||
'upravlenie-analitiki/analiticheskaja-podderzhka/komanda',
|
||||
'upravlenie-analitiki/analiticheskaja-podderzhka/dashbordy',
|
||||
'upravlenie-analitiki/analiticheskaja-podderzhka/issledovanija-i-logi',
|
||||
'upravlenie-analitiki/analiticheskaja-podderzhka/ad-hoc',
|
||||
# Добавлено автоматически через wiki_tree_crawler.py
|
||||
'upravlenie-analitiki/antifraud/bonusy-nachislennye-sotrudnikami-vruchnuju',
|
||||
'upravlenie-analitiki/antifraud/bonusy-za-jekologicheskie-akcii',
|
||||
'upravlenie-analitiki/antifraud/frod-na-kso-krazhi',
|
||||
'upravlenie-analitiki/antifraud/frod-ot-ujazvimosti-mp-v-funkcionalnosti-skanirujj',
|
||||
'upravlenie-analitiki/antifraud/frod-s-fejjkovymi-zakazami',
|
||||
'upravlenie-analitiki/antifraud/frod-v-chaevyx',
|
||||
'upravlenie-analitiki/antifraud/frod-v-limitax',
|
||||
'upravlenie-analitiki/antifraud/frod-v-limitax/frod-v-limitax.-ruchnye-ogranichenija-menedzherov-',
|
||||
'upravlenie-analitiki/antifraud/frod-v-otmenjonnyx-zakazax',
|
||||
'upravlenie-analitiki/antifraud/frod-v-otmenjonnyx-zakazax/zakazy-cherez-storonnie-marketplejjsy',
|
||||
'upravlenie-analitiki/antifraud/frod-v-programmax-lojalnosti',
|
||||
'upravlenie-analitiki/antifraud/frod-v-programmax-lojalnosti/primenenie-kupona-s-limitom-ot-summy-n-rublejj-k-m',
|
||||
'upravlenie-analitiki/antifraud/frod-v-programmax-lojalnosti/xaljavshhiki',
|
||||
'upravlenie-analitiki/antifraud/frod-v-referalnojj-programme',
|
||||
'upravlenie-analitiki/antifraud/frod-v-rejjtingax-i-otzyvax.-klientskijj-put-posta',
|
||||
'upravlenie-analitiki/antifraud/frod-v-vozvratax',
|
||||
'upravlenie-analitiki/antifraud/frod-v-vozvratax/dop.materialy',
|
||||
'upravlenie-analitiki/antifraud/jekonomika-proekta-antifrod',
|
||||
'upravlenie-analitiki/antifraud/jekonomika-proekta-antifrod/jekonomika-antifroda-v-chasti-limitov',
|
||||
'upravlenie-analitiki/antifraud/jekonomika-proekta-antifrod/kak-schitat-soxranjonnye-antifrodom-sredstva',
|
||||
'upravlenie-analitiki/antifraud/jekonomika-proekta-antifrod/primenenie-kuponov-na-pervyjj-zakaz-bolee-1-raza',
|
||||
'upravlenie-analitiki/antifraud/jekonomika-proekta-antifrod/soxranennye-dengi-v-chasti-vozvratov-2.0',
|
||||
'upravlenie-analitiki/antifraud/klasterizacija-i-klassifikacija-klientov-v-vozvrat',
|
||||
'upravlenie-analitiki/antifraud/lzhenovichki',
|
||||
'upravlenie-analitiki/antifraud/lzhenovichki/analitika-vremeni-zhizni-lzhe-novichkov',
|
||||
'upravlenie-analitiki/antifraud/massovost-vozvratov-pervyx-zakazov-po-partnerskojj',
|
||||
'upravlenie-analitiki/antifraud/raschet-marzhinalnosti-po-froderam',
|
||||
'upravlenie-analitiki/antifraud/rasskazhi-o-frode',
|
||||
'upravlenie-analitiki/antifraud/skanirovanie-tovarov-s-dvojjnym-shtrixkodom',
|
||||
'upravlenie-analitiki/antifraud/summarnye-dannye-za-24-jj-god-po-obemu-vozvratovbo',
|
||||
'upravlenie-analitiki/bdd/blok-analiticheskaja-kultura-i-standarty',
|
||||
'upravlenie-analitiki/bdd/blok-dannye',
|
||||
'upravlenie-analitiki/bdd/blok-infrastruktura',
|
||||
'upravlenie-analitiki/bdd/manifest-bdd',
|
||||
'upravlenie-analitiki/bdd/porazgonjat',
|
||||
'upravlenie-analitiki/bdd/porazgonjat/perechen-slozhnyx-proektov',
|
||||
'upravlenie-analitiki/bi-analitiki',
|
||||
'upravlenie-analitiki/bi-analitiki/arxiv',
|
||||
'upravlenie-analitiki/bi-analitiki/arxivacija-otchetov',
|
||||
'upravlenie-analitiki/bi-analitiki/kritichnost-dashbordov---standart',
|
||||
'upravlenie-analitiki/bi-analitiki/obeshhanija-komandy-bi',
|
||||
'upravlenie-analitiki/bi-analitiki/onbording-novichkov',
|
||||
'upravlenie-analitiki/bi-analitiki/opisanie-otchetov',
|
||||
'upravlenie-analitiki/bi-analitiki/plany-razvitija',
|
||||
'upravlenie-analitiki/bi-analitiki/poleznye-resursy',
|
||||
'upravlenie-analitiki/bi-analitiki/self-servisy-ua-vv-self-service',
|
||||
'upravlenie-analitiki/bi-analitiki/standarty-komandy-bi',
|
||||
'upravlenie-analitiki/bi-analitiki/vnutrennjaja-dokumentacija',
|
||||
'upravlenie-analitiki/bi-analitiki/xjendover-dashbordov-v-bi-komandu-ot-analitikov-ua',
|
||||
'upravlenie-analitiki/biznes-analitika/zadachi-1',
|
||||
'upravlenie-analitiki/dlja-sotrudnikov-ua/benefity-dlja-sotrudnikov',
|
||||
'upravlenie-analitiki/dlja-sotrudnikov-ua/dlja-tex-kto-rabotaet-vne-rf',
|
||||
'upravlenie-analitiki/dlja-sotrudnikov-ua/instrukcija-po-voinskomu-uchetu',
|
||||
'upravlenie-analitiki/dlja-sotrudnikov-ua/poleznye-ssylki-dlja-obuchenija',
|
||||
'upravlenie-analitiki/dlja-sotrudnikov-ua/uxod-v-otpusk-i-na-bolnichnyjj',
|
||||
'upravlenie-analitiki/dutykotiki/rabota-dezhurnogo',
|
||||
'upravlenie-analitiki/dutykotiki/rabota-dezhurnogo/obuchenie-po-ponjatnomu-tekstu',
|
||||
'upravlenie-analitiki/dutykotiki/rabota-dezhurnogo/rabota-s-jatrekerom',
|
||||
'upravlenie-analitiki/dutykotiki/rabota-dezhurnogo/shablon-dlja-stati-po-vygruzke',
|
||||
'upravlenie-analitiki/dutykotiki/segmentacii',
|
||||
'upravlenie-analitiki/dutykotiki/segmentacii/segmentacija-clc',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/11',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/1c-napitki',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/2f70ef29029b',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/3-gruppa-roznicy-pokazateli-lp-i-abonement',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/414dcd3c2b4c',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/4814eb73fc5e',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/6a6a82721362',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/6ff40875fcba',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/a1e2856653b5',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/adresa-kontragentov-postavshhikov',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/aktualnyjj-spisok-pozicijj-texnologa',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/analitika-novyx-torgovyx-tochek',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/analitika-po-magazinu-7514',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/analitika-po-produktam-iz-rastitelnojj-linejjki',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/analitika-po-zakazam-na-zavtra',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/analitika-proniknovenija-kategorijj-v-chek',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/analiz-aktivnyx-darkstorov-po-otgruzkam-kontragent',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/analiz-aktivnyx-darkstorov-po-otgruzkam-kontragent-19-11',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/analiz-kommunikacijj-dlja-olesi-kashicynojj-b2b',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/analiz-ottoka',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/analiz-pokupatelejj-assortimenta-podgruppy-morozhe',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/apteki-kol-vo-gen.zakazov-vyruchka-s-vozvratami-i-',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/asr-po-kartam',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/assortiment-1s-napitki-alkogol',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/assortiment-kategorii-supermarket-s-prinadlezhnost',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/assortiment-tovarov',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/avgmedianchecks',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/b2b---klienty-s-dvumja-i-bolee-zakazami',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/barista-i-dr.dolzhnosti-kotorye-rabotali-s-01.01.2',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/bonus-bags',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/bonusnye-karty-sotrudnikov',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/bonusnye-karty-zapolnivshie-formu-registracii-dlja',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/bonusy-za-utilizaciju-s-telefonami-i-kartami-pokup',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/ceny-tovarov-dobrojj-polki',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/chastota-pokupok-tovarov-v-kategorii-suxofrukty',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/cheki-s-nds-i-bez-nds-po-partnerskojj-programme-s-',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/dannye-dlja-rolika-o-vv---otkrytye-tt-novye-goroda',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/dannye-kogorty-novichkov-po-partnjorskojj-programm',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/dannye-o-pokupkax-v-magazinax-po-kartamtelefonam',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/dannye-po-nomenklature',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/dannye-po-pokupateljam-dobrojj-polki',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/dannye-po-prodazham-alkogolja-offlajjn',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/dannye-za-god',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/data-degustacii-i-prodazhi-po-ee-itogam',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/daty-podkljuchenija-tt-k-polochnomu-ili-67-grupp-r',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/dinamika-kart-dlja-kategorii-mjasnye-delikatesy.ko',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/dinamika-prodazh-desertnojj-i-xlebnojj-kategorii',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/dlja-informirovanija-roznicy',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/dolja-tovarov-indilavki-v-korzine-ambassadora',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/dolja-zakazov-s-suxojj-polkojj-zamorozkojj-sgorjac',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/dostavka-4-chasa-i-dachnyjj-jekspress---nomera-tel',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/dovozy',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/e-maily-tt-gde-est-kassiry',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/file-grudki-indejjki',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/frov-vygruzka-vyruchki-po-dnjam',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/good-polka',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/httpsbdo.kaiten.ruspace63173card35882806',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/informacija-o-tt',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/jekstremalnye-obrashhenija',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/kart-po-testeram',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/karty---novichki',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/karty-b2b-s-limitom',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/karty-i-zakazy-po-dobrym-pokupkam',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/karty-po-dobrojj-polke-i-jeko',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/karty-polzovatelejj-u-kogo-byla-vkljuchena-nastroj',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/karty-uchastnikov-akcii-knopsy',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/kategorii-osnovnye-metriki',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/kogorta-partnjorov-partnjorskojj-programmy-razdele',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/kol-vo-onlajjnoflajjn-polzovatelejj-po-asr',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/kol-vo-unikalnyx-kart-pokupatelejj-kosmetiki',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/kolichestvo-chekov-kulinarija-vv',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/kolichestvo-chekov-s-kso-po-rjadu-magazinov',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/kolichestvo-chekov-v-razreze-kass',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/kolichestvo-obrashhenijj-iz-arxiva',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/kolichestvo-obrashhenijj-po-tegam-smena-adresa-i-o',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/kolichestvo-pokupok-torty-pirozhnye-s-13-po-15-fev',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/kolichestvo-prodazh-36161-i-35085',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/kolichestvo-tt-i-novye-goroda-prisutstvija-po-kodu',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/kolichestvo-tt-po-okrugam',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/kolichestvo-uchastnikov-blagotvoritelnojj-akcii',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/kolichestvo-unikalnyx-kart',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/kommentarii-dlja-sborshhika.-frov-vesovye',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/kommentarii-i-zhaloby-k-zakazam-c-14-po-20-avgusta',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/kommentarii-k-zakazu',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/kommentarii-o-proizvoditeljax',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/korzina-pokupatelejj-po-adresu-dostavki',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/limity-arendy-v-1s-finansy---operacii---registr-sv',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/metod-avtorizacii-na-kso-kassa-samoobsluzhivanija',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/molochnye-kategorii-po-vozrastam',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/morozhenoe',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/nachislennye-bonusy--cheki',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/nochnaja-dostavka-zakaz-i-dostavka-osushhestvljali',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/nomenklatura-chestnyjj-znak-za-2023-g',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/nomenklatura-veganstvo--prodakt-menedzher',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/nomenklatury-novinki',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/nomera-bonusnyx-kart',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/nomera-bonusnyx-kart-projavljajushhix-aktivnost-v-',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/nomera-i-daty-dogovorov-b2b',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/nomera-kart-po-vozrastam-18-24-25-34',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/nomera-oshibok-oplat-v-obrashhenijax-pokupatelejj',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/nomerov-bonusnyx-kart-po-tovaru-tvorog-detskijj-ja',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/nomerov-kuratorov-i-pomoshhnikov-dostavki',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/novichki',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/novichki-dlja-darkstorov-gruppy-3',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/novichki-oflajjn-i-novichki-seti',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/novichki-v-onlajjne--top-10-v-pervom-cheke-novichk',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/novinki-v-razbivke-po-mesjacam-kol-vo-tt-i-kol-vo-',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/obogashhenie-vygruzki-dannymi-tt',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/obrashhenija-33-tipa-ot-neavtorizovannyx-polzovate',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/obrashhenija-po-degustacii-v-magazine',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/obrashhenija-po-moloku',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/obrashhenija-po-nomeram-kart',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/obrashhenija-po-syru',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/obrashhenija-po-upakovke-i-jetiketkam',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/obrashhenija-pokupatelejj-po-kategorijam-v-razreze',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/obratnaja-svjaz-po-zootovaram',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/opredelenie-sotrudnikane-sotrudnika-po-nomeru-kart',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/otchet-po-medikamentam',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/otchjot-po-otdelu-bez-upakovki-za-janvar-ijul-2024',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/otdel-bez-upakovki-srednijj-chek',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/otgruzki-s-tt-klientov-b2b',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/otsrochka-platezha-po-ka',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/ottokklienty-8048',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/otzyvy-pokupatelejj-na-cenu',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/parsing-tt-po-geozonam',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/partnjory-partnjorskojj-programmy-region',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/perekljuchenie-pozicijj',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/peresechenie-auditorijj-po-lukumu',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/peresechenie-po-pokupkam-vafel-sladosti',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/pokazateli-po-proektu-indilavka',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/pokupateli-mango',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/pokupateli-restoranov',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/pokupki-u-partnjorov',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/pokupki-v-indilavke-posle-festivalja',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/pokupki-vo-vv-u-kart-kotorye-pokupali-na-obed.ru',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/polzovateli-dlja-achivki',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/polzovateli-mobilnogo-prilozhenija-kotorye-ne-sove',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/poschitat-kolichestvo-stolovyx-priborov-v-zakazax-',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/poslednie-cheki-batarejjki--bonusy-za-utilizaciju',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/postavshhiki-i-stoimost-pechatnojj-upakovki',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/postavshhiki-piva',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/povtornye-pokupki-indilavka',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/povtornye-zakazy-iz-partnjorki-s-primenenie-promok',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/pp-vv-analiz-reklamy-esh-uchis-tvori',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/prichiny-vozvratov-po-segmentam',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/privlechjonnye-novichki-v-indilavku',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/procentnoe-sootnoshenie-shtatnyx-sotrudnikov-i-aut',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/prodazhi-bytovojj-ximii-na-rozliv',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/prodazhi-deserta',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/prodazhi-kulinarii-i-kulinarii-rp-na-2-tt',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/prodazhi-kulinarii-vv-po-nedeljam',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/prodazhi-lavka-vkusa---otbornoe',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/prodazhi-onlajjn-v-moskve',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/prodazhi-po-jajjcu-v-period-pasxi',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/prodazhi-spisanija-zc-po-tov.pozicijam-kotorye-vyv',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/prodazhi-termostakana-za-poslednie-polgoda',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/prodazhi-tovarov-aptek',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/prodazhi-tovarov-dobraja-polka-so-skidkami',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/prodazhi-tovarov-so-stavkojj-nds-10',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/prodazhi-tovarov-supermarketa-na-vajjldberriz-wild',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/prodazhi-trex-tovarov-za-poslednijj-god',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/prodazhi-tt-bez-uchjota-v-cheklajjnax-tovarov-iz-k',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/promokod-krasota--info-po-istochnikam-po-promokoda',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/proschet-vozvratnosti-i-chastoty-pokupok-unikalnyx',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/proverit-kartochki-pokupatelejj-na-nalichie-zakazo',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/proverka-sebestoimosti-komplektov',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/raschet-sr-kolva-zakazov',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/rasshirennaja-vygruzka-po-zakazam-b2b-klientov',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/razmernost-upakovok-skju',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/segmentacija-onlajjnoflajjn-po-kartam',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/shtrixkody-na-nomenklatury',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/skidka-kontragentov.-sbor-bazy',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/skoroportjashhijjsja-assortiment-i-ego-postavshhik',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/sneki-vygruzka-kart-dlja-gruppy-tovarov-dlja-vozra',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/sootnesti-nomera-telefonov-i-nomera-kart-vv',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/sootnoshenie-chisla-klientov-v-razreze-asr',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/sostav-chekov-v-kotoryx-est-tovary-iz-kategorii-ku',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/sostavy-i-postavshhiki-dlja-napitkov-i-alkogolja',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/sostavy-produkcii',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/sotrudniki-kotorye-ustroilis-do-01.01.2013-i-rabot',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/spisanija-po-degustacii-texnolog-proverka-kachestv',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/spisanija-s-tt-i-onlajjn-prodazhi-po-murmansku',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/spisaniyapostavki',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/spisok-aktualnojj-nomenklatury',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/spisok-nomenklatury-po-opredelennomu-prodakt-mened',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/spisok-tovarov-dlja-mobilnogo-prilozhenija-eatfit',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/spisok-tt-s-priznakom-kafe',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/spisok-zakazov-iz-torgovojj-tochki-7087ds-bbr-239',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/sposob-prigotovlenija-v-lichnom-kabinete-postavshh',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/sravnenie-prodazh-dvux-vidov-makaron',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/srednee-i-mediannoe-kolichestvo-chekov-v-moskve-i-',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/srednee-kol-vo-tovara-v-cheke',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/srednee-kolichestvo-paketov-v-zakazax-partnera-sbe',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/srednjaja-stoimost-pokupki-i-chastota-pokupki-xurm',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/srednjaja-summa-realizovannyx-tovarov--summa-ostat',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/statistika-po-kolichestvu-zakazov-kategorii-gorjac',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/statistika-po-prodazham',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/statistika-po-slitym-kartam',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/statistika-sbora-paketov-i-kryshek',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/statusy-postavshhikov',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/summa-bonusov-po-nomeram-kart',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/temperatura-xranenija-tovarov-kategorii-alkogol',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/top-100-po-kolichestvu-onlajjn-zakazov.-tt-ne-v-zo',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/top-5-oxlazhdenki-vkusmil',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/top-50-detskix-tovarov',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/top-darkov-dlja-achivki-blagotvoritelnosti',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/top-pozicijj-v-prodazhax-k-ng-2023',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/top-tovarov-po-vozrastam',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/top-tovarov-s-maksimalnymi-vozvratami',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/top-tovarov-v-pervom-cheke-karty-klienta',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/top-tovary-55',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/top-tovary-geo',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/tt-kafe',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/tt-po-moskve-i-sankt-peterburgu',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/tt-s-dostavkojj',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/unikalnye-pokupateli-tovara-dobryjj-prjanik',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/unikalnyu-karty-po-tipu-zhaloby-33-4-s-razbivkojj',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/vajjtstory-gde-est-sborka-zakazov-jelektronnaja-po',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/ves-tovara-do-i-posle-sborki',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/vitrinyproekty-po-vsem-tt',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/vozvraty-pokupki-i-obrashhenija-pokupatelejj-po-se',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/vozvraty-s-jekspress-dostavkojj-po-servisam',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/vtorsyre-zakaz-uslugi-s-dostavkojj',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/vygruzka-18-24-letnix',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/vygruzka-analitiki',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/vygruzka-auditorii',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/vygruzka-cen-za-period-v-dinamike',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/vygruzka-dannyx-po-onlajjn-zakazam-v-adresax-s-jek',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/vygruzka-dannyx-po-palletam',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/vygruzka-dannyx-predoplaty',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/vygruzka-daty-rozhdenija-i-pola-klientov',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/vygruzka-dlja-sokrashhenija-zatrat-vremeni-linejjn',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/vygruzka-dolejj-kazhdogo-puti-v-korzine-pokupatelj',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/vygruzka-email-texnologov',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/vygruzka-informacii-po-nalichiju-statusa-xrupkoe',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/vygruzka-jeko--i-nejeko--pokupatelejj',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/vygruzka-kart-po-spisku-tt-kto-ne-zakazyval-dostav',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/vygruzka-kolichestva-offlajjn-pokupatelejj-po-regi',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/vygruzka-kolichestva-pokupatelejj-na-lp',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/vygruzka-kuratorov-kurerov-i-kassirov-komplektovsh',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/vygruzka-nomenklatury',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/vygruzka-nomerov-telefonov-i-kart-stavili-negativn',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/vygruzka-nomerov-telefonov-ottok',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/vygruzka-nomerov-telefonov-po-kartam',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/vygruzka-obrashhenijj-otzyvov-i-rejjtinga-sp-vvgo',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/vygruzka-obrashhenijj-po-programme-lojalnosti-novi',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/vygruzka-obrashhenijj-po-samovyvozu',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/vygruzka-planogramm-kafe',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/vygruzka-po-kartam-kategorija-non-fud',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/vygruzka-pokupatelejj-indilavki-za-2024-god',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/vygruzka-postavshhikov',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/vygruzka-poterjannye-pokupateli-po-syru-rossijjsko',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/vygruzka-prinadlezhnosti-k-stmnestm-tovara-s-privj',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/vygruzka-raspredelenija-so-skladov-sumka-kofe-na-2',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/vygruzka-realizacijj-restorannyx-zakazov-jur.licam',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/vygruzka-skju-iz-1s-mjasnye-delikatesy.-kolbasy',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/vygruzka-srednjaja-vyruchka-i-srednee-kol-vo-cheko',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/vygruzka-strok-k-zakazam',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/vygruzka-telefonov-arendodatelejj-mikromarket-stat',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/vygruzka-texnologov-po-proizvoditeljupostavshhiku',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/vygruzka-top-50-tovarov-iz-top-20-kategorijj',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/vygruzka-top-tovarov',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/vygruzka-tovarov-darkstorov',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/vygruzka-tovarov-s-opredelennym-sostavom',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/vygruzka-v-formate-parket-chekov-novichkov',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/vygruzka-zakazov-slotovyx-jekspress-na-blizhajjshi',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/vyruchka-i-kolichestvo-v-kategorii-mjaso-i-zamoroz',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/vyruchka-i-kolichestvo-vegan-tovarov',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/vyruchka-po-dnjam-i-ostatki-po-chasam',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/xarakteristika-nomenklatury-i-id-xarakteristiki',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/zadacha-arbuznyx-kommentariev',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/zakaz-po-telefonu',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/zakazy-dlja-nochlezhki',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/zakazy-oformlennye-cherez-servis-samovyvoz-s-tovar',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/zakazy-oformlennyjj-vne-grafika-raboty-servisa-dar',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/zakazy-partnjorskojj-programmy-sovershjonnye-s-tov',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/zakazy-po-lsk-fjeshn-ooo',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/zapros-bonusnyx-kart-po-tt',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/zapros-kolichestva-chekov-v-torgovyx-tochkax',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/zaprosy-mvd-i-drugix-gos.organov-po-pokupateljam',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/zhaloby-na-neprijatnyjj-zapax-v-tt',
|
||||
'upravlenie-analitiki/dutykotiki/vygruzki/zheltyjj-cennik-po-partneru-jae',
|
||||
'upravlenie-analitiki/dutykotiki/zanimatelno-ob-analitike',
|
||||
'upravlenie-analitiki/dutykotiki/zanimatelno-ob-analitike/ab-testirovanie',
|
||||
'upravlenie-analitiki/dutykotiki/zanimatelno-ob-analitike/bagi',
|
||||
'upravlenie-analitiki/dutykotiki/zanimatelno-ob-analitike/blagotvoritelnost-vo-vkusvill',
|
||||
'upravlenie-analitiki/dutykotiki/zanimatelno-ob-analitike/formulirovka-zaprosa-na-vygruzku',
|
||||
'upravlenie-analitiki/dutykotiki/zanimatelno-ob-analitike/issledovanija',
|
||||
'upravlenie-analitiki/dutykotiki/zanimatelno-ob-analitike/poleznye-sovety-dlja-power-bi',
|
||||
'upravlenie-analitiki/dutykotiki/zanimatelno-ob-analitike/postanovka-zadachi-analitiku',
|
||||
'upravlenie-analitiki/dutykotiki/zanimatelno-ob-analitike/prioritezacija-zadach',
|
||||
'upravlenie-analitiki/dutykotiki/zanimatelno-ob-analitike/segmentacija-lifestyle',
|
||||
'upravlenie-analitiki/dutykotiki/zanimatelno-ob-analitike/texbot',
|
||||
'upravlenie-analitiki/dutykotiki/zanimatelno-ob-analitike/vremja-vypolnenija-zadach',
|
||||
'upravlenie-analitiki/kak-podat-zajavku-analitikam/kak-ispolzovat-shablony-dlja-zadach',
|
||||
'upravlenie-analitiki/logirovanie-mp/aa55c98a3fdf',
|
||||
'upravlenie-analitiki/logirovanie-mp/biznes-trebovanija-k-sisteme-logirovanija',
|
||||
'upravlenie-analitiki/logirovanie-mp/boli-logov',
|
||||
'upravlenie-analitiki/logirovanie-mp/boli-svjazannye-s-logami',
|
||||
'upravlenie-analitiki/logirovanie-mp/cartlogs',
|
||||
'upravlenie-analitiki/logirovanie-mp/cartlogs/korzina',
|
||||
'upravlenie-analitiki/logirovanie-mp/cartlogs/oplaty-5-4',
|
||||
'upravlenie-analitiki/logirovanie-mp/celi-proekta-logirovanie-2.0',
|
||||
'upravlenie-analitiki/logirovanie-mp/korzina',
|
||||
'upravlenie-analitiki/logirovanie-mp/proektlogirovaniya',
|
||||
'upravlenie-analitiki/logirovanie-mp/proektlogirovaniya/itogicustdev',
|
||||
'upravlenie-analitiki/logirovanie-mp/proektlogirovaniya/metrics',
|
||||
'upravlenie-analitiki/logirovanie-mp/proektlogirovaniya/opisanie-novogo-processa-legirovanija',
|
||||
'upravlenie-analitiki/logirovanie-mp/proektlogirovaniya/scenarii-vzaimodejjstvija-mezhdu-uchastnikami-proc',
|
||||
'upravlenie-analitiki/logirovanie-mp/proektlogirovaniya/zony-otvetstvennosti-i-kompetencii-uchastnikov-pro',
|
||||
'upravlenie-analitiki/logirovanie-mp/razdel-1',
|
||||
'upravlenie-analitiki/logirovanie-mp/razdel-1/istorija-versijj',
|
||||
'upravlenie-analitiki/logirovanie-mp/semanticheskaja-model',
|
||||
'upravlenie-analitiki/logirovanie-mp/vitriny-logov',
|
||||
'upravlenie-analitiki/logirovanie-mp/vitriny-logov/primer-specificheskaja-vitrina-s-uzkospecializirov',
|
||||
'upravlenie-analitiki/logirovanie-mp/vitriny-logov/razdel-2',
|
||||
'upravlenie-analitiki/logirovanie-mp/vnedrenie-izmenenijj-v-tekushhijj-process-sozdanij',
|
||||
'upravlenie-analitiki/logirovanie-mp/voprosy-po-chernovomu-arxitekturnomu-resheniju',
|
||||
'upravlenie-analitiki/onboarding/dostupy',
|
||||
'upravlenie-analitiki/onboarding/dostupy/6849a4eb2845',
|
||||
'upravlenie-analitiki/onboarding/formy-jatrekera',
|
||||
'upravlenie-analitiki/onboarding/instrukcii-po-rabote-s-instrumentami',
|
||||
'upravlenie-analitiki/onboarding/instrukcii-po-rabote-s-instrumentami/1s',
|
||||
'upravlenie-analitiki/onboarding/instrukcii-po-rabote-s-instrumentami/analiticheskie-instrumenty',
|
||||
'upravlenie-analitiki/onboarding/instrukcii-po-rabote-s-instrumentami/full-power-bi-desktop',
|
||||
'upravlenie-analitiki/onboarding/instrukcii-po-rabote-s-instrumentami/instruction-ytracker',
|
||||
'upravlenie-analitiki/onboarding/instrukcii-po-rabote-s-instrumentami/instrukcija-po-nastrojjke-vs-code',
|
||||
'upravlenie-analitiki/onboarding/instrukcii-po-rabote-s-instrumentami/instrukcija-po-podkljucheniju-clickhouse-v-dbeave',
|
||||
'upravlenie-analitiki/onboarding/instrukcii-po-rabote-s-instrumentami/nastrojjka-gitlab',
|
||||
'upravlenie-analitiki/onboarding/instrukcii-po-rabote-s-instrumentami/perenos-s-kajjtena-katja',
|
||||
'upravlenie-analitiki/onboarding/instrukcii-po-rabote-s-instrumentami/podkljuchenie-cherez-jupyterhub',
|
||||
'upravlenie-analitiki/onboarding/instrukcii-po-rabote-s-instrumentami/poisk-informacii-po-bd',
|
||||
'upravlenie-analitiki/onboarding/instrukcii-po-rabote-s-instrumentami/pro-moduli-access',
|
||||
'upravlenie-analitiki/onboarding/instrukcii-po-rabote-s-instrumentami/sozdanie-ssh-kljucha',
|
||||
'upravlenie-analitiki/onboarding/instrukcii-po-rabote-s-instrumentami/sql-docs',
|
||||
'upravlenie-analitiki/onboarding/instrukcii-po-rabote-s-instrumentami/ssrs-i-power-bi-otchety',
|
||||
'upravlenie-analitiki/onboarding/instrukcii-po-rabote-s-instrumentami/superset',
|
||||
'upravlenie-analitiki/onboarding/kak-my-vedjom-analiticheskie-zadachi',
|
||||
'upravlenie-analitiki/onboarding/manifest-produktovojj-analitiki',
|
||||
'upravlenie-analitiki/onboarding/manifest-produktovojj-analitiki/komanda-i-vzaimodejjstvie',
|
||||
'upravlenie-analitiki/onboarding/manifest-produktovojj-analitiki/process-raboty-nad-zadachami',
|
||||
'upravlenie-analitiki/onboarding/oformlenie-postojannogo-propuska-v-ofis-vv',
|
||||
'upravlenie-analitiki/onboarding/opisanie-obshhego-analiticheskogo-shablona-opisani',
|
||||
'upravlenie-analitiki/onboarding/stati-pro-dannye-vv',
|
||||
'upravlenie-analitiki/onboarding/stati-pro-dannye-vv/produkty-lojalnosti-kratkijj-gajjd',
|
||||
'upravlenie-analitiki/onboarding/statja-onbordinga-dlja-novichka',
|
||||
'upravlenie-analitiki/onboarding/upravlenie-analitiki',
|
||||
'upravlenie-analitiki/operacionnaja-analitika/analitika-b2b',
|
||||
'upravlenie-analitiki/operacionnaja-analitika/analitika-darkkitchen',
|
||||
'upravlenie-analitiki/operacionnaja-analitika/analitika-mikroservisa-roznicy',
|
||||
'upravlenie-analitiki/operacionnaja-analitika/analitika-partnerov-apteki-restorany-cvety-etc',
|
||||
'upravlenie-analitiki/operacionnaja-analitika/analitika-poslednejj-mili',
|
||||
'upravlenie-analitiki/operacionnaja-analitika/analitika-sborki',
|
||||
'upravlenie-analitiki/operacionnaja-analitika/raspredelenie-urz-ostatki',
|
||||
'upravlenie-analitiki/operacionnaja-analitika/revju-2025',
|
||||
'upravlenie-analitiki/platforma-po-av-testam/arxiv',
|
||||
'upravlenie-analitiki/platforma-po-av-testam/draft-monitoring-sostojanija-platformy',
|
||||
'upravlenie-analitiki/platforma-po-av-testam/oshibki',
|
||||
'upravlenie-analitiki/platforma-po-av-testam/testirovanie/onbording',
|
||||
'upravlenie-analitiki/platforma-po-av-testam/testirovanie/reglamenty-testovojj-dokumentacii',
|
||||
'upravlenie-analitiki/platforma-po-av-testam/testirovanie/regress',
|
||||
'upravlenie-analitiki/platforma-po-av-testam/testirovanie/restapi',
|
||||
'upravlenie-analitiki/platforma-po-av-testam/testirovanie/test-plan-pokrytija-kriticheskogo-funkcionala-ab-t',
|
||||
'upravlenie-analitiki/platforma-po-av-testam/testirovanie/web-funkcional---kalendar',
|
||||
'upravlenie-analitiki/platforma-po-av-testam/validacija-ab-platformy',
|
||||
'upravlenie-analitiki/processy-ua/emk-process/primer-zapolnennogo-shablona-dlja-revju-pa',
|
||||
'upravlenie-analitiki/processy-ua/emk-process/process-revju-pa',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpa/8a1c0312ec94',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpa/8a1c0312ec94/0d4e3c3b36ec',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpa/8a1c0312ec94/klasterizacija-torgovyx-tochek.-podxod-razvitija',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpa/8a1c0312ec94/kojefficienty-sezonnosti-i-razvedyvatelnyjj-analiz',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpa/8a1c0312ec94/metriki-jeffektivnosti',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpa/8a1c0312ec94/okupaemost-tt',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpa/8a1c0312ec94/otcheta-dlja-podbora-pervichnojj-matricy-tovarov-d',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpa/8a1c0312ec94/razlichija-v-sezonnosti-i-v-chekax-budnivyxodnye-i',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpa/e1cb991f8d0e',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpa/e1cb991f8d0e/43eb2cbbfe12',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpa/e1cb991f8d0e/5-na-fr',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpa/e1cb991f8d0e/8325b4e9d0f1',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpa/e1cb991f8d0e/ac709a764736',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpa/e1cb991f8d0e/dopniki-po-unikalnosti-i-jekskljuzivnosti',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpa/e1cb991f8d0e/dorabotka-dashborda-peregovornaja-komanija',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpa/e1cb991f8d0e/e58a3ba2ba0b',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpa/e1cb991f8d0e/jeffektivnost-termochexlov-rc-dark',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpa/e1cb991f8d0e/peredacha-skriptov-olja',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpa/e1cb991f8d0e/rejjting-postavshhika',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpa/e1cb991f8d0e/shablon-dlja-issledovanijj-ua',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpa/metriki-i-skripty-kpa',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpa/metriki-i-skripty-kpa/chasto-ispolzuemye-tablicy-i-predstavlenija',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpa/metriki-i-skripty-kpa/metriki',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpa/metriki-i-skripty-kpa/metriki-gp',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpa/metriki-i-skripty-kpa/poleznye-skripty',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpa/raznye-ceny-i-cenoobrazovanie',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpa/statistika-frov',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpa/zadachi',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpa/zadachi/0b2881f238dd',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpa/zadachi/15fe987417a3',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpa/zadachi/6fb599d9485e',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpa/zadachi/d567d5309fec',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpa/zadachi/vygruzki',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/analitika-platezhnogo-puti',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/analitika-platezhnogo-puti/chaevye',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/analitika-platezhnogo-puti/cheki',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/analitika-platezhnogo-puti/chestnyjj-znak',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/analitika-platezhnogo-puti/eshpay',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/analitika-platezhnogo-puti/kassy',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/analitika-platezhnogo-puti/krepkijj-alkogol',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/analitika-platezhnogo-puti/ks',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/analitika-platezhnogo-puti/lojalnost-i-onlajjn-kassa',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/analitika-platezhnogo-puti/oborudovanie',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/analitika-platezhnogo-puti/onlajjn-oplaty',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/analitika-platezhnogo-puti/oplata-na-meste',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/analitika-platezhnogo-puti/podarochnye-karty',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/analitika-platezhnogo-puti/pravila-postanovki-zadach-analitikam',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/analitika-platezhnogo-puti/privjazki-ka',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/analitika-platezhnogo-puti/spravochnik-platezhnyjj-put',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/analitika-platezhnogo-puti/spravochnik-platezhnyjj-put---logi-kurera',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/analitika-platezhnogo-puti/vozvraty',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/analitika-platezhnogo-puti/vygruzki-dlja-issledovateljj',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/general-team',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/general-team/dq-project',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/general-team/issledovanie-otkuda-polzovateli-nachinajut-sobirat',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/general-team/istochniki-dlja-rascheta-metrik-otchetov-su-i-obes',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/general-team/opisanija-verxneurovnevyx-otchetov',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/general-team/pererabotka-verxneurovnevojj-otchetnosti',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/general-team/plany-2024n1',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/lidery-pa-kpo',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/lidery-pa-kpo/plany-i-itogi',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/lidery-pa-kpo/sink-liderov-pa-kpo',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/obshhaja-dokumentacija',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/obshhaja-dokumentacija/adzhendy-vstrech',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/obshhaja-dokumentacija/novyjj-process-logirovanija',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/obshhaja-dokumentacija/onbording',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/obshhaja-dokumentacija/prioritizacija-zadach-po-rice',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/obshhaja-dokumentacija/process-provedenija-av-testov',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/obshhaja-dokumentacija/processy-pa-kpo',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/obshhaja-dokumentacija/proekty',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/obshhaja-dokumentacija/python-pakety',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/obshhaja-dokumentacija/rabota-s-logami',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/obshhaja-dokumentacija/reestr-dashbordov',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/obshhaja-dokumentacija/reestr-ispolzuemyx-na-238kx-tablic-vitrin-vjux',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-avtorizacii-i-profilja',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-avtorizacii-i-profilja/ab-testy',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-avtorizacii-i-profilja/dashbordy',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-avtorizacii-i-profilja/issledovanija',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-ccg',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-ccg/komandaccg',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-ccg/plany-i-celi',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-ccg/processy-korzina',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-ccg/produktovaja-analitika-geoservisov',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-ccg/produktovaja-analitika-korziny',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-kataloga',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-kataloga/jekran-podderzhki',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-kataloga/katalog',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-kataloga/neaktualnoe',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-kataloga/obshhee-kataloga',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-kataloga/produktovaja-analitika-kartochki-tovara',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-kataloga/rejjtingi-i-otzyvy',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-lojalnosti',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-lojalnosti/dokumentirovanie-zadach',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-lojalnosti/kommunikacii',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-lojalnosti/lojalnost',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-lojalnosti/plany-2024',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-lojalnosti/proekty',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-lojalnosti/shablony',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-lojalnosti/spisok-del-serezhi',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-lojalnosti/vitriny-danny',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-offlajjnery-v-mp',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-offlajjnery-v-mp/magaziny',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-offlajjnery-v-mp/processy',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-platezhnyx-putejj',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-platezhnyx-putejj/chaevye',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-platezhnyx-putejj/onlajjn-oplaty',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-platezhnyx-putejj/oplaty-na-meste',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-platezhnyx-putejj/vozvraty',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-poiska',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-poiska/ab-poisk',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-poiska/dokumentacija-biblioteki-dlja-ab-testov',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-poiska/kak-rabotaet-poisk',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-poiska/logi-poiska-xyunja',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-poiska/metriki-poisk',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-poiska/onbording-v-komandu-poiska',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-poiska/produkty-komandy-analitiki-poiska',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-poiska/proekt-novaja-konkurencija',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-poiska/researches-poisk',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-poiska/search-dashes',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-poiska/vitriny-dannyx-poisk',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-rekomendacijj',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-rekomendacijj/8ff2daf94a28',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-rekomendacijj/ab-rekomendacii',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-rekomendacijj/algoritmicheskie-podborki',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-rekomendacijj/dashbordy-rekomendacii',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-rekomendacijj/issledovanija-rekomendacii',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-rekomendacijj/logi-rekomendacii',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-rekomendacijj/metriki-rekomendacii',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-rekomendacijj/onbording-v-komandu-analitiki-rekomendacijj',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-rekomendacijj/problemy',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-rekomendacijj/rekomendacii-v-mp',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-rekomendacijj/tovary-v-rekomendacijax',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-rekomendacijj/vygruzki',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-sajjta',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-sajjta/ab-tests',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-sajjta/appsflyer',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-sajjta/bjeklog-gipotez',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-sajjta/dokumentacija-sajjta',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-sajjta/jandeks.metrika',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-sajjta/logirovanie-sajjta',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-sajjta/logirovanie-sajjta-slova',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-sajjta/logirovanie-sajjta-slova-f6de9f',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-sajjta/metriki-sajjta',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-sajjta/otchety-komandy-sajjta',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-sajjta/test-vkusvill-plan',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/produktovaja-analitika-sajjta/zadachi',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/proekty-pa-kpo',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/proekty-pa-kpo/domen-driven-design',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/proekty-pa-kpo/logirovanie',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/proekty-pa-kpo/novaja-konkurencija-metriki',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/proekty-pa-kpo/shablon',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/verxneurovnevaja-analitika',
|
||||
'upravlenie-analitiki/produktovaja-analitika-kpo/verxneurovnevaja-analitika/zadachi',
|
||||
'upravlenie-analitiki/produktovaja-analitika-lojalnosti/4689cbbc6dc5',
|
||||
'upravlenie-analitiki/produktovaja-analitika-lojalnosti/dokumentacija-po-zadacham',
|
||||
'upravlenie-analitiki/produktovaja-analitika-lojalnosti/globalnye-issledovanija',
|
||||
'upravlenie-analitiki/produktovaja-analitika-lojalnosti/komanda',
|
||||
'upravlenie-analitiki/produktovaja-analitika-lojalnosti/mexaniki-lojalnosti',
|
||||
'upravlenie-analitiki/produktovaja-analitika-lojalnosti/obshhaja-informacija',
|
||||
'upravlenie-analitiki/produktovaja-analitika-lojalnosti/otchety',
|
||||
'upravlenie-analitiki/produktovaja-analitika-lojalnosti/segmentacii',
|
||||
'upravlenie-analitiki/produktovaja-analitika-lojalnosti/slovar-metrik',
|
||||
'upravlenie-analitiki/put-novichka/kak-podat-zajavku-na-dostupy',
|
||||
'upravlenie-analitiki/put-novichka/perevod-sotrudnika',
|
||||
'upravlenie-analitiki/put-novichka/pomoshh-v-provedenii-onboarding',
|
||||
'upravlenie-analitiki/put-novichka/vzaimodejjstvie-upravlenija-analitiki-ua-i-upravle',
|
||||
'upravlenie-analitiki/research-analytics/analiz-jeffektivnosti-otveta-na-otzyv',
|
||||
'upravlenie-analitiki/research-analytics/assistent-antifrod-dose-na-klienta',
|
||||
'upravlenie-analitiki/research-analytics/avtootvety-na-gorjachejj-linii',
|
||||
'upravlenie-analitiki/research-analytics/b2b-containers',
|
||||
'upravlenie-analitiki/research-analytics/busting-tovarov-sgorjacha',
|
||||
'upravlenie-analitiki/research-analytics/favorite-in-tap-bar',
|
||||
'upravlenie-analitiki/research-analytics/finansovaja-model-fudomatov-obedy',
|
||||
'upravlenie-analitiki/research-analytics/gen-ai',
|
||||
'upravlenie-analitiki/research-analytics/gen-ai/copilot-vygruzki',
|
||||
'upravlenie-analitiki/research-analytics/gen-ai/genai-bt',
|
||||
'upravlenie-analitiki/research-analytics/gen-ai/rag',
|
||||
'upravlenie-analitiki/research-analytics/modelirovanie-blokirovki-postavshhikov',
|
||||
'upravlenie-analitiki/research-analytics/nps',
|
||||
'upravlenie-analitiki/research-analytics/ocenka-jeffektivnosti-soobshhenija-ob-izmenenijax-',
|
||||
'upravlenie-analitiki/research-analytics/oec-metrika',
|
||||
'upravlenie-analitiki/research-analytics/poleznye-bazovye-materialy',
|
||||
'upravlenie-analitiki/research-analytics/prognozirovanie-pl-obedy',
|
||||
'upravlenie-analitiki/research-analytics/snjatie-tovara-s-prodazhi',
|
||||
'upravlenie-analitiki/research-analytics/zajavka-na-otrabotku-kachestva',
|
||||
]
|
||||
|
||||
MAX_DEPTH = 4 # Максимальная глубина обхода ссылок
|
||||
DELAY = 0.3 # Пауза между запросами (сек), чтобы не перегружать API
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def main():
|
||||
started_at = pendulum.now(tz='Europe/Moscow')
|
||||
print(f'[{started_at.to_datetime_string()}] Запуск wiki_sync')
|
||||
|
||||
if not API_KEY or not ORG_ID:
|
||||
print('✗ Не заданы API_KEY / ORG_ID в .env')
|
||||
sys.exit(1)
|
||||
|
||||
# 1. Обход Wiki
|
||||
pages = crawl(
|
||||
root_slugs=ROOT_SLUGS,
|
||||
api_token=API_KEY,
|
||||
org_id=ORG_ID,
|
||||
max_depth=MAX_DEPTH,
|
||||
delay=DELAY,
|
||||
)
|
||||
|
||||
if not pages:
|
||||
print('Страниц не найдено. Завершение.')
|
||||
return
|
||||
|
||||
# 2. Сохранение в Supabase
|
||||
db = SupabaseManager()
|
||||
if not db.connect():
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
result = db.upsert_wiki_pages(pages)
|
||||
print(
|
||||
f'\n✓ Страницы сохранены:\n'
|
||||
f' Новых / изменённых: {result["inserted"]}\n'
|
||||
f' Без изменений: {result["unchanged"]}\n'
|
||||
f' Всего страниц: {len(pages)}'
|
||||
)
|
||||
|
||||
# 3. Генерация embeddings для новых/изменённых страниц
|
||||
print('\nГенерируем embeddings...')
|
||||
emb_result = upsert_embeddings(db, pages)
|
||||
print(
|
||||
f'✓ Embeddings:\n'
|
||||
f' Сгенерировано: {emb_result["embedded"]}\n'
|
||||
f' Без изменений: {emb_result["skipped"]}'
|
||||
)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
finished_at = pendulum.now(tz='Europe/Moscow')
|
||||
duration = (finished_at - started_at).in_seconds()
|
||||
print(f'[{finished_at.to_datetime_string()}] Готово за {duration}с')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
146
wiki_tree_crawler.py
Normal file
146
wiki_tree_crawler.py
Normal file
@@ -0,0 +1,146 @@
|
||||
"""
|
||||
Обход {% tree %} страниц Яндекс Вики через Playwright.
|
||||
|
||||
Находит дочерние страницы, которые не видны через API (рендерятся динамически).
|
||||
Возвращает список slug-ов которых нет в wiki_pages — их нужно добавить в ROOT_SLUGS.
|
||||
|
||||
Запуск:
|
||||
python wiki_tree_crawler.py
|
||||
"""
|
||||
import re
|
||||
import time
|
||||
from pathlib import Path
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv(Path(__file__).parent / '.env')
|
||||
|
||||
from playwright.sync_api import sync_playwright
|
||||
|
||||
AUTH_FILE = Path(__file__).parent / 'wiki_auth.json'
|
||||
WIKI_BASE = 'https://wiki.yandex.ru'
|
||||
DELAY = 0.5 # пауза между запросами
|
||||
|
||||
|
||||
def slug_from_url(url: str) -> str | None:
|
||||
"""Извлечь slug из URL wiki.yandex.ru/slug/..."""
|
||||
match = re.match(r'https?://wiki\.yandex\.ru/([^?#]+?)/?$', url)
|
||||
if not match:
|
||||
return None
|
||||
slug = match.group(1).strip('/')
|
||||
# Отфильтровать служебные страницы
|
||||
if any(slug.startswith(p) for p in ['.files', 'homepage/', 'cdp-']):
|
||||
return None
|
||||
return slug
|
||||
|
||||
|
||||
def get_child_slugs(page, parent_slug: str) -> list[str]:
|
||||
"""Открыть страницу и собрать все ссылки на дочерние страницы."""
|
||||
url = f'{WIKI_BASE}/{parent_slug}'
|
||||
page.goto(url, wait_until='networkidle', timeout=30000)
|
||||
time.sleep(DELAY)
|
||||
|
||||
# Ищем ссылки в левом навигационном дереве и в содержимом страницы
|
||||
links = page.eval_on_selector_all(
|
||||
'a[href]',
|
||||
'els => els.map(el => el.href)'
|
||||
)
|
||||
|
||||
child_slugs = []
|
||||
for link in links:
|
||||
slug = slug_from_url(link)
|
||||
if slug and slug.startswith(parent_slug + '/') and slug != parent_slug:
|
||||
child_slugs.append(slug)
|
||||
|
||||
return list(set(child_slugs))
|
||||
|
||||
|
||||
def find_tree_pages_in_db() -> list[tuple[str, str]]:
|
||||
"""Получить из Supabase все страницы с {% tree %} в контенте."""
|
||||
import sys
|
||||
sys.path.insert(0, str(Path(__file__).parent))
|
||||
from supabase import SupabaseManager
|
||||
|
||||
db = SupabaseManager()
|
||||
db.connect()
|
||||
db.cursor.execute("""
|
||||
SELECT slug, value->>'title' AS title
|
||||
FROM wiki_pages
|
||||
WHERE value->>'content' LIKE '%{%% tree%%}%'
|
||||
ORDER BY slug
|
||||
""")
|
||||
rows = db.cursor.fetchall()
|
||||
db.close()
|
||||
return rows
|
||||
|
||||
|
||||
def get_all_known_slugs() -> set[str]:
|
||||
"""Все slug-и которые уже есть в wiki_pages."""
|
||||
import sys
|
||||
sys.path.insert(0, str(Path(__file__).parent))
|
||||
from supabase import SupabaseManager
|
||||
|
||||
db = SupabaseManager()
|
||||
db.connect()
|
||||
db.cursor.execute("SELECT slug FROM wiki_pages")
|
||||
slugs = {row[0] for row in db.cursor.fetchall()}
|
||||
db.close()
|
||||
return slugs
|
||||
|
||||
|
||||
def main():
|
||||
if not AUTH_FILE.exists():
|
||||
print(f'✗ Файл авторизации не найден: {AUTH_FILE}')
|
||||
print(' Сначала запусти: python wiki_auth.py')
|
||||
return
|
||||
|
||||
ROOT_PREFIX = 'upravlenie-analitiki'
|
||||
|
||||
print('Загружаем список страниц с {% tree %} из Supabase...')
|
||||
all_tree_pages = find_tree_pages_in_db()
|
||||
tree_pages = [
|
||||
(s, t) for s, t in all_tree_pages
|
||||
if s == ROOT_PREFIX or s.startswith(ROOT_PREFIX + '/')
|
||||
]
|
||||
known_slugs = get_all_known_slugs()
|
||||
print(f'Страниц с {{% tree %}} (всего): {len(all_tree_pages)}, в разделе УА: {len(tree_pages)}')
|
||||
print()
|
||||
|
||||
new_slugs = []
|
||||
|
||||
with sync_playwright() as p:
|
||||
browser = p.chromium.launch(headless=True)
|
||||
context = browser.new_context(storage_state=str(AUTH_FILE))
|
||||
page = context.new_page()
|
||||
|
||||
for slug, title in tree_pages:
|
||||
print(f'→ {slug} «{title}»')
|
||||
try:
|
||||
children = get_child_slugs(page, slug)
|
||||
new = [s for s in children if s not in known_slugs]
|
||||
print(f' Найдено дочерних: {len(children)}, новых: {len(new)}')
|
||||
for s in sorted(children):
|
||||
marker = ' ← NEW' if s in new else ''
|
||||
print(f' {s}{marker}')
|
||||
new_slugs.extend(new)
|
||||
except Exception as e:
|
||||
print(f' ✗ Ошибка: {e}')
|
||||
print()
|
||||
|
||||
browser.close()
|
||||
|
||||
new_slugs = list(set(new_slugs))
|
||||
|
||||
if new_slugs:
|
||||
print('=' * 60)
|
||||
print(f'Найдено {len(new_slugs)} новых slug-ов для ROOT_SLUGS:')
|
||||
print()
|
||||
for s in sorted(new_slugs):
|
||||
print(f" '{s}',")
|
||||
print()
|
||||
print('Добавь их в ROOT_SLUGS в wiki_sync.py и запусти wiki_sync.py')
|
||||
else:
|
||||
print('✓ Новых страниц не найдено')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
125
yandex_wiki.py
Normal file
125
yandex_wiki.py
Normal file
@@ -0,0 +1,125 @@
|
||||
"""
|
||||
Модуль для обхода страниц Yandex Wiki API
|
||||
"""
|
||||
import re
|
||||
import time
|
||||
import hashlib
|
||||
import requests
|
||||
import pendulum
|
||||
|
||||
|
||||
WIKI_API = 'https://api.wiki.yandex.net/v1'
|
||||
|
||||
|
||||
def _make_headers(api_token: str, org_id: str) -> dict:
|
||||
return {
|
||||
'Authorization': f'OAuth {api_token}',
|
||||
'X-Org-Id': org_id,
|
||||
}
|
||||
|
||||
|
||||
def get_page(slug: str, api_token: str, org_id: str) -> dict | None:
|
||||
"""Получить страницу по slug. Возвращает None если не найдена."""
|
||||
try:
|
||||
resp = requests.get(
|
||||
f'{WIKI_API}/pages',
|
||||
headers=_make_headers(api_token, org_id),
|
||||
params={'slug': slug, 'fields': 'content,attributes'},
|
||||
timeout=15,
|
||||
)
|
||||
if resp.status_code == 200:
|
||||
return resp.json()
|
||||
return None
|
||||
except requests.RequestException as e:
|
||||
print(f' ✗ Ошибка при запросе slug={slug}: {e}')
|
||||
return None
|
||||
|
||||
|
||||
def extract_slugs_from_content(content: str) -> list[str]:
|
||||
"""Извлечь slug-ссылки из wiki-разметки страницы."""
|
||||
slugs = []
|
||||
|
||||
# [[slug]] и [[slug|текст]]
|
||||
slugs += re.findall(r'\[\[([^\]|#]+)', content)
|
||||
|
||||
# ((https://wiki.yandex.ru/slug текст)) или ((https://wiki.yandex.ru/slug))
|
||||
url_matches = re.findall(r'https://wiki\.yandex\.ru/([^\s)#]+)', content)
|
||||
slugs += url_matches
|
||||
|
||||
# Нормализуем: убираем пробелы и слэши по краям
|
||||
result = []
|
||||
for s in slugs:
|
||||
s = s.strip().strip('/')
|
||||
if s and not s.startswith('http') and len(s) < 200:
|
||||
result.append(s)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def content_hash(content: str) -> str:
|
||||
"""MD5-хеш содержимого страницы для определения изменений."""
|
||||
return hashlib.md5(content.encode('utf-8')).hexdigest()
|
||||
|
||||
|
||||
def crawl(
|
||||
root_slugs: list[str],
|
||||
api_token: str,
|
||||
org_id: str,
|
||||
max_depth: int = 4,
|
||||
delay: float = 0.3,
|
||||
) -> list[dict]:
|
||||
"""
|
||||
Обойти дерево страниц начиная с root_slugs.
|
||||
|
||||
Args:
|
||||
root_slugs: Список стартовых slug-ов (например ['upravlenie-analitiki'])
|
||||
api_token: OAuth-токен
|
||||
org_id: ID организации
|
||||
max_depth: Максимальная глубина обхода
|
||||
delay: Пауза между запросами в секундах
|
||||
|
||||
Returns:
|
||||
Список словарей с данными страниц
|
||||
"""
|
||||
dt_load = pendulum.now(tz='Europe/Moscow')
|
||||
visited: set[str] = set()
|
||||
pages: list[dict] = []
|
||||
|
||||
def _crawl(slug: str, depth: int):
|
||||
if depth > max_depth or slug in visited:
|
||||
return
|
||||
visited.add(slug)
|
||||
|
||||
print(f' {" " * depth}→ {slug}')
|
||||
page = get_page(slug, api_token, org_id)
|
||||
time.sleep(delay)
|
||||
|
||||
if not page:
|
||||
return
|
||||
|
||||
raw_content = page.get('content', '')
|
||||
attrs = page.get('attributes', {})
|
||||
|
||||
modified_at = attrs.get('modified_at') or attrs.get('updated_at')
|
||||
|
||||
pages.append({
|
||||
'pg_load_dttm': dt_load,
|
||||
'slug': page.get('slug', slug),
|
||||
'wiki_page_id': page.get('id'),
|
||||
'title': page.get('title', ''),
|
||||
'page_type': page.get('page_type', ''),
|
||||
'modified_at': modified_at,
|
||||
'content': raw_content,
|
||||
'content_hash': content_hash(raw_content),
|
||||
})
|
||||
|
||||
# Рекурсивно обходим ссылки
|
||||
for child_slug in extract_slugs_from_content(raw_content):
|
||||
_crawl(child_slug, depth + 1)
|
||||
|
||||
print(f'Начинаем обход wiki, корневые разделы: {root_slugs}')
|
||||
for root in root_slugs:
|
||||
_crawl(root, 0)
|
||||
|
||||
print(f'✓ Обход завершён. Найдено страниц: {len(pages)}')
|
||||
return pages
|
||||
Reference in New Issue
Block a user