perf(ops): начинать листинг PMC после последней залитой статьи
Первый прогон нового конвейера показал слабое место: листинг бакета шёл с начала, и заливка часами перемалывала уже существующие статьи как дубли — из 300 полученных 300 оказались дублями. S3 умеет start-after, и стартовый ключ выводится прямо из базы: ext_id вида `pmc:PMC10000000` соответствует ключу `PMC10000000.1`, бакет отдаётся лексикографически. Теперь при отсутствии сохранённого токена (первый прогон после ручных заливок) листинг начинается после максимального залитого. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -51,6 +51,7 @@ class PMCBulkParser(BaseParser):
|
||||
self,
|
||||
limit: int = 1000,
|
||||
resume_token: str | None = None,
|
||||
start_after: str | None = None,
|
||||
progress_cb: ProgressCallback | None = None,
|
||||
**_ignored: Any,
|
||||
) -> Iterator[dict[str, Any]]:
|
||||
@@ -59,6 +60,10 @@ class PMCBulkParser(BaseParser):
|
||||
Args:
|
||||
limit: сколько статей отдать
|
||||
resume_token: продолжить листинг с сохранённой страницы бакета
|
||||
start_after: ключ, после которого начинать листинг. Нужен, когда
|
||||
токена нет (первый прогон после ручных заливок): без него
|
||||
листинг пошёл бы с начала бакета и часами перемалывал уже
|
||||
залитые статьи как дубли
|
||||
progress_cb: см. base.ProgressCallback
|
||||
"""
|
||||
token = resume_token
|
||||
@@ -66,7 +71,10 @@ class PMCBulkParser(BaseParser):
|
||||
|
||||
while given < limit:
|
||||
try:
|
||||
ids, token = self._list_page(token, want=min(200, limit - given))
|
||||
ids, token = self._list_page(
|
||||
token, want=min(200, limit - given),
|
||||
start_after=None if token else start_after,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error("PMC bulk: листинг не удался: %s", e)
|
||||
return
|
||||
@@ -91,12 +99,16 @@ class PMCBulkParser(BaseParser):
|
||||
if token is None:
|
||||
return
|
||||
|
||||
def _list_page(self, token: str | None, want: int) -> tuple[list[str], str | None]:
|
||||
def _list_page(
|
||||
self, token: str | None, want: int, start_after: str | None = None
|
||||
) -> tuple[list[str], str | None]:
|
||||
"""Одна страница листинга бакета: id статей и токен следующей страницы."""
|
||||
url = f"{BUCKET}/?list-type=2&max-keys=1000"
|
||||
if token:
|
||||
# В токене бывают + и / — без экранирования S3 отвечает 400
|
||||
url += f"&continuation-token={quote(token, safe='')}"
|
||||
elif start_after:
|
||||
url += f"&start-after={quote(start_after, safe='')}"
|
||||
resp = self.client.get(url)
|
||||
resp.raise_for_status()
|
||||
ids = KEY_RE.findall(resp.text)[:want]
|
||||
|
||||
Reference in New Issue
Block a user