fix(ops): не хоронить источник после одного пустого прогона
Сторож считал источник исчерпанным, если прошлый прогон не добавил и не выбрал ни одной статьи. Но ровно так же выглядит обрыв связи с API и прогон на устаревшем коде парсера: сегодня CORE после единственной неудачи был помечен исчерпанным и больше не запускался — молча, без ошибки в интерфейсе. Теперь нужно два пустых прогона подряд. Разовый сбой переживём, а реально кончившийся источник остановится всего на один прогон позже. Заодно таймаут запроса к CORE снижен с 60 до 30 секунд: три попытки по минуте отъедали 180 секунд из 1500 бюджета на одной залипшей странице — та же грабля, что чинили в pmc_bulk. Обычный ответ приходит за 7 секунд. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -115,12 +115,25 @@ def main() -> None:
|
|||||||
|
|
||||||
started = []
|
started = []
|
||||||
for source_id, source_type, last_run_id in sources:
|
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:
|
if last_run_id and not args.keep_going_empty:
|
||||||
prev = s.get(ParseRun, last_run_id)
|
last_two = s.execute(text("""
|
||||||
if prev and prev.status == "done" and prev.added == 0 and prev.fetched == 0:
|
SELECT status, added, fetched FROM parse_runs
|
||||||
print(f"{source_type}: источник исчерпан, пропускаем")
|
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
|
continue
|
||||||
|
|
||||||
src = s.get(ParseSource, source_id)
|
src = s.get(ParseSource, source_id)
|
||||||
|
|||||||
@@ -75,10 +75,12 @@ class COREParser(BaseParser):
|
|||||||
self.api_key = api_key or os.environ.get("CORE_API_KEY", "")
|
self.api_key = api_key or os.environ.get("CORE_API_KEY", "")
|
||||||
if not self.api_key:
|
if not self.api_key:
|
||||||
logger.warning("CORE: ключ не задан (CORE_API_KEY) — API ответит 401")
|
logger.warning("CORE: ключ не задан (CORE_API_KEY) — API ответит 401")
|
||||||
# Страница весит ~5 МБ, 12с как у PMC тут мало: обычный ответ ~7с.
|
# Страница весит ~5 МБ, обычный ответ ~7с. 30с — запас на джиттер в
|
||||||
# 60с — с запасом на джиттер, но без риска съесть весь бюджет прогона
|
# десять раз, но без риска съесть бюджет прогона: три попытки по 60с
|
||||||
|
# отъедали 180с из 1500с на одной залипшей странице (поймано 16.09.2026,
|
||||||
|
# та же грабля, что чинили в pmc_bulk)
|
||||||
self.client = httpx.Client(
|
self.client = httpx.Client(
|
||||||
timeout=60,
|
timeout=30,
|
||||||
headers={
|
headers={
|
||||||
"Authorization": f"Bearer {self.api_key}",
|
"Authorization": f"Bearer {self.api_key}",
|
||||||
"User-Agent": "AcademicHelper/1.0 (noreply@jze9.ru)",
|
"User-Agent": "AcademicHelper/1.0 (noreply@jze9.ru)",
|
||||||
|
|||||||
Reference in New Issue
Block a user