Spaces:
Running
Running
Hashtag search + hot topics: add bongda & bongdaplus (Jina); Jina article fallback
Browse files- app_v2_entry.py +80 -6
app_v2_entry.py
CHANGED
|
@@ -71,7 +71,29 @@ def _s_vietnamnet(topic,limit=6):
|
|
| 71 |
if len(items)>=limit:break
|
| 72 |
except:pass
|
| 73 |
return items
|
| 74 |
-
def _s_bongda(topic,limit=
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
items=[]
|
| 76 |
try:
|
| 77 |
r=req.get(f"https://bongda.com.vn/tim-kiem.html?q={quote(topic)}",headers={'User-Agent':'Mozilla/5.0'},timeout=8);soup=BeautifulSoup(r.text,'lxml')
|
|
@@ -133,9 +155,9 @@ def _s_thethaovanhoa(topic,limit=5):
|
|
| 133 |
return items
|
| 134 |
def _search_all(topic,limit=36):
|
| 135 |
results={}
|
| 136 |
-
with ThreadPoolExecutor(
|
| 137 |
-
futs={ex.submit(_s_vnexpress,topic,8):'vne',ex.submit(_s_dantri,topic,8):'dt',ex.submit(_s_vietnamnet,topic,6):'vnn',ex.submit(_s_bongda,topic,
|
| 138 |
-
for f in as_completed(futs,timeout=
|
| 139 |
try:results[futs[f]]=f.result()
|
| 140 |
except:results[futs[f]]=[]
|
| 141 |
srcs=list(results.values());out=[];seen=set();mx=max((len(s) for s in srcs),default=0)
|
|
@@ -145,9 +167,40 @@ def _search_all(topic,limit=36):
|
|
| 145 |
return out[:limit]
|
| 146 |
# Override article endpoint
|
| 147 |
app.router.routes=[r for r in app.router.routes if not(getattr(r,'path',None)=='/api/article' and 'GET' in getattr(r,'methods',set()))]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
def _scrape_generic(url):
|
| 149 |
try:
|
| 150 |
-
r=req.get(url,headers={'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36','Accept-Language':'vi-VN,vi;q=0.9'},timeout=15,allow_redirects=True);r.encoding='utf-8'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 151 |
for tag in soup.find_all(['script','style','nav','footer','aside','form','noscript']):tag.decompose()
|
| 152 |
h1=soup.find('h1');ogt=soup.find('meta',property='og:title');title=(h1.get_text(strip=True) if h1 else '')or(ogt.get('content','') if ogt else '')
|
| 153 |
ogd=soup.find('meta',property='og:description');summary=ogd.get('content','') if ogd else ''
|
|
@@ -166,8 +219,12 @@ def _scrape_generic(url):
|
|
| 166 |
im=el if el.name=='img' else el.find('img')
|
| 167 |
if im:src=im.get('data-src') or im.get('src') or'';(body.append({'type':'img','src':'https:'+src if src.startswith('//') else src}) if src and'base64' not in src else None)
|
| 168 |
if not body and summary:body=[{'type':'p','text':summary}]
|
|
|
|
|
|
|
|
|
|
| 169 |
return{'title':_clean(title),'summary':_clean(summary),'og_image':og_img,'body':body[:50],'source':'generic','url':url}
|
| 170 |
-
except:
|
|
|
|
| 171 |
@app.get('/api/article')
|
| 172 |
def api_article_v2(url:str=Query(...)):
|
| 173 |
from main import scrape_vne_article,scrape_bbc_article,scrape_dantri_article,scrape_genk_article,scrape_ttvh_article
|
|
@@ -198,6 +255,23 @@ def _get_hot_topics():
|
|
| 198 |
phrase=' '.join(words[i:i+n])
|
| 199 |
if 8<=len(phrase)<=45:key=phrase.lower();freq[key]=freq.get(key,0)+1;display[key]=phrase
|
| 200 |
except:continue
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 201 |
ranked=sorted(freq.items(),key=lambda x:x[1],reverse=True);topics=[];seen=set()
|
| 202 |
for key,count in ranked:
|
| 203 |
is_dup=any(len(set(e.split())&set(key.split()))/max(len(set(e.split())),len(set(key.split())),1)>0.6 for e in seen)
|
|
|
|
| 71 |
if len(items)>=limit:break
|
| 72 |
except:pass
|
| 73 |
return items
|
| 74 |
+
def _s_bongda(topic,limit=6):
|
| 75 |
+
"""bongda.com.vn via Jina homepage (site is JS-protected); keyword-filtered."""
|
| 76 |
+
items=[]
|
| 77 |
+
try:
|
| 78 |
+
from main import scrape_bongda_jina
|
| 79 |
+
for a in scrape_bongda_jina(40):
|
| 80 |
+
if _has_kw(topic,a.get('title','')):
|
| 81 |
+
items.append({'title':a['title'],'url':a['link'],'via':'Bóng Đá'})
|
| 82 |
+
if len(items)>=limit:break
|
| 83 |
+
except:pass
|
| 84 |
+
return items
|
| 85 |
+
def _s_bongdaplus(topic,limit=6):
|
| 86 |
+
"""bongdaplus.vn via Jina homepage (JS SPA); keyword-filtered."""
|
| 87 |
+
items=[]
|
| 88 |
+
try:
|
| 89 |
+
from main import scrape_bongdaplus_jina
|
| 90 |
+
for a in scrape_bongdaplus_jina(40):
|
| 91 |
+
if _has_kw(topic,a.get('title','')):
|
| 92 |
+
items.append({'title':a['title'],'url':a['link'],'via':'Bóng Đá+'})
|
| 93 |
+
if len(items)>=limit:break
|
| 94 |
+
except:pass
|
| 95 |
+
return items
|
| 96 |
+
def _s_bongda_OLD(topic,limit=5):
|
| 97 |
items=[]
|
| 98 |
try:
|
| 99 |
r=req.get(f"https://bongda.com.vn/tim-kiem.html?q={quote(topic)}",headers={'User-Agent':'Mozilla/5.0'},timeout=8);soup=BeautifulSoup(r.text,'lxml')
|
|
|
|
| 155 |
return items
|
| 156 |
def _search_all(topic,limit=36):
|
| 157 |
results={}
|
| 158 |
+
with ThreadPoolExecutor(10) as ex:
|
| 159 |
+
futs={ex.submit(_s_vnexpress,topic,8):'vne',ex.submit(_s_dantri,topic,8):'dt',ex.submit(_s_vietnamnet,topic,6):'vnn',ex.submit(_s_bongda,topic,6):'bd',ex.submit(_s_bongdaplus,topic,6):'bdp',ex.submit(_s_genk,topic,5):'gk',ex.submit(_s_thanhnien,topic,6):'tn',ex.submit(_s_tuoitre,topic,6):'tt',ex.submit(_s_thethaovanhoa,topic,5):'tvh'}
|
| 160 |
+
for f in as_completed(futs,timeout=16):
|
| 161 |
try:results[futs[f]]=f.result()
|
| 162 |
except:results[futs[f]]=[]
|
| 163 |
srcs=list(results.values());out=[];seen=set();mx=max((len(s) for s in srcs),default=0)
|
|
|
|
| 167 |
return out[:limit]
|
| 168 |
# Override article endpoint
|
| 169 |
app.router.routes=[r for r in app.router.routes if not(getattr(r,'path',None)=='/api/article' and 'GET' in getattr(r,'methods',set()))]
|
| 170 |
+
def _jina_article(url):
|
| 171 |
+
"""Read an article via r.jina.ai (for Cloudflare/SPA sites: vatvostudio, bongdaplus)."""
|
| 172 |
+
try:
|
| 173 |
+
r=req.get("https://r.jina.ai/"+url,headers={'Accept':'text/markdown','X-Return-Format':'markdown','User-Agent':'Mozilla/5.0'},timeout=35)
|
| 174 |
+
if r.status_code!=200 or not r.text:return None
|
| 175 |
+
lines=[x.rstrip() for x in r.text.splitlines()]
|
| 176 |
+
title='';og_img='';summary='';body=[]
|
| 177 |
+
for ln in lines[:40]:
|
| 178 |
+
if ln.startswith('Title:') and not title:title=ln.replace('Title:','',1).strip()
|
| 179 |
+
# first content image
|
| 180 |
+
for ln in lines:
|
| 181 |
+
mi=re.search(r'!\[[^\]]*\]\((https?://[^)]+)\)',ln)
|
| 182 |
+
if mi and 'wp-content' in mi.group(1) or (mi and 'media' in mi.group(1)):og_img=mi.group(1);break
|
| 183 |
+
intxt=False
|
| 184 |
+
for ln in lines:
|
| 185 |
+
if ln.startswith('Markdown Content:'):intxt=True;continue
|
| 186 |
+
if not intxt:continue
|
| 187 |
+
t=re.sub(r'!\[[^\]]*\]\([^)]+\)','',ln) # drop images
|
| 188 |
+
t=re.sub(r'\[([^\]]+)\]\([^)]+\)',r'\1',t) # unwrap links
|
| 189 |
+
t=re.sub(r'[#>*_`]+','',t).strip()
|
| 190 |
+
if len(t)>=40:body.append({'type':'p','text':t})
|
| 191 |
+
if len(body)>=60:break
|
| 192 |
+
if not title and body:title=body[0]['text'][:90]
|
| 193 |
+
if not body:return None
|
| 194 |
+
return {'title':_clean(title),'summary':_clean(body[0]['text'][:200]) if body else '','og_image':og_img,'body':body,'source':'jina','url':url}
|
| 195 |
+
except:return None
|
| 196 |
def _scrape_generic(url):
|
| 197 |
try:
|
| 198 |
+
r=req.get(url,headers={'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36','Accept-Language':'vi-VN,vi;q=0.9'},timeout=15,allow_redirects=True);r.encoding='utf-8'
|
| 199 |
+
# JS SPA / Cloudflare-protected sites -> read via Jina
|
| 200 |
+
if r.status_code>=400 or 'bongdaplus.vn' in url or 'vatvostudio.vn' in url or 'Just a moment' in r.text[:600]:
|
| 201 |
+
jr=_jina_article(url)
|
| 202 |
+
if jr and jr.get('body'):return jr
|
| 203 |
+
soup=BeautifulSoup(r.text,'lxml')
|
| 204 |
for tag in soup.find_all(['script','style','nav','footer','aside','form','noscript']):tag.decompose()
|
| 205 |
h1=soup.find('h1');ogt=soup.find('meta',property='og:title');title=(h1.get_text(strip=True) if h1 else '')or(ogt.get('content','') if ogt else '')
|
| 206 |
ogd=soup.find('meta',property='og:description');summary=ogd.get('content','') if ogd else ''
|
|
|
|
| 219 |
im=el if el.name=='img' else el.find('img')
|
| 220 |
if im:src=im.get('data-src') or im.get('src') or'';(body.append({'type':'img','src':'https:'+src if src.startswith('//') else src}) if src and'base64' not in src else None)
|
| 221 |
if not body and summary:body=[{'type':'p','text':summary}]
|
| 222 |
+
if not body:
|
| 223 |
+
jr=_jina_article(url)
|
| 224 |
+
if jr and jr.get('body'):return jr
|
| 225 |
return{'title':_clean(title),'summary':_clean(summary),'og_image':og_img,'body':body[:50],'source':'generic','url':url}
|
| 226 |
+
except:
|
| 227 |
+
return _jina_article(url)
|
| 228 |
@app.get('/api/article')
|
| 229 |
def api_article_v2(url:str=Query(...)):
|
| 230 |
from main import scrape_vne_article,scrape_bbc_article,scrape_dantri_article,scrape_genk_article,scrape_ttvh_article
|
|
|
|
| 255 |
phrase=' '.join(words[i:i+n])
|
| 256 |
if 8<=len(phrase)<=45:key=phrase.lower();freq[key]=freq.get(key,0)+1;display[key]=phrase
|
| 257 |
except:continue
|
| 258 |
+
# Add bongda.com.vn + bongdaplus.vn (JS sites, no RSS) via Jina-scraped homepage titles
|
| 259 |
+
def _ingest_titles(titles):
|
| 260 |
+
for title in titles:
|
| 261 |
+
title=_clean(title)
|
| 262 |
+
if not title:continue
|
| 263 |
+
title=re.sub(r'\s*[-|].*$','',title)
|
| 264 |
+
words=[w for w in re.findall(r'[A-Za-zÀ-ỹ0-9]+',title) if len(w)>2 and w.lower() not in _STOP]
|
| 265 |
+
if len(words)<2:continue
|
| 266 |
+
for n in(3,4,2):
|
| 267 |
+
for i in range(max(0,len(words)-n+1)):
|
| 268 |
+
phrase=' '.join(words[i:i+n])
|
| 269 |
+
if 8<=len(phrase)<=45:key=phrase.lower();freq[key]=freq.get(key,0)+1;display[key]=phrase
|
| 270 |
+
try:
|
| 271 |
+
from main import scrape_bongda_jina,scrape_bongdaplus_jina
|
| 272 |
+
_ingest_titles([a.get('title','') for a in scrape_bongda_jina(20)])
|
| 273 |
+
_ingest_titles([a.get('title','') for a in scrape_bongdaplus_jina(20)])
|
| 274 |
+
except:pass
|
| 275 |
ranked=sorted(freq.items(),key=lambda x:x[1],reverse=True);topics=[];seen=set()
|
| 276 |
for key,count in ranked:
|
| 277 |
is_dup=any(len(set(e.split())&set(key.split()))/max(len(set(e.split())),len(set(key.split())),1)>0.6 for e in seen)
|