fix(ops): читать дамп Википедии с диска — сетевой поток рвётся на 5.6 ГБ
Wikimedia закрывает долгие потоковые соединения: обрыв пришёлся на 32 МБ из 5.9 ГБ. Скачать файл с докачкой (curl -C -) и читать локально надёжнее, поэтому у скрипта появился --dump-file; чтение из сети осталось запасным путём. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -63,24 +63,10 @@ def clean_wikitext(raw: str) -> str:
|
||||
return text.strip()
|
||||
|
||||
|
||||
def iter_pages(min_chars: int):
|
||||
"""Потоково читать дамп и отдавать статьи основного пространства имён."""
|
||||
import httpx
|
||||
|
||||
decomp = bz2.BZ2Decompressor()
|
||||
def _pages_from_chunks(chunks, min_chars: int):
|
||||
"""Разобрать поток распакованного XML на статьи основного пространства имён."""
|
||||
buf = ""
|
||||
# Wikimedia отдаёт 403 без осмысленного User-Agent — по их правилам он должен
|
||||
# называть приложение и давать контакт
|
||||
headers = {"User-Agent": "AcademicHelper/1.0 (https://academic.jze9.ru; noreply@jze9.ru)"}
|
||||
with httpx.stream("GET", DUMP_URL, timeout=120, follow_redirects=True, headers=headers) as resp:
|
||||
resp.raise_for_status()
|
||||
for chunk in resp.iter_bytes(4 * 1024 * 1024):
|
||||
try:
|
||||
raw = decomp.decompress(chunk)
|
||||
except EOFError:
|
||||
break
|
||||
if not raw:
|
||||
continue
|
||||
for raw in chunks:
|
||||
buf += raw.decode("utf-8", errors="replace")
|
||||
|
||||
while True:
|
||||
@@ -106,6 +92,48 @@ def iter_pages(min_chars: int):
|
||||
buf = buf[-1024 * 1024:]
|
||||
|
||||
|
||||
def iter_pages(min_chars: int, dump_file: str | None):
|
||||
"""Статьи из локального дампа либо, если файла нет, прямо из сети.
|
||||
|
||||
Локальный файл предпочтителен: Wikimedia рвёт долгие потоковые соединения
|
||||
(проверено — обрыв на 32 МБ из 5.9 ГБ), а скачать файл можно с докачкой
|
||||
(`curl -C -`) и потом читать сколько угодно.
|
||||
"""
|
||||
decomp = bz2.BZ2Decompressor()
|
||||
|
||||
if dump_file:
|
||||
def chunks():
|
||||
with open(dump_file, "rb") as fh:
|
||||
while True:
|
||||
part = fh.read(4 * 1024 * 1024)
|
||||
if not part:
|
||||
return
|
||||
try:
|
||||
yield decomp.decompress(part)
|
||||
except EOFError:
|
||||
return
|
||||
yield from _pages_from_chunks(chunks(), min_chars)
|
||||
return
|
||||
|
||||
import httpx
|
||||
|
||||
# Wikimedia отдаёт 403 без осмысленного User-Agent — по их правилам он должен
|
||||
# называть приложение и давать контакт
|
||||
headers = {"User-Agent": "AcademicHelper/1.0 (https://academic.jze9.ru; noreply@jze9.ru)"}
|
||||
|
||||
def net_chunks():
|
||||
with httpx.stream("GET", DUMP_URL, timeout=120, follow_redirects=True,
|
||||
headers=headers) as resp:
|
||||
resp.raise_for_status()
|
||||
for chunk in resp.iter_bytes(4 * 1024 * 1024):
|
||||
try:
|
||||
yield decomp.decompress(chunk)
|
||||
except EOFError:
|
||||
return
|
||||
|
||||
yield from _pages_from_chunks(net_chunks(), min_chars)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
ap = argparse.ArgumentParser(description=__doc__,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
@@ -113,6 +141,8 @@ def main() -> None:
|
||||
ap.add_argument("--batch", type=int, default=500, help="статей в транзакции")
|
||||
ap.add_argument("--min-chars", type=int, default=2000, help="минимальная длина текста")
|
||||
ap.add_argument("--fp-per-doc", type=int, default=2000, help="максимум отпечатков на статью")
|
||||
ap.add_argument("--dump-file", default=None,
|
||||
help="путь к заранее скачанному дампу (надёжнее, чем читать из сети)")
|
||||
args = ap.parse_args()
|
||||
|
||||
import psycopg2
|
||||
@@ -165,7 +195,7 @@ def main() -> None:
|
||||
added += fresh
|
||||
conn.commit()
|
||||
|
||||
for pid, title, text in iter_pages(args.min_chars):
|
||||
for pid, title, text in iter_pages(args.min_chars, args.dump_file):
|
||||
batch.append((pid, title, text))
|
||||
if len(batch) >= args.batch:
|
||||
flush(batch)
|
||||
|
||||
Reference in New Issue
Block a user