bep40 commited on
Commit
2fff765
·
verified ·
1 Parent(s): 68024cb

Fix VnEgo (use /video page), DanTri always OG image, frontend DanTri in-app read

Browse files
Files changed (1) hide show
  1. main.py +43 -37
main.py CHANGED
@@ -25,8 +25,8 @@ def _get(url,headers=None):
25
  h=headers or HEADERS;r=requests.get(url,headers=h,timeout=15);r.encoding="utf-8"
26
  return BeautifulSoup(r.text,"lxml")
27
 
28
- # ===== DANTRI =====
29
  def scrape_dantri_hot():
 
30
  try:
31
  soup=_get("https://dantri.com.vn/tin-nong.htm")
32
  arts=[];seen=set()
@@ -37,36 +37,27 @@ def scrape_dantri_hot():
37
  if href in seen or not href.endswith(".htm"):continue
38
  if href=="https://dantri.com.vn/tin-nong.htm":continue
39
  seen.add(href)
40
- img=a.find("img")
41
- if not img and a.parent:img=a.parent.find("img")
42
- img_src=""
43
- if img:img_src=img.get("data-src") or img.get("src","")
44
- if img_src and("base64" in img_src or len(img_src)<10):img_src=""
45
- arts.append({"title":title,"link":href,"img":img_src,"source":"dantri"})
46
- if len(arts)>=15:break
47
  def _og(art):
48
- if art["img"]:return
49
  try:
50
  r2=requests.get(art["link"],headers=HEADERS,timeout=8);r2.encoding="utf-8"
51
  og=BeautifulSoup(r2.text,"lxml").find("meta",property="og:image")
52
  art["img"]=og.get("content","") if og else ""
53
  except:pass
54
- need=[a for a in arts if not a["img"]][:5]
55
- if need:
56
- with ThreadPoolExecutor(3) as ex:list(ex.map(_og,need))
57
- return arts[:12]
58
  except:return[]
59
 
60
  def scrape_dantri_article(url):
61
- """Parse dantri.com.vn article for in-app reading."""
62
  try:
63
  r=requests.get(url,headers=HEADERS,timeout=15);r.encoding="utf-8"
64
  soup=BeautifulSoup(r.text,"lxml")
65
  for tag in soup.find_all(["script","style","nav","footer","aside"]):tag.decompose()
66
  h1=soup.find("h1")
67
  og=soup.find("meta",property="og:image");og_img=og.get("content","") if og else ""
68
- # DanTri content selectors
69
- content=soup.select_one("div.singular-content") or soup.select_one("div.e-magazine__body") or soup.select_one("article.e-magazine") or soup.select_one("div.detail-content")
70
  body=[]
71
  if content:
72
  for el in content.find_all(["p","h2","h3","figure","img"],recursive=True):
@@ -81,7 +72,6 @@ def scrape_dantri_article(url):
81
  if im:
82
  s=im.get("data-src") or im.get("src","")
83
  if s and"base64" not in s and len(s)>10:body.append({"type":"img","src":s,"alt":im.get("alt","")})
84
- # Fallback: find all p in page if content block empty
85
  if not body:
86
  for p in soup.find_all("p"):
87
  t=p.get_text(strip=True)
@@ -93,32 +83,50 @@ def scrape_dantri_article(url):
93
  return{"title":h1.get_text(strip=True) if h1 else "","summary":desc,"og_image":og_img,"body":body,"source":"dantri","url":url}
94
  except:return None
95
 
96
- # ===== VNEXPRESS VIDEO (VnEgo) =====
97
  def scrape_vne_video():
98
- """Scrape VnExpress VnEgo section."""
99
  try:
100
- soup=_get("https://vnexpress.net/vne-go")
101
  arts=[];seen=set()
102
- for a in soup.find_all("a",href=True):
 
 
103
  href=a.get("href","");title=a.get("title","") or a.get_text(strip=True)
104
- if not title or len(title)<8 or"javascript:" in href:continue
105
- if not href.startswith("http"):continue
106
- if href in seen:continue
107
- if"/video/" not in href and"/vne-go/" not in href:continue
108
  seen.add(href)
109
- img=a.find("img")
110
- if not img:
111
- parent=a.parent
112
- if parent:img=parent.find("img")
113
- img_src=""
114
- if img:
115
- img_src=img.get("data-src") or img.get("src","")
116
  if img_src and"blank" in img_src:
