From c13ad09e3ad1fa1bf2f58c2b4ae6b4fe8090b4be Mon Sep 17 00:00:00 2001 From: Maciej Pienczyn Date: Thu, 15 Jan 2026 08:06:40 +0100 Subject: [PATCH] =?UTF-8?q?feat(zopk):=20Skrypt=20do=20naprawy=20=C5=BAr?= =?UTF-8?q?=C3=B3de=C5=82=20news=C3=B3w=20z=20Google=20News?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem: Newsy z Google News RSS miały source_domain='news.google.com' i favicon Google zamiast prawdziwego źródła. Rozwiązanie: Nowy skrypt fix_google_news_sources.py który: - Wyciąga nazwę źródła z tytułu (po " - ") - Mapuje 59 źródeł na ich prawdziwe domeny - Aktualizuje source_domain i image_url (favicon) Wynik: 143/143 newsów zaktualizowanych z poprawnymi źródłami. Co-Authored-By: Claude Opus 4.5 --- scripts/fix_google_news_sources.py | 220 +++++++++++++++++++++++++++++ 1 file changed, 220 insertions(+) create mode 100644 scripts/fix_google_news_sources.py diff --git a/scripts/fix_google_news_sources.py b/scripts/fix_google_news_sources.py new file mode 100644 index 0000000..a665514 --- /dev/null +++ b/scripts/fix_google_news_sources.py @@ -0,0 +1,220 @@ +#!/usr/bin/env python3 +""" +Skrypt do naprawy źródeł newsów z Google News. + +Problem: Newsy z Google News RSS mają source_domain='news.google.com' +i favicon Google zamiast prawdziwego źródła. + +Rozwiązanie: Wyciągnij nazwę źródła z tytułu (po " - ") i zaktualizuj: +- source_domain na prawdziwą domenę +- image_url na favicon prawdziwej domeny + +Użycie: + python scripts/fix_google_news_sources.py --dry-run # Test + python scripts/fix_google_news_sources.py # Produkcja +""" + +import os +import sys +import argparse + +# Dodaj ścieżkę projektu +PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +sys.path.insert(0, PROJECT_ROOT) + +from dotenv import load_dotenv +load_dotenv(os.path.join(PROJECT_ROOT, '.env')) + +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker + +DATABASE_URL = os.getenv('DATABASE_URL') +if not DATABASE_URL: + print("❌ Błąd: Brak zmiennej DATABASE_URL w .env") + sys.exit(1) + +# Mapowanie nazw źródeł na domeny +# Klucz: nazwa źródła z tytułu (po " - ") +# Wartość: domena do użycia w favicon URL +SOURCE_TO_DOMAIN = { + # Portale z .pl w nazwie - użyj bezpośrednio + "Bankier.pl": "bankier.pl", + "Bizblog.pl": "bizblog.pl", + "Bydgoszcz.Wyborcza.pl": "bydgoszcz.wyborcza.pl", + "CIRE.pl": "cire.pl", + "GazetaPrawna.pl": "gazetaprawna.pl", + "GospodarkaMorska.pl": "gospodarkamorska.pl", + "Gov.pl": "gov.pl", + "Gramwzielone.pl": "gramwzielone.pl", + "Green-news.pl": "green-news.pl", + "Inzynieria.com": "inzynieria.com", + "Money.pl": "money.pl", + "PolsatNews.pl": "polsatnews.pl", + "Trojmiasto.pl": "trojmiasto.pl", + "ekoszalin.pl": "ekoszalin.pl", + "enerad.pl": "enerad.pl", + "naTemat.pl": "natemat.pl", + "polskieradio.pl": "polskieradio.pl", + "trojmiasto.wyborcza.pl": "trojmiasto.wyborcza.pl", + "wnp.pl": "wnp.pl", + "www.wejherowo.pl": "wejherowo.pl", + "xyz.pl": "xyz.pl", + + # Portale biznesowe + "Biznes Interia": "biznes.interia.pl", + "Business Insider Polska": "businessinsider.com.pl", + "Forbes": "forbes.pl", + "Forsal": "forsal.pl", + "Newsweek": "newsweek.pl", + "Obserwator Finansowy": "obserwatorfinansowy.pl", + "Rzeczpospolita": "rp.pl", + "wGospodarce": "wgospodarce.pl", + "Strefa Biznesu": "strefabiznesu.pl", + + # Portale branżowe + "Defence24": "defence24.pl", + "Energetyka24": "energetyka24.com", + "GlobEnergia": "globenergia.pl", + "Investmap": "investmap.pl", + "Portal Morski": "portalmorski.pl", + "Portal Obronny": "portalobronny.pl", + "Portal Samorządowy": "portalsamorzadowy.pl", + "Polska Morska": "polska-morska.pl", + "Rynek Infrastruktury": "rynekinfrastruktury.pl", + "Top-Oze": "top-oze.pl", + "FOCUS ON Business": "focusonbusiness.eu", + + # Regionalne + "Dziennik Bałtycki": "dziennikbaltycki.pl", + "Głos Pomorza": "gp24.pl", + "Kaszuby24": "kaszuby24.pl", + "Nadmorski24": "nadmorski24.pl", + "Portal Kujawski": "portalkujawski.pl", + "Pracodawcy Pomorza": "pracodawcypomorza.pl", + "Rumia – naturalnie pomysłowa": "rumia.eu", + "Tygodnik Bydgoski": "tygodnikbydgoski.pl", + "Zawsze Pomorze": "zawszepomorze.pl", + + # Radio i TV + "Radio Gdańsk": "radiogdansk.pl", + "Radio Weekend FM": "weekendfm.pl", + "Polskie Radio 24": "polskieradio24.pl", + "Polskie Radio Koszalin": "prkoszalin.pl", + "TVP Gdańsk": "gdansk.tvp.pl", + "TVP Bydgoszcz": "bydgoszcz.tvp.pl", + "TVP Info": "tvp.info", + + # Inne + "Polska Agencja Prasowa SA": "pap.pl", + "OKO.press": "oko.press", +} + + +def get_domain_favicon(domain: str) -> str: + """Zwróć URL favicona przez Google API.""" + return f"https://www.google.com/s2/favicons?domain={domain}&sz=128" + + +def extract_source_from_title(title: str) -> str | None: + """Wyciągnij źródło z tytułu (po ostatnim ' - ').""" + if ' - ' not in title: + return None + return title.rsplit(' - ', 1)[-1].strip() + + +def main(): + parser = argparse.ArgumentParser(description='Napraw źródła newsów z Google News') + parser.add_argument('--dry-run', action='store_true', help='Tryb testowy - nie zapisuj') + parser.add_argument('--limit', type=int, default=None, help='Limit newsów') + args = parser.parse_args() + + print("=" * 70) + print("Google News Source Fixer") + print("=" * 70) + + if args.dry_run: + print("🔍 TRYB TESTOWY - zmiany NIE będą zapisane\n") + + engine = create_engine(DATABASE_URL) + Session = sessionmaker(bind=engine) + session = Session() + + try: + from database import ZOPKNews + + # Pobierz newsy z Google News z favicon + query = session.query(ZOPKNews).filter( + ZOPKNews.status.in_(['approved', 'auto_approved']), + ZOPKNews.source_domain == 'news.google.com', + ZOPKNews.image_url.like('%s2/favicons%') + ).order_by(ZOPKNews.published_at.desc()) + + if args.limit: + query = query.limit(args.limit) + + news_items = query.all() + + print(f"📰 Znaleziono {len(news_items)} newsów do przetworzenia\n") + + stats = { + 'processed': 0, + 'mapped': 0, + 'unknown': 0, + 'no_pattern': 0 + } + unknown_sources = set() + + for i, news in enumerate(news_items, 1): + source_name = extract_source_from_title(news.title) + + if not source_name: + stats['no_pattern'] += 1 + print(f"[{i}] ⚠ Brak wzorca ' - ' w tytule: {news.title[:50]}...") + continue + + domain = SOURCE_TO_DOMAIN.get(source_name) + + if domain: + stats['processed'] += 1 + stats['mapped'] += 1 + favicon_url = get_domain_favicon(domain) + + if not args.dry_run: + news.source_domain = domain + news.image_url = favicon_url + session.commit() + print(f"[{i}] ✓ {source_name} → {domain}") + else: + print(f"[{i}] [DRY-RUN] {source_name} → {domain}") + else: + stats['unknown'] += 1 + unknown_sources.add(source_name) + print(f"[{i}] ✗ Nieznane źródło: {source_name}") + + print("\n" + "=" * 70) + print("PODSUMOWANIE") + print("=" * 70) + print(f"Przetworzono: {stats['processed']}") + print(f" - Zmapowane: {stats['mapped']}") + print(f" - Nieznane źródła: {stats['unknown']}") + print(f" - Brak wzorca w tytule: {stats['no_pattern']}") + + if unknown_sources: + print(f"\n⚠ Nieznane źródła ({len(unknown_sources)}) - dodaj do SOURCE_TO_DOMAIN:") + for src in sorted(unknown_sources): + print(f' "{src}": "",') + + if args.dry_run: + print("\n⚠️ To był tryb testowy. Uruchom bez --dry-run aby zapisać.") + + except Exception as e: + print(f"❌ Błąd: {e}") + import traceback + traceback.print_exc() + session.rollback() + finally: + session.close() + + +if __name__ == '__main__': + main()