Files
anti-plagiarism/docs/INGESTION.md
jze9 06363c7401
Some checks failed
Deploy / deploy (push) Has been cancelled
Deploy / test (push) Has been cancelled
feat(ops): заливка русской Википедии — единственный доступный источник объёма
У корпуса катастрофически мало пригодного русского текста: 99 883 документа
КиберЛенинки лежат со средней глубиной 30 отпечатков, то есть заголовок с
аннотацией. Проверил все русскоязычные источники, о которых имело смысл думать:

- Википедия ru — работает: дамп 5.6 ГБ читается потоком (на app-хосте всего
  22 ГБ свободно, поэтому без сохранения на диск), ~919 отпечатков на статью.
  Wikimedia отдаёт 403 без осмысленного User-Agent — учтено;
- КиберЛенинка — блокирует выкачку после ~130 запросов (замер 28.08);
- OpenAlex language:ru + OA — заявлено 395 410 работ, но по прямым pdf_url
  скачалось 2 из 10, остальное 403 издателей; метка языка ненадёжна — в выдаче
  англоязычные журналы. Массово не годится;
- eLIBRARY.RU — только по договору, Math-Net.Ru — 403 на архиве.

Википедия формально не научный источник, но студенты копируют из неё чаще, чем
из статей, а по объёму связного русского текста альтернатив среди доступного нет.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 20:53:10 +05:00

14 KiB
Raw Blame History

Наполнение корпуса — runbook

Текущее состояние (на 2026-08-31)

  • 177 247 документов, русский большинство: ru 99 884, en 77 036, остальные языки — единицы/десятки. Проблема «не с чем сравнивать русские работы» из более ранней версии этого документа закрыта.
  • По источникам: CyberLeninka 99 883, OpenAlex 49 276, PMC 15 010, arXiv 13 077, user_submission (проверенные пользователями работы, не источник для сравнения сами с собой — см. ARCHITECTURE.md §7) 1.
  • Повторный прогон по уже залитым источникам даёт почти одни дубли (типично 1490 из 1500 на источник): прирост дают только новые публикации. Реальный рост корпуса — поднятый limit или новые темы, а не повторный запуск.
  • OpenAlex при массовом запуске упирается в лимит вежливого пула (10 req/s на mailto, общий для всех воркеров) — пауза между страницами поднята до 1с.
  • Добавлен 4-й парсер — PMC (PubMed Central, scripts/parsers/pmc.py), англоязычные научные статьи открытого доступа.
  • Массовое расширение по дисциплинам теперь двумя сидерами: seed_ru_sources.py (русскоязычные, CyberLeninka/OpenAlex lang=ru) и scripts/seed_broad_corpus.py (англоязычные — OpenAlex/arXiv/PMC по широкому списку дисциплин, добавлен позже первой волны).
  • Операционный риск массовой докачки (consumer_timeout RabbitMQ vs долгие таски) закрыт бюджетом времени прогона и worker_prefetch_multiplier=1 — подробности и почему префетч тут главный, см. DR-HA.md §6.

Глубина индексации — чем реально располагает детекция (замер 2026-08-28)

Мерить глубину по minio_key (сохранён ли текст в MinIO) нельзя: PMC, например, кладёт тело статьи в отпечатки прямо при заливке, не сохраняя файл. Честный показатель — число отпечатков на документ: аннотация даёт десятки, полный текст — тысячи.

Источник Документов Глубоко (≥500 отпечатков) Среднее отпечатков
КиберЛенинка 99 883 0 (0.0%) 30
OpenAlex 49 276 6 240 (12.7%) 907
PMC 14 910 14 519 (97.4%) 2 208
arXiv 13 077 12 236 (93.6%) 4 035

