fix(sales-finder): повтор загрузки сайта в шаге рекламируется — флак не выбрасывает рекламодателя

Проверка 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) <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-07-13 05:16:49 +03:00
parent fd9358104c
commit a49e1a356c
3 changed files with 82 additions and 11 deletions
@@ -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 ""
@@ -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, "<html>ok</html>"))
assert got == "<html>ok</html>"
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, "<html>ok</html>")
got = fetch_site_html("x.ru", get=get, attempts=2)
assert got == "<html>ok</html>" 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: "<html>rendered</html>", attempts=2)
assert got == "<html>rendered</html>"
def test_error_status_not_success_then_render():
got = fetch_site_html("x.ru", get=lambda u, **k: R(500, "err"),
render=lambda d: "<html>r</html>")
assert got == "<html>r</html>"
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")) == ""
+5 -11
View File
@@ -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-фолбэка, чтобы не тормозить: