Spaces:
Running
Running
Fix: restore working app_v2_entry.py + add /api/match/{event_id}/detail route
Browse files- app_v2_entry.py +22 -187
app_v2_entry.py
CHANGED
|
@@ -71,25 +71,15 @@ 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 |
-
"""bongda.com.vn via Jina homepage (site is JS-protected); keyword-filtered."""
|
| 76 |
items=[]
|
| 77 |
try:
|
| 78 |
-
|
| 79 |
-
for a in
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 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
|
|
@@ -102,7 +92,7 @@ def _s_genk(topic,limit=5):
|
|
| 102 |
if t and len(t)>15 and _has_kw(topic,t):
|
| 103 |
if href.startswith('/'):href='https://genk.vn'+href
|
| 104 |
items.append({'title':t,'url':href,'via':'GenK'})
|
| 105 |
-
if len(items)>=limit
|
| 106 |
except:pass
|
| 107 |
return items
|
| 108 |
def _s_thanhnien(topic,limit=6):
|
|
@@ -114,7 +104,7 @@ def _s_thanhnien(topic,limit=6):
|
|
| 114 |
if t and len(t)>15 and _has_kw(topic,t):
|
| 115 |
if not href.startswith('http'):href='https://thanhnien.vn'+href
|
| 116 |
items.append({'title':t,'url':href,'via':'Thanh Niên'})
|
| 117 |
-
if len(items)>=limit
|
| 118 |
except:pass
|
| 119 |
return items
|
| 120 |
def _s_tuoitre(topic,limit=6):
|
|
@@ -126,7 +116,7 @@ def _s_tuoitre(topic,limit=6):
|
|
| 126 |
if t and len(t)>15 and _has_kw(topic,t):
|
| 127 |
if not href.startswith('http'):href='https://tuoitre.vn'+href
|
| 128 |
items.append({'title':t,'url':href,'via':'Tuổi Trẻ'})
|
| 129 |
-
if len(items)>=limit
|
| 130 |
except:pass
|
| 131 |
return items
|
| 132 |
def _s_thethaovanhoa(topic,limit=5):
|
|
@@ -138,14 +128,14 @@ def _s_thethaovanhoa(topic,limit=5):
|
|
| 138 |
if t and len(t)>15 and _has_kw(topic,t):
|
| 139 |
if not href.startswith('http'):href='https://thethaovanhoa.vn'+href
|
| 140 |
items.append({'title':t,'url':href,'via':'TT&VH'})
|
| 141 |
-
if len(items)>=limit
|
| 142 |
except:pass
|
| 143 |
return items
|
| 144 |
def _search_all(topic,limit=36):
|
| 145 |
results={}
|
| 146 |
-
with ThreadPoolExecutor(
|
| 147 |
-
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,
|
| 148 |
-
for f in as_completed(futs,timeout=
|
| 149 |
try:results[futs[f]]=f.result()
|
| 150 |
except:results[futs[f]]=[]
|
| 151 |
srcs=list(results.values());out=[];seen=set();mx=max((len(s) for s in srcs),default=0)
|
|
@@ -153,132 +143,11 @@ def _search_all(topic,limit=36):
|
|
| 153 |
for s in srcs:
|
| 154 |
if i<len(s) and s[i].get('url') and s[i]['url'] not in seen:seen.add(s[i]['url']);out.append(s[i])
|
| 155 |
return out[:limit]
|
| 156 |
-
|
| 157 |
-
from main import (_yt_jina_page, _yt_rss_shorts, SHORTS_FALLBACK,
|
| 158 |
-
_YT_CHANNEL_IDS, _cached, _cache_ttl_yt)
|
| 159 |
-
|
| 160 |
-
def _yt_jina_shorts_classified(channel, count=24):
|
| 161 |
-
items=[];seen=set()
|
| 162 |
-
for path, tab_short in (("videos", False), ("shorts", True)):
|
| 163 |
-
md=_yt_jina_page(channel, path)
|
| 164 |
-
if not md:continue
|
| 165 |
-
for m in re.finditer(r'\[([^\]\[]{8,160})\]\(https?://(?:www\.)?youtube\.com/(shorts/|watch\?v=)([A-Za-z0-9_-]{11})[^)]*\)', md):
|
| 166 |
-
title=html_lib.unescape(m.group(1)).strip().strip('"')
|
| 167 |
-
form=m.group(2); vid=m.group(3)
|
| 168 |
-
if vid in seen:continue
|
| 169 |
-
tl=title.lower()
|
| 170 |
-
if tl in ("home home","shorts shorts","image") or title.startswith("!") or title.startswith("Image"):continue
|
| 171 |
-
is_short = True if form.startswith("shorts/") else tab_short
|
| 172 |
-
seen.add(vid)
|
| 173 |
-
items.append({"title":title,"link":f"https://www.youtube.com/watch?v={vid}",
|
| 174 |
-
"img":f"https://i.ytimg.com/vi/{vid}/hqdefault.jpg","source":"yt",
|
| 175 |
-
"id":vid,"channel":channel,"is_short":is_short})
|
| 176 |
-
if len(items)>=count:break
|
| 177 |
-
return items
|
| 178 |
-
|
| 179 |
-
def _scrape_shorts_v2():
|
| 180 |
-
vids=[]
|
| 181 |
-
with ThreadPoolExecutor(2) as ex:
|
| 182 |
-
futs=[ex.submit(_yt_jina_shorts_classified,ch,24) for ch in ["baodantri7941","baosuckhoedoisongboyte"]]
|
| 183 |
-
for f in as_completed(futs):
|
| 184 |
-
try:
|
| 185 |
-
r=f.result()
|
| 186 |
-
if r:vids.extend(r)
|
| 187 |
-
except:pass
|
| 188 |
-
have=set(v.get("channel") for v in vids)
|
| 189 |
-
for ch in ["baodantri7941","baosuckhoedoisongboyte"]:
|
| 190 |
-
if ch not in have:
|
| 191 |
-
for v in _yt_rss_shorts(ch,24):
|
| 192 |
-
v.setdefault("is_short",True);vids.append(v)
|
| 193 |
-
merged=[];seen=set()
|
| 194 |
-
for v in vids+[dict(x) for x in SHORTS_FALLBACK]:
|
| 195 |
-
vid=v.get("id")
|
| 196 |
-
if not vid or vid in seen:continue
|
| 197 |
-
if "is_short" not in v:
|
| 198 |
-
t=(v.get("title","") or "").lower()
|
| 199 |
-
v["is_short"]= ("#shorts" in t) or ("short" in t)
|
| 200 |
-
seen.add(vid);merged.append(v)
|
| 201 |
-
return merged[:40]
|
| 202 |
-
|
| 203 |
-
app.router.routes=[r for r in app.router.routes if not(getattr(r,'path',None)=='/api/shorts' and 'GET' in getattr(r,'methods',set()))]
|
| 204 |
-
@app.get('/api/shorts')
|
| 205 |
-
def api_shorts_v2():
|
| 206 |
-
return JSONResponse(_cached("yt_shorts_v4",_scrape_shorts_v2,ttl=_cache_ttl_yt))
|
| 207 |
-
|
| 208 |
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()))]
|
| 209 |
-
_JINA_BOILER_STOP=['facebooktelegram','copy link','follow us','bài viết liên quan','tin liên quan',
|
| 210 |
-
'cùng chuyên mục','bình luận','trả lời','bản quyền','địa chỉ:','lưu ý:','đăng ký nhận tin',
|
| 211 |
-
'chia sẻ bài viết','tags:','© ','bongdaplus.vn.','vật vờ studio để cập nhật','mời các bạn theo dõi',
|
| 212 |
-
'cập nhật tin tức, thủ thuật']
|
| 213 |
-
_JINA_NAV=['đăng nhập','tạo tài khoản','reading:','sponsored by']
|
| 214 |
-
_JINA_IMG_DENY=['logo','avatar','sponsor','vvaba','ductrinh','longnguyen','hoangtrinh','vvs_logo',
|
| 215 |
-
'cropped-','login.png','/icon','placeholder','/ads/','banner','giaminhmedia','-330x220','-420x280',
|
| 216 |
-
'-150x','-100x','thumb']
|
| 217 |
-
_JINA_AD_DENY=['giaminhmedia','delivery/cl.php','[hot]','[video','săn ngay','giảm giá','khuyến mãi',
|
| 218 |
-
'mua ngay','đặt mua','shopee','lazada','tiki.vn']
|
| 219 |
-
def _jina_strip(s):
|
| 220 |
-
s=re.sub(r'!\[[^\]]*\]\([^)]*\)','',s)
|
| 221 |
-
s=re.sub(r'\[([^\]]*)\]\([^)]*\)',r'\1',s)
|
| 222 |
-
s=re.sub(r'</?[a-zA-Z][^>]*>','',s)
|
| 223 |
-
s=re.sub(r'[#>*`_]+','',s)
|
| 224 |
-
return re.sub(r'\s+',' ',s).strip()
|
| 225 |
-
def _jina_good_img(u,alt=''):
|
| 226 |
-
ul=u.lower();al=(alt or '').lower()
|
| 227 |
-
if any(k in ul for k in _JINA_IMG_DENY):return False
|
| 228 |
-
if 'vật vờ studio' in al or 'sponsored' in al:return False
|
| 229 |
-
if not re.search(r'\.(jpg|jpeg|png|webp|gif)',ul) and 'imgthumbnail' not in ul and '/media/' not in ul.lower():return False
|
| 230 |
-
return True
|
| 231 |
-
def _jina_article(url):
|
| 232 |
-
try:
|
| 233 |
-
r=req.get("https://r.jina.ai/"+url,headers={'Accept':'text/markdown','X-Return-Format':'markdown','User-Agent':'Mozilla/5.0'},timeout=18)
|
| 234 |
-
if r.status_code!=200 or not r.text:return None
|
| 235 |
-
lines=r.text.splitlines()
|
| 236 |
-
title=''
|
| 237 |
-
for ln in lines[:30]:
|
| 238 |
-
if ln.startswith('Title:'):title=ln.replace('Title:','',1).strip();break
|
| 239 |
-
tw=set(w for w in re.findall(r'[A-Za-zÀ-ỹ0-9]+',title.lower()) if len(w)>3)
|
| 240 |
-
start=0
|
| 241 |
-
for i,ln in enumerate(lines):
|
| 242 |
-
if ln.startswith('# '):
|
| 243 |
-
hw=set(re.findall(r'[A-Za-zÀ-ỹ0-9]+',_jina_strip(ln).lower()))
|
| 244 |
-
if tw and len(tw&hw)>=max(2,len(tw)//2):start=i+1
|
| 245 |
-
body=[];og_img=''
|
| 246 |
-
for ln in lines[start:]:
|
| 247 |
-
low=_jina_strip(ln).lower()
|
| 248 |
-
if low and any(b in low for b in _JINA_BOILER_STOP):break
|
| 249 |
-
if any(a in ln.lower() for a in _JINA_AD_DENY):continue
|
| 250 |
-
mi=re.search(r'!\[([^\]]*)\]\((https?://[^)\s]+)\)',ln)
|
| 251 |
-
if mi and _jina_good_img(mi.group(2),mi.group(1)):
|
| 252 |
-
src=mi.group(2)
|
| 253 |
-
if not og_img:og_img=src
|
| 254 |
-
body.append({'type':'img','src':src});continue
|
| 255 |
-
s=ln.strip();is_h=bool(re.match(r'^#{2,4}\s',s));txt=_jina_strip(ln)
|
| 256 |
-
if not txt or len(txt)<40:continue
|
| 257 |
-
if any(p in txt.lower() for p in _JINA_NAV):continue
|
| 258 |
-
raw=re.sub(r'!\[[^\]]*\]\([^)]*\)','',ln);lt=''.join(re.findall(r'\[([^\]]*)\]\([^)]*\)',raw))
|
| 259 |
-
if len(lt)>len(txt)*0.5:continue
|
| 260 |
-
body.append({'type':'heading' if is_h else 'p','text':txt})
|
| 261 |
-
if len([b for b in body if b['type']!='img'])>=40:break
|
| 262 |
-
if not og_img:
|
| 263 |
-
for ln in lines:
|
| 264 |
-
mi=re.search(r'!\[([^\]]*)\]\((https?://[^)\s]+)\)',ln)
|
| 265 |
-
if mi and _jina_good_img(mi.group(2),mi.group(1)):og_img=mi.group(2);break
|
| 266 |
-
out=[]
|
| 267 |
-
for b in body:
|
| 268 |
-
if out and b==out[-1]:continue
|
| 269 |
-
out.append(b)
|
| 270 |
-
if not title and out:title=next((b['text'] for b in out if b['type']=='p'),'')[:90]
|
| 271 |
-
if not out:return None
|
| 272 |
-
first_p=next((b['text'] for b in out if b['type']=='p'),'')
|
| 273 |
-
return {'title':_clean(title),'summary':_clean(first_p[:200]),'og_image':og_img,'image':og_img,'body':out,'source':'jina','url':url}
|
| 274 |
-
except:return None
|
| 275 |
def _scrape_generic(url):
|
| 276 |
try:
|
| 277 |
-
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'
|
| 278 |
-
if r.status_code>=400 or 'bongdaplus.vn' in url or 'vatvostudio.vn' in url or 'Just a moment' in r.text[:600]:
|
| 279 |
-
jr=_jina_article(url)
|
| 280 |
-
if jr and jr.get('body'):return jr
|
| 281 |
-
soup=BeautifulSoup(r.text,'lxml')
|
| 282 |
for tag in soup.find_all(['script','style','nav','footer','aside','form','noscript']):tag.decompose()
|
| 283 |
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 '')
|
| 284 |
ogd=soup.find('meta',property='og:description');summary=ogd.get('content','') if ogd else ''
|
|
@@ -297,39 +166,19 @@ def _scrape_generic(url):
|
|
| 297 |
im=el if el.name=='img' else el.find('img')
|
| 298 |
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)
|
| 299 |
if not body and summary:body=[{'type':'p','text':summary}]
|
| 300 |
-
if not body:
|
| 301 |
-
jr=_jina_article(url)
|
| 302 |
-
if jr and jr.get('body'):return jr
|
| 303 |
return{'title':_clean(title),'summary':_clean(summary),'og_image':og_img,'body':body[:50],'source':'generic','url':url}
|
| 304 |
-
except:
|
| 305 |
-
return _jina_article(url)
|
| 306 |
-
_ARTICLE_CACHE={};_ARTICLE_TTL=900
|
| 307 |
-
def _article_cache_get(url):
|
| 308 |
-
e=_ARTICLE_CACHE.get(url)
|
| 309 |
-
if e and time.time()-e['t']<_ARTICLE_TTL:return e['d']
|
| 310 |
-
return None
|
| 311 |
-
def _article_cache_put(url,data):
|
| 312 |
-
if data and data.get('body'):
|
| 313 |
-
_ARTICLE_CACHE[url]={'d':data,'t':time.time()}
|
| 314 |
-
if len(_ARTICLE_CACHE)>300:
|
| 315 |
-
for k in sorted(_ARTICLE_CACHE,key=lambda k:_ARTICLE_CACHE[k]['t'])[:100]:_ARTICLE_CACHE.pop(k,None)
|
| 316 |
@app.get('/api/article')
|
| 317 |
def api_article_v2(url:str=Query(...)):
|
| 318 |
-
cached=_article_cache_get(url)
|
| 319 |
-
if cached:return JSONResponse(cached)
|
| 320 |
from main import scrape_vne_article,scrape_bbc_article,scrape_dantri_article,scrape_genk_article,scrape_ttvh_article
|
| 321 |
-
used_generic=False
|
| 322 |
if 'vnexpress.net' in url:data=scrape_vne_article(url)
|
| 323 |
elif 'bbc.com' in url:data=scrape_bbc_article(url)
|
| 324 |
elif 'dantri.com.vn' in url:data=scrape_dantri_article(url)
|
| 325 |
elif 'genk.vn' in url:data=scrape_genk_article(url)
|
| 326 |
elif 'thethaovanhoa.vn' in url:data=scrape_ttvh_article(url)
|
| 327 |
-
else:data=_scrape_generic(url)
|
| 328 |
-
if
|
| 329 |
-
|
| 330 |
-
if data and data.get('body'):
|
| 331 |
-
_article_cache_put(url,data);return JSONResponse(data)
|
| 332 |
-
return JSONResponse(data if data else{'error':'Không đọc được','url':url})
|
| 333 |
_hot_cache={'t':0,'d':[]}
|
| 334 |
def _get_hot_topics():
|
| 335 |
now=time.time()
|
|
@@ -349,22 +198,6 @@ def _get_hot_topics():
|
|
| 349 |
phrase=' '.join(words[i:i+n])
|
| 350 |
if 8<=len(phrase)<=45:key=phrase.lower();freq[key]=freq.get(key,0)+1;display[key]=phrase
|
| 351 |
except:continue
|
| 352 |
-
def _ingest_titles(titles):
|
| 353 |
-
for title in titles:
|
| 354 |
-
title=_clean(title)
|
| 355 |
-
if not title:continue
|
| 356 |
-
title=re.sub(r'\s*[-|].*$','',title)
|
| 357 |
-
words=[w for w in re.findall(r'[A-Za-zÀ-ỹ0-9]+',title) if len(w)>2 and w.lower() not in _STOP]
|
| 358 |
-
if len(words)<2:continue
|
| 359 |
-
for n in(3,4,2):
|
| 360 |
-
for i in range(max(0,len(words)-n+1)):
|
| 361 |
-
phrase=' '.join(words[i:i+n])
|
| 362 |
-
if 8<=len(phrase)<=45:key=phrase.lower();freq[key]=freq.get(key,0)+1;display[key]=phrase
|
| 363 |
-
try:
|
| 364 |
-
from main import scrape_bongda_jina,scrape_bongdaplus_jina
|
| 365 |
-
_ingest_titles([a.get('title','') for a in scrape_bongda_jina(20)])
|
| 366 |
-
_ingest_titles([a.get('title','') for a in scrape_bongdaplus_jina(20)])
|
| 367 |
-
except:pass
|
| 368 |
ranked=sorted(freq.items(),key=lambda x:x[1],reverse=True);topics=[];seen=set()
|
| 369 |
for key,count in ranked:
|
| 370 |
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)
|
|
@@ -390,6 +223,8 @@ def _ht(topic:str=Query(...),page:int=Query(default=0)):
|
|
| 390 |
def _cat():return JSONResponse([])
|
| 391 |
@app.get('/api/storage_status')
|
| 392 |
def _st():return JSONResponse({'persistent':os.path.isdir('/data') and os.access('/data',os.W_OK)})
|
|
|
|
|
|
|
| 393 |
DATA_DIR='/data' if os.path.isdir('/data') else os.path.join(os.path.dirname(os.path.abspath(__file__)),'data')
|
| 394 |
os.makedirs(DATA_DIR,exist_ok=True);IF=os.path.join(DATA_DIR,'interactions_v2.json');CF=os.path.join(DATA_DIR,'comments_v2.json')
|
| 395 |
_il=threading.Lock();_cl=threading.Lock()
|
|
|
|
| 71 |
if len(items)>=limit:break
|
| 72 |
except:pass
|
| 73 |
return items
|
| 74 |
+
def _s_bongda(topic,limit=5):
|
|
|
|
| 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')
|
| 78 |
+
for a in soup.select('h3 a[href], .title a[href]')[:limit*2]:
|
| 79 |
+
t=_clean(a.get_text(strip=True));href=a.get('href','')
|
| 80 |
+
if t and len(t)>15 and _has_kw(topic,t):
|
| 81 |
+
if not href.startswith('http'):href='https://bongda.com.vn'+href
|
| 82 |
+
items.append({'title':t,'url':href,'via':'Bóng Đá'})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
if len(items)>=limit:break
|
| 84 |
except:pass
|
| 85 |
return items
|
|
|
|
| 92 |
if t and len(t)>15 and _has_kw(topic,t):
|
| 93 |
if href.startswith('/'):href='https://genk.vn'+href
|
| 94 |
items.append({'title':t,'url':href,'via':'GenK'})
|
| 95 |
+
if len(items)>=limit:break
|
| 96 |
except:pass
|
| 97 |
return items
|
| 98 |
def _s_thanhnien(topic,limit=6):
|
|
|
|
| 104 |
if t and len(t)>15 and _has_kw(topic,t):
|
| 105 |
if not href.startswith('http'):href='https://thanhnien.vn'+href
|
| 106 |
items.append({'title':t,'url':href,'via':'Thanh Niên'})
|
| 107 |
+
if len(items)>=limit:break
|
| 108 |
except:pass
|
| 109 |
return items
|
| 110 |
def _s_tuoitre(topic,limit=6):
|
|
|
|
| 116 |
if t and len(t)>15 and _has_kw(topic,t):
|
| 117 |
if not href.startswith('http'):href='https://tuoitre.vn'+href
|
| 118 |
items.append({'title':t,'url':href,'via':'Tuổi Trẻ'})
|
| 119 |
+
if len(items)>=limit:break
|
| 120 |
except:pass
|
| 121 |
return items
|
| 122 |
def _s_thethaovanhoa(topic,limit=5):
|
|
|
|
| 128 |
if t and len(t)>15 and _has_kw(topic,t):
|
| 129 |
if not href.startswith('http'):href='https://thethaovanhoa.vn'+href
|
| 130 |
items.append({'title':t,'url':href,'via':'TT&VH'})
|
| 131 |
+
if len(items)>=limit:break
|
| 132 |
except:pass
|
| 133 |
return items
|
| 134 |
def _search_all(topic,limit=36):
|
| 135 |
results={}
|
| 136 |
+
with ThreadPoolExecutor(8) as ex:
|
| 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,5):'bd',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'}
|
| 138 |
+
for f in as_completed(futs,timeout=14):
|
| 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)
|
|
|
|
| 143 |
for s in srcs:
|
| 144 |
if i<len(s) and s[i].get('url') and s[i]['url'] not in seen:seen.add(s[i]['url']);out.append(s[i])
|
| 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';soup=BeautifulSoup(r.text,'lxml')
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
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:return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
|
|
|
| 174 |
if 'vnexpress.net' in url:data=scrape_vne_article(url)
|
| 175 |
elif 'bbc.com' in url:data=scrape_bbc_article(url)
|
| 176 |
elif 'dantri.com.vn' in url:data=scrape_dantri_article(url)
|
| 177 |
elif 'genk.vn' in url:data=scrape_genk_article(url)
|
| 178 |
elif 'thethaovanhoa.vn' in url:data=scrape_ttvh_article(url)
|
| 179 |
+
else:data=_scrape_generic(url)
|
| 180 |
+
if data and data.get('body'):return JSONResponse(data)
|
| 181 |
+
data=_scrape_generic(url);return JSONResponse(data if data else{'error':'Không đọc được','url':url})
|
|
|
|
|
|
|
|
|
|
| 182 |
_hot_cache={'t':0,'d':[]}
|
| 183 |
def _get_hot_topics():
|
| 184 |
now=time.time()
|
|
|
|
| 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)
|
|
|
|
| 223 |
def _cat():return JSONResponse([])
|
| 224 |
@app.get('/api/storage_status')
|
| 225 |
def _st():return JSONResponse({'persistent':os.path.isdir('/data') and os.access('/data',os.W_OK)})
|
| 226 |
+
@app.get('/s')
|
| 227 |
+
async def _sh(url:str='',title:str='',img:str=''):return HTMLResponse(f'<!DOCTYPE html><html><head><meta property="og:title" content="{_clean(title)}"><meta property="og:image" content="{_clean(img)}"><meta http-equiv="refresh" content="0;url={_clean(url) or "/"}"></head><body></body></html>')
|
| 228 |
DATA_DIR='/data' if os.path.isdir('/data') else os.path.join(os.path.dirname(os.path.abspath(__file__)),'data')
|
| 229 |
os.makedirs(DATA_DIR,exist_ok=True);IF=os.path.join(DATA_DIR,'interactions_v2.json');CF=os.path.join(DATA_DIR,'comments_v2.json')
|
| 230 |
_il=threading.Lock();_cl=threading.Lock()
|