feat(zopk): Skrypt do naprawy źródeł newsów z Google News
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>
This commit is contained in:
parent
8055589a08
commit
c13ad09e3a
220
scripts/fix_google_news_sources.py
Normal file
220
scripts/fix_google_news_sources.py
Normal file
@ -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()
|
||||
Loading…
Reference in New Issue
Block a user