omni-parse: 12 провайдеров, sqlite+fts5, simhash-дедуп, фильтр релевантности

This commit is contained in:
dev
2026-10-25 13:16:56 +03:00
commit 02816cacfb
22 changed files with 1613 additions and 0 deletions

99
omni/providers/reddit.py Normal file
View File

@@ -0,0 +1,99 @@
"""Reddit без ключей: поиск по всему реддиту и по сабам, сортировка new/top."""
import urllib.parse
from .. import net
from ..model import Item
DEFAULT_SUBS = ["netsec", "cybersecurity", "webscraping", "selfhosted", "webdev", "Python"]
UA = {"User-Agent": "omni-parse/1.0 (research; contact: local)"}
def available(name="reddit"):
return True, "pullpush (архив) + публичный json, ключи не нужны"
def _pullpush(query, limit=25, subreddit=None, kind="submission"):
q = urllib.parse.quote(query or "")
url = f"https://api.pullpush.io/reddit/search/{kind}/?q={q}&size={min(limit, 100)}&sort=desc"
if subreddit:
url += f"&subreddit={subreddit}"
r = net.get(url, headers=UA, json_mode=True)
data = (r.json() or {}).get("data") or []
out = []
for d in data:
if not (d.get("title") or d.get("body")):
continue
it = _mk(d, subreddit)
it.kind = "thread" if kind == "submission" else "comment"
if kind == "comment":
it.title = (d.get("link_title") or it.text[:100])
out.append(it)
return out
def _rows(payload):
out = []
for ch in (payload or {}).get("data", {}).get("children", []):
d = ch.get("data") or {}
if not d.get("title"):
continue
out.append(d)
return out
def _mk(d, sub=None):
perm = d.get("permalink") or ""
url = "https://www.reddit.com" + perm if perm else d.get("url", "")
body = (d.get("selftext") or "")[:1800]
it = Item("reddit", "thread", url=url, title=d.get("title", ""), text=body,
source="r/" + (d.get("subreddit") or sub or ""), author=d.get("author", ""),
published=str(int(d.get("created_utc") or 0)),
extra={"ups": d.get("ups"), "comments": d.get("num_comments"),
"sub": d.get("subreddit"), "ext": d.get("url", "")[:300]})
it.score = float(d.get("ups") or 0) ** 0.5 + float(d.get("num_comments") or 0) ** 0.4
return it
def _reddit_native(query, limit=20):
q = urllib.parse.quote(query)
r = net.get(f"https://www.reddit.com/search.json?q={q}&limit={min(limit, 25)}&sort=relevance&t=month",
headers={**UA, "Accept": "application/json"}, json_mode=True)
if r.status != 200:
return []
return [_mk(d) for d in _rows(r.json())]
def _relevant(query, item):
toks = [t for t in __import__("re").findall(r"\w{3,}", (query or "").lower())]
if not toks:
return True
body = ((item.title or "") + " " + (item.text or "")).lower()
hit = sum(1 for t in toks if t in body)
title_hit = sum(1 for t in toks if t in (item.title or "").lower())
return hit >= max(2, int(len(toks) * 0.7)) and title_hit >= 1
def search_reddit(query, limit=20, subs=None, include_comments=True):
items = _pullpush(query, limit=limit)
if len(items) < 5:
items += _reddit_native(query, limit=limit)
for sub in (subs or [])[:3]:
items += _pullpush(query, limit=10, subreddit=sub)
if include_comments and len(items) < limit:
items += _pullpush(query, limit=10, kind="comment")[:8]
items = [i for i in items if _relevant(query, i)]
uniq, seen = [], set()
for it in items:
k = it.url or it.title
if k in seen:
continue
seen.add(k)
uniq.append(it)
return uniq[:limit * 2]
def hot(subs=None, limit=20):
out = []
for sub in (subs or DEFAULT_SUBS)[:6]:
out += _pullpush("", limit=min(limit, 15), subreddit=sub)
return out