Spaces:
Runtime error
Runtime error
Delete server/internet_agent.py
Browse files- server/internet_agent.py +0 -259
server/internet_agent.py
DELETED
|
@@ -1,259 +0,0 @@
|
|
| 1 |
-
"""Бесплатный интернет-агент с минимум 3 способами поиска"""
|
| 2 |
-
|
| 3 |
-
import re
|
| 4 |
-
import requests
|
| 5 |
-
import hashlib
|
| 6 |
-
from datetime import datetime, timedelta
|
| 7 |
-
from typing import Dict, List, Tuple, Any, Optional
|
| 8 |
-
from collections import Counter
|
| 9 |
-
|
| 10 |
-
class FreeInternetAgent:
|
| 11 |
-
"""Интернет-агент с бесплатными поисковыми системами (минимум 3 способа)"""
|
| 12 |
-
|
| 13 |
-
def __init__(self, cache_ttl: int = 3600):
|
| 14 |
-
self.session = requests.Session()
|
| 15 |
-
self.session.headers.update({
|
| 16 |
-
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
|
| 17 |
-
})
|
| 18 |
-
self.cache: Dict[str, Tuple[Any, datetime]] = {}
|
| 19 |
-
self.cache_ttl = cache_ttl
|
| 20 |
-
self._has_bs4 = False
|
| 21 |
-
try:
|
| 22 |
-
import bs4
|
| 23 |
-
self._has_bs4 = True
|
| 24 |
-
except ImportError:
|
| 25 |
-
pass
|
| 26 |
-
|
| 27 |
-
# SearXNG инстансы
|
| 28 |
-
self.searxng_instances = [
|
| 29 |
-
"https://searx.be",
|
| 30 |
-
"https://search.bus-hit.me",
|
| 31 |
-
"https://searx.nixnet.xyz",
|
| 32 |
-
"https://searx.tuxcloud.net",
|
| 33 |
-
"https://searx.moe",
|
| 34 |
-
]
|
| 35 |
-
|
| 36 |
-
def _get_cache_key(self, *args, **kwargs) -> str:
|
| 37 |
-
key = f"{args}_{sorted(kwargs.items())}"
|
| 38 |
-
return hashlib.md5(key.encode()).hexdigest()
|
| 39 |
-
|
| 40 |
-
def _get_from_cache(self, key: str) -> Optional[Any]:
|
| 41 |
-
if key in self.cache:
|
| 42 |
-
data, timestamp = self.cache[key]
|
| 43 |
-
if datetime.now() - timestamp < timedelta(seconds=self.cache_ttl):
|
| 44 |
-
return data
|
| 45 |
-
else:
|
| 46 |
-
del self.cache[key]
|
| 47 |
-
return None
|
| 48 |
-
|
| 49 |
-
def _save_to_cache(self, key: str, data: Any) -> None:
|
| 50 |
-
self.cache[key] = (data, datetime.now())
|
| 51 |
-
|
| 52 |
-
def search_web(self, query: str, num_results: int = 5) -> List[Dict[str, str]]:
|
| 53 |
-
"""Умный поиск с несколькими источниками (минимум 3 способа)"""
|
| 54 |
-
cache_key = self._get_cache_key('search', query, num_results)
|
| 55 |
-
cached = self._get_from_cache(cache_key)
|
| 56 |
-
if cached is not None:
|
| 57 |
-
return cached
|
| 58 |
-
|
| 59 |
-
results = []
|
| 60 |
-
|
| 61 |
-
# Способ 1: SearXNG (мета-поиск)
|
| 62 |
-
results = self._search_searxng(query, num_results)
|
| 63 |
-
|
| 64 |
-
# Способ 2: DuckDuckGo API
|
| 65 |
-
if not results:
|
| 66 |
-
results = self._search_duckduckgo(query, num_results)
|
| 67 |
-
|
| 68 |
-
# Способ 3: Google (парсинг)
|
| 69 |
-
if not results:
|
| 70 |
-
results = self._search_google(query, num_results)
|
| 71 |
-
|
| 72 |
-
# Способ 4: Яндекс (для русского) — опционально
|
| 73 |
-
if not results and any(ord(c) > 1024 for c in query):
|
| 74 |
-
results = self._search_yandex(query, num_results)
|
| 75 |
-
|
| 76 |
-
self._save_to_cache(cache_key, results)
|
| 77 |
-
return results
|
| 78 |
-
|
| 79 |
-
def _search_searxng(self, query: str, num_results: int) -> List[Dict[str, str]]:
|
| 80 |
-
results = []
|
| 81 |
-
for instance in self.searxng_instances:
|
| 82 |
-
try:
|
| 83 |
-
url = f"{instance}/search"
|
| 84 |
-
params = {
|
| 85 |
-
"q": query,
|
| 86 |
-
"format": "json",
|
| 87 |
-
"categories": "general",
|
| 88 |
-
"engines": "google,bing,duckduckgo,startpage",
|
| 89 |
-
"language": "en",
|
| 90 |
-
"pageno": 1
|
| 91 |
-
}
|
| 92 |
-
response = self.session.get(url, params=params, timeout=20)
|
| 93 |
-
response.raise_for_status()
|
| 94 |
-
data = response.json()
|
| 95 |
-
|
| 96 |
-
if 'results' in data:
|
| 97 |
-
for item in data['results'][:num_results]:
|
| 98 |
-
results.append({
|
| 99 |
-
'title': item.get('title', '')[:100],
|
| 100 |
-
'url': item.get('url', ''),
|
| 101 |
-
'snippet': item.get('content', '')[:200],
|
| 102 |
-
'source': 'searxng',
|
| 103 |
-
'engine': item.get('engine', '')
|
| 104 |
-
})
|
| 105 |
-
if results:
|
| 106 |
-
print(f"🔍 SearXNG: {len(results)} результатов")
|
| 107 |
-
break
|
| 108 |
-
except Exception as e:
|
| 109 |
-
continue
|
| 110 |
-
return results
|
| 111 |
-
|
| 112 |
-
def _search_duckduckgo(self, query: str, num_results: int) -> List[Dict[str, str]]:
|
| 113 |
-
results = []
|
| 114 |
-
try:
|
| 115 |
-
url = f"https://api.duckduckgo.com/?q={query}&format=json&no_html=1&skip_disambig=1"
|
| 116 |
-
response = self.session.get(url, timeout=15)
|
| 117 |
-
data = response.json()
|
| 118 |
-
|
| 119 |
-
if 'RelatedTopics' in data:
|
| 120 |
-
for item in data['RelatedTopics'][:num_results]:
|
| 121 |
-
if 'Text' in item and 'FirstURL' in item:
|
| 122 |
-
results.append({
|
| 123 |
-
'title': item['Text'][:100],
|
| 124 |
-
'url': item['FirstURL'],
|
| 125 |
-
'snippet': item.get('Text', '')[:200],
|
| 126 |
-
'source': 'duckduckgo'
|
| 127 |
-
})
|
| 128 |
-
print(f"🦆 DuckDuckGo: {len(results)} результатов")
|
| 129 |
-
except Exception as e:
|
| 130 |
-
print(f"⚠️ DuckDuckGo ошибка: {e}")
|
| 131 |
-
return results
|
| 132 |
-
|
| 133 |
-
def _search_google(self, query: str, num_results: int) -> List[Dict[str, str]]:
|
| 134 |
-
results = []
|
| 135 |
-
if not self._has_bs4:
|
| 136 |
-
return results
|
| 137 |
-
try:
|
| 138 |
-
from bs4 import BeautifulSoup
|
| 139 |
-
url = f"https://www.google.com/search?q={query}&num={num_results * 2}"
|
| 140 |
-
response = self.session.get(url, timeout=20)
|
| 141 |
-
soup = BeautifulSoup(response.text, 'html.parser')
|
| 142 |
-
|
| 143 |
-
for g in soup.find_all('div', class_='g'):
|
| 144 |
-
title_elem = g.find('h3')
|
| 145 |
-
link_elem = g.find('a')
|
| 146 |
-
snippet_elem = g.find('div', class_='VwiC3b')
|
| 147 |
-
|
| 148 |
-
if title_elem and link_elem:
|
| 149 |
-
title = title_elem.get_text()
|
| 150 |
-
link = link_elem.get('href', '')
|
| 151 |
-
snippet = snippet_elem.get_text() if snippet_elem else ''
|
| 152 |
-
if link.startswith('/url?q='):
|
| 153 |
-
link = link.split('/url?q=')[1].split('&')[0]
|
| 154 |
-
if link.startswith('http'):
|
| 155 |
-
results.append({
|
| 156 |
-
'title': title[:100],
|
| 157 |
-
'url': link,
|
| 158 |
-
'snippet': snippet[:200],
|
| 159 |
-
'source': 'google'
|
| 160 |
-
})
|
| 161 |
-
if len(results) >= num_results:
|
| 162 |
-
break
|
| 163 |
-
print(f"🔍 Google: {len(results)} результатов")
|
| 164 |
-
except Exception as e:
|
| 165 |
-
print(f"⚠️ Google ошибка: {e}")
|
| 166 |
-
return results
|
| 167 |
-
|
| 168 |
-
def _search_yandex(self, query: str, num_results: int) -> List[Dict[str, str]]:
|
| 169 |
-
results = []
|
| 170 |
-
if not self._has_bs4:
|
| 171 |
-
return results
|
| 172 |
-
try:
|
| 173 |
-
from bs4 import BeautifulSoup
|
| 174 |
-
url = f"https://yandex.ru/search/?text={query}&numdoc={num_results}"
|
| 175 |
-
response = self.session.get(url, timeout=20)
|
| 176 |
-
soup = BeautifulSoup(response.text, 'html.parser')
|
| 177 |
-
|
| 178 |
-
for item in soup.find_all('li', class_='serp-item'):
|
| 179 |
-
link_elem = item.find('a', class_='link')
|
| 180 |
-
snippet_elem = item.find('div', class_='text-container')
|
| 181 |
-
if link_elem:
|
| 182 |
-
title = link_elem.get_text()
|
| 183 |
-
link = link_elem.get('href', '')
|
| 184 |
-
snippet = snippet_elem.get_text() if snippet_elem else ''
|
| 185 |
-
if link.startswith('http'):
|
| 186 |
-
results.append({
|
| 187 |
-
'title': title[:100],
|
| 188 |
-
'url': link,
|
| 189 |
-
'snippet': snippet[:200],
|
| 190 |
-
'source': 'yandex'
|
| 191 |
-
})
|
| 192 |
-
if len(results) >= num_results:
|
| 193 |
-
break
|
| 194 |
-
print(f"🔍 Яндекс: {len(results)} результатов")
|
| 195 |
-
except Exception as e:
|
| 196 |
-
print(f"⚠️ Яндекс ошибка: {e}")
|
| 197 |
-
return results
|
| 198 |
-
|
| 199 |
-
def fetch_page(self, url: str, max_size: int = 50000) -> Dict[str, Any]:
|
| 200 |
-
try:
|
| 201 |
-
response = self.session.get(url, timeout=30)
|
| 202 |
-
response.raise_for_status()
|
| 203 |
-
content = response.text[:max_size]
|
| 204 |
-
return {
|
| 205 |
-
"url": url,
|
| 206 |
-
"status": response.status_code,
|
| 207 |
-
"content": content,
|
| 208 |
-
"length": len(content),
|
| 209 |
-
"headers": dict(response.headers)
|
| 210 |
-
}
|
| 211 |
-
except Exception as e:
|
| 212 |
-
return {"url": url, "error": str(e)}
|
| 213 |
-
|
| 214 |
-
def analyze_website(self, url: str) -> Dict[str, Any]:
|
| 215 |
-
page = self.fetch_page(url)
|
| 216 |
-
if "error" in page:
|
| 217 |
-
return page
|
| 218 |
-
content = page.get("content", "")
|
| 219 |
-
return {
|
| 220 |
-
"url": url,
|
| 221 |
-
"title": content.split("<title>")[1].split("</title>")[0] if "<title>" in content else "N/A",
|
| 222 |
-
"has_forms": "form" in content.lower(),
|
| 223 |
-
"has_scripts": "<script" in content.lower(),
|
| 224 |
-
"links_count": content.count("<a "),
|
| 225 |
-
"size": len(content)
|
| 226 |
-
}
|
| 227 |
-
|
| 228 |
-
def fetch_multiple(self, urls: List[str], max_total: int = 50000) -> List[Dict[str, Any]]:
|
| 229 |
-
results = []
|
| 230 |
-
total = 0
|
| 231 |
-
for url in urls:
|
| 232 |
-
page = self.fetch_page(url, max_size=min(10000, max_total - total))
|
| 233 |
-
if "content" in page:
|
| 234 |
-
total += len(page["content"])
|
| 235 |
-
results.append(page)
|
| 236 |
-
if total >= max_total:
|
| 237 |
-
break
|
| 238 |
-
return results
|
| 239 |
-
|
| 240 |
-
def clear_cache(self) -> str:
|
| 241 |
-
count = len(self.cache)
|
| 242 |
-
self.cache.clear()
|
| 243 |
-
return f"🗑️ Кэш очищен ({count} записей)"
|
| 244 |
-
|
| 245 |
-
def get_cache_stats(self) -> str:
|
| 246 |
-
return f"📊 Кэш: {len(self.cache)} записей, TTL: {self.cache_ttl} секунд"
|
| 247 |
-
|
| 248 |
-
def _extract_keywords(self, content: str) -> List[str]:
|
| 249 |
-
words = re.findall(r'\b[a-zA-Z]{4,}\b', content.lower())
|
| 250 |
-
return [w for w, _ in Counter(words).most_common(10)]
|
| 251 |
-
|
| 252 |
-
def _detect_language(self, content: str) -> str:
|
| 253 |
-
ru_chars = len(re.findall(r'[а-яА-Я]', content))
|
| 254 |
-
en_chars = len(re.findall(r'[a-zA-Z]', content))
|
| 255 |
-
if ru_chars > en_chars * 0.5:
|
| 256 |
-
return "ru"
|
| 257 |
-
return "en"
|
| 258 |
-
|
| 259 |
-
INTERNET_AGENT = FreeInternetAgent(cache_ttl=3600)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|