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