Главная слабость — русская часть корпуса. 56% базы (КиберЛенинка) в индексе представлено заголовком и аннотацией: списывание из тела русской статьи L1 не найдёт, хотя сервис рассчитан именно на русских студентов. Причина не в алгоритме: search API отдаёт только аннотацию и OCR-фрагмент (~700 символов), а url ведёт на HTML-страницу — докачка по нему бесполезна (замер: 0 из 8). Частично лечится scripts/ops/backfill_cyberleninka_pdf.py: PDF доступен прямым адресом {url}/pdf (проверено: 44 из 50, в среднем 23 тыс. символов, глубина 30 → ~1450 отпечатков).

Но массовый прогон упирается в защиту сайта. Замер 2026-08-28: первые ~130 статей скачались штатно, дальше КиберЛенинка перестала отдавать PDF и начала возвращать HTML-заглушку ~5.7 КБ — счётчик успехов замер на 122, скрипт работал вхолостую. Расчёт «48 часов на 100 тысяч при 1 req/s» этим опровергнут. Чтобы углубить русскую часть корпуса, нужен другой подход: заметно большие паузы, разные исходящие адреса или договорённость с источником.

Покрытие L3: 60 993 документа числились векторизованными ошибочно — отметки сброшены faiss_reconcile.py, после чего 31.08 запущен пересчёт всех 84 094 документов без вектора. Считает новый сервер эмбеддингов (192.168.1.40, 3.6 док/с против 2.2 у прежнего), полный проход занимает около 6.5 часов.

Источники русского текста — что проверено (31.08.2026)

Источник Объём Годен для массовой заливки
Википедия ru дамп 5.6 ГБ, ~2 млн статей да — bulk_ingest_wikipedia_ru.py, потоком без блокировок, ~919 отпечатков на статью. Требует осмысленный User-Agent, иначе 403
КиберЛенинка ~3 млн статей нет — блокирует выкачку PDF после ~130 запросов
OpenAlex language:ru + OA заявлено 395 410 практически нет — по прямым pdf_url скачалось 2 из 10 (остальное 403 издателей), а метка языка ненадёжна: в выдаче попадаются англоязычные журналы
eLIBRARY.RU (РИНЦ) ~40 млн только по договору, публичной выгрузки нет
Math-Net.Ru российские матжурналы архив отдаёт 403
НЭБ, BASE, вузовские DSpace — открыты, механизм выгрузки не проверялся

Википедия формально не научный источник, но студенты копируют из неё чаще всего, а по объёму связного русского текста ей нет альтернативы среди доступного.

Массовая заливка: bulk вместо API

Постраничные API дают 1-2 статьи в секунду и упираются в rate limit — миллионы так не залить. Для объёма есть scripts/ops/bulk_ingest_pmc.py: открытый бакет pmc-oa-opendata (ключ не нужен) отдаёт у каждой статьи готовый извлечённый текст. Проверено 31.08: 100 статей, 183 090 отпечатков — 1831 на статью, то есть настоящая глубина, а не аннотация.

Ограничение по темпу: 0.3 статьи/с, миллион в один поток — около 40 суток. Скачивание тут не узкое место (12 статей/с в 12 потоков), упирается в последовательную обработку документа; для миллионов её надо распараллелить по ядрам воркера. Планировать объём: 1 млн статей ≈ 2.5 млрд отпечатков ≈ 400 ГБ в базе с индексами (сейчас 113 млн строк занимают 12 ГБ).

Что подготовлено

  • Парсер CyberLeninka починен (scripts/parsers/cyberleninka.py): раньше слал GET на /api/search → HTTP 405; теперь POST с JSON-телом (mode=articles), authors из списка, чистка <b>/HTML-сущностей. Проверено вживую (5/5 тем) + юнит-тесты (scripts/parsers/tests/), в гейте CI.
  • Прод-путь готов: index.run_parser(source_id) уже умеет cyberleninka и openalex c lang=ru.
  • Сидер источников: scripts/seed_ru_sources.py — 30 студенческих дисциплин.

Запуск русской заливки

