Skip to content

Commit

Permalink
Merge pull request #700 from ruggsea/other-italian-newspapers
Browse files Browse the repository at this point in the history
Support for 2 new italian newspapers - Corriere della Sera & Il Giornale
  • Loading branch information
MaxDall authored Feb 5, 2025
2 parents babf123 + 0bdd503 commit 7455d33
Show file tree
Hide file tree
Showing 10 changed files with 359 additions and 14 deletions.
19 changes: 16 additions & 3 deletions docs/supported_publishers.md
Original file line number Diff line number Diff line change
Expand Up @@ -1276,16 +1276,29 @@
<code>CorriereDellaSera</code>
</td>
<td>
<div>Corriere Della Sera</div>
<div>Corriere della Sera</div>
</td>
<td>
<a href="https://www.corriere.it/">
<a href="https://www.corriere.it">
<span>www.corriere.it</span>
</a>
</td>
<td>&#160;</td>
<td>&#160;</td>
</tr>
<tr>
<td>
<code>topics</code>
<code>IlGiornale</code>
</td>
<td>
<div>Il Giornale</div>
</td>
<td>
<a href="https://www.ilgiornale.it">
<span>www.ilgiornale.it</span>
</a>
</td>
<td>&#160;</td>
<td>&#160;</td>
</tr>
<tr>
Expand Down
73 changes: 69 additions & 4 deletions src/fundus/publishers/it/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
from datetime import datetime, timedelta
from typing import Optional

from dateutil.rrule import MONTHLY, rrule

from fundus.publishers.base_objects import Publisher, PublisherGroup
from fundus.publishers.it.corriere_della_sera import CorriereDellaSeraParser
from fundus.publishers.it.il_giornale import IlGiornaleParser
from fundus.publishers.it.la_repubblica import LaRepubblicaParser
from fundus.scraping.url import RSSFeed, Sitemap
from fundus.scraping.filter import regex_filter
from fundus.scraping.url import NewsMap, RSSFeed, Sitemap


class IT(metaclass=PublisherGroup):
Expand All @@ -25,11 +28,73 @@ class IT(metaclass=PublisherGroup):
)

