From a49e1a356c1192fbfe272a034d8e4500eecdab41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Mon, 13 Jul 2026 05:16:49 +0300 Subject: [PATCH] =?UTF-8?q?fix(sales-finder):=20=D0=BF=D0=BE=D0=B2=D1=82?= =?UTF-8?q?=D0=BE=D1=80=20=D0=B7=D0=B0=D0=B3=D1=80=D1=83=D0=B7=D0=BA=D0=B8?= =?UTF-8?q?=20=D1=81=D0=B0=D0=B9=D1=82=D0=B0=20=D0=B2=20=D1=88=D0=B0=D0=B3?= =?UTF-8?q?=D0=B5=20=D1=80=D0=B5=D0=BA=D0=BB=D0=B0=D0=BC=D0=B8=D1=80=D1=83?= =?UTF-8?q?=D0=B5=D1=82=D1=81=D1=8F=20=E2=80=94=20=D1=84=D0=BB=D0=B0=D0=BA?= =?UTF-8?q?=20=D0=BD=D0=B5=20=D0=B2=D1=8B=D0=B1=D1=80=D0=B0=D1=81=D1=8B?= =?UTF-8?q?=D0=B2=D0=B0=D0=B5=D1=82=20=D1=80=D0=B5=D0=BA=D0=BB=D0=B0=D0=BC?= =?UTF-8?q?=D0=BE=D0=B4=D0=B0=D1=82=D0=B5=D0=BB=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Проверка advertises лезет на сайт фирмы; загрузка флакует (таймаут, TLS на Windows), не открылся -> advertises=не проверено -> фирма вылетала из списка (drop_not_advertising). Из-за этого улов по городам плясал (Хабаровск 13->8 между прогонами). Новый salesfinder/sitefetch.fetch_site_html: повтор requests (https/http) attempts раз, затем xfetch-рендер. web/app._fetch_site переведён на него. Тесты +6, всего 191 зелёный. Co-Authored-By: Claude Opus 4.8 (1M context) --- моя/sales-finder/salesfinder/sitefetch.py | 29 ++++++++++++++ моя/sales-finder/tests/test_sitefetch.py | 48 +++++++++++++++++++++++ моя/sales-finder/web/app.py | 16 +++----- 3 files changed, 82 insertions(+), 11 deletions(-) create mode 100644 моя/sales-finder/salesfinder/sitefetch.py create mode 100644 моя/sales-finder/tests/test_sitefetch.py diff --git a/моя/sales-finder/salesfinder/sitefetch.py b/моя/sales-finder/salesfinder/sitefetch.py new file mode 100644 index 00000000..db10c49c --- /dev/null +++ b/моя/sales-finder/salesfinder/sitefetch.py @@ -0,0 +1,29 @@ +"""Загрузка HTML сайта фирмы для шага «рекламируется?». Сеть флакует (таймауты, TLS-хендшейк +на Windows), поэтому ПОВТОРЯЕМ: не открылся с первого раза — не значит «не рекламируется». +Раньше не открывшийся сайт давал «не проверено», и фирма молча вылетала из списка.""" + +_UA = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " + "(KHTML, like Gecko) Chrome/124.0 Safari/537.36"} + + +def fetch_site_html(domain, get, render=None, attempts: int = 2, timeout: int = 12) -> str: + """HTML сайта: быстрый `get` (https, потом http), с повтором `attempts` раз; если так и не + вышло — `render` (xfetch-рендер, у него свой внутренний повтор). Пусто = не смогли достать. + + get / render инъектируются (тестируем без сети). Успех = статус <400 И непустой текст.""" + if not domain: + return "" + for _ in range(max(1, attempts)): + for scheme in ("https://", "http://"): + try: + r = get(scheme + domain, headers=_UA, timeout=timeout, allow_redirects=True) + if getattr(r, "status_code", 600) < 400 and getattr(r, "text", ""): + return r.text + except Exception: + pass + if render: + try: + return render(domain) or "" + except Exception: + return "" + return "" diff --git a/моя/sales-finder/tests/test_sitefetch.py b/моя/sales-finder/tests/test_sitefetch.py new file mode 100644 index 00000000..9a401e0f --- /dev/null +++ b/моя/sales-finder/tests/test_sitefetch.py @@ -0,0 +1,48 @@ +"""Загрузка HTML сайта фирмы с повтором — чтобы флак сети не выбрасывал рекламодателя +из списка (шаг «рекламируется?» лезет на сайт; не открылся → «не проверено» → фирма вылетала).""" +from salesfinder.sitefetch import fetch_site_html + + +class R: + def __init__(self, status, text): + self.status_code = status + self.text = text + + +def test_returns_text_on_success(): + got = fetch_site_html("x.ru", get=lambda u, **k: R(200, "ok")) + assert got == "ok" + + +def test_retries_transient_failure_across_attempts(): + calls = {"n": 0} + def get(u, **k): + calls["n"] += 1 + if calls["n"] < 3: # оба схемы в 1-й попытке падают + raise TimeoutError("медленно") + return R(200, "ok") + got = fetch_site_html("x.ru", get=get, attempts=2) + assert got == "ok" and calls["n"] == 3 + + +def test_falls_back_to_render_when_requests_fail(): + def get(u, **k): + raise ConnectionError() + got = fetch_site_html("x.ru", get=get, render=lambda d: "rendered", attempts=2) + assert got == "rendered" + + +def test_error_status_not_success_then_render(): + got = fetch_site_html("x.ru", get=lambda u, **k: R(500, "err"), + render=lambda d: "r") + assert got == "r" + + +def test_empty_when_all_fail_and_no_render(): + def get(u, **k): + raise ConnectionError() + assert fetch_site_html("x.ru", get=get, render=None, attempts=2) == "" + + +def test_empty_domain(): + assert fetch_site_html("", get=lambda u, **k: R(200, "x")) == "" diff --git a/моя/sales-finder/web/app.py b/моя/sales-finder/web/app.py index 70cefbc3..460ae616 100644 --- a/моя/sales-finder/web/app.py +++ b/моя/sales-finder/web/app.py @@ -64,20 +64,14 @@ def _read_secret(name): return "" def _fetch_site(domain): - """HTML сайта фирмы: быстрый requests, при блоке/пустоте — через xfetch (рендер).""" + """HTML сайта фирмы: быстрый requests с ПОВТОРОМ (флак сети не должен выбрасывать + рекламодателя), при полном провале — через xfetch (рендер).""" import requests from salesfinder.xfetch import render as xf_render - ua = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " - "(KHTML, like Gecko) Chrome/124.0 Safari/537.36"} - for scheme in ("https://", "http://"): - try: - r = requests.get(scheme + domain, headers=ua, timeout=12, allow_redirects=True) - if r.status_code < 400 and r.text: - return r.text - except requests.RequestException: - pass + from salesfinder.sitefetch import fetch_site_html key = _read_secret("xfetch_key.txt") - return xf_render("https://" + domain, key) if key else "" + render = (lambda d: xf_render("https://" + d, key)) if key else None + return fetch_site_html(domain, get=requests.get, render=render) def _fetch_site_url(url): """HTML страницы реквизитов — ТОЛЬКО быстрый requests (без xfetch-фолбэка, чтобы не тормозить: