bep40 commited on
Commit
43634cd
·
verified ·
1 Parent(s): 2ef4f2f

Fix article reader for vatvostudio/bongda/bongdaplus: clean body (strip nav/ads/related), extract cover + inline images

Browse files
Files changed (1) hide show
  1. 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 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:
 
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: