fix(pegas-memory): web fetch SOCKS5h proxy fallback + stale_guard supported double-count fix
- ingest.parse_web: direct fetch; on block/timeout retry via socks5h://127.0.0.1:1080 (SOCKS_FALLBACK_PROXY); raises ValueError if both fail. - config: add SOCKS_FALLBACK_PROXY default (socks5h://127.0.0.1:1080). - self_improve.stale_guard: supported LLM verdict now sets status_change='supported' (authoritative), no fallthrough to age fallback -> no double-checked/skipped_no_source. - README: document auto-fallback.
This commit is contained in:
1 parent
eca8a11e23
commit
13e0b87446
4 files changed
+44
-15
No files matched your search
@@ -76,7 +76,10 @@ Cloudflare anti-bot (neo4j.com — 403). Поведение research-цикла:
|
|||||||
```bash
|
```bash
|
||||||
export PROXY_URL="socks5://127.0.0.1:1080" # hysteria2
|
export PROXY_URL="socks5://127.0.0.1:1080" # hysteria2
|
||||||
```
|
```
|
||||||
Когда прокси задан, `parse_web` ходит через него; иначе — напрямую.
|
|
||||||
|
**Авто-повор:** `parse_web` сначала грузит URL напрямую; при таймауте/блокировке повторяет запрос через `socks5h://127.0.0.1:1080` (дефолт,
|
||||||
|
`SOCKS_FALLBACK_PROXY`). Если и проки недоступен — падает с
|
||||||
|
`ValueError: no main content ... (direct + ... fallback failed)`.
|
||||||
Поддержка SOCKS5 уже установлена (`httpx[socks]`).
|
Поддержка SOCKS5 уже установлена (`httpx[socks]`).
|
||||||
|
|
||||||
## Управление пробелами (gaps)
|
## Управление пробелами (gaps)
|
||||||
|
|||||||
@@ -19,5 +19,6 @@ ATOM_STATUSES = ("candidate", "verified", "stale", "contested")
|
|||||||
|
|
||||||
DEDUP_THRESHOLD = float(os.getenv("DEDUP_THRESHOLD", "0.90")) # порог семантической дедупликации
|
DEDUP_THRESHOLD = float(os.getenv("DEDUP_THRESHOLD", "0.90")) # порог семантической дедупликации
|
||||||
|
|
||||||
PROXY_URL = os.getenv("PROXY_URL", "") # опционально: socks5://127.0.0.1:1080 или http://...
|
PROXY_URL = os.getenv("PROXY_URL", "") # явный прокси: используется всегда (socks5://… или http://…)
|
||||||
|
SOCKS_FALLBACK_PROXY = os.getenv("SOCKS_FALLBACK_PROXY", "socks5h://127.0.0.1:1080") # авто-повор через прокси при ошибке прямого запроса
|
||||||
USER_AGENT = os.getenv("USER_AGENT", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36")
|
USER_AGENT = os.getenv("USER_AGENT", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36")
|
||||||
+36
-13
@@ -59,26 +59,49 @@ def parse_epub(path: str) -> ParsedDoc:
|
|||||||
doc.kind = "epub"
|
doc.kind = "epub"
|
||||||
return doc
|
return doc
|
||||||
|
|
||||||
def _http_client():
|
def _http_client(proxy=None, timeout: int = 30):
|
||||||
"""httpx.Client с опциональным прокси и единым User-Agent."""
|
"""httpx.Client с единым User-Agent.
|
||||||
|
|
||||||
|
proxy: явный прокси (None = прямой запрос). Если проси не передан, но
|
||||||
|
config.PROXY_URL задан — используется он.
|
||||||
|
"""
|
||||||
import httpx
|
import httpx
|
||||||
from . import config
|
from . import config
|
||||||
kwargs = dict(timeout=30, follow_redirects=True,
|
kwargs = dict(timeout=timeout, follow_redirects=True,
|
||||||
headers={"User-Agent": config.USER_AGENT})
|
headers={"User-Agent": config.USER_AGENT})
|
||||||
if config.PROXY_URL:
|
use_proxy = proxy or (config.PROXY_URL or None)
|
||||||
kwargs["proxy"] = config.PROXY_URL
|
if use_proxy:
|
||||||
|
kwargs["proxy"] = use_proxy
|
||||||
return httpx.Client(**kwargs)
|
return httpx.Client(**kwargs)
|
||||||
|
|
||||||
def parse_web(url: str) -> ParsedDoc:
|
def _fetch_text(url: str, proxy: str | None = None) -> str | None:
|
||||||
|
"""Fetch + extract main content. Tries direct; on error/empty retries `proxy`.
|
||||||
|
|
||||||
|
trafilatura.extract has no timeout -> fetch via httpx with timeout.
|
||||||
|
"""
|
||||||
import trafilatura
|
import trafilatura
|
||||||
# fetch через httpx с таймаутом + прокси (trafilatura.fetch_url не имеет таймаута и виснет)
|
for _lbl, _prx in (("direct", None), ("proxy", proxy)):
|
||||||
with _http_client() as client:
|
try:
|
||||||
r = client.get(url)
|
with _http_client(proxy=_prx, timeout=30) as client:
|
||||||
r.raise_for_status()
|
r = client.get(url)
|
||||||
text = trafilatura.extract(r.text, include_comments=False, include_tables=True,
|
r.raise_for_status()
|
||||||
url=url)
|
text = trafilatura.extract(r.text, include_comments=False,
|
||||||
|
include_tables=True, url=url)
|
||||||
|
if text:
|
||||||
|
return text
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
return None
|
||||||
|
|
||||||
|
def parse_web(url: str) -> ParsedDoc:
|
||||||
|
from . import config
|
||||||
|
# direct fetch + socks5h fallback on block/timeout
|
||||||
|
text = _fetch_text(url, proxy=config.SOCKS_FALLBACK_PROXY)
|
||||||
if not text:
|
if not text:
|
||||||
raise ValueError(f"trafilatura: no main content for {url}")
|
raise ValueError(
|
||||||
|
f"no main content for {url} "
|
||||||
|
f"(direct + {config.SOCKS_FALLBACK_PROXY} fallback failed)"
|
||||||
|
)
|
||||||
doc = parse_text(text, uri=url, title=url)
|
doc = parse_text(text, uri=url, title=url)
|
||||||
doc.kind = "web"
|
doc.kind = "web"
|
||||||
return doc
|
return doc
|
||||||
@@ -198,6 +198,8 @@ def stale_guard(conn, *, atom_id=None, all: bool = False, limit: int = 20,
|
|||||||
status_change = "stale"
|
status_change = "stale"
|
||||||
reason = f"source_{v['verdict']}: {v.get('reason', '')}"
|
reason = f"source_{v['verdict']}: {v.get('reason', '')}"
|
||||||
else:
|
else:
|
||||||
|
# content-staleness re-fetch succeeded -> verdict is authoritative
|
||||||
|
status_change = "supported"
|
||||||
reason = "supported"
|
reason = "supported"
|
||||||
if status_change is None:
|
if status_change is None:
|
||||||
# proxy-free fallback: возраст контента
|
# proxy-free fallback: возраст контента
|
||||||
|
|||||||
Reference in new issue
Block a user