From 9123844a87e0e1bf27c9d90004c9eefcdbd6ecd6 Mon Sep 17 00:00:00 2001 From: jze9 Date: Wed, 16 Sep 2026 18:34:14 +0500 Subject: [PATCH] =?UTF-8?q?fix(ops):=20=D0=BD=D0=B5=20=D1=85=D0=BE=D1=80?= =?UTF-8?q?=D0=BE=D0=BD=D0=B8=D1=82=D1=8C=20=D0=B8=D1=81=D1=82=D0=BE=D1=87?= =?UTF-8?q?=D0=BD=D0=B8=D0=BA=20=D0=BF=D0=BE=D1=81=D0=BB=D0=B5=20=D0=BE?= =?UTF-8?q?=D0=B4=D0=BD=D0=BE=D0=B3=D0=BE=20=D0=BF=D1=83=D1=81=D1=82=D0=BE?= =?UTF-8?q?=D0=B3=D0=BE=20=D0=BF=D1=80=D0=BE=D0=B3=D0=BE=D0=BD=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Сторож считал источник исчерпанным, если прошлый прогон не добавил и не выбрал ни одной статьи. Но ровно так же выглядит обрыв связи с API и прогон на устаревшем коде парсера: сегодня CORE после единственной неудачи был помечен исчерпанным и больше не запускался — молча, без ошибки в интерфейсе. Теперь нужно два пустых прогона подряд. Разовый сбой переживём, а реально кончившийся источник остановится всего на один прогон позже. Заодно таймаут запроса к CORE снижен с 60 до 30 секунд: три попытки по минуте отъедали 180 секунд из 1500 бюджета на одной залипшей странице — та же грабля, что чинили в pmc_bulk. Обычный ответ приходит за 7 секунд. Co-Authored-By: Claude Opus 5 --- scripts/ops/keep_ingesting.py | 23 ++++++++++++++++++----- scripts/parsers/core.py | 8 +++++--- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/scripts/ops/keep_ingesting.py b/scripts/ops/keep_ingesting.py index bdc8fcd..f738568 100755 --- a/scripts/ops/keep_ingesting.py +++ b/scripts/ops/keep_ingesting.py @@ -115,12 +115,25 @@ def main() -> None: started = [] for source_id, source_type, last_run_id in sources: - # Источник исчерпан, если прошлый прогон не добавил ни одной статьи - # и не был отменён: дальше в дампе/бакете для нас ничего нет + # Источник исчерпан, если прогоны перестали добавлять статьи: дальше + # в дампе/бакете/выдаче для нас ничего нет. + # + # Но ОДИН пустой прогон — ещё не приговор: ровно так же выглядят + # обрыв связи с API и прогон на устаревшем коде парсера. На CORE + # 16.09.2026 из-за этого источник был помечен исчерпанным после + # единственной неудачи и больше не запускался — молча, без ошибки. + # Поэтому ждём два пустых прогона подряд: разовый сбой переживём, + # а реально кончившийся источник остановится всего на один прогон + # позже. if last_run_id and not args.keep_going_empty: - prev = s.get(ParseRun, last_run_id) - if prev and prev.status == "done" and prev.added == 0 and prev.fetched == 0: - print(f"{source_type}: источник исчерпан, пропускаем") + last_two = s.execute(text(""" + SELECT status, added, fetched FROM parse_runs + WHERE source_id = :sid ORDER BY id DESC LIMIT 2 + """), {"sid": source_id}).all() + if len(last_two) == 2 and all( + r.status == "done" and r.added == 0 and r.fetched == 0 for r in last_two + ): + print(f"{source_type}: источник исчерпан (два пустых прогона), пропускаем") continue src = s.get(ParseSource, source_id) diff --git a/scripts/parsers/core.py b/scripts/parsers/core.py index db77e74..44a9d0c 100644 --- a/scripts/parsers/core.py +++ b/scripts/parsers/core.py @@ -75,10 +75,12 @@ class COREParser(BaseParser): self.api_key = api_key or os.environ.get("CORE_API_KEY", "") if not self.api_key: logger.warning("CORE: ключ не задан (CORE_API_KEY) — API ответит 401") - # Страница весит ~5 МБ, 12с как у PMC тут мало: обычный ответ ~7с. - # 60с — с запасом на джиттер, но без риска съесть весь бюджет прогона + # Страница весит ~5 МБ, обычный ответ ~7с. 30с — запас на джиттер в + # десять раз, но без риска съесть бюджет прогона: три попытки по 60с + # отъедали 180с из 1500с на одной залипшей странице (поймано 16.09.2026, + # та же грабля, что чинили в pmc_bulk) self.client = httpx.Client( - timeout=60, + timeout=30, headers={ "Authorization": f"Bearer {self.api_key}", "User-Agent": "AcademicHelper/1.0 (noreply@jze9.ru)",