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 <noreply@anthropic.com>
221 lines
7.0 KiB
Python
221 lines
7.0 KiB
Python
#!/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()
|