CorriereDellaSera = Publisher(
name="Corriere Della Sera",
domain="https://www.corriere.it/",
name="Corriere della Sera",
domain="https://www.corriere.it",
parser=CorriereDellaSeraParser,
sources=[
RSSFeed("https://www.corriere.it/feed-hp/homepage.xml"),
# Main RSS feeds
RSSFeed("https://www.corriere.it/rss/homepage.xml"),
RSSFeed("https://www.corriere.it/rss/ultimora.xml"), ## Current empty but could be in use
RSSFeed("https://www.corriere.it/dynamic-feed/rss/section/Dataroom.xml"),
RSSFeed("https://www.corriere.it/dynamic-feed/rss/section/lettere-al-direttore.xml"),
RSSFeed("https://www.corriere.it/dynamic-feed/rss/section/lo-dico-al-corriere.xml"),
RSSFeed("https://www.corriere.it/dynamic-feed/rss/section/frammenti-di-ferruccio-de-bortoli.xml"),
# Main sitemaps
Sitemap("https://www.corriere.it/rss/sitemap_v2.xml"),
# Dynamic sitemaps - Last 100 articles
Sitemap("https://www.corriere.it/dynamic-sitemap/sitemap-last-100/Economia.xml"),
Sitemap("https://www.corriere.it/dynamic-sitemap/sitemap-last-100/Salute.xml"),
Sitemap("https://www.corriere.it/dynamic-sitemap/sitemap-last-100/Scienze.xml"),
Sitemap("https://www.corriere.it/dynamic-sitemap/sitemap-last-100/Interni.xml"),
Sitemap("https://www.corriere.it/dynamic-sitemap/sitemap-last-100/Esteri.xml"),
Sitemap("https://www.corriere.it/dynamic-sitemap/sitemap-last-100/Sport.xml"),
Sitemap("https://www.corriere.it/dynamic-sitemap/sitemap-last-100/Politica.xml"),
Sitemap("https://www.corriere.it/dynamic-sitemap/sitemap-last-100/Salute__Figli__e__Genitori.xml"),
Sitemap("https://www.corriere.it/dynamic-sitemap/sitemap-last-100/Salute__Sportello__Cancro.xml"),
Sitemap("https://www.corriere.it/dynamic-sitemap/sitemap-last-100/Elezioni.xml"),
Sitemap("https://www.corriere.it/dynamic-sitemap/sitemap-last-100/Tecnologia.xml"),
Sitemap("https://www.corriere.it/dynamic-sitemap/sitemap-last-100/Offerte__recensioni.xml"),
Sitemap("https://www.corriere.it/dynamic-sitemap/sitemap-last-100/Lotterie.xml"),
Sitemap("https://www.corriere.it/dynamic-sitemap/sitemap-last-100/Spettacoli.xml"),
Sitemap("https://www.corriere.it/dynamic-sitemap/sitemap-last-100/Scuola.xml"),
Sitemap("https://www.corriere.it/dynamic-sitemap/sitemap-last-100/Animali.xml"),
Sitemap("https://www.corriere.it/dynamic-sitemap/sitemap-last-100/Opinioni.xml"),
Sitemap("https://www.corriere.it/dynamic-sitemap/sitemap-last-100/Caffe-gramellini.xml"),
Sitemap("https://www.corriere.it/dynamic-sitemap/sitemap-last-100/Ultimo-banco.xml"),
Sitemap("https://www.corriere.it/dynamic-sitemap/sitemap-last-100/Letti-da-rifarei.xml"),
Sitemap("https://www.corriere.it/dynamic-sitemap/sitemap-last-100/Piccole-dosi.xml"),
Sitemap("https://www.corriere.it/dynamic-sitemap/sitemap-last-100/L-angolo.xml"),
Sitemap("https://www.corriere.it/dynamic-sitemap/sitemap-last-100/Padiglione-italia.xml"),
Sitemap("https://www.corriere.it/dynamic-sitemap/sitemap-last-100/Facce-nuove.xml"),
Sitemap("https://www.corriere.it/dynamic-sitemap/sitemap-last-100/Ritorno-in-solferino.xml"),
Sitemap("https://www.corriere.it/dynamic-sitemap/sitemap-last-100/Oriente-occidente.xml"),
Sitemap("https://www.corriere.it/dynamic-sitemap/sitemap-last-100/Sette.xml"),
Sitemap("https://www.corriere.it/dynamic-sitemap/sitemap-last-100/Moda.xml"),
Sitemap("https://www.corriere.it/dynamic-sitemap/sitemap-last-100/BuoneNotizie.xml"),
Sitemap("https://www.corriere.it/dynamic-sitemap/sitemap-last-100/lettere__al__direttore.xml"),
Sitemap("https://www.corriere.it/dynamic-sitemap/sitemap-last-100/lo__dico__al__corriere.xml"),
Sitemap("https://www.corriere.it/dynamic-sitemap/sitemap-last-100/Frammenti-ferruccio-de-bortoli.xml"),
# Section sitemaps
Sitemap("https://www.corriere.it/rss/sitemap/Motori.xml"),
Sitemap("https://www.corriere.it/rss/sitemap/Cultura.xml"),
Sitemap("https://www.corriere.it/rss/sitemap/lettere-al-direttore.xml"),
Sitemap("https://www.corriere.it/rss/sitemap/lo-dico-al-corriere.xml"),
Sitemap("https://www.corriere.it/rss/sitemap/Cook-Last.xml"),
],
)

IlGiornale = Publisher(
name="Il Giornale",
domain="https://www.ilgiornale.it",
parser=IlGiornaleParser,
sources=[
# Main RSS feed (removed the one returning 404)
RSSFeed("https://www.ilgiornale.it/feed.xml"),
# Main sitemaps - excluding video and image sitemaps
NewsMap("https://www.ilgiornale.it/sitemap/google-news.xml"),
Sitemap(
"https://www.ilgiornale.it/sitemap/indice.xml",
sitemap_filter=regex_filter(r"\*/video/|\*/image/"),
),
],
)
10 changes: 10 additions & 0 deletions src/fundus/publishers/it/corriere_della_sera.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,16 @@ def publishing_date(self) -> Optional[datetime]:
date_str = self.precomputed.ld.xpath_search("//NewsArticle/datePublished", scalar=True)
return generic_date_parsing(date_str)

