-
Notifications
You must be signed in to change notification settings - Fork 107
Add VN publisher (VnExpress) #802
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| from fundus.publishers.base_objects import Publisher, PublisherGroup | ||
| from fundus.publishers.vn.vnexpress import VnExpressIntlParser | ||
| from fundus.scraping.filter import inverse, regex_filter | ||
| from fundus.scraping.url import NewsMap, RSSFeed, Sitemap | ||
|
|
||
| class VN(metaclass=PublisherGroup): | ||
| default_language = "vi" | ||
|
|
||
| VnExpress = Publisher( | ||
| name="VnExpress", | ||
| domain="https://vnexpress.net/", | ||
| parser=VnExpressIntlParser, | ||
| sources=[ | ||
| RSSFeed("https://vnexpress.net/rss/tin-moi-nhat.rss"), | ||
| Sitemap("https://vnexpress.net/sitemap.xml"), | ||
| NewsMap("https://vnexpress.net/google-news-sitemap.xml"), | ||
| ], | ||
| suppress_robots=True, | ||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,108 @@ | ||
| from datetime import datetime | ||
| from typing import List, Optional, Any | ||
| from lxml.cssselect import CSSSelector | ||
| from lxml.etree import XPath | ||
| from fundus.parser import ArticleBody, BaseParser, ParserProxy, attribute | ||
| from fundus.parser.utility import ( | ||
| extract_article_body_with_selector, | ||
| generic_author_parsing, | ||
| generic_date_parsing, | ||
| ) | ||
|
|
||
|
|
||
| class VnExpressIntlParser(ParserProxy): | ||
| class V1(BaseParser): | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
| _summary_selector = CSSSelector("p.description") | ||
| _paragraph_selector = CSSSelector("article.fck_detail > p") | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In this article, the author is also extracted from the bottom of the article. |
||
| _subheadline_selector = CSSSelector("article.fck_detail > h2") | ||
|
|
||
| @attribute | ||
| def title(self) -> Optional[str]: | ||
| title_list: List[Any] = self.precomputed.ld.xpath_search("//NewsArticle/headline") | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can use |
||
| if title_list and isinstance(title_list[0], str): | ||
| title: str = title_list[0] | ||
| return title | ||
|
|
||
| title_meta = self.precomputed.meta.get("og:title") | ||
| if title_meta and isinstance(title_meta, str): | ||
| return title_meta | ||
|
|
||
| title_nodes = CSSSelector("h1.title-detail")(self.precomputed.doc) | ||
| if title_nodes: | ||
| title_text: str = title_nodes[0].text_content().strip() | ||
| return title_text | ||
|
|
||
| return None | ||
|
|
||
| @attribute | ||
| def authors(self) -> List[str]: | ||
| author_data_list: List[Any] = self.precomputed.ld.xpath_search("//NewsArticle/author") | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can pass in whatever you get back directly into |
||
| if author_data_list: | ||
| author_ld = author_data_list[0] | ||
| authors = generic_author_parsing(author_ld) | ||
| if authors: | ||
| return authors | ||
|
|
||
| author_nodes = CSSSelector("p.author_mail strong")(self.precomputed.doc) | ||
| if author_nodes: | ||
| return [node.text_content().strip() for node in author_nodes if node.text_content().strip()] | ||
|
|
||
| return [] | ||
|
|
||
| @attribute | ||
| def publishing_date(self) -> Optional[datetime]: | ||
| date_list: List[Any] = self.precomputed.ld.xpath_search("//NewsArticle/datePublished") | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here, you can use |
||
| if date_list and isinstance(date_list[0], str): | ||
| date_str: str = date_list[0] | ||
| return generic_date_parsing(date_str) | ||
|
|
||
| date_meta = self.precomputed.meta.get("article:published_time") | ||
| if date_meta and isinstance(date_meta, str): | ||
| return generic_date_parsing(date_meta) | ||
|
|
||
| return None | ||
|
|
||
| @attribute | ||
| def body(self) -> Optional[ArticleBody]: | ||
| return extract_article_body_with_selector( | ||
| self.precomputed.doc, | ||
| summary_selector=self._summary_selector, | ||
| paragraph_selector=self._paragraph_selector, | ||
| subheadline_selector=self._subheadline_selector, | ||
| ) | ||
|
|
||
| def _parse_ld_keywords(self) -> List[str]: | ||
| keywords_list: List[Any] = self.precomputed.ld.xpath_search("//NewsArticle/keywords") | ||
|
|
||
| if not keywords_list: | ||
| return [] | ||
|
|
||
| keywords = keywords_list[0] if keywords_list else None | ||
|
|
||
| result: List[str] = [] | ||
| if isinstance(keywords, list): | ||
| for item in keywords: | ||
| if isinstance(item, str): | ||
| result.extend([k.strip() for k in item.split(',') if k.strip()]) | ||
| elif isinstance(item, list): | ||
| result.extend([k.strip() for k in item if isinstance(k, str) and k.strip()]) | ||
| elif isinstance(keywords, str): | ||
| result = [k.strip() for k in keywords.split(',') if k.strip()] | ||
|
|
||
| return result | ||
|
|
||
| def _parse_meta_topics(self) -> List[str]: | ||
| section = self.precomputed.meta.get("article:section") | ||
| if section and isinstance(section, str): | ||
| return [section] | ||
| meta_keywords = self.precomputed.meta.get("keywords") | ||
| if meta_keywords and isinstance(meta_keywords, str): | ||
| return [k.strip() for k in meta_keywords.split(',') if k.strip()] | ||
| return [] | ||
|
|
||
| @attribute | ||
| def topics(self) -> List[str]: | ||
| ld_topics = self._parse_ld_keywords() | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can simplify this greatly by just using |
||
| if ld_topics: | ||
| return ld_topics | ||
| return self._parse_meta_topics() | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are also some bloat topic like |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -83,8 +83,7 @@ def url_filter(url: str) -> bool: | |
|
|
||
|
|
||
| class SupportsBool(Protocol): | ||
| def __bool__(self) -> bool: | ||
| ... | ||
| def __bool__(self) -> bool: ... | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You probably have a different |
||
|
|
||
|
|
||
| class ExtractionFilter(Protocol): | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| { | ||
| "V1": { | ||
| "authors": [ | ||
| "VnExpress" | ||
| ], | ||
| "body": { | ||
| "summary": [ | ||
| "Người đàn ông 63 tuổi đau bụng kéo dài, bác sĩ chẩn đoán ung thư lymphoma ruột non - căn bệnh ít gặp trong hệ tiêu hóa." | ||
| ], | ||
| "sections": [ | ||
| { | ||
| "headline": [], | ||
| "paragraphs": [ | ||
| "Ngày 22/10, BS.CK2 Cao Thị Hồng, Trưởng Trung tâm Kiểm tra sức khỏe Chợ Rẫy Việt Nhật (HECI), Bệnh viện Chợ Rẫy, cho biết bệnh nhân đau bụng âm ỉ, ăn uống khó tiêu, đầy hơi và tiêu chảy kéo dài nhiều tuần, sụt 2-3 kg. Ông nghĩ bị rối loạn tiêu hóa thông thường, song uống thuốc không khỏi.", | ||
| "Bác sĩ đánh giá bệnh nhân nhiều dấu hiệu bất thường nên nội soi dạ dày và đại tràng lấy mẫu sinh thiết phát hiện tế bào lympho không điển hình, nghi ngờ ác tính. Xét nghiệm hóa mô miễn dịch xác định bệnh nhân mắc ung thư lymphoma không Hodgkin ruột non. Bác sĩ nhiều chuyên khoa tiêu hóa, huyết học, giải phẫu bệnh cùng hội chẩn, xây dựng phác đồ điều trị cho bệnh nhân.", | ||
| "Theo bác sĩ Hồng, lymphoma đường ruột là u lympho ác tính ngoài hạch phát triển từ mô lympho trong thành ruột, chiếm khoảng 15-25% ca ung thư ruột non, gần 5% ca ung thư dạ dày và 0,2-1% ung thư đại trực tràng. Hầu hết lymphoma đường tiêu hóa thuộc nhóm không Hodgkin, với tỷ lệ mắc mới toàn cầu khoảng 5,6 ca trên 100.000 dân mỗi năm.", | ||
| "Bệnh xuất hiện chủ yếu ở người trên 60 tuổi, đặc biệt là nam giới. Lymphoma đường ruột thường có các triệu chứng như đau bụng, sụt cân, buồn nôn, tiêu chảy hoặc tiêu phân có máu...", | ||
| "Nội soi và tầm soát sức khỏe định kỳ giúp phát hiện sớm các bệnh lý ác tính đường tiêu hóa. Hướng điều trị thường phụ thuộc vào loại cũng như giai đoạn bệnh, có thể phẫu thuật, hóa trị hoặc xạ trị.", | ||
| "Lê Phương" | ||
| ] | ||
| } | ||
| ] | ||
| }, | ||
| "publishing_date": "2025-10-22 19:25:52+07:00", | ||
| "title": "Đau bụng kéo dài, phát hiện ung thư lymphoma ruột non", | ||
| "topics": [ | ||
| "ung thư ruột non", | ||
| "đau bụng", | ||
| "rối loạn tiêu hóa" | ||
| ] | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| { | ||
| "VnExpress_2025_10_22.html.gz": { | ||
| "url": "https://vnexpress.net/dau-bung-keo-dai-phat-hien-ung-thu-lymphoma-ruot-non-4954454.html", | ||
| "crawl_date": "2025-10-22 14:57:05.843578" | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like they tricked you with the sitemap links, They all redirect you to home
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead you can add the other RSSFeeds from here: https://vnexpress.net/rss as sources