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