amogaddy commited on
Commit
61adc39
·
verified ·
1 Parent(s): aa8f481

Sync aggiornamento

Browse files
Files changed (1) hide show
  1. scraper.py +91 -35
scraper.py CHANGED
@@ -1,69 +1,125 @@
 
 
1
  import trafilatura
2
- from duckduckgo_search import DDGS
3
  from errors import get_logger, GenerAIError, ErrorCode, fmt_exc
4
 
5
  log = get_logger("scraper")
6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
 
8
  def search_and_extract(query: str, max_results: int = 3) -> list[dict]:
9
- """Search DuckDuckGo and extract clean text. Returns [] on total failure."""
10
  log.info("Ricerca web per: %r", query)
11
 
12
- # ── 1. DuckDuckGo search ───────────────────────────────────────────────────
13
- try:
14
- with DDGS() as ddgs:
15
- hits = list(ddgs.text(query, max_results=max_results * 2))
16
- log.debug("DuckDuckGo ha restituito %d risultati grezzi", len(hits))
17
- except Exception as e:
18
- err = GenerAIError(ErrorCode.WEB_SEARCH_FAILED, f"DuckDuckGo non raggiungibile: {fmt_exc(e)}", cause=e)
19
- err.log(log)
20
- return []
21
 
22
  if not hits:
23
- log.warning("[%s] Nessun risultato DuckDuckGo per: %r", ErrorCode.WEB_NO_RESULTS.value, query)
24
  return []
25
 
26
- # ── 2. Fetch + extract each URL ────────────────────────────────────────────
27
  results = []
28
  for hit in hits:
29
  if len(results) >= max_results:
30
  break
31
 
32
- url = hit.get("href", "")
 
 
 
 
33
  if not url:
34
- log.debug("Hit senza URL, saltato: %s", hit)
35
  continue
36
 
37
  log.debug("Fetching: %s", url)
 
38
  try:
39
  downloaded = trafilatura.fetch_url(url, timeout=10)
 
 
 
 
 
 
 
 
40
  except Exception as e:
41
- log.warning("[%s] fetch_url fallito per %s — %s", ErrorCode.WEB_FETCH_FAILED.value, url, fmt_exc(e))
42
- continue
43
 
44
- if not downloaded:
45
- log.debug("fetch_url ha restituito None per: %s", url)
46
- continue
47
-
48
- try:
49
- text = trafilatura.extract(
50
- downloaded,
51
- include_links=False,
52
- include_images=False,
53
- include_tables=False,
54
- no_fallback=False,
55
- )
56
- except Exception as e:
57
- log.warning("[%s] estrazione testo fallita per %s — %s", ErrorCode.WEB_EXTRACT_FAILED.value, url, fmt_exc(e))
58
- continue
59
 
60
- if not text or len(text) < 150:
61
- log.debug("Testo troppo corto (%d chars) per: %s", len(text) if text else 0, url)
62
  continue
63
 
64
  log.info("Estratti %d chars da: %s", len(text), url)
65
  results.append({
66
- "title": hit.get("title", ""),
67
  "url": url,
68
  "text": text[:2000],
69
  })
 
1
+ import os
2
+ import requests
3
  import trafilatura
 
4
  from errors import get_logger, GenerAIError, ErrorCode, fmt_exc
5
 
6
  log = get_logger("scraper")
7
 
