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:
@@ -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")) == ""
|
||||
@@ -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-фолбэка, чтобы не тормозить:
|
||||
|
||||
Reference in New Issue
Block a user