Spaces:
Running
Running
Fix article reader for vatvostudio/bongda/bongdaplus: clean body (strip nav/ads/related), extract cover + inline images
Browse files- app_v2_entry.py +64 -21
app_v2_entry.py
CHANGED
|
@@ -167,31 +167,74 @@ def _search_all(topic,limit=36):
|
|
| 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
|
|
|
|
| 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=
|
| 176 |
-
title=''
|
| 177 |
-
for ln in lines[:
|
| 178 |
-
if ln.startswith('Title:')
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
if
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 195 |
except:return None
|
| 196 |
def _scrape_generic(url):
|
| 197 |
try:
|
|
|
|
| 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 |
+
_JINA_BOILER_STOP=['facebooktelegram','copy link','follow us','bài viết liên quan','tin liên quan',
|
| 171 |
+
'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',
|
| 172 |
+
'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',
|
| 173 |
+
'cập nhật tin tức, thủ thuật']
|
| 174 |
+
_JINA_NAV=['đăng nhập','tạo tài khoản','reading:','sponsored by']
|
| 175 |
+
_JINA_IMG_DENY=['logo','avatar','sponsor','vvaba','ductrinh','longnguyen','hoangtrinh','vvs_logo',
|
| 176 |
+
'cropped-','login.png','/icon','placeholder','/ads/','banner','giaminhmedia','-330x220','-420x280',
|
| 177 |
+
'-150x','-100x','thumb']
|
| 178 |
+
_JINA_AD_DENY=['giaminhmedia','delivery/cl.php','[hot]','[video','săn ngay','giảm giá','khuyến mãi',
|
| 179 |
+
'mua ngay','đặt mua','shopee','lazada','tiki.vn']
|
| 180 |
+
def _jina_strip(s):
|
| 181 |
+
s=re.sub(r'!\[[^\]]*\]\([^)]*\)','',s)
|
| 182 |
+
s=re.sub(r'\[([^\]]*)\]\([^)]*\)',r'\1',s)
|
| 183 |
+
s=re.sub(r'</?[a-zA-Z][^>]*>','',s)
|
| 184 |
+
s=re.sub(r'[#>*`_]+','',s)
|
| 185 |
+
return re.sub(r'\s+',' ',s).strip()
|
| 186 |
+
def _jina_good_img(u,alt=''):
|
| 187 |
+
ul=u.lower();al=(alt or '').lower()
|
| 188 |
+
if any(k in ul for k in _JINA_IMG_DENY):return False
|
| 189 |
+
if 'vật vờ studio' in al or 'sponsored' in al:return False
|
| 190 |
+
if not re.search(r'\.(jpg|jpeg|png|webp|gif)',ul) and 'imgthumbnail' not in ul and '/media/' not in ul.lower():return False
|
| 191 |
+
return True
|
| 192 |
def _jina_article(url):
|
| 193 |
+
"""Read ONLY the main article body via r.jina.ai (Cloudflare/SPA sites: vatvostudio, bongdaplus, bongda).
|
| 194 |
+
Strips navigation menus, related-article lists, comments, ads and footer; keeps cover + inline images."""
|
| 195 |
try:
|
| 196 |
r=req.get("https://r.jina.ai/"+url,headers={'Accept':'text/markdown','X-Return-Format':'markdown','User-Agent':'Mozilla/5.0'},timeout=35)
|
| 197 |
if r.status_code!=200 or not r.text:return None
|
| 198 |
+
lines=r.text.splitlines()
|
| 199 |
+
title=''
|
| 200 |
+
for ln in lines[:30]:
|
| 201 |
+
if ln.startswith('Title:'):title=ln.replace('Title:','',1).strip();break
|
| 202 |
+
tw=set(w for w in re.findall(r'[A-Za-zÀ-ỹ0-9]+',title.lower()) if len(w)>3)
|
| 203 |
+
# body starts after the LAST markdown H1 matching the title (= the real headline)
|
| 204 |
+
start=0
|
| 205 |
+
for i,ln in enumerate(lines):
|
| 206 |
+
if ln.startswith('# '):
|
| 207 |
+
hw=set(re.findall(r'[A-Za-zÀ-ỹ0-9]+',_jina_strip(ln).lower()))
|
| 208 |
+
if tw and len(tw&hw)>=max(2,len(tw)//2):start=i+1
|
| 209 |
+
body=[];og_img=''
|
| 210 |
+
for ln in lines[start:]:
|
| 211 |
+
low=_jina_strip(ln).lower()
|
| 212 |
+
if low and any(b in low for b in _JINA_BOILER_STOP):break # related/footer -> stop
|
| 213 |
+
if any(a in ln.lower() for a in _JINA_AD_DENY):continue # skip ads/video embeds
|
| 214 |
+
mi=re.search(r'!\[([^\]]*)\]\((https?://[^)\s]+)\)',ln)
|
| 215 |
+
if mi and _jina_good_img(mi.group(2),mi.group(1)):
|
| 216 |
+
src=mi.group(2)
|
| 217 |
+
if not og_img:og_img=src
|
| 218 |
+
body.append({'type':'img','src':src});continue
|
| 219 |
+
s=ln.strip();is_h=bool(re.match(r'^#{2,4}\s',s));txt=_jina_strip(ln)
|
| 220 |
+
if not txt or len(txt)<40:continue
|
| 221 |
+
if any(p in txt.lower() for p in _JINA_NAV):continue
|
| 222 |
+
raw=re.sub(r'!\[[^\]]*\]\([^)]*\)','',ln);lt=''.join(re.findall(r'\[([^\]]*)\]\([^)]*\)',raw))
|
| 223 |
+
if len(lt)>len(txt)*0.5:continue # mostly link -> nav junk
|
| 224 |
+
body.append({'type':'heading' if is_h else 'p','text':txt})
|
| 225 |
+
if len([b for b in body if b['type']!='img'])>=40:break
|
| 226 |
+
if not og_img:
|
| 227 |
+
for ln in lines:
|
| 228 |
+
mi=re.search(r'!\[([^\]]*)\]\((https?://[^)\s]+)\)',ln)
|
| 229 |
+
if mi and _jina_good_img(mi.group(2),mi.group(1)):og_img=mi.group(2);break
|
| 230 |
+
out=[]
|
| 231 |
+
for b in body:
|
| 232 |
+
if out and b==out[-1]:continue
|
| 233 |
+
out.append(b)
|
| 234 |
+
if not title and out:title=next((b['text'] for b in out if b['type']=='p'),'')[:90]
|
| 235 |
+
if not out:return None
|
| 236 |
+
first_p=next((b['text'] for b in out if b['type']=='p'),'')
|
| 237 |
+
return {'title':_clean(title),'summary':_clean(first_p[:200]),'og_image':og_img,'image':og_img,'body':out,'source':'jina','url':url}
|
| 238 |
except:return None
|
| 239 |
def _scrape_generic(url):
|
| 240 |
try:
|