8
+ # Istanza SearXNG — configurabile via Secret SEARXNG_URL
9
+ SEARXNG_URL = os.environ.get("SEARXNG_URL", "https://searx.be")
10
+ _active_instance = SEARXNG_URL
11
+
12
+ PUBLIC_INSTANCES = [
13
+ "https://searx.be",
14
+ "https://search.bus-hit.me",
15
+ "https://searxng.site",
16
+ ]
17
+
18
+ # Motori attivi dentro SearXNG — brave, bing (Edge), torch, duckduckgo come fallback
19
+ SEARXNG_ENGINES = os.environ.get("SEARXNG_ENGINES", "brave,bing,torch,duckduckgo")
20
+
21
+ def get_active_instance() -> str:
22
+ return _active_instance
23
+
24
+ def set_active_instance(url: str):
25
+ global _active_instance
26
+ _active_instance = url.rstrip("/")
27
+ log.info("Istanza SearXNG cambiata: %s", _active_instance)
28
+
29
+ def get_engines() -> str:
30
+ return SEARXNG_ENGINES
31
+
32
+ def set_engines(engines: str):
33
+ global SEARXNG_ENGINES
34
+ SEARXNG_ENGINES = engines
35
+ log.info("Motori SearXNG aggiornati: %s", SEARXNG_ENGINES)
36
+
37
+
38
+ def _searxng_search(query: str, max_results: int) -> list[dict]:
39
+ """Cerca con SearXNG usando più motori. Prova più istanze se la prima fallisce."""
40
+ active = get_active_instance()
41
+ instances = [active] + [i for i in PUBLIC_INSTANCES if i != active]
42
+ engines = get_engines()
43
+
44
+ for instance in instances:
45
+ try:
46
+ resp = requests.get(
47
+ f"{instance}/search",
48
+ params={"q": query, "format": "json", "language": "it", "engines": engines},
49
+ timeout=8,
50
+ headers={"User-Agent": "GenerAI/1.0"},
51
+ )
52
+ if resp.status_code == 200:
53
+ data = resp.json()
54
+ hits = data.get("results", [])[:max_results * 2]
55
+ if hits:
56
+ log.info("SearXNG (%s) [%s] → %d risultati", instance, engines, len(hits))
57
+ return hits
58
+ # Nessun risultato con questi motori → riprova senza filtro motori
59
+ resp2 = requests.get(
60
+ f"{instance}/search",
61
+ params={"q": query, "format": "json", "language": "it"},
62
+ timeout=8,
63
+ headers={"User-Agent": "GenerAI/1.0"},
64
+ )
65
+ if resp2.status_code == 200:
66
+ hits2 = resp2.json().get("results", [])[:max_results * 2]
67
+ log.info("SearXNG (%s) [fallback tutti motori] → %d risultati", instance, len(hits2))
68
+ return hits2
69
+ except Exception as e:
70
+ log.warning("SearXNG %s fallita: %s", instance, fmt_exc(e))
71
+ return []
72
+
73
 
74
  def search_and_extract(query: str, max_results: int = 3) -> list[dict]:
75
+ """Cerca con SearXNG ed estrae testo pulito. Returns [] on total failure."""
76
  log.info("Ricerca web per: %r", query)
77
 
78
+ hits = _searxng_search(query, max_results)
 
 
 
 
 
 
 
 
79
 
80
  if not hits:
81
+ log.warning("[%s] Nessun risultato SearXNG per: %r", ErrorCode.WEB_NO_RESULTS.value, query)
82
  return []
83
 
 
84
  results = []
85
  for hit in hits:
86
  if len(results) >= max_results:
87
  break
88
 
89
+ # SearXNG include già uno snippet — usalo se il fetch fallisce
90
+ url = hit.get("url", "")
91
+ title = hit.get("title", "")
92
+ snippet = hit.get("content", "")
93
+
94
  if not url:
 
95
  continue
96
 
97
  log.debug("Fetching: %s", url)
98
+ text = None
99
  try:
100
  downloaded = trafilatura.fetch_url(url, timeout=10)
101
+ if downloaded:
102
+ text = trafilatura.extract(
103
+ downloaded,
104
+ include_links=False,
105
+ include_images=False,
106
+ include_tables=False,
107
+ no_fallback=False,
108
+ )
109
  except Exception as e:
110
+ log.warning("[%s] fetch fallito per %s — %s", ErrorCode.WEB_FETCH_FAILED.value, url, fmt_exc(e))
 
111
 
112
+ # Fallback allo snippet SearXNG se trafilatura non estrae nulla
113
+ if not text or len(text) < 100:
114
+ text = snippet
 
 
 
 
 
 
 
 
 
 
 
 
115
 
116
+ if not text or len(text) < 50:
117
+ log.debug("Testo troppo corto per: %s", url)
118
  continue
119
 
120
  log.info("Estratti %d chars da: %s", len(text), url)
121
  results.append({
122
+ "title": title,
123
  "url": url,
124
  "text": text[:2000],
125
  })