BlogWriter / pipeline /searxng_client.py
vivekchakraverty's picture
Add grounded blog-post generator pipeline
31fa536
Raw
History Blame Contribute Delete
2.69 kB
"""Step 2: query the in-container SearXNG JSON API and aggregate results by domain."""
from __future__ import annotations
from typing import Dict, List
from urllib.parse import urlparse
import requests
import tldextract
from . import config
def registrable_domain(url: str) -> str:
"""Return the registrable domain (e.g. 'blog.example.co.uk' -> 'example.co.uk')."""
ext = tldextract.extract(url)
if ext.domain and ext.suffix:
return f"{ext.domain}.{ext.suffix}".lower()
return (urlparse(url).netloc or url).lower()
def _search_one(term: str) -> List[dict]:
params = {"q": term, "format": "json", "safesearch": "1", "language": "en"}
try:
r = requests.get(
f"{config.SEARXNG_URL}/search",
params=params,
timeout=config.HTTP_TIMEOUT,
headers={"User-Agent": "BlogPostGenerator/1.0"},
)
r.raise_for_status()
return r.json().get("results", []) or []
except Exception:
return []
def search(terms: List[str], top_n: int = config.TOP_N) -> List[dict]:
"""Run all queries, aggregate scores per domain, and return the top-N domains.
Each returned item: {domain, url, title, snippet, score}. One representative URL
(the highest-scoring) is kept per registrable domain so OpenPageRank ranks domains.
"""
by_domain: Dict[str, dict] = {}
for term in terms:
for res in _search_one(term):
url = res.get("url")
if not url or not url.startswith("http"):
continue
dom = registrable_domain(url)
if not dom:
continue
score = float(res.get("score") or 0.0)
entry = by_domain.get(dom)
if entry is None:
by_domain[dom] = {
"domain": dom,
"url": url,
"title": res.get("title") or "",
"snippet": res.get("content") or "",
"score": score,
"_best": score, # highest single-result score seen for this domain
}
else:
entry["score"] += score
# keep the single best-scoring page as the domain's representative URL
if score > entry["_best"]:
entry["_best"] = score
entry["url"] = url
entry["title"] = res.get("title") or entry["title"]
entry["snippet"] = res.get("content") or entry["snippet"]
ranked = sorted(by_domain.values(), key=lambda x: x["score"], reverse=True)
for e in ranked:
e.pop("_best", None)
return ranked[:top_n]