# 1. (на app-хосте / в контейнере worker-indexer, где есть psycopg2 и прод-.env
#    — .env на проде теперь генерируется из Infisical на каждом деплое, см.
#    ARCHITECTURE.md §10, руками его не редактировать)
#    Посмотреть план:
python scripts/seed_ru_sources.py
#    Создать источники в parse_sources (лимит на дисциплину):
python scripts/seed_ru_sources.py --apply --limit 500

# 2. Проверить пару источников на темпе/качестве, затем запустить заливку:
#    • Админ-панель → «Источники» → «Запустить» (или «Запустить всё»), ЛИБО
#    • Celery: index.run_parser.delay(source_id) по каждому id

Управление заливкой из админки

Всё, что ниже, доступно на странице «Источники» — CLI для этого больше не нужен:

  • Шкала загрузки у каждого источника: стадия (выборка → индексация), сколько получено из скольки, сколько добавлено/дублей/ошибок. Раскрытая строка — журнал прогона по шагам с таймингами (таблица parse_runs).
  • «Запустить всё» — прогон по всем включённым источникам; уже идущие пропускаются. «Остановить всё» и остановка по одному — кооперативная отмена: воркер останавливается сам на ближайшем тике, не обрывая запись в базу.
  • Пакетное добавление — один тип источника + список тем (по строке), опционально с немедленным запуском. Заменяет seed_*.py для разовых расширений.
  • Загрузка работ в базу — PDF/DOCX/TXT прямо в корпус сравнения (index.ingest_upload, source=manual_upload), минуя проверку и отстойник.
  • Страница «Отладка» — очереди, воркеры, покрытие эмбеддингами, зависшие и упавшие прогоны (см. ARCHITECTURE.md §12).

После большой заливки — свериться с покрытием L3 (панель отладки, «Векторов в индексе»). Порядок именно такой, из двух шагов:

  1. scripts/ops/faiss_reconcile.py (в контейнере worker-gpu) — сверяет отметки faiss_id с реальным содержимым индекса и обнуляет ложные. Без этого шага документы, потерявшие вектор при пересоздании индекса, не попадут на пересчёт: они всё ещё «отмечены».
  2. scripts/ops/reembed_missing.py (в контейнере worker-indexer) — отправляет gpu.embed_documents для всех faiss_id IS NULL.

Оба по умолчанию dry-run, отправляют/меняют только с --apply.

Прогон со статусом partial — это не ошибка: сработал бюджет времени (PARSER_TIME_BUDGET_S, 1500с), заливка остановилась раньше consumer_timeout RabbitMQ. Остаток добирается повторным запуском источника.

Заливка сама: fetch (rate-limit 1 req/s) → add_document (дедуп по ext_id, fingerprints L1, MinHash L2) → батч-эмбеддинги gpu.embed_documents (L3). ~30 тем × 500 ≈ 15K русских документов на первый заход.

Проверка результата

SELECT lang, count(*) FROM documents GROUP BY lang ORDER BY 2 DESC;   -- должен появиться ru
SELECT source, count(*) FROM documents WHERE source='cyberleninka';   -- > 0

Масштаб (следующий уровень)

  • Больше тем + выше --limit; добавить OpenAlex lang=ru (качество ниже — англ. заголовки с меткой ru).
  • Для миллионов — bulk, а не постраничный API. Снапшот OpenAlex для этого не годится: там только метаданные, а миллионы аннотаций детекции не дают (см. провал КиберЛенинки выше). Рабочий источник полных текстов — бакет pmc-oa-opendata, см. «Массовая заливка» выше.
  • На масштабе обязателен VECTOR_BACKEND=qdrant (FAISS flat не тянет), а таблица fingerprints (уже ~113M строк на 177K доков — партиционирование стоит планировать заранее, не постфактум) потребует партиционирования. См. ARCHITECTURE.md и DR-HA.md.