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)",