Commit 38b8a85a authored by Marco Schmiedel's avatar Marco Schmiedel

fix

parent 832a665b
{
"fileId": "01509001-bd4e-4462-93ad-dc5066fd729a",
"originalPath": "work/models/deal_deal.py",
"currentPath": "work/models/deal_deal.py",
"hash": "099f755dbb69b9e183dcf39898a9cb00f6eed50a9fede3188d0171f421476186",
"docContent": "<p><br></p>",
"checkedStatus": "done",
"comments": [],
"lastCheckedTimestamp": 1744626243451,
"lastFileModificationTimestamp": 1744625938604.1194,
"flaggedForCopy": false
}
{
"fileId": "24784b38-54dc-4000-9d2a-f59082ebbc1c",
"originalPath": "work/models/base_base.py",
"currentPath": "work/models/base_base.py",
"hash": "ac06e8d1df3e4d73f4cc72b071faaf8536fdf8471b453e384512b98845b4e566",
"docContent": "<p><br></p>",
"checkedStatus": "done",
"comments": [],
"lastCheckedTimestamp": 1744626237630,
"lastFileModificationTimestamp": 1744625911470.088,
"flaggedForCopy": false
}
{
"fileId": "2bcab2ab-e02d-4503-b29c-4b741cff2c87",
"originalPath": "work/notebooks/ImportBase.ipynb",
"currentPath": "work/notebooks/ImportBase.ipynb",
"hash": "a783a40298808a6c9db766ba799d8503e525e1a0389729dc54a96d0d512399c1",
"docContent": "<p><br></p>",
"checkedStatus": "done",
"comments": [],
"lastCheckedTimestamp": 1744626641878,
"lastFileModificationTimestamp": 1744626637248.7,
"flaggedForCopy": false
}
{
"fileId": "2d1520cf-72fc-48b2-9249-380bc40499ed",
"originalPath": "work/notebooks/GetDataFromMaui.ipynb",
"currentPath": "work/notebooks/GetDataFromMaui.ipynb",
"hash": "e2d6ebb28291ed4181c547bd6dee318102be9f937145e1dfeb6c1ff0a001cb39",
"docContent": "<p><br></p>",
"checkedStatus": "done",
"comments": [],
"lastCheckedTimestamp": 1744625554372,
"lastFileModificationTimestamp": 1744625453090.3838
}
{
"fileId": "38b9eebe-955e-4052-a0f6-29c69b1242b3",
"originalPath": "work/config/MysqlConfig.py",
"currentPath": "work/config/MysqlConfig.py",
"hash": "f9c624640584ecc2400d5053b7b366a6b2305ecdf836221aa95e3e169254af79",
"docContent": "<p><br></p>",
"checkedStatus": "done",
"comments": [
{
"commentId": "56c5adba-20f4-4524-a894-41f81ab7ca55",
"text": "This data is currently stored statically and should be dynamically linked to the Docker container at the appropriate time.",
"timestamp": 1744622354948
}
],
"lastCheckedTimestamp": 1744624735475,
"lastFileModificationTimestamp": 1744624729595.99
}
{
"fileId": "5d19e01b-5fd7-4215-8da7-6b0a95956727",
"originalPath": "work/models/_system.py",
"currentPath": "work/models/_system.py",
"hash": "86d0b6279cb483585f66646c598db95bbda853070ef80a5b05218826dea61299",
"docContent": "<p><br></p>",
"checkedStatus": "done",
"comments": [],
"lastCheckedTimestamp": 1744626235100,
"lastFileModificationTimestamp": 1744625929875.2092
}
...@@ -2,10 +2,10 @@ ...@@ -2,10 +2,10 @@
"fileId": "5ec4e9ba-309d-438b-8e17-ce5802f3deb2", "fileId": "5ec4e9ba-309d-438b-8e17-ce5802f3deb2",
"originalPath": "work/workbench/Documentation.md", "originalPath": "work/workbench/Documentation.md",
"currentPath": "work/workbench/Documentation.md", "currentPath": "work/workbench/Documentation.md",
"hash": "7f2058d974b71f0a0da97fc41b918c41c33a4905c15cd54d189ec8d1311fa972", "hash": "f56f35397937e2b3c5e0f8507385b65d8afb0a3321c649466d9698212dc14a62",
"docContent": "<p><br></p>", "docContent": "<p><br></p>",
"checkedStatus": "done", "checkedStatus": "done",
"comments": [], "comments": [],
"lastCheckedTimestamp": 1744619683307, "lastCheckedTimestamp": 1744626251158,
"lastFileModificationTimestamp": 1744619461883.1462 "lastFileModificationTimestamp": 1744626180275.6394
} }
{
"fileId": "766dc461-001e-4901-8faf-263820ad96cd",
"originalPath": "work/manager/MysqlManager.py",
"currentPath": "work/manager/MysqlManager.py",
"hash": "c016d5cb9c9b391d19e41323196715c0e57b5838846232d2bdf761f53293e1b4",
"docContent": "<p><br></p>",
"checkedStatus": "done",
"comments": [],
"lastCheckedTimestamp": 1744624751868,
"lastFileModificationTimestamp": 1744624748400.9556,
"flaggedForCopy": false
}
{
"fileId": "858c1430-9b46-4bb8-bb8d-4e5380be9c0d",
"originalPath": "work/models/provisiongroup_pgro.py",
"currentPath": "work/models/provisiongroup_pgro.py",
"hash": "3c4c4f905da78503daf856aa9f9ab1b0bac3e8c959ad23ec6f260f3964c8f98f",
"docContent": "<p><br></p>",
"checkedStatus": "done",
"comments": [],
"lastCheckedTimestamp": 1744626248304,
"lastFileModificationTimestamp": 1744625950442.8225,
"flaggedForCopy": false
}
{
"fileId": "9384da7d-c01c-4108-8ada-f94de6e61346",
"originalPath": "work/models/basegroup_bgro.py",
"currentPath": "work/models/basegroup_bgro.py",
"hash": "46899d2f0bba4f6a2dcbe92272eccf5f4196411d4c15a0d65275d118bfafa758",
"docContent": "<p><br></p>",
"checkedStatus": "done",
"comments": [],
"lastCheckedTimestamp": 1744626240017,
"lastFileModificationTimestamp": 1744625913887.2903,
"flaggedForCopy": false
}
{
"fileId": "e146d34c-9d63-4ea0-b8b8-5f8d503f34f6",
"originalPath": "work/models/option_opti.py",
"currentPath": "work/models/option_opti.py",
"hash": "b2da20cf0b8a0d239fc042773ecacca52777230bb7b2c418fe6d45d55fc3e37b",
"docContent": "<p><br></p>",
"checkedStatus": "done",
"comments": [],
"lastCheckedTimestamp": 1744626245976,
"lastFileModificationTimestamp": 1744625945396.1123,
"flaggedForCopy": false
}
<p><br></p>
\ No newline at end of file
# The MySQL host address.
MYSQL_HOST = "itmax-backoffice-prod-aurora-r3dbcluster-1e8hysitdwijk.cluster-crb5tahpiszg.eu-central-1.rds.amazonaws.com"
# The MySQL username for the connection.
MYSQL_USER = "labor-itmaxmaster"
# The MySQL password for the connection.
MYSQL_PASSWORD = "floz09sx3dTyx144gy"
# The MySQL database name.
MYSQL_DATABASE = "itmax_tarifs"
# The MySQL port number.
MYSQL_PORT = 3306
# If set to True, the MysqlManager will establish an SSH tunnel when connecting to MySQL.
USE_SSH_TUNNEL = True
# The SSH host address (the remote SSH server to tunnel through).
SSH_HOST = "jumphost.bugsmasher.online"
# The SSH port number (default is usually 22).
SSH_PORT = 22
# The SSH username for the tunnel.
SSH_USERNAME = "root"
# The SSH password for the tunnel.
SSH_PASSWORD = "7dHz2xO8ct1143T"
\ No newline at end of file
import sys; sys.path.append("..")
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
import config.MysqlConfig as DatabaseConfig
from sshtunnel import SSHTunnelForwarder
# We create a new class called MysqlManager.
class MysqlManager:
# The following constructor initializes the MySQL connection.
def __init__(self):
# Instead of reading a configuration file, we import the Python configuration.
# We construct the configuration dictionary using values from the DatabaseConfig module.
self.config = {
"host": DatabaseConfig.MYSQL_HOST,
"user": DatabaseConfig.MYSQL_USER,
"password": DatabaseConfig.MYSQL_PASSWORD,
"database": DatabaseConfig.MYSQL_DATABASE,
"port": DatabaseConfig.MYSQL_PORT
}
# Check if an SSH tunnel should be used.
if getattr(DatabaseConfig, "USE_SSH_TUNNEL", False):
# Initialize the SSH tunnel using SSHTunnelForwarder.
self.tunnel = SSHTunnelForwarder(
(DatabaseConfig.SSH_HOST, DatabaseConfig.SSH_PORT),
ssh_username=DatabaseConfig.SSH_USERNAME,
ssh_password=DatabaseConfig.SSH_PASSWORD,
remote_bind_address=(self.config["host"], self.config["port"])
)
# Start the SSH tunnel.
self.tunnel.start()
# Set the host to localhost and port to the tunnel's local bind port.
host = "127.0.0.1"
port = self.tunnel.local_bind_port
else:
# No SSH tunnel is used.
self.tunnel = None
host = self.config["host"]
port = self.config["port"]
# Construct the MySQL engine using the connection details.
engine = create_engine(
f"mysql+pymysql://{self.config['user']}:{self.config['password']}@{host}:{port}/{self.config['database']}",
echo=False
)
# Start the database session.
self.session = sessionmaker(bind=engine)()
# "getSession" provides the database connection to other modules.
def getSession(self):
return self.session
# The following method closes the database connection and stops the SSH tunnel if active.
def close(self):
self.session.close()
if self.tunnel:
self.tunnel.stop()
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey
from sqlalchemy.orm import relationship
from models._system import Base
from models.basegroup_bgro import BasegroupBgro
class BaseBase(Base):
__tablename__ = 'base_base'
id_base = Column(Integer, primary_key=True, autoincrement=True)
basegroup_base = Column(Integer, ForeignKey('basegroup_bgro.id_bgro'))
provider_base = Column(String(255), nullable=False)
providercode_base = Column(String(255))
name_base = Column(String(255), nullable=False)
alias_base = Column(String(255))
network_base = Column(Integer, nullable=False)
type_base = Column(Integer, nullable=False)
created_base = Column(DateTime, nullable=False)
updated_base = Column(DateTime, nullable=False)
basegroup = relationship("BasegroupBgro", back_populates="bases")
deals = relationship("DealDeal", back_populates="base")
options = relationship("OptionOpti", back_populates="base")
from sqlalchemy import Column, Integer, String, DateTime
from sqlalchemy.orm import relationship
from models._system import Base
class BasegroupBgro(Base):
__tablename__ = 'basegroup_bgro'
id_bgro = Column(Integer, primary_key=True, autoincrement=True)
name_bgro = Column(String(255), nullable=False)
created_bgro = Column(DateTime, nullable=False)
updated_bgro = Column(DateTime, nullable=False)
bases = relationship("BaseBase", back_populates="basegroup")
from sqlalchemy import Column, Integer, String, Numeric, DateTime, ForeignKey
from sqlalchemy.orm import relationship
from models._system import Base
from models.provisiongroup_pgro import ProvisiongroupPgro
class DealDeal(Base):
__tablename__ = 'deal_deal'
__table_args__ = {"mysql_engine": "InnoDB"}
id_deal = Column(Integer, primary_key=True, autoincrement=True)
provisiongroup_deal = Column(Integer, ForeignKey('provisiongroup_pgro.id_pgro'))
base_deal = Column(Integer, ForeignKey('base_base.id_base'), nullable=False)
providercode_deal = Column(String(255))
alias_deal = Column(String(255))
price_deal = Column(Numeric(8, 5), nullable=False)
starts_deal = Column(DateTime, nullable=False)
stops_deal = Column(DateTime)
provision1_deal = Column(Numeric(10, 5), nullable=False, default=0.00000)
provision2_deal = Column(Numeric(10, 5), nullable=False, default=0.00000)
provision3_deal = Column(Numeric(10, 5), nullable=False, default=0.00000)
provision4_deal = Column(Numeric(10, 5), nullable=False, default=0.00000)
created_deal = Column(DateTime, nullable=False)
updated_deal = Column(DateTime, nullable=False)
base = relationship("BaseBase", back_populates="deals")
provisiongroup = relationship("ProvisiongroupPgro", back_populates="deals")
from sqlalchemy import Column, Integer, String, Numeric, DateTime, ForeignKey
from sqlalchemy.orm import relationship
from models._system import Base
from models.provisiongroup_pgro import ProvisiongroupPgro
class OptionOpti(Base):
__tablename__ = 'option_opti'
__table_args__ = {"mysql_engine": "InnoDB"}
id_opti = Column(Integer, primary_key=True, autoincrement=True)
provisiongroup_opti = Column(Integer, ForeignKey('provisiongroup_pgro.id_pgro'))
base_opti = Column(Integer, ForeignKey('base_base.id_base'), nullable=False)
providercode_opti = Column(String(255))
providercategory_opti = Column(String(255))
name_opti = Column(String(255), nullable=False)
alias_opti = Column(String(255))
price_opti = Column(Numeric(8, 5), nullable=False)
starts_opti = Column(DateTime, nullable=False)
stops_opti = Column(DateTime)
provision1_opti = Column(Numeric(10, 5), nullable=False, default=0.00000)
provision2_opti = Column(Numeric(10, 5), nullable=False, default=0.00000)
provision3_opti = Column(Numeric(10, 5), nullable=False, default=0.00000)
provision4_opti = Column(Numeric(10, 5), nullable=False, default=0.00000)
created_opti = Column(DateTime, nullable=False)
updated_opti = Column(DateTime, nullable=False)
base = relationship("BaseBase", back_populates="options")
provisiongroup = relationship("ProvisiongroupPgro", back_populates="options")
from sqlalchemy import Column, Integer, String, Numeric, DateTime
from sqlalchemy.orm import relationship
from models._system import Base
class ProvisiongroupPgro(Base):
__tablename__ = 'provisiongroup_pgro'
id_pgro = Column(Integer, primary_key=True, autoincrement=True)
name_pgro = Column(String(255), nullable=False)
percent_pgro = Column(Numeric(5, 2), nullable=False, default=0.00)
created_pgro = Column(DateTime, nullable=False)
updated_pgro = Column(DateTime)
deals = relationship("DealDeal", back_populates="provisiongroup")
options = relationship("OptionOpti", back_populates="provisiongroup")
...@@ -2,152 +2,21 @@ ...@@ -2,152 +2,21 @@
"cells": [ "cells": [
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 5, "execution_count": 2,
"id": "193fb1fb-6ad1-447e-b641-73857be9d9dd", "id": "193fb1fb-6ad1-447e-b641-73857be9d9dd",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
"name": "stdout", "ename": "KeyboardInterrupt",
"output_type": "stream", "evalue": "",
"text": [ "output_type": "error",
"3877325 - D1 - Allnet Flat 20 GB Telekom (Okt 2024)\n", "traceback": [
"3877349 - D1 - Allnet Flat 20 GB Telekom (Okt 2024) mit Smartphone 10\n", "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"3877337 - D1 - Allnet Flat 20 GB Telekom (Okt 2024) mit Smartphone 5\n", "\u001b[0;31mKeyboardInterrupt\u001b[0m Traceback (most recent call last)",
"3878213 - D1 - Allnet Flat 25 GB Telekom (Okt 2024)\n", "Cell \u001b[0;32mIn[2], line 536\u001b[0m\n\u001b[1;32m 534\u001b[0m \u001b[38;5;66;03m# Diese Anweisungen führen den gesamten Ablauf aus.\u001b[39;00m\n\u001b[1;32m 535\u001b[0m login(manager, MAUI_USERNAME, MAUI_PASSWORD, MAUI_AUTHCODE)\n\u001b[0;32m--> 536\u001b[0m \u001b[43mopenLaufzeitvertrag\u001b[49m\u001b[43m(\u001b[49m\u001b[43mmanager\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 537\u001b[0m time\u001b[38;5;241m.\u001b[39msleep(\u001b[38;5;241m10\u001b[39m)\n\u001b[1;32m 538\u001b[0m scrapeData(manager)\n",
"3878237 - D1 - Allnet Flat 25 GB Telekom (Okt 2024) mit Smartphone 10\n", "Cell \u001b[0;32mIn[2], line 96\u001b[0m, in \u001b[0;36mopenLaufzeitvertrag\u001b[0;34m(manager)\u001b[0m\n\u001b[1;32m 93\u001b[0m wait \u001b[38;5;241m=\u001b[39m WebDriverWait(manager\u001b[38;5;241m.\u001b[39mdriver, \u001b[38;5;241m10\u001b[39m)\n\u001b[1;32m 95\u001b[0m \u001b[38;5;66;03m# Diese Variablenzuweisung wartet, bis das Element mit dem Text \"Laufzeitvertrag\" im DOM vorhanden ist.\u001b[39;00m\n\u001b[0;32m---> 96\u001b[0m laufzeit_element \u001b[38;5;241m=\u001b[39m \u001b[43mwait\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43muntil\u001b[49m\u001b[43m(\u001b[49m\u001b[43mEC\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mpresence_of_element_located\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 97\u001b[0m \u001b[43m \u001b[49m\u001b[43m(\u001b[49m\u001b[43mBy\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mXPATH\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43m//a[contains(text(),\u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[38;5;124;43mLaufzeitvertrag\u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[38;5;124;43m)]\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[1;32m 98\u001b[0m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 100\u001b[0m \u001b[38;5;66;03m# Diese Variablenzuweisung liest das href-Attribut des gefundenen Elements aus.\u001b[39;00m\n\u001b[1;32m 101\u001b[0m url \u001b[38;5;241m=\u001b[39m laufzeit_element\u001b[38;5;241m.\u001b[39mget_attribute(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mhref\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n",
"3878225 - D1 - Allnet Flat 25 GB Telekom (Okt 2024) mit Smartphone 5\n", "File \u001b[0;32m~/anaconda3/lib/python3.10/site-packages/selenium/webdriver/support/wait.py:102\u001b[0m, in \u001b[0;36mWebDriverWait.until\u001b[0;34m(self, method, message)\u001b[0m\n\u001b[1;32m 100\u001b[0m screen \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mgetattr\u001b[39m(exc, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mscreen\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;28;01mNone\u001b[39;00m)\n\u001b[1;32m 101\u001b[0m stacktrace \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mgetattr\u001b[39m(exc, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mstacktrace\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;28;01mNone\u001b[39;00m)\n\u001b[0;32m--> 102\u001b[0m \u001b[43mtime\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43msleep\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_poll\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 103\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m time\u001b[38;5;241m.\u001b[39mmonotonic() \u001b[38;5;241m>\u001b[39m end_time:\n\u001b[1;32m 104\u001b[0m \u001b[38;5;28;01mbreak\u001b[39;00m\n",
"3878165 - D1 - Allnet Flat 35 GB Telekom (Okt 2024)\n", "\u001b[0;31mKeyboardInterrupt\u001b[0m: "
"3878189 - D1 - Allnet Flat 35 GB Telekom (Okt 2024) mit Smartphone 10\n",
"3878177 - D1 - Allnet Flat 35 GB Telekom (Okt 2024) mit Smartphone 5\n",
"3905336 - D1 - Allnet Flat 60 GB Telekom (Okt 2024)\n",
"3905360 - D1 - Allnet Flat 60 GB Telekom (Okt 2024) mit Smartphone 10\n",
"3905348 - D1 - Allnet Flat 60 GB Telekom (Okt 2024) mit Smartphone 5\n",
"3877289 - D1 - Allnet Flat 8 GB Telekom (Okt 2024)\n",
"3877313 - D1 - Allnet Flat 8 GB Telekom (Okt 2024) mit Smartphone 10\n",
"3877301 - D1 - Allnet Flat 8 GB Telekom (Okt 2024) mit Smartphone 5\n",
"3878261 - D1 - Allnet Flat 80 GB Telekom (Okt 2024)\n",
"3878285 - D1 - Allnet Flat 80 GB Telekom (Okt 2024) mit Smartphone 10\n",
"3878273 - D1 - Allnet Flat 80 GB Telekom (Okt 2024) mit Smartphone 5\n",
"3398811 - D1 - green Data L (2021)\n",
"3435075 - D1 - green Data L (2021) mit Hardware 10\n",
"3398807 - D1 - green Data M (2021)\n",
"3435071 - D1 - green Data M (2021) mit Hardware 10\n",
"3398803 - D1 - green Data S (2021)\n",
"3435067 - D1 - green Data S (2021) mit Hardware 10\n",
"3398815 - D1 - green Data XL (2021)\n",
"3435079 - D1 - green Data XL (2021) mit Hardware 10\n",
"3649995 - D1 - green LTE 15 GB Telekom (Jul 2023) mit Smartphone 20\n",
"3596218 - D1 - green LTE 25 GB Telekom (Jul 2023) mit Smartphone 10\n",
"3650007 - D1 - green LTE 25 GB Telekom (Jul 2023) mit Smartphone 20\n",
"3650019 - D1 - green LTE 40 GB Telekom (Jul 2023) mit Smartphone 20\n",
"3650031 - D1 - green LTE 60 GB Telekom (Jul 2023) mit Smartphone 20\n",
"3461557 - D2 - green Data L (SEP 2022)\n",
"3461561 - D2 - green Data L mit Smartphone 10 (SEP 2022)\n",
"3446028 - D2 - green Data S (Jul 2022)\n",
"3461565 - D2 - green Data XL (SEP 2022)\n",
"3461569 - D2 - green Data XL mit Smartphone 10 (SEP 2022)\n",
"3765006 - D2 - green LTE 20 GB VF (April 2024) mit Smartphone 5\n",
"3782151 - O2 - Allnet Flat 10 GB (Jun 2024)\n",
"3833962 - O2 - Allnet Flat 10+7 GB (Aug 2024)\n",
"3833968 - O2 - Allnet Flat 10+7 GB (Aug 2024) mit Smartphone 10\n",
"3833965 - O2 - Allnet Flat 10+7 GB (Aug 2024) mit Smartphone 5\n",
"3975848 - O2 - Allnet Flat 100 GB (Nov 2024)\n",
"3975869 - O2 - Allnet Flat 100 GB (Nov 2024) 1M\n",
"3833971 - O2 - Allnet Flat 15+10 GB (Aug 2024)\n",
"3833977 - O2 - Allnet Flat 15+10 GB (Aug 2024) mit Smartphone 10\n",
"3833974 - O2 - Allnet Flat 15+10 GB (Aug 2024) mit Smartphone 5\n",
"3975851 - O2 - Allnet Flat 150 GB (Nov 2024)\n",
"3975872 - O2 - Allnet Flat 150 GB (Nov 2024) 1M\n",
"3975854 - O2 - Allnet Flat 200 GB (Nov 2024)\n",
"3975875 - O2 - Allnet Flat 200 GB (Nov 2024) 1M\n",
"3833983 - O2 - Allnet Flat 25+10 GB (Aug 2024)\n",
"3833989 - O2 - Allnet Flat 25+10 GB (Aug 2024) mit Smartphone 10\n",
"3833986 - O2 - Allnet Flat 25+10 GB (Aug 2024) mit Smartphone 5\n",
"3975860 - O2 - Allnet Flat 300 GB (Nov 2024)\n",
"3975881 - O2 - Allnet Flat 300 GB (Nov 2024) 1M\n",
"3833995 - O2 - Allnet Flat 35+15 GB (Aug 2024)\n",
"3834001 - O2 - Allnet Flat 35+15 GB (Aug 2024) mit Smartphone 10\n",
"3833998 - O2 - Allnet Flat 35+15 GB (Aug 2024) mit Smartphone 5\n",
"3833953 - O2 - Allnet Flat 5+2 GB (Aug 2024)\n",
"3833959 - O2 - Allnet Flat 5+2 GB (Aug 2024) mit Smartphone 10\n",
"3833956 - O2 - Allnet Flat 5+2 GB (Aug 2024) mit Smartphone 5\n",
"3975845 - O2 - Allnet Flat 70 GB (Nov 2024)\n",
"3975866 - O2 - Allnet Flat 70 GB (Nov 2024) 1M\n",
"3974111 - O2 - Datentarif 400 (Dez 2024)\n",
"3833776 - O2 - family & friends 25+10 GB (Aug 2024)\n",
"3833773 - O2 - family & friends 5+2 GB (Aug 2024)\n",
"3476568 - D1 - KM Allnet Flat 10 GB (D1) 24M (Nov 2022)\n",
"3697996 - D1 - KM Allnet Flat 12 GB (D1) 24M (Nov 2023)\n",
"3877175 - D1 - KM Allnet Flat 15 GB (D1) 24M (Okt 2024)\n",
"3711205 - D1 - KM Allnet Flat 22 GB LTE50 (D1) 24M (Nov 2023)\n",
"3877193 - D1 - KM Allnet Flat 25 GB LTE50 (D1) 24M (Okt 2024)\n",
"3711328 - D1 - KM Allnet Flat 32 GB LTE50 (D1) 24M (Nov 2023)\n",
"3877211 - D1 - KM Allnet Flat 40 GB LTE50 (D1) 1M (Okt 2024)\n",
"3877208 - D1 - KM Allnet Flat 40 GB LTE50 (D1) 24M (Okt 2024)\n",
"3388975 - D1 - KM Minuten Tarif 24M 1GB/100Min/100SMS (D1)\n",
"3389002 - D1 - KM Minuten Tarif 24M 2GB/100Min/100SMS (D1)\n",
"3380690 - D2 - KM Allnet Flat 10GB/LTE50/Voice-&SMS-Flat (VF) 24M\n",
"3743289 - D2 - KM Allnet Flat 17GB/LTE50/Voice-&SMS-Flat (VF) 24M\n",
"3743301 - D2 - KM Allnet Flat 27GB/LTE50/Voice-&SMS-Flat (VF) 24M (Jan 2024)\n",
"3415206 - D2 - KM Allnet Flat 30GB/LTE50/Voice-&SMS-Flat (VF) 24M\n",
"3473445 - D2 - KM Allnet Flat 40GB/LTE50/Voice-&SMS-Flat (VF) 24M\n",
"3385059 - D2 - KM Daten Flat 10GB/LTE50 (VF) 24M\n",
"3385056 - D2 - KM Daten Flat 5GB/LTE50 (VF) 24M\n",
"3861200 - D2 - KM Family & Friends 20 GB 24M\n",
"3861206 - D2 - KM Family & Friends 5 GB 24M\n",
"3387917 - D2 - KM Minuten Tarif 2GB/LTE21/100Min/100SMS (VF) 24M\n",
"3243715 - D2 - Minuten Tarif / Smartphone Flat 24M 1000MB/100Min (VF)\n",
"3833026 - F1 - Family & Friends 25+10 GB (Aug 2024) 24M\n",
"3833020 - F1 - Family & Friends 5+2 GB (Aug 2024) 24M\n",
"3832003 - D1 - Magenta Mobil Basic mit Smartphone 10 (Aug 2024)\n",
"3832111 - D1 - Magenta Mobil L mit Smartphone 10 (Aug 2024)\n",
"3907595 - D1 - Magenta Mobil L Young 5G mit Smartphone 10 (Okt 2024)\n",
"3696895 - D1 - Magenta Mobil L Young 5G mit Smartphone 20 (Mai 2022)\n",
"3832075 - D1 - Magenta Mobil M mit Smartphone 10 (Aug 2024)\n",
"3907559 - D1 - Magenta Mobil M Young 5G mit Smartphone 10 (Okt 2024)\n",
"3696883 - D1 - Magenta Mobil M Young 5G mit Smartphone 20 (Mai 2022)\n",
"3832039 - D1 - Magenta Mobil S mit Smartphone 10 (Aug 2024)\n",
"3907523 - D1 - Magenta Mobil S Young 5G mit Smartphone 10 (Okt 2024)\n",
"3696871 - D1 - Magenta Mobil S Young 5G mit Smartphone 20 (Mai 2022)\n",
"3332926 - D1 - Magenta Mobil Speedbox 100 GB\n",
"3832147 - D1 - Magenta Mobil XL mit Smartphone 10 (Aug 2024)\n",
"3907631 - D1 - Magenta Mobil XL Young 5G mit Smartphone 10 (Okt 2024)\n",
"3696907 - D1 - Magenta Mobil XL Young 5G mit Smartphone 20 (Mai 2022)\n",
"3448928 - D1 - Smart Connect M (Juli 2022)\n",
"3337095 - D1 - Smart Connect S (2019)\n",
"3430251 - D1 - Smart Connect S 12 Monate (Mrz 2022)\n",
"3337091 - D1 - Smart Connect S mit Smartphone 5 (2019)\n",
"3936372 - D2 - Vodafone GigaMobil L mit Smartphone 10 (Nov24)\n",
"3936336 - D2 - Vodafone GigaMobil M mit Smartphone 10 (Nov24)\n",
"3936300 - D2 - Vodafone GigaMobil S mit Smartphone 10 (Nov24)\n",
"3936408 - D2 - Vodafone GigaMobil XL mit Smartphone 10 (Nov24)\n",
"3936264 - D2 - Vodafone GigaMobil XS mit Smartphone 10 (Nov24)\n",
"3750758 - D2 - Vodafone GigaMobil Young L mit Smartphone 10\n",
"3750710 - D2 - Vodafone GigaMobil Young M mit Smartphone 10\n",
"3750083 - D2 - Vodafone GigaMobil Young S mit Smartphone 10\n",
"3750806 - D2 - Vodafone GigaMobil Young XL mit Smartphone 10\n",
"3973286 - O2 - o2 Mobile L (Nov 2024)\n",
"3973292 - O2 - o2 Mobile L (Nov 2024) mit Smartphone 10\n",
"3973289 - O2 - o2 Mobile L (Nov 2024) mit Smartphone 5\n",
"3973277 - O2 - o2 Mobile M PROMO (Nov 2024)\n",
"3973283 - O2 - o2 Mobile M PROMO (Nov 2024) mit Smartphone 10\n",
"3973280 - O2 - o2 Mobile M PROMO (Nov 2024) mit Smartphone 5\n",
"3973259 - O2 - o2 Mobile S (Nov 2024)\n",
"3973265 - O2 - o2 Mobile S (Nov 2024) mit Smartphone 10\n",
"3973262 - O2 - o2 Mobile S (Nov 2024) mit Smartphone 5\n",
"3973250 - O2 - o2 Mobile Starter Flex (Nov 2024)\n",
"3973256 - O2 - o2 Mobile Starter Flex (Nov 2024) mit Smartphone 10\n",
"3973253 - O2 - o2 Mobile Starter Flex (Nov 2024) mit Smartphone 5\n",
"3782346 - O2 - o2 Mobile Unlimited Max (Jun 2024)\n",
"3782349 - O2 - o2 Mobile Unlimited Max (Jun 2024) 1M\n",
"3935061 - O2 - o2 Mobile Unlimited on Demand (Okt 2024)\n",
"3782322 - O2 - o2 Mobile Unlimited Smart (Jun 2024)\n",
"3782325 - O2 - o2 Mobile Unlimited Smart (Jun 2024) 1M\n",
"3782331 - O2 - o2 Mobile Unlimited Smart (Jun 2024) mit Smartphone 10\n",
"3782328 - O2 - o2 Mobile Unlimited Smart (Jun 2024) mit Smartphone 5\n",
"3973295 - O2 - o2 Mobile XL (Nov 2024)\n",
"3973301 - O2 - o2 Mobile XL (Nov 2024) mit Smartphone 10\n",
"3973298 - O2 - o2 Mobile XL (Nov 2024) mit Smartphone 5\n"
] ]
} }
], ],
...@@ -280,27 +149,14 @@ ...@@ -280,27 +149,14 @@
"\n", "\n",
" # Diese Versuchsstruktur sucht das Element für den Tarifpreis anhand der ID.\n", " # Diese Versuchsstruktur sucht das Element für den Tarifpreis anhand der ID.\n",
" try:\n", " try:\n",
"\n",
" # Diese Variablenzuweisung findet das Element, das den Tarifpreis anzeigt.\n",
" price_element = driver.find_element(By.ID, \"preis_anzeige_tarif\")\n", " price_element = driver.find_element(By.ID, \"preis_anzeige_tarif\")\n",
"\n",
" # Diese Variablenzuweisung liest den sichtbaren Text aus dem gefundenen Element.\n",
" price_text = price_element.text\n", " price_text = price_element.text\n",
"\n",
" # Diese Variablenzuweisung durchsucht den Text nach einem Muster, das den Betrag in EUR findet.\n",
" match = re.search(r'([\\d\\.,]+)\\s*EUR', price_text)\n", " match = re.search(r'([\\d\\.,]+)\\s*EUR', price_text)\n",
"\n",
" # Diese Bedingung prüft, ob ein Wert im Text gefunden wurde.\n",
" if match:\n", " if match:\n",
"\n",
" # Diese Variablenzuweisung wandelt das gefundene Match in eine float-Zahl um.\n",
" raw_str = match.group(1).replace(\",\", \".\")\n", " raw_str = match.group(1).replace(\",\", \".\")\n",
" gross_price = float(raw_str)\n", " gross_price = float(raw_str)\n",
"\n",
" # Diese Variablenzuweisung dividiert den Bruttopreis durch 1.19 und rundet auf 5 Nachkommastellen.\n",
" price_net = round(gross_price / 1.19, 5)\n", " price_net = round(gross_price / 1.19, 5)\n",
" except:\n", " except:\n",
" # Diese Ausnahme wird ausgelöst, wenn das Element oder der Text nicht gefunden wurde.\n",
" pass\n", " pass\n",
"\n", "\n",
" # Diese Anweisung gibt den Nettopreis zurück.\n", " # Diese Anweisung gibt den Nettopreis zurück.\n",
...@@ -314,46 +170,24 @@ ...@@ -314,46 +170,24 @@
"\n", "\n",
" # Diese Versuchsstruktur sucht das Dropdown für Aktionen anhand seines Namens.\n", " # Diese Versuchsstruktur sucht das Dropdown für Aktionen anhand seines Namens.\n",
" try:\n", " try:\n",
"\n",
" # Diese Variablenzuweisung sucht das Aktionen-Select-Element.\n",
" campaign_select = driver.find_element(By.NAME, \"am_aktion_select\")\n", " campaign_select = driver.find_element(By.NAME, \"am_aktion_select\")\n",
"\n",
" # Diese Variablenzuweisung findet alle Option-Elemente innerhalb des Selects.\n",
" campaign_options = campaign_select.find_elements(By.TAG_NAME, \"option\")\n", " campaign_options = campaign_select.find_elements(By.TAG_NAME, \"option\")\n",
"\n",
" # Diese Schleife durchläuft jede Option, um sie zu analysieren.\n",
" for copt in campaign_options:\n", " for copt in campaign_options:\n",
"\n",
" # Diese Variablenzuweisung extrahiert den Value, der die ID der Aktion enthält.\n",
" val = copt.get_attribute(\"value\")\n", " val = copt.get_attribute(\"value\")\n",
"\n",
" # Diese Variablenzuweisung extrahiert den sichtbaren Text der Option.\n",
" txt = copt.text.strip()\n", " txt = copt.text.strip()\n",
"\n",
" # Diese Bedingung überspringt ungültige oder Platzhalter-Optionen.\n",
" if not val or val in [\" |\", \"-1|\", \"|\", \"-1|\"]:\n", " if not val or val in [\" |\", \"-1|\", \"|\", \"-1|\"]:\n",
" continue\n", " continue\n",
"\n",
" # Diese Variablenzuweisung teilt den Value an der Pipe, um die ID zu erhalten.\n",
" parts_val = val.split(\"|\")\n", " parts_val = val.split(\"|\")\n",
" campaign_id = parts_val[0].strip()\n", " campaign_id = parts_val[0].strip()\n",
"\n",
" # Diese Bedingung verarbeitet nur sinnvolle Einträge.\n",
" if not campaign_id:\n", " if not campaign_id:\n",
" continue\n", " continue\n",
"\n",
" # Diese Variablenzuweisung versucht, den Aktionsnamen aus dem sichtbaren Text zu extrahieren.\n",
" if \"-\" in txt:\n", " if \"-\" in txt:\n",
" splitted = txt.split(\"-\", 1)\n", " splitted = txt.split(\"-\", 1)\n",
" campaign_name = splitted[1].strip()\n", " campaign_name = splitted[1].strip()\n",
" else:\n", " else:\n",
" campaign_name = txt\n", " campaign_name = txt\n",
"\n",
" # Diese Anweisung fügt ein Tupel (aktions-id, aktionsname) der Ergebnisliste hinzu.\n",
" campaigns_list.append((campaign_id, campaign_name))\n", " campaigns_list.append((campaign_id, campaign_name))\n",
"\n",
" except:\n", " except:\n",
" # Diese Ausnahme wird ausgelöst, wenn das Select-Element nicht gefunden wurde.\n",
" pass\n", " pass\n",
"\n", "\n",
" # Diese Anweisung gibt die Liste mit (aktions-id, aktionsname) zurück.\n", " # Diese Anweisung gibt die Liste mit (aktions-id, aktionsname) zurück.\n",
...@@ -370,18 +204,17 @@ ...@@ -370,18 +204,17 @@
" wait = WebDriverWait(driver, 20)\n", " wait = WebDriverWait(driver, 20)\n",
"\n", "\n",
" # Diese Anweisung öffnet die 4 CSV-Dateien im Schreibmodus und legt die Header fest.\n", " # Diese Anweisung öffnet die 4 CSV-Dateien im Schreibmodus und legt die Header fest.\n",
" # Diese Dateien werden im Ordner ../cache geschrieben.\n",
" with open(\"../cache/plans.csv\", mode=\"w\", newline=\"\", encoding=\"utf-8\") as plansfile, \\\n", " with open(\"../cache/plans.csv\", mode=\"w\", newline=\"\", encoding=\"utf-8\") as plansfile, \\\n",
" open(\"../cache/campaigns.csv\", mode=\"w\", newline=\"\", encoding=\"utf-8\") as campaignsfile, \\\n", " open(\"../cache/campaigns.csv\", mode=\"w\", newline=\"\", encoding=\"utf-8\") as campaignsfile, \\\n",
" open(\"../cache/options.csv\", mode=\"w\", newline=\"\", encoding=\"utf-8\") as optionsfile, \\\n", " open(\"../cache/options.csv\", mode=\"w\", newline=\"\", encoding=\"utf-8\") as optionsfile, \\\n",
" open(\"../cache/categorys.csv\", mode=\"w\", newline=\"\", encoding=\"utf-8\") as categorysfile:\n", " open(\"../cache/categorys.csv\", mode=\"w\", newline=\"\", encoding=\"utf-8\") as categorysfile:\n",
"\n", "\n",
" # Diese Variablenzuweisung erstellt Writer-Objekte für alle vier CSV-Dateien.\n",
" plans_writer = csv.writer(plansfile, delimiter=\";\")\n", " plans_writer = csv.writer(plansfile, delimiter=\";\")\n",
" campaigns_writer = csv.writer(campaignsfile, delimiter=\";\")\n", " campaigns_writer = csv.writer(campaignsfile, delimiter=\";\")\n",
" options_writer = csv.writer(optionsfile, delimiter=\";\")\n", " options_writer = csv.writer(optionsfile, delimiter=\";\")\n",
" categorys_writer = csv.writer(categorysfile, delimiter=\";\")\n", " categorys_writer = csv.writer(categorysfile, delimiter=\";\")\n",
"\n", "\n",
" # Diese Anweisung schreibt die Kopfzeilen in die jeweiligen CSV-Dateien.\n",
" plans_writer.writerow([\"id\", \"provider\", \"network\", \"name\", \"price\"])\n", " plans_writer.writerow([\"id\", \"provider\", \"network\", \"name\", \"price\"])\n",
" campaigns_writer.writerow([\"id\", \"plan\", \"name\"])\n", " campaigns_writer.writerow([\"id\", \"plan\", \"name\"])\n",
" options_writer.writerow([\"id\", \"category\", \"plan\", \"name\", \"price\"])\n", " options_writer.writerow([\"id\", \"category\", \"plan\", \"name\", \"price\"])\n",
...@@ -406,96 +239,53 @@ ...@@ -406,96 +239,53 @@
" # Diese Schleife iteriert über alle gefundenen Tarifwelten.\n", " # Diese Schleife iteriert über alle gefundenen Tarifwelten.\n",
" for tw in tarifwelten:\n", " for tw in tarifwelten:\n",
"\n", "\n",
" # Diese Variablenzuweisung wartet, bis das Radio-Element für die aktuelle Tarifwelt klickbar ist.\n",
" tw_radio = wait.until(\n", " tw_radio = wait.until(\n",
" EC.element_to_be_clickable((By.XPATH, f'//input[@name=\"tarif_welt\" and @value=\"{tw}\"]'))\n", " EC.element_to_be_clickable((By.XPATH, f'//input[@name=\"tarif_welt\" and @value=\"{tw}\"]'))\n",
" )\n", " )\n",
"\n",
" # Diese Anweisung wartet, bis das Hintergrund-Layer-Element \"bg_layer\" unsichtbar ist.\n",
" WebDriverWait(driver, timeout=60).until(\n", " WebDriverWait(driver, timeout=60).until(\n",
" EC.invisibility_of_element_located((By.ID, \"bg_layer\"))\n", " EC.invisibility_of_element_located((By.ID, \"bg_layer\"))\n",
" )\n", " )\n",
"\n",
" # Diese Anweisung klickt auf die Radio-Option für die aktuelle Tarifwelt.\n",
" driver.execute_script(\"arguments[0].click();\", tw_radio)\n", " driver.execute_script(\"arguments[0].click();\", tw_radio)\n",
"\n", "\n",
" # Diese Schleife iteriert über alle gefundenen Netze.\n", " # Diese Schleife iteriert über alle gefundenen Netze.\n",
" for net in netze:\n", " for net in netze:\n",
"\n", "\n",
" # Diese Variablenzuweisung wartet, bis das Radio-Element für das aktuelle Netz klickbar ist.\n",
" net_radio = wait.until(\n", " net_radio = wait.until(\n",
" EC.presence_of_element_located((By.XPATH, f'//input[@name=\"netz\" and @value=\"{net}\"]'))\n", " EC.presence_of_element_located((By.XPATH, f'//input[@name=\"netz\" and @value=\"{net}\"]'))\n",
" )\n", " )\n",
"\n",
" # Diese Anweisung klickt auf die Radio-Option für das aktuelle Netz.\n",
" driver.execute_script(\"arguments[0].click();\", net_radio)\n", " driver.execute_script(\"arguments[0].click();\", net_radio)\n",
"\n",
" # Diese Anweisung wartet erneut, bis das Hintergrund-Layer-Element \"bg_layer\" unsichtbar ist.\n",
" WebDriverWait(driver, timeout=60).until(\n", " WebDriverWait(driver, timeout=60).until(\n",
" EC.invisibility_of_element_located((By.ID, \"bg_layer\"))\n", " EC.invisibility_of_element_located((By.ID, \"bg_layer\"))\n",
" )\n", " )\n",
"\n",
" # Diese Anweisung ruft die Hilfsfunktion auf, um zu prüfen, ob das Dropdown bereit ist.\n",
" wait_for_dropdown_ready(driver, wait)\n", " wait_for_dropdown_ready(driver, wait)\n",
"\n", "\n",
" # Diese Variablenzuweisung sucht das Dropdown-Element mit Name \"tarif_id\" und speichert es ab.\n",
" dropdown = wait.until(EC.presence_of_element_located((By.NAME, \"tarif_id\")))\n", " dropdown = wait.until(EC.presence_of_element_located((By.NAME, \"tarif_id\")))\n",
"\n",
" # Diese Variablenzuweisung erstellt ein Select-Objekt, um das Dropdown zu steuern.\n",
" select_obj = Select(dropdown)\n", " select_obj = Select(dropdown)\n",
"\n",
" # Diese Variablenzuweisung ermittelt die Gesamtzahl der Optionen im Dropdown.\n",
" total_options = len(select_obj.options)\n", " total_options = len(select_obj.options)\n",
"\n", "\n",
" # Diese Schleife iteriert über alle Tarif-Optionen des aktuellen Dropdowns.\n", " # Diese Schleife iteriert über alle Tarif-Optionen des aktuellen Dropdowns.\n",
" for i in range(total_options):\n", " for i in range(total_options):\n",
"\n", "\n",
" # Diese Anweisung sucht das Dropdown neu, um StaleElementReference zu vermeiden.\n",
" dropdown = wait.until(EC.presence_of_element_located((By.NAME, \"tarif_id\")))\n", " dropdown = wait.until(EC.presence_of_element_located((By.NAME, \"tarif_id\")))\n",
" select_obj = Select(dropdown)\n", " select_obj = Select(dropdown)\n",
" all_opts = select_obj.options\n", " all_opts = select_obj.options\n",
"\n",
" # Diese Bedingung überprüft, ob der Index über die Länge der verfügbaren Optionen hinausgeht.\n",
" if i >= len(all_opts):\n", " if i >= len(all_opts):\n",
" break\n", " break\n",
"\n",
" # Diese Variablenzuweisung nimmt die aktuelle Option aus der Dropdown-Liste.\n",
" opt = all_opts[i]\n", " opt = all_opts[i]\n",
"\n",
" # Diese Variablenzuweisung speichert den Text der aktuellen Option, bereinigt um Leerzeichen.\n",
" opt_text = opt.text.strip()\n", " opt_text = opt.text.strip()\n",
"\n",
" # Diese Bedingung überspringt ungültige oder leere Optionen.\n",
" if opt_text in [\"Bitte wählen Sie aus...\", \"\"]:\n", " if opt_text in [\"Bitte wählen Sie aus...\", \"\"]:\n",
" continue\n", " continue\n",
"\n",
" # Diese Anweisung ruft erneut die Hilfsfunktion auf, um sicherzustellen, dass das Dropdown bereit ist.\n",
" wait_for_dropdown_ready(driver, wait)\n", " wait_for_dropdown_ready(driver, wait)\n",
"\n",
" # Diese Anweisung wählt die Option mit dem gefundenen Text aus.\n",
" select_obj.select_by_visible_text(opt_text)\n", " select_obj.select_by_visible_text(opt_text)\n",
"\n",
" # Diese Variablenzuweisung holt den Wert des ausgewählten Tarifs aus dem value-Attribut.\n",
" tariff_id = opt.get_attribute(\"value\")\n", " tariff_id = opt.get_attribute(\"value\")\n",
"\n",
" # Diese Anweisung gibt den aktuellen Tarif in der Konsole aus (Debug-Ausgabe).\n",
" print(f\"{tariff_id} - {net} - {opt_text}\")\n", " print(f\"{tariff_id} - {net} - {opt_text}\")\n",
"\n",
" # Diese Anweisung wartet, bis das Hintergrund-Layer-Element \"bg_layer\" unsichtbar ist.\n",
" WebDriverWait(driver, timeout=60).until(\n", " WebDriverWait(driver, timeout=60).until(\n",
" EC.invisibility_of_element_located((By.ID, \"bg_layer\"))\n", " EC.invisibility_of_element_located((By.ID, \"bg_layer\"))\n",
" )\n", " )\n",
"\n",
" # Diese Zuweisung wird erneut ausgeführt, damit das Dropdown-Objekt vorhanden bleibt.\n",
" dropdown = wait.until(EC.presence_of_element_located((By.NAME, \"tarif_id\")))\n", " dropdown = wait.until(EC.presence_of_element_located((By.NAME, \"tarif_id\")))\n",
"\n",
" # Diese Anweisung pausiert den Ablauf kurz, damit das Dropdown korrekt reagieren kann.\n",
" time.sleep(1)\n", " time.sleep(1)\n",
"\n", "\n",
" # Diese Variablenzuweisung parst den Nettopreis des aktuellen Tarifs.\n",
" plan_price_net = parse_plan_price(driver)\n", " plan_price_net = parse_plan_price(driver)\n",
"\n",
" # Diese Variablenzuweisung ruft die Funktion auf, um alle verfügbaren Aktionen zu ermitteln.\n",
" campaigns = parse_campaigns(driver)\n", " campaigns = parse_campaigns(driver)\n",
"\n", "\n",
" # Diese Anweisung schreibt den aktuellen Plan in die plans.csv.\n", " # Diese Anweisung schreibt den aktuellen Plan in die plans.csv.\n",
...@@ -528,6 +318,13 @@ ...@@ -528,6 +318,13 @@
" categorys_writer\n", " categorys_writer\n",
" )\n", " )\n",
"\n", "\n",
" # Diese Anweisung stellt sicher, dass die Daten in den CSV-Dateien nicht erst am Ende\n",
" # des ganzen Ablaufs, sondern bereits nach jedem Tarifdurchlauf geschrieben werden.\n",
" plansfile.flush()\n",
" campaignsfile.flush()\n",
" optionsfile.flush()\n",
" categorysfile.flush()\n",
"\n",
"# Diese Funktion sammelt auf der zweiten Seite die Optionsdaten und schreibt sie zusammen mit den Tarifinformationen\n", "# Diese Funktion sammelt auf der zweiten Seite die Optionsdaten und schreibt sie zusammen mit den Tarifinformationen\n",
"# in die Dateien options.csv und categorys.csv.\n", "# in die Dateien options.csv und categorys.csv.\n",
"def scrapeOption(manager, tariff_id, net, tariff_text, options_writer, categorys_writer):\n", "def scrapeOption(manager, tariff_id, net, tariff_text, options_writer, categorys_writer):\n",
...@@ -554,23 +351,17 @@ ...@@ -554,23 +351,17 @@
"\n", "\n",
" # Diese Schleife geht alle extrahierten Optionszeilen durch.\n", " # Diese Schleife geht alle extrahierten Optionszeilen durch.\n",
" for line in options_data:\n", " for line in options_data:\n",
"\n",
" # Diese Variablenzuweisung teilt die Zeile anhand des Semikolons auf.\n",
" parts = line.split(\";\")\n", " parts = line.split(\";\")\n",
"\n",
" # parts[0] => category_id\n", " # parts[0] => category_id\n",
" # parts[1] => item_id\n", " # parts[1] => item_id\n",
" # parts[2] => category_name\n", " # parts[2] => category_name\n",
" # parts[3] => item_name\n", " # parts[3] => item_name\n",
" # parts[4] => price_str\n", " # parts[4] => price_str\n",
"\n", "\n",
" # Diese Variablenzuweisung konvertiert die Preisangabe in eine float-Zahl und dividiert durch 1.19.\n",
" try:\n", " try:\n",
" gross_price = float(parts[4])\n", " gross_price = float(parts[4])\n",
" except ValueError:\n", " except ValueError:\n",
" gross_price = 0.0\n", " gross_price = 0.0\n",
"\n",
" # Diese Variablenzuweisung wandelt den Bruttopreis in Nettopreis um und rundet auf 5 Nachkommastellen.\n",
" net_price = round(gross_price / 1.19, 5)\n", " net_price = round(gross_price / 1.19, 5)\n",
"\n", "\n",
" # Diese Anweisung schreibt in options.csv.\n", " # Diese Anweisung schreibt in options.csv.\n",
...@@ -628,7 +419,6 @@ ...@@ -628,7 +419,6 @@
" # Diese Schleife durchläuft alle gefundenen Tabellen und interpretiert sie als Kategorien.\n", " # Diese Schleife durchläuft alle gefundenen Tabellen und interpretiert sie als Kategorien.\n",
" for tbl in all_tables:\n", " for tbl in all_tables:\n",
"\n", "\n",
" # Diese Variablenzuweisung sucht in der aktuellen Tabelle nach einem Input-Feld mit Name service_code[...]_check.\n",
" cat_input = tbl.find(\"input\", attrs={\"name\": category_check_pattern})\n", " cat_input = tbl.find(\"input\", attrs={\"name\": category_check_pattern})\n",
"\n", "\n",
" # Diese Bedingung prüft, ob ein solches Feld gefunden wurde.\n", " # Diese Bedingung prüft, ob ein solches Feld gefunden wurde.\n",
...@@ -649,39 +439,48 @@ ...@@ -649,39 +439,48 @@
" else:\n", " else:\n",
" continue\n", " continue\n",
"\n", "\n",
" # Diese Variablenzuweisung sucht nach dem Tabellenkopf mit der Klasse \"tb_head\", um den Kategorienamen zu lesen.\n",
" cat_name_el = tbl.find(\"td\", class_=\"tb_head\")\n", " cat_name_el = tbl.find(\"td\", class_=\"tb_head\")\n",
"\n",
" # Diese Bedingung prüft, ob ein Tabellenkopf gefunden wurde.\n",
" if cat_name_el:\n", " if cat_name_el:\n",
" cat_text = cat_name_el.get_text(strip=True)\n", " cat_text = cat_name_el.get_text(strip=True)\n",
" cat_text = re.sub(r'^\\xa0+', '', cat_text).strip()\n", " cat_text = re.sub(r'^\\xa0+', '', cat_text).strip()\n",
" else:\n", " else:\n",
" cat_text = \"Unbekannte Kategorie\"\n", " cat_text = \"Unbekannte Kategorie\"\n",
"\n", "\n",
" # Diese Bedingung entfernt HTML-NBSPs, falls vorhanden.\n",
" if cat_text.startswith(\"&nbsp;\"):\n", " if cat_text.startswith(\"&nbsp;\"):\n",
" cat_text = cat_text.replace(\"&nbsp;\", \"\").strip()\n", " cat_text = cat_text.replace(\"&nbsp;\", \"\").strip()\n",
"\n",
" # Diese Bedingung setzt einen Standardwert, falls kein Text ermittelt werden konnte.\n",
" if not cat_text:\n", " if not cat_text:\n",
" cat_text = \"Unbekannte Kategorie\"\n", " cat_text = \"Unbekannte Kategorie\"\n",
"\n", "\n",
" # Diese Variablenzuweisung sucht alle Input-Felder, deren value mit dem Muster G\\d+ oder O\\d+ übereinstimmt.\n", " # Diese Suche findet alle Checkboxen (G\\d+) oder einfache Optionen (O\\d+).\n",
" inputs = tbl.find_all(\"input\", attrs={\"value\": item_value_pattern})\n", " inputs = tbl.find_all(\"input\", attrs={\"value\": item_value_pattern})\n",
"\n", "\n",
" # Diese Abfrage sucht alle Select-Elemente mit name=\"service_code[...]\", um Sub-Optionen zu identifizieren.\n",
" sub_selects = tbl.find_all(\"select\", attrs={\"name\": re.compile(r\"service_code\\[\\w+_S\\d+\\]\")})\n",
"\n",
" # Diese Schleife durchläuft alle gefundenen Inputs.\n", " # Diese Schleife durchläuft alle gefundenen Inputs.\n",
" for inp in inputs:\n", " for inp in inputs:\n",
"\n",
" item_id = inp.get(\"value\", \"\").strip()\n", " item_id = inp.get(\"value\", \"\").strip()\n",
" if not item_id:\n", " if not item_id:\n",
" continue\n", " continue\n",
"\n",
" # Diese Abfrage prüft, ob es sich um einen G-Eintrag mit Sub-Select handeln könnte.\n",
" # Falls ja, soll dieser G-Wert nicht als Option gespeichert werden,\n",
" # weil hier ein Dropdown mit O-Einträgen (echte Optionen) folgt.\n",
" if item_id.startswith(\"G\") and has_sub_select_for_id(item_id, sub_selects):\n",
" # An dieser Stelle wird der G-Wert nicht direkt als Option erfasst,\n",
" # da im nachfolgenden Select die eigentlichen O-Optionen liegen.\n",
" continue\n",
"\n",
" item_label_tag = inp.find_next(\"a\", attrs={\"id\": f\"err_{item_id}\"})\n", " item_label_tag = inp.find_next(\"a\", attrs={\"id\": f\"err_{item_id}\"})\n",
" if not item_label_tag:\n", " if not item_label_tag:\n",
" item_label_tag = inp.find_next(\"a\", attrs={\"name\": f\"err_{item_id}\"})\n", " item_label_tag = inp.find_next(\"a\", attrs={\"name\": f\"err_{item_id}\"})\n",
"\n",
" if item_label_tag and item_label_tag.text.strip():\n", " if item_label_tag and item_label_tag.text.strip():\n",
" item_name = item_label_tag.text.strip()\n", " item_name = item_label_tag.text.strip()\n",
" else:\n", " else:\n",
" item_name = \"Unbekannt\"\n", " item_name = \"Unbekannt\"\n",
"\n",
" container_for_price = item_label_tag.parent if item_label_tag else inp.parent\n", " container_for_price = item_label_tag.parent if item_label_tag else inp.parent\n",
" combined_text = container_for_price.get_text(\" \", strip=True) if container_for_price else \"\"\n", " combined_text = container_for_price.get_text(\" \", strip=True) if container_for_price else \"\"\n",
" m_price = price_pattern.search(combined_text)\n", " m_price = price_pattern.search(combined_text)\n",
...@@ -698,18 +497,62 @@ ...@@ -698,18 +497,62 @@
" price_str = \"0.0\"\n", " price_str = \"0.0\"\n",
" else:\n", " else:\n",
" price_str = f\"{price_val}\"\n", " price_str = f\"{price_val}\"\n",
"\n",
" if item_name == \"Unbekannt\" or cat_text == \"Unbekannte Kategorie\":\n", " if item_name == \"Unbekannt\" or cat_text == \"Unbekannte Kategorie\":\n",
" continue\n", " continue\n",
" if cat_text in [\"Sonstige Angaben\", \"Pflicht-Angaben\"]:\n", " if cat_text in [\"Sonstige Angaben\", \"Pflicht-Angaben\"]:\n",
" continue\n", " continue\n",
"\n",
" line = f\"{category_id};{item_id};{cat_text};{item_name};{price_str}\"\n", " line = f\"{category_id};{item_id};{cat_text};{item_name};{price_str}\"\n",
" results.append(line)\n", " results.append(line)\n",
"\n", "\n",
" # Diese Schleife durchsucht jede Sub-Select-Box innerhalb der aktuellen Tabelle.\n",
" # Wenn eine Select-Box existiert, werden alle <option>-Einträge als eigenständige Optionen erfasst.\n",
" for s in sub_selects:\n",
" s_name = s.get(\"name\", \"\")\n",
" if not s_name:\n",
" continue\n",
"\n",
" # Diese Abfrage versucht, einen Bezug zum aktuellen G-Wert (z. B. G3120771) aus dem Namen zu erkennen.\n",
" # Beispiel: name=\"service_code[G179_S1]\" -> Dann gehört diese Select-Box zur Kategorie G179.\n",
" # Wir nehmen an, dass diese Select-Boxen zur weiter oben gefundenen cat_input (category_id) gehören.\n",
" if category_id not in s_name:\n",
" # Wenn die ID nicht im Select-Namen vorkommt, gehört der Select zu einer anderen Kategorie.\n",
" continue\n",
"\n",
" option_tags = s.find_all(\"option\", attrs={\"value\": re.compile(r\"^O\\d+$\")})\n",
" for opt_tag in option_tags:\n",
" opt_id = opt_tag.get(\"value\", \"\").strip()\n",
" if not opt_id:\n",
" continue\n",
" opt_text = opt_tag.get_text(strip=True)\n",
" if not opt_text or opt_text == \"Bitte wählen Sie aus...\":\n",
" continue\n",
"\n",
" # Für diese Sub-Option wird kein Preis im HTML notiert. Wir setzen ihn daher auf 0.0.\n",
" # Bei Bedarf könnte man später weitere Logik ergänzen.\n",
" price_str = \"0.0\"\n",
"\n",
" line = f\"{category_id};{opt_id};{cat_text};{opt_text};{price_str}\"\n",
" results.append(line)\n",
"\n",
" # Diese Anweisung gibt die gesammelten Daten zurück.\n", " # Diese Anweisung gibt die gesammelten Daten zurück.\n",
" return results\n", " return results\n",
"\n", "\n",
"# Diese Hilfsfunktion prüft, ob für eine bestimmte G-ID ein Sub-Select im selben Block existiert.\n",
"def has_sub_select_for_id(g_id, sub_selects):\n",
"\n",
" # Diese Schleife durchsucht alle Select-Elemente und prüft, ob der Name mit dem G-Wert korrespondiert.\n",
" # Beispielsweise: G179 -> name=\"service_code[G179_S1]\"\n",
" # Wenn ein entsprechendes Select-Element gefunden wird, geben wir True zurück.\n",
" for s in sub_selects:\n",
" s_name = s.get(\"name\", \"\")\n",
" if g_id in s_name:\n",
" return True\n",
" return False\n",
"\n",
"# Diese Variablenzuweisung erzeugt eine Instanz des Managers mit den gewünschten Parametern.\n", "# Diese Variablenzuweisung erzeugt eine Instanz des Managers mit den gewünschten Parametern.\n",
"manager = SeleniumManager(headless=False, geckodriver_path=\"/opt/homebrew/bin/geckodriver\")\n", "manager = SeleniumManager(headless=True, geckodriver_path=\"/opt/homebrew/bin/geckodriver\")\n",
"\n", "\n",
"# Diese Anweisungen führen den gesamten Ablauf aus.\n", "# Diese Anweisungen führen den gesamten Ablauf aus.\n",
"login(manager, MAUI_USERNAME, MAUI_PASSWORD, MAUI_AUTHCODE)\n", "login(manager, MAUI_USERNAME, MAUI_PASSWORD, MAUI_AUTHCODE)\n",
......
{
"cells": [
{
"cell_type": "code",
"execution_count": 4,
"id": "193fb1fb-6ad1-447e-b641-73857be9d9dd",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Done\n"
]
}
],
"source": [
"#!/usr/bin/env python3\n",
"\n",
"# Importiert das Modul sys, damit auf Systemfunktionen zugegriffen werden kann.\n",
"import sys\n",
"\n",
"# Fügt das übergeordnete Verzeichnis zum Python-Pfad hinzu, damit lokale Module gefunden werden.\n",
"sys.path.append(\"..\")\n",
"\n",
"# Importiert das Modul os, um Betriebssystemfunktionen wie Pfadoperationen zu nutzen.\n",
"import os\n",
"\n",
"# Importiert das Modul csv, um CSV-Dateien einlesen und verarbeiten zu können.\n",
"import csv\n",
"\n",
"# Importiert das Modul datetime, um Datums- und Zeitfunktionen zu verwenden.\n",
"import datetime\n",
"\n",
"# Importiert die Klasse MysqlManager, um eine Verbindung zur MySQL-Datenbank herzustellen.\n",
"from manager.MysqlManager import MysqlManager\n",
"\n",
"# Importiert die Basisklasse Base, um SQLAlchemy-Modelle zu definieren.\n",
"from models._system import Base\n",
"\n",
"# Importiert die Klasse BasegroupBgro, um auf die Tabelle basegroup_bgro zuzugreifen.\n",
"from models.basegroup_bgro import BasegroupBgro\n",
"\n",
"# Importiert die Klasse BaseBase, um auf die Tabelle base_base zuzugreifen.\n",
"from models.base_base import BaseBase\n",
"\n",
"# Importiert die Klasse ProvisiongroupPgro, um auf die Tabelle provisiongroup_pgro zuzugreifen.\n",
"from models.provisiongroup_pgro import ProvisiongroupPgro\n",
"\n",
"# Importiert die Klasse DealDeal, um auf die Tabelle deal_deal zuzugreifen.\n",
"from models.deal_deal import DealDeal\n",
"\n",
"# Importiert die Klasse OptionOpti, um auf die Tabelle option_opti zuzugreifen.\n",
"from models.option_opti import OptionOpti\n",
"\n",
"# Definiert den Pfad zur CSV-Datei im Cache-Verzeichnis.\n",
"csv_file_path = os.path.join('..', 'cache', 'plans.csv')\n",
"\n",
"# Erzeugt eine Instanz des MysqlManagers, um die MySQL-Verbindung zu verwalten.\n",
"mysql_manager = MysqlManager()\n",
"\n",
"# Öffnet eine neue Session über den MysqlManager, um Datenbankoperationen durchzuführen.\n",
"session = mysql_manager.getSession()\n",
"\n",
"# Öffnet die CSV-Datei im Lese-Modus mit UTF-8-Kodierung und Semikolon als Trennzeichen.\n",
"with open(csv_file_path, newline='', encoding='utf-8') as csvfile:\n",
"\n",
" # Erstellt ein DictReader-Objekt, um Zeilen aus der CSV-Datei als Dictionary abzurufen.\n",
" reader = csv.DictReader(csvfile, delimiter=';')\n",
" \n",
" # Durchläuft jede Zeile, die aus der CSV-Datei gelesen wird.\n",
" for row in reader:\n",
"\n",
" # Speichert den in der CSV-Datei angegebenen Wert 'id' in der Variable csv_id und entfernt Leerzeichen.\n",
" csv_id = row.get('id', '').strip()\n",
"\n",
" # Speichert den in der CSV-Datei angegebenen Wert 'provider' in der Variable provider und entfernt Leerzeichen.\n",
" provider = row.get('provider', '').strip()\n",
"\n",
" # Speichert den in der CSV-Datei angegebenen Wert 'network' in der Variable network und entfernt Leerzeichen.\n",
" network = row.get('network', '').strip()\n",
"\n",
" # Speichert den in der CSV-Datei angegebenen Wert 'name' in der Variable name und entfernt Leerzeichen.\n",
" name = row.get('name', '').strip()\n",
"\n",
" # Speichert den in der CSV-Datei angegebenen Wert 'price' in der Variable price und entfernt Leerzeichen.\n",
" price = row.get('price', '').strip()\n",
" \n",
" # Kombiniert den String \"Freenet | \" mit dem Wert aus provider, um provider_base_val zu erhalten.\n",
" provider_base_val = \"Freenet | \" + provider\n",
" \n",
" # Führt eine Abfrage auf der Tabelle base_base durch, um einen vorhandenen Datensatz mit der gleichen Kombination zu finden.\n",
" existing_record = session.query(BaseBase).filter_by(\n",
" provider_base=provider_base_val,\n",
" providercode_base=csv_id\n",
" ).first()\n",
" \n",
" # Prüft, ob bereits ein Datensatz mit dieser Kombination existiert.\n",
" if existing_record:\n",
"\n",
" # Überspringt die Verarbeitung für diesen Datensatz, da er bereits existiert.\n",
" continue\n",
" \n",
" # Überprüft, ob der Netzwerk-String \"D1\" ist, und setzt network_val auf 1.\n",
" if network == \"D1\":\n",
" network_val = 1\n",
"\n",
" # Überprüft, ob der Netzwerk-String \"D2\" ist, und setzt network_val auf 2.\n",
" elif network == \"D2\":\n",
" network_val = 2\n",
"\n",
" # Überprüft, ob der Netzwerk-String \"O2\" ist, und setzt network_val auf 4.\n",
" elif network == \"O2\":\n",
" network_val = 4\n",
"\n",
" # Setzt network_val auf 0, falls keine der oben genannten Bedingungen zutrifft.\n",
" else:\n",
" network_val = 0\n",
"\n",
" # Setzt den Typ für alle neuen Datensätze standardmäßig auf 0.\n",
" type_val = 0\n",
"\n",
" # Holt das aktuelle Datum und die aktuelle Uhrzeit für die Felder created_base und updated_base.\n",
" now = datetime.datetime.now()\n",
"\n",
" # Erzeugt ein neues BaseBase-Objekt mit den aus der CSV-Datei ausgelesenen und verarbeiteten Werten.\n",
" new_record = BaseBase(\n",
" provider_base=provider_base_val,\n",
" providercode_base=csv_id,\n",
" basegroup_base=1,\n",
" name_base=name,\n",
" network_base=network_val,\n",
" type_base=type_val,\n",
" created_base=now,\n",
" updated_base=now\n",
" )\n",
"\n",
" # Hängt dieses neue Objekt an die aktuelle Session an, damit es später in die Datenbank geschrieben wird.\n",
" session.add(new_record)\n",
"\n",
"# Überträgt alle gesammelten Änderungen an die Datenbank.\n",
"session.commit()\n",
"\n",
"# Schließt die offene Session, sodass keine weiteren Datenbankzugriffe mehr stattfinden.\n",
"session.close()\n",
"\n",
"# Gibt zum Abschluss eine kurze Meldung aus.\n",
"print(\"Done\")\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d6387255-71e0-40b4-8579-674320efc4db",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"id": "001e79bd-8dff-4753-8754-6ec3101784dd",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.9"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
...@@ -38,6 +38,7 @@ In diesem Feld wird das verwendete Funknetz definiert. ...@@ -38,6 +38,7 @@ In diesem Feld wird das verwendete Funknetz definiert.
### type_base ### type_base
Dieses Feld legt den Tariftyp fest. Dieses Feld legt den Tariftyp fest.
**Mögliche Werte:** **Mögliche Werte:**
- **0**: Unbekannt
- **1**: Voice - **1**: Voice
- **2**: Daten - **2**: Daten
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment