-
Notifications
You must be signed in to change notification settings - Fork 107
add klassegegenklasse (DE) publisher + parser + tests + tables #809
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,151 @@ | ||
| import re | ||
| from datetime import datetime | ||
| 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 ( | ||
| extract_article_body_with_selector, | ||
| generic_author_parsing, | ||
| generic_date_parsing, | ||
| generic_topic_parsing, | ||
| image_extraction, | ||
| ) | ||
|
|
||
|
|
||
| class KlasseGegenKlasseParser(ParserProxy): | ||
| class V1(BaseParser): | ||
| _paragraph_selector = CSSSelector("article p, main article p, .post-content p, .entry-content p, .content p") | ||
| _summary_selector = CSSSelector( | ||
| "article .entry-content > p:first-child, article > p:first-child, .post-content > p:first-child" | ||
| ) | ||
| _subheadline_selector = CSSSelector("article h2, .entry-content h2, .post-content h2") | ||
|
|
||
| @attribute | ||
| def body(self) -> Optional[ArticleBody]: | ||
| return extract_article_body_with_selector( | ||
| self.precomputed.doc, | ||
| summary_selector=self._summary_selector, | ||
| subheadline_selector=self._subheadline_selector, | ||
| paragraph_selector=self._paragraph_selector, | ||
| ) | ||
|
|
||
| @attribute | ||
| def authors(self) -> List[str]: | ||
| # 1) nur Meta (kein LD) | ||
| res = generic_author_parsing(self.precomputed.meta.get("author")) | ||
| if res: | ||
| return res | ||
|
|
||
| # 2) DOM-Fallbacks (WP-typisch) | ||
| nodes = self.precomputed.doc.xpath( | ||
| "//a[@rel='author']/text()" | ||
| " | //span[contains(@class,'author')]//a/text()" | ||
| " | //div[contains(@class,'author')]//a/text()" | ||
| " | //div[contains(@class,'byline')]//a/text()" | ||
| " | //span[contains(@class,'byline')]//a/text()" | ||
| " | //a[contains(@href,'/autor/') or contains(@href,'/author/')]/text()" | ||
| ) | ||
|
Comment on lines
+43
to
+50
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. Clicking through some of the articles it seems to me that the author can usually be found at the same place. Could you elaborate on your reasoning for adding all the extra selectors? |
||
| vals = [t.strip() for t in nodes if t and t.strip()] | ||
| seen, out = set(), [] | ||
| for v in vals: | ||
| if v not in seen: | ||
| seen.add(v) | ||
| out.append(v) | ||
| return out | ||
|
|
||
| @attribute | ||
| def publishing_date(self) -> Optional[datetime]: | ||
|
Comment on lines
+59
to
+60
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. Same as with |
||
| # 1) Meta | ||
| for cand in ( | ||
| self.precomputed.meta.get("article:published_time"), | ||
| self.precomputed.meta.get("og:article:published_time"), | ||
| self.precomputed.meta.get("date"), | ||
| ): | ||
| dt = generic_date_parsing(cand) | ||
| if dt: | ||
| return dt | ||
|
|
||
| # 2) <time datetime> | ||
| for val in self.precomputed.doc.xpath("//time[@datetime]/@datetime"): | ||
| dt = generic_date_parsing(val) | ||
| if dt: | ||
| return dt | ||
|
|
||
| # 3) sichtbarer Datumstext (inkl. dd.mm.yyyy) | ||
| texts: list[str] = [] | ||
| texts += [t.strip() for t in self.precomputed.doc.xpath("//time//text()") if t and t.strip()] | ||
| texts += [ | ||
| t.strip() | ||
| for t in self.precomputed.doc.xpath( | ||
| "//*[contains(@class,'date') or contains(@class,'datum') or contains(@class,'entry-date') " | ||
| "or contains(@class,'meta-date') or contains(@class,'posted-on') or contains(@class,'post-meta')]//text()" | ||
| ) | ||
| if t and t.strip() | ||
| ] | ||
|
|
||
| for t in texts: | ||
| dt = generic_date_parsing(t) | ||
| if dt: | ||
| return dt | ||
|
|
||
| m = None | ||
| for t in texts: | ||
| m = re.search(r"\b(\d{1,2}\.\d{1,2}\.\d{4})(?:\s+(\d{1,2}:\d{2}))?\b", t) | ||
| if m: | ||
| d, tm = m.group(1), m.group(2) | ||
| try: | ||
| if tm: | ||
| return datetime.strptime(f"{d} {tm}", "%d.%m.%Y %H:%M") | ||
| return datetime.strptime(d, "%d.%m.%Y") | ||
| except ValueError: | ||
| pass | ||
|
|
||
| # 4) notfalls Roh-HTML | ||
| m = re.search(r"\b(\d{1,2}\.\d{1,2}\.\d{4})\b", self.precomputed.html or "") | ||
| if m: | ||
| try: | ||
| return datetime.strptime(m.group(1), "%d.%m.%Y") | ||
| except ValueError: | ||
| pass | ||
|
|
||
| # 5) optional: modified als Fallback | ||
| for cand in ( | ||
| self.precomputed.meta.get("article:modified_time"), | ||
| self.precomputed.meta.get("og:updated_time"), | ||
| ): | ||
| dt = generic_date_parsing(cand) | ||
| if dt: | ||
| return dt | ||
|
|
||
| return None | ||
|
|
||
| @attribute | ||
| def topics(self) -> List[str]: | ||
| # 1) Meta | ||
| res = generic_topic_parsing( | ||
| self.precomputed.meta.get("keywords") or self.precomputed.meta.get("news_keywords") | ||
| ) | ||
| if res: | ||
| return res | ||
|
|
||
| # 2) DOM: WP-typische Orte + Permalinks | ||
| nodes = self.precomputed.doc.xpath( | ||
| "//a[@rel='tag']/text() | //a[@rel='category tag']/text()" | ||
| " | //div[contains(@class,'tags') or contains(@class,'tags-links') or contains(@class,'post-tags') " | ||
| " or contains(@class,'cat-links') or contains(@class,'post-categories') " | ||
| " or contains(@class,'entry-taxonomy') or contains(@class,'entry-taxonomies')]//a/text()" | ||
| " | //a[contains(@href,'/schlagwort/') or contains(@href,'/thema/') or contains(@href,'/kategorie/') " | ||
| " or contains(@href,'/tag/') or contains(@href,'/category/')]/text()" | ||
| " | //meta[@property='article:tag']/@content" | ||
| ) | ||
| vals = [s.strip().lstrip("#") for s in nodes if s and s.strip()] | ||
|
|
||
| seen, out = set(), [] | ||
| for v in vals: | ||
| if v not in seen: | ||
| seen.add(v) | ||
| out.append(v) | ||
| return out | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| { | ||
| "V1": { | ||
| "authors": [ | ||
| "Joschua Klein" | ||
| ], | ||
| "body": { | ||
| "summary": [], | ||
| "sections": [ | ||
| { | ||
| "headline": [], | ||
| "paragraphs": [ | ||
| "Der Angriff auf palästinasolidarische Menschen ist ein Angriff auf die demokratischen Freiheiten in diesem Land. Und das geht uns alle etwas an.", | ||
| "Die Palästina-Solidaritätsbewegung erfährt besonders in Berlin seit dem 7. Oktober 2025 noch gesteigerte Repression durch den deutschen Staat. Diese Ausweitung von massiver Polizeigewalt, willkürlichen Festnahmen, fadenscheinigen Versammlungsverboten und rassistischer Diskriminierung sind nicht nur direkte Angriffe auf die Pro-Palästina-Bewegung, sondern stellen auch drastische Einschnitte der allgemeinen Meinungs- und Versammlungsfreiheit und Rechtsstaatlichkeit dar – vermeintliche Grundpfeiler unserer Demokratie.", | ||
| "Dies wird auch längst international bestätigt, zuletzt durch die UN. Diese ruft Deutschland dazu auf, die Kriminalisierung und Polizeigewalt gegen palästinasolidarische Menschen einzustellen. Neben den schon lange anhaltenden Beschränkungen und Verboten von Pro-Palästina Versammlungen, der Kriminalisierung der Verteidigung von Menschenrechten und palästinischer Identität, und unverhältnismäßiger Polizeigewalt wurden insbesondere die Großeinkesslung, die gewaltsamen Festnahmen, und das willkürliche Versammlungsverbot des 7. Oktober in Berlin benannt. Seitdem wurden sogar ausgewiesene parlamentarische Beobachter von der Links-Partei durch die Polizei grundlos angegriffen und gewaltsam festgenommen.", | ||
| "Meinungsfreiheit, Versammlungsfreiheit und Rechtsstaatlichkeit werden von der Regierung konsequent untergraben. Deutschland ist in Europa ein trauriger Vorreiter in der Normalisierung von Grundrechtsverletzungen und dem Rückbau von demokratischen Freiheiten.", | ||
| "Die massive Repression wird nicht auf die Pro-Palästina-Bewegung beschränkt bleiben. Vielmehr muss sie als Barometer der autoritären Wende in Deutschland allgemein verstanden werden. Sie zeigt, wie der Staat auch dem gesellschaftlichen Widerstand gegen Maßnahmen wie die unbeliebte Wiedereinführung des Wehrdienstes und das Spardiktat zugunsten von massiver Aufrüstung begegnen möchte. Dies sehen wir mit der Ausweitung der brutalen Polizeigewalt auf Proteste von Rheinmetall-Entwaffnen und dem Berliner Bündnis gegen Waffenproduktion. Jegliche Kritik am Staat soll erstickt und seine Rechenschaft für die unrechtmäßige Unterdrückung der Bevölkerung – ganz zu schweigen von der Unterstützung des Genozids – entflohen werden.", | ||
| "Es ist leider kaum zu erwarten, dass die verantwortlichen parlamentarischen Parteien und staatlichen Organe den Repressionskurs spontan ändern. Denn die Exekutive, Judikative und Legislative kollaborieren in der Repression und delegitimieren sich damit zunehmend. Auch die Links-Partei muss viel mehr Tatkraft zeigen, die Polizeigewalt aufzuklären und ihr Einhalt zu gebieten.", | ||
| "Denn gerade Linke und Gewerkschaften müssen erwarten, dass ihre Aktionen im Zuge des allgemeinen Rechtsrucks aller parlamentarischen Parteien auch zunehmend ins Visier der staatlichen Repression kommen werden. Daher fordern wir diese auf, sich grundsätzlich solidarisch mit Palästina und palästinasolidarischen Menschen, die Repression erfahren, zu stellen und es nicht bei der Mitorganisation von wenigen Großdemos zu belassen.", | ||
| "Gehen wir zusammen weiter in Solidarität mit Palästina und gegen Militarisierung auf die Straße, so stehen wir auch für die Versammlungsfreiheit und Meinungsfreiheit ein – für alle, aber besonders gegen Rechts. Sprechen wir Palästina und die Repression der Solidaritätsbewegung in unseren Universitäten, Betrieben, Vereinen und Freundeskreisen an, so finden wir ungeahnte Mitstreiter:innen. Hören und verbreiten wir palästinensische Stimmen, so leisten wir Widerstand im Kleinen gegen Versuche, diese zu unterdrücken. Der unrechtmäßige Angriff auf palästinasolidarische Menschen ist auch ein Angriff auf die demokratischen Grundfreiheiten in diesem Land. Diesen gilt es gemeinsam abzuwehren." | ||
| ] | ||
| } | ||
| ] | ||
| }, | ||
| "publishing_date": "2025-10-22 00:00:00", | ||
| "topics": [ | ||
| "Palästina", | ||
| "Polizeigewalt", | ||
| "Repression und Militarismus" | ||
| ] | ||
| } | ||
| } |
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.
It looks like the sitemap includes a lot of unnecessary entries. You can use the
sitemap_filterparameter ofSitemapto exclude unwanted sitemap URLs. For example,sitemap_filter=inverse(regex_filter("wp-sitemap-posts-post"))will exclude any sitemap that does not contain the substringwp-sitemap-posts-post.