117
- source=img.find_next("source") or (a.find("source") if a else None)
118
  if source:img_src=source.get("srcset","").split(",")[0].strip().split(" ")[0]
119
- if not img_src or len(img_src)<10:img_src=""
120
  arts.append({"title":title,"link":href,"img":img_src,"source":"vne-video"})
121
- if len(arts)>=15:break
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  return arts[:12]
123
  except:return[]
124
 
@@ -280,7 +288,6 @@ def extract_bdp_video(url):
280
  return{"src":source.get("src","") if source else "","poster":video.get("poster","")}
281
  except:return None
282
 
283
- # All VnExpress categories
284
  VNE_CATS={"thoi-su":("https://vnexpress.net/thoi-su","Thời Sự"),"the-gioi":("https://vnexpress.net/the-gioi","Thế Giới"),"kinh-doanh":("https://vnexpress.net/kinh-doanh","Kinh Doanh"),"cong-nghe":("https://vnexpress.net/so-hoa","Công Nghệ"),"the-thao":("https://vnexpress.net/the-thao","Thể Thao"),"giai-tri":("https://vnexpress.net/giai-tri","Giải Trí"),"suc-khoe":("https://vnexpress.net/suc-khoe","Sức Khỏe"),"giao-duc":("https://vnexpress.net/giao-duc","Giáo Dục"),"phap-luat":("https://vnexpress.net/phap-luat","Pháp Luật"),"du-lich":("https://vnexpress.net/du-lich","Du Lịch"),"xe":("https://vnexpress.net/oto-xe-may","Xe"),"doi-song":("https://vnexpress.net/doi-song","Đời Sống")}
285
 
286
  @app.get("/api/homepage")
@@ -288,7 +295,6 @@ def api_homepage():
288
  def _f():
289
  articles=[]
290
  with ThreadPoolExecutor(8) as ex:
291
- # Load ALL VNE categories for homepage
292
  futs={ex.submit(scrape_vne,VNE_CATS[k][0]):VNE_CATS[k][1] for k in VNE_CATS}
293
  futs[ex.submit(scrape_bbc_vietnamese)]="BBC"
294
  for f in as_completed(futs):
 
25
  h=headers or HEADERS;r=requests.get(url,headers=h,timeout=15);r.encoding="utf-8"
26
  return BeautifulSoup(r.text,"lxml")
27
 
 
28
  def scrape_dantri_hot():
29
+ """Scrape DanTri tin nong - always fetch OG image for each article."""
30
  try:
31
  soup=_get("https://dantri.com.vn/tin-nong.htm")
32
  arts=[];seen=set()
 
37
  if href in seen or not href.endswith(".htm"):continue
38
  if href=="https://dantri.com.vn/tin-nong.htm":continue
39
  seen.add(href)
40
+ arts.append({"title":title,"link":href,"img":"","source":"dantri"})
41
+ if len(arts)>=12:break
42
+ # Always fetch OG image for ALL DanTri articles
 
 
 
 
43
  def _og(art):
 
44
  try:
45
  r2=requests.get(art["link"],headers=HEADERS,timeout=8);r2.encoding="utf-8"
46
  og=BeautifulSoup(r2.text,"lxml").find("meta",property="og:image")
47
  art["img"]=og.get("content","") if og else ""
48
  except:pass
49
+ with ThreadPoolExecutor(4) as ex:list(ex.map(_og,arts[:10]))
50
+ return [a for a in arts if a["img"]][:10]
 
 
51
  except:return[]
52
 
53
  def scrape_dantri_article(url):
 
54
  try:
55
  r=requests.get(url,headers=HEADERS,timeout=15);r.encoding="utf-8"
56
  soup=BeautifulSoup(r.text,"lxml")
57
  for tag in soup.find_all(["script","style","nav","footer","aside"]):tag.decompose()
58
  h1=soup.find("h1")
59
  og=soup.find("meta",property="og:image");og_img=og.get("content","") if og else ""
60
+ content=soup.select_one("div.singular-content") or soup.select_one("div.e-magazine__body") or soup.select_one("article")
 
61
  body=[]
62
  if content:
63
  for el in content.find_all(["p","h2","h3","figure","img"],recursive=True):
 
72
  if im:
73
  s=im.get("data-src") or im.get("src","")
