#!/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()