@attribute
def topics(self) -> List[str]:
breadcrumb_items = self.precomputed.ld.xpath_search("//BreadcrumbList/itemListElement/*/name")
if breadcrumb_items:
return generic_topic_parsing(breadcrumb_items[1:])
section = self.precomputed.ld.xpath_search("//NewsArticle/articleSection", scalar=True)
if section:
return generic_topic_parsing([section])
return []

@attribute
def images(self) -> List[Image]:
return image_extraction(
Expand Down
100 changes: 100 additions & 0 deletions src/fundus/publishers/it/il_giornale.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import re
from datetime import datetime
from typing import List, Optional

from lxml.cssselect import CSSSelector
from lxml.etree import XPath, tostring
from lxml.html import HtmlElement, document_fromstring

from fundus.parser import ArticleBody, BaseParser, Image, ParserProxy, attribute
from fundus.parser.data import ImageVersion
from fundus.parser.utility import (
extract_article_body_with_selector,
generic_author_parsing,
generic_date_parsing,
generic_topic_parsing,
image_extraction,
transform_breaks_to_paragraphs,
)


class IlGiornaleParser(ParserProxy):
class V1(BaseParser):
# Selectors for article body parts
_paragraph_selector = XPath(
"//div[contains(@class, 'typography--content')]//p[text() or strong or em] | //div[@class='banner banner--spaced-block banner-evo' and (text() or em or strong)]"
)
_subheadline_selector = CSSSelector("div.typography--content h2:not([class])")
_summary_selector = CSSSelector("p.article__abstract, div.article__abstract")
_image_selector = XPath(
"//div[contains(@class, 'article__media')]//img | //section[contains(@class, 'article__content')]//img"
)

@attribute
def title(self) -> Optional[str]:
# First try JSON-LD
title = self.precomputed.ld.xpath_search("//NewsArticle/headline", scalar=True)
if title:
return str(title)
# Fallback to meta tags
return self.precomputed.meta.get("og:title")

@attribute
def authors(self) -> List[str]:
# Extract authors from schema.org NewsArticle data
authors = self.precomputed.ld.xpath_search("//NewsArticle/author")
if authors:
return generic_author_parsing(authors)
return []

@attribute
def publishing_date(self) -> Optional[datetime]:
# Try JSON-LD first
date_str = self.precomputed.ld.xpath_search("//NewsArticle/datePublished", scalar=True)
if not date_str:
# Fallback to meta tags
date_str = self.precomputed.meta.get("article:published_time")
return generic_date_parsing(date_str)

@attribute
def body(self) -> Optional[ArticleBody]:
# Clean up HTML by removing ads and handling em/strong/cite tags
html_string = tostring(self.precomputed.doc).decode("utf-8")
html_string = re.sub(r"</?(em|strong|cite)>", "", html_string)
html_string = re.sub(r"<!-- EVOLUTION ADV -->", "", html_string)
doc = document_fromstring(html_string)

# Transform br tags to paragraphs for better structure
doc = transform_breaks_to_paragraphs(doc)

# Extract article body using utility function
return extract_article_body_with_selector(
doc,
paragraph_selector=self._paragraph_selector,
subheadline_selector=self._subheadline_selector,
summary_selector=self._summary_selector,
)

@attribute
def topics(self) -> List[str]:
# Try to get topics from keywords
keywords = self.precomputed.ld.bf_search("keywords")
if keywords:
return generic_topic_parsing(keywords)

# Fallback to articleSection
section = self.precomputed.ld.xpath_search("//NewsArticle/articleSection", scalar=True)
if section:
return generic_topic_parsing([section])

return []

@attribute
def images(self) -> List[Image]:
# Extract images using the utility function
return image_extraction(
doc=self.precomputed.doc,
paragraph_selector=self._paragraph_selector,
image_selector=self._image_selector,
caption_selector=XPath(".//figcaption/text()"),
)
18 changes: 11 additions & 7 deletions tests/resources/parser/test_data/it/CorriereDellaSera.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,21 @@
],
"body": {
"summary": [
"La premier italiana Giorgia Meloni è partita (a sorpresa) per una missione in Florida dal presidente eletto degli Usa. Segreta l'agenda dell'incontro, ma il New York Times rivela:«La premier ha premuto sul caso di Cecilia Sala»"
"La premier italiana Giorgia Meloni è partita (a sorpresa) per una missione in Florida dal presidente eletto degli Usa. L'elogio di Trump: «Ha preso d'assalto l'Europa». Segreta l'agenda dell'incontro, ma il New York Times rivela:«La premier ha premuto sul caso di Cecilia Sala»"
],
"sections": [
{
"headline": [],
"paragraphs": [
"DALLA NOSTRA CORRISPONDENTENEW YORK - Giorgia Meloni è arrivata a Mar-a-Lago alle 19:29 locali, dopo l’atterraggio del suo volo partito da Ciampino all’aeroporto di Palm Beach, per una missione lampo - e nata nel riserbo più assoluto - che l'ha portata a incontrare il presidente eletto degli Stati Uniti, Donald Trump.",
"DALLA NOSTRA CORRISPONDENTENEW YORK - Giorgia Meloni è arrivata a Mar-a-Lago alle 19:29 locali - l'1:29 delmattino di domenica, ora italiana - dopo l’atterraggio del suo volo partito da Ciampino all’aeroporto di Palm Beach, per una missione lampo - e nata nel riserbo più assoluto - che l'ha portata a incontrare il presidente eletto degli Stati Uniti, Donald Trump.",
"«Una bella serata, lo ringrazio per l'accoglienza. Pronti a lavorare insieme», ha twittato poi la premier nella serata di domenica.",
"In una delle prime foto scattate dagli ospiti nel salone della residenza in Florida - che è anche un resort - , si vede la presidente del Consiglio italiana insieme a Trump. Al loro fianco, il senatore della Florida Marco Rubio nominato segretario di Stato da Trump, il deputato della Florida Mike Waltz, nominato consigliere per la sicurezza nazionale, Scott Bessent, nominato segretario del Tesoro, l’ambasciatrice d’Italia negli Usa Mariangela Zappia e l’imprenditore texano Tilman Fertitta, nominato ambasciatore Usa in Italia.",
"Trump ha salutato gli ospiti nel salone d'onore, predisposto per la proiezione di un film e, seguito da Meloni e dalla delegazione, si è recato al piano di sopra, tornando intorno alle nove di sera. Non si è visto invece Elon Musk - che potrebbe però aver giocato un ruolo per l'organizzazione dell'incontro."
]
},
{
"headline": [
"Trump su Meloni: «Ha preso d'assalto l'Europa». Poi il film"
"Donald Trump su Giorgia Meloni: «Ha preso d'assalto l'Europa». Poi il film"
],
"paragraphs": [
"«È molto emozionante, sono qui con una donna fantastica, la premier italiana», ha detto Trump agli ospiti riuniti nel salone. «Ha davvero preso d'assalto l'Europa». Rubio, dando il benvenuto alla premier, l'ha poi definita: «Un'ottima alleata, un leader forte».",
Expand Down Expand Up @@ -55,7 +56,7 @@
{
"versions": [
{
"url": "https://dimages2.corriereobjects.it/files/main_image/files/fp/uploads/2025/01/05/677a43307deb3.r_d.580-548-6933.jpeg",
"url": "https://dimages2.corriereobjects.it/files/main_image/files/fp/uploads/2025/01/05/677aba3f831f8.r_d.1558-1018-2445.jpeg",
"query_width": null,
"size": {
"width": 572,
Expand All @@ -65,13 +66,16 @@
}
],
"is_cover": true,
"description": "Trump e Meloni a mar a lago",
"description": "Meloni e Trump a Mar-a-Lago",
"caption": null,
"authors": [],
"position": 1449
"position": 1450
}
],
"publishing_date": "2025-01-05 03:56:38+01:00",
"title": "Trump accoglie Meloni a Mar-a-Lago: «Ha preso d'assalto l'Europa». Il Nyt: «La premier ha premuto aggressivamente per Cecilia Sala». Salvini: «Bene Giorgia. E noi diciamo \"go Donald go\"»"
"title": "Giorgia Meloni da Donald Trump a Mar-a-Lago: «Bella serata, pronti a lavorare insieme». Il Nyt: «Su Cecilia Sala la premier ha premuto aggressivamente»",
"topics": [
"Esteri"
]
}
}
Binary file not shown.
Binary file not shown.
Loading

0 comments on commit 7455d33

Please sign in to comment.