74
  if s and"base64" not in s and len(s)>10:body.append({"type":"img","src":s,"alt":im.get("alt","")})
 
75
  if not body:
76
  for p in soup.find_all("p"):
77
  t=p.get_text(strip=True)
 
83
  return{"title":h1.get_text(strip=True) if h1 else "","summary":desc,"og_image":og_img,"body":body,"source":"dantri","url":url}
84
  except:return None
85
 
 
86
  def scrape_vne_video():
87
+ """Scrape VnExpress video page (server-rendered, not SPA like /vne-go)."""
88
  try:
89
+ soup=_get("https://vnexpress.net/video")
90
  arts=[];seen=set()
91
+ for it in soup.select("article.item-news")[:15]:
92
+ a=it.select_one("a[href]")
93
+ if not a:continue
94
  href=a.get("href","");title=a.get("title","") or a.get_text(strip=True)
95
+ if not title or len(title)<8 or href in seen:continue
 
 
 
96
  seen.add(href)
97
+ im=it.find("img");img_src=""
98
+ if im:
99
+ img_src=im.get("data-src") or im.get("src","")
 
 
 
 
100
  if img_src and"blank" in img_src:
101
+ source=it.find("source")
102
  if source:img_src=source.get("srcset","").split(",")[0].strip().split(" ")[0]
 
103
  arts.append({"title":title,"link":href,"img":img_src,"source":"vne-video"})
104
+ # Fallback: if article.item-news not found, try generic links
105
+ if not arts:
106
+ for a in soup.find_all("a",href=True):
107
+ href=a.get("href","");title=a.get("title","") or a.get_text(strip=True)
108
+ if not title or len(title)<10:continue
109
+ if not href.startswith("http") or href in seen:continue
110
+ if"/video/" not in href:continue
111
+ seen.add(href)
112
+ img=a.find("img")
113
+ if not img and a.parent:img=a.parent.find("img")
114
+ img_src=""
115
+ if img:img_src=img.get("data-src") or img.get("src","")
116
+ if img_src and("blank" in img_src or"base64" in img_src):img_src=""
117
+ arts.append({"title":title,"link":href,"img":img_src,"source":"vne-video"})
118
+ if len(arts)>=12:break
119
+ # Fetch OG for missing images
120
+ def _og(art):
121
+ if art["img"]:return
122
+ try:
123
+ r2=requests.get(art["link"],headers=HEADERS,timeout=8);r2.encoding="utf-8"
124
+ og=BeautifulSoup(r2.text,"lxml").find("meta",property="og:image")
125
+ art["img"]=og.get("content","") if og else ""
126
+ except:pass
127
+ need=[a for a in arts if not a["img"]][:5]
128
+ if need:
129
+ with ThreadPoolExecutor(3) as ex:list(ex.map(_og,need))
130
  return arts[:12]
131
  except:return[]
132
 
 
288
  return{"src":source.get("src","") if source else "","poster":video.get("poster","")}
289
  except:return None
290
 
 
291
  VNE_CATS={"thoi-su":("https://vnexpress.net/thoi-su","Thời Sự"),"the-gioi":("https://vnexpress.net/the-gioi","Thế Giới"),"kinh-doanh":("https://vnexpress.net/kinh-doanh","Kinh Doanh"),"cong-nghe":("https://vnexpress.net/so-hoa","Công Nghệ"),"the-thao":("https://vnexpress.net/the-thao","Thể Thao"),"giai-tri":("https://vnexpress.net/giai-tri","Giải Trí"),"suc-khoe":("https://vnexpress.net/suc-khoe","Sức Khỏe"),"giao-duc":("https://vnexpress.net/giao-duc","Giáo Dục"),"phap-luat":("https://vnexpress.net/phap-luat","Pháp Luật"),"du-lich":("https://vnexpress.net/du-lich","Du Lịch"),"xe":("https://vnexpress.net/oto-xe-may","Xe"),"doi-song":("https://vnexpress.net/doi-song","Đời Sống")}
292
 
293
  @app.get("/api/homepage")
 
295
  def _f():
296
  articles=[]
297
  with ThreadPoolExecutor(8) as ex:
 
298
  futs={ex.submit(scrape_vne,VNE_CATS[k][0]):VNE_CATS[k][1] for k in VNE_CATS}
299
  futs[ex.submit(scrape_bbc_vietnamese)]="BBC"
300
  for f in as_completed(futs):