diff --git a/docs/supported_publishers.md b/docs/supported_publishers.md index 2f9daf60..d8cf5972 100644 --- a/docs/supported_publishers.md +++ b/docs/supported_publishers.md @@ -1284,6 +1284,21 @@     + + + MainichiShimbun + + +
Mainichi Shimbun
+ + + + mainichi.jp + + +   +   + TheJapanNews diff --git a/src/fundus/parser/utility.py b/src/fundus/parser/utility.py index 6895902b..e67d422e 100644 --- a/src/fundus/parser/utility.py +++ b/src/fundus/parser/utility.py @@ -391,11 +391,20 @@ def generic_text_extraction_with_css(doc, selector: XPath) -> Optional[str]: return strip_nodes_to_text(nodes) -def generic_topic_parsing(keywords: Optional[Union[str, List[str]]], delimiter: str = ",") -> List[str]: +def generic_topic_parsing( + keywords: Optional[Union[str, List[str]]], delimiter: Union[str, List[str]] = "," +) -> List[str]: + if isinstance(delimiter, str): + delimiter = [delimiter] + if not keywords: topics = [] elif isinstance(keywords, str): - topics = [cleaned for keyword in keywords.split(delimiter) if (cleaned := keyword.strip())] + topics = [ + cleaned + for keyword in re.split(pattern=f"[{''.join(delimiter)}]", string=keywords) + if (cleaned := keyword.strip()) + ] elif isinstance(keywords, list) and all(isinstance(s, str) for s in keywords): topics = keywords else: diff --git a/src/fundus/publishers/jp/__init__.py b/src/fundus/publishers/jp/__init__.py index 13a28c01..2b37673c 100644 --- a/src/fundus/publishers/jp/__init__.py +++ b/src/fundus/publishers/jp/__init__.py @@ -1,10 +1,11 @@ from fundus.publishers.base_objects import Publisher, PublisherGroup from fundus.publishers.jp.asahi_shimbun import AsahiShimbunParser +from fundus.publishers.jp.mainichi_shimbun import MainichiShimbunParser from fundus.publishers.jp.the_japan_news import TheJapanNewsParser from fundus.publishers.jp.tokyo_chunichi_shimbun import TokyoChunichiShimbunParser from fundus.publishers.jp.yomiuri_shimbun import YomiuriShimbunParser from fundus.scraping.filter import regex_filter -from fundus.scraping.url import NewsMap, Sitemap +from fundus.scraping.url import NewsMap, RSSFeed, Sitemap class JP(metaclass=PublisherGroup): @@ -51,3 +52,12 @@ class JP(metaclass=PublisherGroup): parser=TokyoChunichiShimbunParser, sources=[NewsMap("https://www.chunichi.co.jp/sitemap.xml")], ) + + MainichiShimbun = Publisher( + name="Mainichi Shimbun", + domain="https://mainichi.jp/", + parser=MainichiShimbunParser, + sources=[ + RSSFeed("https://mainichi.jp/rss/etc/mainichi-flash.rss"), + ], + ) diff --git a/src/fundus/publishers/jp/mainichi_shimbun.py b/src/fundus/publishers/jp/mainichi_shimbun.py new file mode 100644 index 00000000..7521ddd5 --- /dev/null +++ b/src/fundus/publishers/jp/mainichi_shimbun.py @@ -0,0 +1,66 @@ +import datetime +import re +from typing import List, Optional + +from lxml.cssselect import CSSSelector +from lxml.etree import XPath + +from fundus.parser import ArticleBody, BaseParser, Image, ParserProxy, attribute +from fundus.parser.utility import ( + apply_substitution_pattern_over_list, + extract_article_body_with_selector, + generic_author_parsing, + generic_date_parsing, + generic_topic_parsing, + image_extraction, + normalize_whitespace, +) + + +class MainichiShimbunParser(ParserProxy): + class V1(BaseParser): + _paragraph_selector = CSSSelector("#articledetail-body > p") + _subheadline_selector = CSSSelector("#articledetail-body > h2") + + _topic_bloat_pattern = re.compile("速報") + + @attribute + def body(self) -> Optional[ArticleBody]: + return extract_article_body_with_selector( + self.precomputed.doc, + paragraph_selector=self._paragraph_selector, + subheadline_selector=self._subheadline_selector, + ) + + @attribute + def title(self) -> Optional[str]: + if (title := self.precomputed.meta.get("title")) is not None: + return normalize_whitespace(title) + return None + + @attribute + def publishing_date(self) -> Optional[datetime.datetime]: + return generic_date_parsing(self.precomputed.ld.bf_search("datePublished")) + + @attribute + def authors(self) -> List[str]: + return generic_author_parsing(self.precomputed.meta.get("cXenseParse:author")) + + @attribute + def topics(self) -> List[str]: + return apply_substitution_pattern_over_list( + generic_topic_parsing(self.precomputed.meta.get("keywords"), delimiter=[",", "・"]), + self._topic_bloat_pattern, + ) + + @attribute + def images(self) -> List[Image]: + return image_extraction( + doc=self.precomputed.doc, + paragraph_selector=self._paragraph_selector, + image_selector=XPath("//figure//img[not(ancestor::a[contains(@class,'articledetail-image-scale')])]"), + upper_boundary_selector=CSSSelector("#main"), + # https://regex101.com/r/awU0Rq/1 + author_selector=re.compile(r"(、|=(?=.*?撮影$))(?P[^、]*?)(撮影)?\s*$"), + relative_urls=True, + ) diff --git a/tests/resources/parser/test_data/jp/MainichiShimbun.json b/tests/resources/parser/test_data/jp/MainichiShimbun.json new file mode 100644 index 00000000..d290dd44 --- /dev/null +++ b/tests/resources/parser/test_data/jp/MainichiShimbun.json @@ -0,0 +1,61 @@ +{ + "V1": { + "authors": [ + "松岡大地" + ], + "body": { + "summary": [], + "sections": [ + { + "headline": [], + "paragraphs": [ + "パレスチナ自治区ガザ地区のイスラム組織ハマスとイスラエルの停戦交渉を仲介しているカタールの外務省報道官は14日、「これまでで最も合意に近づいている」と語った。対立する双方ともに停戦合意の最終草案に同意しているとみられる。早期停戦を求めるトランプ次期米大統領の就任が20日に迫る中、停戦実現の期待が高まっている。", + "米ニュースサイト「アクシオス」によると、イスラエルと仲介国は最終草案に合意。ハマスも14日の声明で、指導部が交渉内容に満足していると明らかにし、カタールでの今回の交渉で「明確で包括的な合意」がまとまることへの期待を示した。", + "アクシオスが報じた合意案によると、「第1段階」で42日間の停戦を実施。イスラエル軍はガザとエジプトの境界などから徐々に撤退し、ハマスは女性や子どもなど33人の人質を解放する。第1段階の停戦中に、恒久的停戦やイスラエル軍の完全撤退を含む「第2段階」に向けた協議を始めるという。", + "停戦に向けた交渉が大詰めを迎える中、バイデン米大統領は13日、カタールのタミム首長と停戦合意に向けて電話協議を実施。ハマスもタミム氏やトルコの諜報(ちょうほう)機関トップと協議した。", + "こうした動きにネタニヤフ連立政権の一角を占める極右政党は反発。対パレスチナ強硬派のスモトリッチ財務相は、停戦合意を「大惨事」だとし、合意に賛成しない方針を示した。ネタニヤフ首相はベングビール国家治安相と会談し、政権維持へ協力を求めた。", + "イスラエル軍は13日もガザ地区への攻撃を続け、中東メディアによると、45人が死亡した。ガザ保健当局によると、2023年10月の戦闘開始以降のガザ側の死者は、4万6584人になった。", + "一方で、イスラエル軍は13日、ガザ北部の戦闘でイスラエル兵5人が死亡したと発表した。ガザでは約100人の人質が拘束されている。【エルサレム松岡大地、カイロ金子淳】" + ] + } + ] + }, + "images": [ + { + "versions": [ + { + "url": "https://cdn.mainichi.jp/vol1/2023/06/06/20230606k0000m030221000p/9.webp?1", + "query_width": null, + "size": null, + "type": "image/webp" + }, + { + "url": "https://cdn.mainichi.jp/vol1/2023/06/06/20230606k0000m030221000p/9.jpg?1", + "query_width": null, + "size": { + "width": 800, + "height": 528 + }, + "type": "image/jpeg" + } + ], + "is_cover": true, + "description": "イスラエルの国旗=同国で2019年5月", + "caption": "イスラエルの国旗=同国で2019年5月", + "authors": [], + "position": 891 + } + ], + "publishing_date": "2025-01-14 21:17:27+09:00", + "title": "イスラエルとハマスの停戦「最も合意に近い」 最終案に双方同意か", + "topics": [ + "国際", + "中東", + "緊迫する中東情勢", + "松岡大地", + "イスラエル", + "カタール", + "パレスチナ" + ] + } +} diff --git a/tests/resources/parser/test_data/jp/MainichiShimbun_2025_01_14.html.gz b/tests/resources/parser/test_data/jp/MainichiShimbun_2025_01_14.html.gz new file mode 100644 index 00000000..92ee9d64 Binary files /dev/null and b/tests/resources/parser/test_data/jp/MainichiShimbun_2025_01_14.html.gz differ diff --git a/tests/resources/parser/test_data/jp/meta.info b/tests/resources/parser/test_data/jp/meta.info index 7ce6b780..73999a82 100644 --- a/tests/resources/parser/test_data/jp/meta.info +++ b/tests/resources/parser/test_data/jp/meta.info @@ -7,6 +7,10 @@ "url": "https://www.chunichi.co.jp/article/1011185", "crawl_date": "2025-01-13 18:10:25.145717" }, + "MainichiShimbun_2025_01_14.html.gz": { + "url": "https://mainichi.jp/articles/20250114/k00/00m/030/335000c", + "crawl_date": "2025-01-14 14:55:19.277555" + }, "TheJapanNews_2024_10_13.html.gz": { "url": "https://japannews.yomiuri.co.jp/politics/politics-government/20241013-216478/", "crawl_date": "2024-10-13 16:27:01.520980"