bundle erkennung verbessert und json vergleich
This commit is contained in:
parent
c38bff314d
commit
0dc125293d
|
|
@ -3,13 +3,14 @@ import requests
|
||||||
import json
|
import json
|
||||||
import hashlib
|
import hashlib
|
||||||
import logging
|
import logging
|
||||||
|
import difflib
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
from sqlalchemy import create_engine, Column, Integer, String, Float, DateTime, ForeignKey, Text
|
from sqlalchemy import create_engine, Column, Integer, String, Float, DateTime, ForeignKey, Text
|
||||||
from sqlalchemy.orm import declarative_base, relationship, sessionmaker
|
from sqlalchemy.orm import declarative_base, relationship, sessionmaker
|
||||||
|
|
||||||
# Konfiguriere Logging – passe das Level bei Bedarf an (DEBUG, INFO, WARNING, ERROR)
|
# Konfiguriere Logging – ändere das Level bei Bedarf (DEBUG, INFO, WARNING, ERROR)
|
||||||
DEBUG_LEVEL = logging.DEBUG
|
DEBUG_LEVEL = logging.DEBUG
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
level=DEBUG_LEVEL,
|
level=DEBUG_LEVEL,
|
||||||
|
|
@ -18,7 +19,7 @@ logging.basicConfig(
|
||||||
)
|
)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Basis-Klasse für SQLAlchemy-Modelle (SQLAlchemy 2.0-konform)
|
# Basis-Klasse für SQLAlchemy-Modelle (2.0-konform)
|
||||||
Base = declarative_base()
|
Base = declarative_base()
|
||||||
|
|
||||||
# ---------------------------
|
# ---------------------------
|
||||||
|
|
@ -29,10 +30,10 @@ class Bundle(Base):
|
||||||
__tablename__ = 'bundles'
|
__tablename__ = 'bundles'
|
||||||
id = Column(Integer, primary_key=True)
|
id = Column(Integer, primary_key=True)
|
||||||
machine_name = Column(String, unique=True)
|
machine_name = Column(String, unique=True)
|
||||||
human_name = Column(String) # Falls vorhanden; kann leer bleiben, wenn nicht extrahiert
|
human_name = Column(String) # Kann leer bleiben, wenn nicht extrahiert
|
||||||
current_version_id = Column(Integer, ForeignKey('bundle_versions.id'))
|
current_version_id = Column(Integer, ForeignKey('bundle_versions.id'))
|
||||||
|
|
||||||
# Beziehung zur aktuellen Version; post_update=True hilft bei zirkulären Abhängigkeiten
|
# Beziehung zur aktuellen Version (post_update=True hilft bei zirkulären Abhängigkeiten)
|
||||||
current_version = relationship("BundleVersion", uselist=False,
|
current_version = relationship("BundleVersion", uselist=False,
|
||||||
foreign_keys=[current_version_id],
|
foreign_keys=[current_version_id],
|
||||||
post_update=True)
|
post_update=True)
|
||||||
|
|
@ -41,7 +42,7 @@ class Bundle(Base):
|
||||||
foreign_keys=lambda: [BundleVersion.bundle_id])
|
foreign_keys=lambda: [BundleVersion.bundle_id])
|
||||||
# Verkaufshistorie
|
# Verkaufshistorie
|
||||||
sales_history = relationship("BundleSalesHistory", back_populates="bundle")
|
sales_history = relationship("BundleSalesHistory", back_populates="bundle")
|
||||||
# Einzelne Elemente des Bundles (z. B. die enthaltenen Bücher, Spiele etc.)
|
# Einzelne Elemente des Bundles (z. B. enthaltene Bücher, Spiele etc.)
|
||||||
items = relationship("BundleItem", back_populates="bundle")
|
items = relationship("BundleItem", back_populates="bundle")
|
||||||
|
|
||||||
class BundleVersion(Base):
|
class BundleVersion(Base):
|
||||||
|
|
@ -49,7 +50,7 @@ class BundleVersion(Base):
|
||||||
id = Column(Integer, primary_key=True)
|
id = Column(Integer, primary_key=True)
|
||||||
bundle_id = Column(Integer, ForeignKey('bundles.id'))
|
bundle_id = Column(Integer, ForeignKey('bundles.id'))
|
||||||
version_hash = Column(String)
|
version_hash = Column(String)
|
||||||
version_data = Column(Text) # Relevante Bundle-Daten als JSON-String
|
version_data = Column(Text) # JSON-Daten als String
|
||||||
timestamp = Column(DateTime, default=datetime.utcnow)
|
timestamp = Column(DateTime, default=datetime.utcnow)
|
||||||
|
|
||||||
# Explizite Angabe des Fremdschlüssels
|
# Explizite Angabe des Fremdschlüssels
|
||||||
|
|
@ -70,7 +71,7 @@ class BundleItem(Base):
|
||||||
bundle_id = Column(Integer, ForeignKey('bundles.id'))
|
bundle_id = Column(Integer, ForeignKey('bundles.id'))
|
||||||
title = Column(String) # Titel des Elements (z. B. Buch- oder Spielname)
|
title = Column(String) # Titel des Elements (z. B. Buch- oder Spielname)
|
||||||
category = Column(String) # Kategorie, z. B. "book", "game" oder "software"
|
category = Column(String) # Kategorie, z. B. "book", "game" oder "software"
|
||||||
details = Column(Text) # Optional: ganze Detaildaten als JSON-String
|
details = Column(Text) # Optionale Detaildaten als JSON-String
|
||||||
|
|
||||||
bundle = relationship("Bundle", back_populates="items")
|
bundle = relationship("Bundle", back_populates="items")
|
||||||
|
|
||||||
|
|
@ -79,16 +80,27 @@ class BundleItem(Base):
|
||||||
# ---------------------------
|
# ---------------------------
|
||||||
|
|
||||||
def calculate_hash(data: dict) -> str:
|
def calculate_hash(data: dict) -> str:
|
||||||
"""Berechnet einen SHA-256-Hash aus dem sortierten JSON-String der relevanten Daten."""
|
"""Berechnet einen SHA-256-Hash aus dem sortierten JSON-String der Daten."""
|
||||||
json_string = json.dumps(data, sort_keys=True, ensure_ascii=False)
|
json_string = json.dumps(data, sort_keys=True, ensure_ascii=False)
|
||||||
hash_value = hashlib.sha256(json_string.encode('utf-8')).hexdigest()
|
hash_value = hashlib.sha256(json_string.encode('utf-8')).hexdigest()
|
||||||
logger.debug(f"Berechneter Hash: {hash_value}")
|
logger.debug(f"Berechneter Hash: {hash_value}")
|
||||||
return hash_value
|
return hash_value
|
||||||
|
|
||||||
|
def log_diff(old_data: dict, new_data: dict):
|
||||||
|
"""
|
||||||
|
Vergleicht zwei JSON-Daten (als dict) und gibt einen unified diff als String aus.
|
||||||
|
Nur im DEBUG-Level wird der detaillierte Vergleich ausgegeben.
|
||||||
|
"""
|
||||||
|
old_str = json.dumps(old_data, sort_keys=True, indent=4, ensure_ascii=False).splitlines()
|
||||||
|
new_str = json.dumps(new_data, sort_keys=True, indent=4, ensure_ascii=False).splitlines()
|
||||||
|
diff = difflib.unified_diff(old_str, new_str, fromfile="alte_version", tofile="neue_version", lineterm="")
|
||||||
|
diff_text = "\n".join(diff)
|
||||||
|
logger.debug("Unterschiede zwischen den Versionen:\n" + diff_text)
|
||||||
|
|
||||||
def fetch_bundle_data(url: str) -> dict:
|
def fetch_bundle_data(url: str) -> dict:
|
||||||
"""
|
"""
|
||||||
Lädt die Detailseite eines Bundles und extrahiert den JSON-Inhalt aus dem <script>-Tag
|
Lädt die Detailseite eines Bundles und extrahiert den JSON-Inhalt aus dem <script>-Tag
|
||||||
mit id="webpack-bundle-page-data" (dieser enthält die detaillierten Bundle-Daten).
|
mit id="webpack-bundle-page-data" (diese Seite enthält die detaillierten Bundle-Daten).
|
||||||
"""
|
"""
|
||||||
logger.info(f"Rufe Bundle-Daten von {url} ab...")
|
logger.info(f"Rufe Bundle-Daten von {url} ab...")
|
||||||
response = requests.get(url)
|
response = requests.get(url)
|
||||||
|
|
@ -117,13 +129,15 @@ def process_bundle(session, url: str):
|
||||||
logger.error(f"Fehler beim Laden der Bundle-Daten von {url}: {e}")
|
logger.error(f"Fehler beim Laden der Bundle-Daten von {url}: {e}")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Es wird angenommen, dass die Bundle-Daten unter dem Schlüssel "bundleData" stehen.
|
||||||
bundle_data = data.get("bundleData", {})
|
bundle_data = data.get("bundleData", {})
|
||||||
machine_name = bundle_data.get("machine_name", "")
|
machine_name = bundle_data.get("machine_name", "")
|
||||||
human_name = bundle_data.get("human_name", "")
|
human_name = bundle_data.get("human_name", "")
|
||||||
logger.info(f"Verarbeite Bundle '{human_name}' (machine_name: {machine_name})")
|
logger.info(f"Verarbeite Bundle '{human_name}' (machine_name: {machine_name})")
|
||||||
|
|
||||||
# Hier definieren wir den relevanten Datenausschnitt (anpassen, falls nötig)
|
# Definiere hier den relevanten Datenausschnitt (anpassen, falls nötig)
|
||||||
relevant_data = bundle_data
|
relevant_data = bundle_data
|
||||||
|
|
||||||
new_hash = calculate_hash(relevant_data)
|
new_hash = calculate_hash(relevant_data)
|
||||||
|
|
||||||
# Suche, ob das Bundle bereits existiert
|
# Suche, ob das Bundle bereits existiert
|
||||||
|
|
@ -143,7 +157,17 @@ def process_bundle(session, url: str):
|
||||||
.first())
|
.first())
|
||||||
|
|
||||||
if latest_version is None or latest_version.version_hash != new_hash:
|
if latest_version is None or latest_version.version_hash != new_hash:
|
||||||
logger.info(f"Neue Version für Bundle '{human_name}' wird gespeichert (alter Hash: {latest_version.version_hash if latest_version else 'None'}, neuer Hash: {new_hash}).")
|
if latest_version:
|
||||||
|
if logger.isEnabledFor(logging.DEBUG):
|
||||||
|
try:
|
||||||
|
old_data = json.loads(latest_version.version_data)
|
||||||
|
log_diff(old_data, relevant_data)
|
||||||
|
except Exception as ex:
|
||||||
|
logger.debug(f"Fehler beim Diff-Vergleich: {ex}")
|
||||||
|
else:
|
||||||
|
logger.info("Hashabweichung festgestellt – es gibt Änderungen in den Bundle-Daten.")
|
||||||
|
else:
|
||||||
|
logger.info("Keine vorherige Version gefunden – neues Bundle wird gespeichert.")
|
||||||
new_version = BundleVersion(
|
new_version = BundleVersion(
|
||||||
bundle_id=bundle.id,
|
bundle_id=bundle.id,
|
||||||
version_hash=new_hash,
|
version_hash=new_hash,
|
||||||
|
|
@ -178,7 +202,7 @@ def process_bundle(session, url: str):
|
||||||
# ---------------------------
|
# ---------------------------
|
||||||
# Extraktion der Bundle-Elemente
|
# Extraktion der Bundle-Elemente
|
||||||
# ---------------------------
|
# ---------------------------
|
||||||
# Wir gehen hier davon aus, dass in den Bundle-Daten unter dem Schlüssel "items" eine Liste der enthaltenen Produkte steht.
|
# Hier wird angenommen, dass in den Bundle-Daten unter "items" eine Liste der enthaltenen Produkte steht.
|
||||||
items = bundle_data.get("items", [])
|
items = bundle_data.get("items", [])
|
||||||
if items:
|
if items:
|
||||||
logger.info(f"Es wurden {len(items)} Elemente im Bundle '{human_name}' gefunden.")
|
logger.info(f"Es wurden {len(items)} Elemente im Bundle '{human_name}' gefunden.")
|
||||||
|
|
@ -186,7 +210,7 @@ def process_bundle(session, url: str):
|
||||||
title = item.get("title", "Unbekannt")
|
title = item.get("title", "Unbekannt")
|
||||||
category = item.get("category", "Unbekannt")
|
category = item.get("category", "Unbekannt")
|
||||||
details = json.dumps(item, sort_keys=True, ensure_ascii=False)
|
details = json.dumps(item, sort_keys=True, ensure_ascii=False)
|
||||||
# Prüfen, ob das Element bereits gespeichert wurde (zum Beispiel anhand des Titels)
|
# Prüfen, ob das Element bereits gespeichert wurde (z. B. anhand des Titels)
|
||||||
existing_item = session.query(BundleItem).filter_by(bundle_id=bundle.id, title=title).first()
|
existing_item = session.query(BundleItem).filter_by(bundle_id=bundle.id, title=title).first()
|
||||||
if existing_item:
|
if existing_item:
|
||||||
logger.debug(f"Element '{title}' im Bundle '{human_name}' existiert bereits – Überspringe.")
|
logger.debug(f"Element '{title}' im Bundle '{human_name}' existiert bereits – Überspringe.")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue