fix: audit findings — edge cases in paginate, output, run

- paginate.py: max(nums) on empty set → guard
- paginate.py: HEAD fallback to GET if server doesn't support HEAD
- output.py: disk health check every 500 topics
- run.py: avoid page-1 extra redirect
- __main__.py: URL validation (must contain vwts.ru)
This commit is contained in:
2026-06-04 08:58:09 +03:00
parent 010b10c43e
commit 1cfa6d1595
4 changed files with 32 additions and 13 deletions
+2 -2
View File
@@ -25,8 +25,8 @@ logging.basicConfig(
)
if __name__ == "__main__":
if len(sys.argv) < 2:
print("❌ Укажи URL раздела. Пример:")
if len(sys.argv) < 2 or "vwts.ru" not in sys.argv[1]:
print("❌ Укажи URL раздела vwts.ru. Пример:")
print(" python -m vwts_scraper "
"https://vwts.ru/forum/vag/benzinovye-dvigateli/")
sys.exit(1)
+8
View File
@@ -39,3 +39,11 @@ def save_topic(section_slug: str, section_name: str, topic: dict,
st["topic_count"] += 1
log.info(f" 💾 part_{st['file_index']:04d}.jsonl "
f"({fpath.stat().st_size/1024/1024:.1f} MB)")
if st["topic_count"] % 500 == 0:
# Каждые 500 тем — проверяем что диск ещё жив
try:
fpath.stat()
except OSError as e:
log.critical(f"❌ Ошибка диска: {e}")
raise
+20 -10
View File
@@ -44,7 +44,20 @@ def count(soup: BeautifulSoup) -> int:
except ValueError:
pass # "…", "Назад", "Вперёд"
return max(nums) if nums else 1
return max(nums) if nums else 1 # защита от пустого pageNav
def _head_or_get(session, url: str, timeout=(10, 30)) -> int:
"""HEAD-запрос с fallback на GET (не все серверы поддерживают HEAD)."""
try:
r = session.head(url, timeout=timeout, allow_redirects=True)
if r.status_code in (405, 501): # HEAD не поддерживается
r = session.get(url, timeout=timeout,
allow_redirects=True, stream=True)
r.close()
return r.status_code
except Exception:
return 500
def verify_last(section_url: str, claimed: int, session) -> int:
@@ -57,19 +70,16 @@ def verify_last(section_url: str, claimed: int, session) -> int:
return claimed
try:
import requests
r = session.head(f"{section_url}page-{claimed}",
timeout=(10, 30), allow_redirects=True)
if r.status_code < 400:
status = _head_or_get(session, f"{section_url}page-{claimed}")
if status < 400:
return claimed
# Бинарный поиск
# Бинарный поиск последней существующей страницы
lo, hi = 1, claimed
while lo < hi:
mid = (lo + hi + 1) // 2
hr = session.head(f"{section_url}page-{mid}",
timeout=(10, 30), allow_redirects=True)
if hr.status_code >= 400:
hr = _head_or_get(session, f"{section_url}page-{mid}")
if hr >= 400:
hi = mid - 1
else:
lo = mid
@@ -78,4 +88,4 @@ def verify_last(section_url: str, claimed: int, session) -> int:
return lo
except Exception:
return claimed # не смогли — верим сайту
return claimed
+2 -1
View File
@@ -48,7 +48,8 @@ def run(section_url: str):
consecutive_404 = 0
continue
url = f"{section_url}page-{pg}"
# page-1 = базовый URL (чтобы не было лишнего редиректа)
url = section_url if pg == 1 else f"{section_url}page-{pg}"
log.info(f"📄 [{pg}/{total_pages}]")
page_soup = get(url)