From 20c1d00443515c2e22d4b42f709e221ae9668816 Mon Sep 17 00:00:00 2001 From: jze9 Date: Tue, 15 Sep 2026 13:39:45 +0500 Subject: [PATCH] =?UTF-8?q?fix(pmc):=20=D0=BD=D0=B5=20=D0=B4=D0=B0=D0=B2?= =?UTF-8?q?=D0=B0=D1=82=D1=8C=20=D0=B7=D0=B0=D0=BB=D0=B8=D0=BF=D1=88=D0=B5?= =?UTF-8?q?=D0=BC=D1=83=20=D0=B7=D0=B0=D0=BF=D1=80=D0=BE=D1=81=D1=83=20?= =?UTF-8?q?=D1=81=D1=8A=D0=B5=D1=81=D1=82=D1=8C=20=D0=B2=D0=B5=D1=81=D1=8C?= =?UTF-8?q?=20=D0=B1=D1=8E=D0=B4=D0=B6=D0=B5=D1=82=20=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D0=B3=D0=BE=D0=BD=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Таймаут запроса к бакету был 60с при бюджете таска 1500с — один залипший GET отъедал почти весь прогон. Плюс map() отдаёт результаты строго по порядку отправки, поэтому один медленный запрос блокировал все 11 уже готовых потоков. Таймаут снижен до 12с (обычный GET укладывается в доли секунды, 12с — запас на джиттер), map() заменён на as_completed: готовые результаты отдаются сразу, не дожидаясь залипшего соседа. Co-Authored-By: Claude Opus 4.8 --- scripts/parsers/pmc_bulk.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/scripts/parsers/pmc_bulk.py b/scripts/parsers/pmc_bulk.py index e27819d..c9787ff 100644 --- a/scripts/parsers/pmc_bulk.py +++ b/scripts/parsers/pmc_bulk.py @@ -14,7 +14,7 @@ import logging import re from collections.abc import Iterator -from concurrent.futures import ThreadPoolExecutor +from concurrent.futures import ThreadPoolExecutor, as_completed from typing import Any from urllib.parse import quote @@ -39,8 +39,11 @@ class PMCBulkParser(BaseParser): def __init__(self, workers: int = 12) -> None: super().__init__() self.workers = workers + # 60с на файл при бюджете задачи в 1500с — один залипший запрос съедал + # почти весь бюджет. Обычный GET сюда укладывается в доли секунды, + # 12с — с большим запасом на джиттер, но без риска съесть весь прогон self.client = httpx.Client( - timeout=60, + timeout=12, headers={"User-Agent": "AcademicHelper/1.0 (noreply@jze9.ru)"}, ) # Позиция листинга: бакет отдаётся страницами, и продолжать прогон @@ -82,8 +85,13 @@ class PMCBulkParser(BaseParser): logger.info("PMC bulk: бакет закончился") return + # as_completed вместо map(): map() отдаёт результаты строго по + # порядку отправки, поэтому один залипший запрос блокирует все + # уже готовые — даже если остальные 11 потоков давно отработали with ThreadPoolExecutor(max_workers=self.workers) as pool: - for raw in pool.map(self._fetch_article, ids): + futures = {pool.submit(self._fetch_article, i): i for i in ids} + for future in as_completed(futures): + raw = future.result() if raw is None: continue given += 1