erweiterung um bundle elemente
This commit is contained in:
parent
19a01a4f90
commit
ff89f339b8
|
|
@ -21,13 +21,15 @@ logger = logging.getLogger(__name__)
|
||||||
# Basis-Klasse für SQLAlchemy-Modelle (SQLAlchemy 2.0-konform)
|
# Basis-Klasse für SQLAlchemy-Modelle (SQLAlchemy 2.0-konform)
|
||||||
Base = declarative_base()
|
Base = declarative_base()
|
||||||
|
|
||||||
# Tabelle für das Bundle (statische Identifikation)
|
# ---------------------------
|
||||||
|
# Datenbank-Modelle
|
||||||
|
# ---------------------------
|
||||||
|
|
||||||
class Bundle(Base):
|
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)
|
human_name = Column(String) # Falls vorhanden; kann leer bleiben, wenn nicht extrahiert
|
||||||
# current_version_id verweist auf die aktuelle Version in bundle_versions
|
|
||||||
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
|
||||||
|
|
@ -39,8 +41,9 @@ 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.)
|
||||||
|
items = relationship("BundleItem", back_populates="bundle")
|
||||||
|
|
||||||
# Tabelle für Versionen eines Bundles
|
|
||||||
class BundleVersion(Base):
|
class BundleVersion(Base):
|
||||||
__tablename__ = 'bundle_versions'
|
__tablename__ = 'bundle_versions'
|
||||||
id = Column(Integer, primary_key=True)
|
id = Column(Integer, primary_key=True)
|
||||||
|
|
@ -49,10 +52,9 @@ class BundleVersion(Base):
|
||||||
version_data = Column(Text) # Relevante Bundle-Daten als JSON-String
|
version_data = Column(Text) # Relevante Bundle-Daten als JSON-String
|
||||||
timestamp = Column(DateTime, default=datetime.utcnow)
|
timestamp = Column(DateTime, default=datetime.utcnow)
|
||||||
|
|
||||||
# Eindeutige Beziehung: wir verwenden hier explizit bundle_id
|
# Explizite Angabe des Fremdschlüssels
|
||||||
bundle = relationship("Bundle", back_populates="versions", foreign_keys=[bundle_id])
|
bundle = relationship("Bundle", back_populates="versions", foreign_keys=[bundle_id])
|
||||||
|
|
||||||
# Tabelle für Verkaufshistorie
|
|
||||||
class BundleSalesHistory(Base):
|
class BundleSalesHistory(Base):
|
||||||
__tablename__ = 'bundle_sales_history'
|
__tablename__ = 'bundle_sales_history'
|
||||||
id = Column(Integer, primary_key=True)
|
id = Column(Integer, primary_key=True)
|
||||||
|
|
@ -62,15 +64,32 @@ class BundleSalesHistory(Base):
|
||||||
|
|
||||||
bundle = relationship("Bundle", back_populates="sales_history")
|
bundle = relationship("Bundle", back_populates="sales_history")
|
||||||
|
|
||||||
|
class BundleItem(Base):
|
||||||
|
__tablename__ = 'bundle_items'
|
||||||
|
id = Column(Integer, primary_key=True)
|
||||||
|
bundle_id = Column(Integer, ForeignKey('bundles.id'))
|
||||||
|
title = Column(String) # Titel des Elements (z. B. Buch- oder Spielname)
|
||||||
|
category = Column(String) # Kategorie, z. B. "book", "game" oder "software"
|
||||||
|
details = Column(Text) # Optional: ganze Detaildaten als JSON-String
|
||||||
|
|
||||||
|
bundle = relationship("Bundle", back_populates="items")
|
||||||
|
|
||||||
|
# ---------------------------
|
||||||
|
# Hilfsfunktionen
|
||||||
|
# ---------------------------
|
||||||
|
|
||||||
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 relevanten 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 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 'webpack-bundle-page-data'."""
|
"""
|
||||||
|
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).
|
||||||
|
"""
|
||||||
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)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
@ -80,7 +99,7 @@ def fetch_bundle_data(url: str) -> dict:
|
||||||
logger.error("Kein JSON-Datenblock 'webpack-bundle-page-data' gefunden!")
|
logger.error("Kein JSON-Datenblock 'webpack-bundle-page-data' gefunden!")
|
||||||
raise ValueError("Kein JSON-Datenblock gefunden!")
|
raise ValueError("Kein JSON-Datenblock gefunden!")
|
||||||
data = json.loads(script_tag.string)
|
data = json.loads(script_tag.string)
|
||||||
logger.debug(f"Erhaltener JSON-Block: {str(data)[:200]} ...")
|
logger.debug(f"Erhaltener JSON-Block (gekürzt): {str(data)[:200]} ...")
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def process_bundle(session, url: str):
|
def process_bundle(session, url: str):
|
||||||
|
|
@ -90,6 +109,7 @@ def process_bundle(session, url: str):
|
||||||
- Berechnet einen Hash des relevanten Datenbereichs
|
- Berechnet einen Hash des relevanten Datenbereichs
|
||||||
- Vergleicht mit der letzten Version in der DB und speichert bei Änderung eine neue Version
|
- Vergleicht mit der letzten Version in der DB und speichert bei Änderung eine neue Version
|
||||||
- Speichert Verkaufszahlen in einer separaten Tabelle
|
- Speichert Verkaufszahlen in einer separaten Tabelle
|
||||||
|
- Extrahiert und speichert, falls vorhanden, einzelne Bundle-Elemente
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
data = fetch_bundle_data(url)
|
data = fetch_bundle_data(url)
|
||||||
|
|
@ -102,21 +122,21 @@ def process_bundle(session, url: str):
|
||||||
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})")
|
||||||
|
|
||||||
# Wir nehmen das gesamte bundleData als relevanten Datenausschnitt
|
# Hier definieren wir 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
|
||||||
bundle = session.query(Bundle).filter_by(machine_name=machine_name).first()
|
bundle = session.query(Bundle).filter_by(machine_name=machine_name).first()
|
||||||
if not bundle:
|
if not bundle:
|
||||||
logger.info(f"Neues Bundle gefunden: {human_name}")
|
logger.info(f"Neues Bundle gefunden: '{human_name}' (machine_name: {machine_name})")
|
||||||
bundle = Bundle(machine_name=machine_name, human_name=human_name)
|
bundle = Bundle(machine_name=machine_name, human_name=human_name)
|
||||||
session.add(bundle)
|
session.add(bundle)
|
||||||
session.commit()
|
session.commit()
|
||||||
else:
|
else:
|
||||||
logger.debug(f"Bundle '{human_name}' existiert bereits (ID: {bundle.id})")
|
logger.debug(f"Bundle '{human_name}' existiert bereits (ID: {bundle.id})")
|
||||||
|
|
||||||
# Hole die aktuellste Version dieses Bundles
|
# Vergleiche mit der aktuellsten Version
|
||||||
latest_version = (session.query(BundleVersion)
|
latest_version = (session.query(BundleVersion)
|
||||||
.filter_by(bundle_id=bundle.id)
|
.filter_by(bundle_id=bundle.id)
|
||||||
.order_by(BundleVersion.timestamp.desc())
|
.order_by(BundleVersion.timestamp.desc())
|
||||||
|
|
@ -136,7 +156,7 @@ def process_bundle(session, url: str):
|
||||||
else:
|
else:
|
||||||
logger.info(f"Bundle '{human_name}' hat sich nicht geändert.")
|
logger.info(f"Bundle '{human_name}' hat sich nicht geändert.")
|
||||||
|
|
||||||
# Verkaufszahlen extrahieren – angenommen, sie stehen entweder direkt in bundleData oder unter basic_data
|
# Verkaufszahlen extrahieren – hier wird angenommen, dass sie unter einem entsprechenden Schlüssel stehen
|
||||||
bundles_sold = bundle_data.get("bundles_sold|decimal")
|
bundles_sold = bundle_data.get("bundles_sold|decimal")
|
||||||
if bundles_sold is None:
|
if bundles_sold is None:
|
||||||
bundles_sold = bundle_data.get("basic_data", {}).get("bundles_sold|decimal")
|
bundles_sold = bundle_data.get("basic_data", {}).get("bundles_sold|decimal")
|
||||||
|
|
@ -151,10 +171,38 @@ def process_bundle(session, url: str):
|
||||||
session.commit()
|
session.commit()
|
||||||
logger.info(f"Verkaufszahlen für Bundle '{human_name}' aktualisiert: {sales_value}")
|
logger.info(f"Verkaufszahlen für Bundle '{human_name}' aktualisiert: {sales_value}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Fehler beim Speichern der Verkaufszahlen für {human_name}: {e}")
|
logger.error(f"Fehler beim Speichern der Verkaufszahlen für '{human_name}': {e}")
|
||||||
else:
|
else:
|
||||||
logger.debug(f"Keine Verkaufszahlen für Bundle '{human_name}' gefunden.")
|
logger.debug(f"Keine Verkaufszahlen für Bundle '{human_name}' gefunden.")
|
||||||
|
|
||||||
|
# ---------------------------
|
||||||
|
# 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.
|
||||||
|
items = bundle_data.get("items", [])
|
||||||
|
if items:
|
||||||
|
logger.info(f"Es wurden {len(items)} Elemente im Bundle '{human_name}' gefunden.")
|
||||||
|
for item in items:
|
||||||
|
title = item.get("title", "Unbekannt")
|
||||||
|
category = item.get("category", "Unbekannt")
|
||||||
|
details = json.dumps(item, sort_keys=True, ensure_ascii=False)
|
||||||
|
# Prüfen, ob das Element bereits gespeichert wurde (zum Beispiel anhand des Titels)
|
||||||
|
existing_item = session.query(BundleItem).filter_by(bundle_id=bundle.id, title=title).first()
|
||||||
|
if existing_item:
|
||||||
|
logger.debug(f"Element '{title}' im Bundle '{human_name}' existiert bereits – Überspringe.")
|
||||||
|
else:
|
||||||
|
new_item = BundleItem(
|
||||||
|
bundle_id=bundle.id,
|
||||||
|
title=title,
|
||||||
|
category=category,
|
||||||
|
details=details
|
||||||
|
)
|
||||||
|
session.add(new_item)
|
||||||
|
logger.debug(f"Neues Element '{title}' im Bundle '{human_name}' wird gespeichert.")
|
||||||
|
session.commit()
|
||||||
|
else:
|
||||||
|
logger.debug(f"Keine Bundle-Elemente (items) im Bundle '{human_name}' gefunden.")
|
||||||
|
|
||||||
def get_bundle_urls(overview_url: str) -> list:
|
def get_bundle_urls(overview_url: str) -> list:
|
||||||
"""
|
"""
|
||||||
Ruft die Übersichtsseite ab und extrahiert alle Bundle-URLs aus dem JSON-Datenblock im <script>-Tag 'landingPage-json-data'.
|
Ruft die Übersichtsseite ab und extrahiert alle Bundle-URLs aus dem JSON-Datenblock im <script>-Tag 'landingPage-json-data'.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue