Cześć,
Uczę się Pythona i próbuję zrobić prostą aplikację dla potrzeb swoich badań, niestety napotkałem problem z paskiem postępu: podczas pracy w ogóle nic na nim się nie wyświetla, czy ktoś z Was mógłby pomóc?
Gdybyście zauważyli jakieś błędy dajcie znać co mogę zrobić by mój kod był lepszy ;)
from selenium import webdriver
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException
import pandas as pd
import time
import tkinter as tk
import subprocess
import os
import sys
import threading
import queue
from dotenv import load_dotenv
from tkinter import Tk, Label, StringVar, ttk, filedialog, Entry, Button, Checkbutton, IntVar, BooleanVar
from datetime import datetime, timedelta, date
def get_login_credentials():
def submit():
global username, password
username, password = username_entry.get(), pass_entry.get()
root.destroy()
def toggle_password():
if show_pass.get() == 1:
pass_entry.config(show='')
else:
pass_entry.config(show='*')
root = Tk()
root.title("Logowanie do Carelink Medtronic")
# Pole - Nazwa użytkownika
Label(root, text="Nazwa użytkownika:").grid(row=0, column=0, padx=10, pady=10)
username_entry = Entry(root)
username_entry.grid(row=0, column=1, padx=10, pady=10)
username_entry.focus_set() # Ustawienie kursora w polu Nazwa użytkownika
# Pole - Hasło
Label(root, text="Hasło:").grid(row=1, column=0, padx=10, pady=10)
pass_entry = Entry(root, show="*")
pass_entry.grid(row=1, column=1, padx=10, pady=10)
# Checkbox do podglądu hasła
show_pass = IntVar()
checkbtn_pass = Checkbutton(root, text="Pokaż hasło", variable=show_pass, command=toggle_password)
checkbtn_pass.grid(row=2, column=1, padx=10, pady=5)
# Przycisk logowania
login_button = Button(root, text="Zaloguj", command=submit, state="disabled")
login_button.grid(row=3, column=0, columnspan=2, pady=10)
# Aktualizacja stanu przycisku logowania
def update_button_state(*_):
login_button.config(state="normal" if username_entry.get() and pass_entry.get() else "disabled")
username_entry.bind("<KeyRelease>", update_button_state)
pass_entry.bind("<KeyRelease>", update_button_state)
# Obsługa klawisza Enter
root.bind_all("<Return>", lambda e: submit() if login_button["state"] == "normal" else None)
# Wyśrodkowanie okna
root.update_idletasks()
width, height = root.winfo_width(), root.winfo_height()
x = (root.winfo_screenwidth() - width) // 2
y = (root.winfo_screenheight() - height) // 2
root.geometry(f"{width}x{height}+{x}+{y}")
root.mainloop()
get_login_credentials()
# Wybór pliku Excel
def choose_excel_file():
root = Tk()
root.withdraw()
return filedialog.askopenfilename(title="Wybierz plik Excel z listą pacjentek", filetypes=[("Pliki Excel", "*.xlsx *.xls")])
# Ładowanie danych z Excela
excel_file = choose_excel_file()
if excel_file:
df = pd.read_excel(excel_file)
required_columns = {'Nazwisko', 'Imię', 'początek ciąży', 'Data porodu'}
if not required_columns.issubset(df.columns):
raise ValueError("Brak jednej lub więcej wymaganych kolumn w pliku Excel.")
# Wybór folderu do pobierania CSV
Tk().withdraw()
download_folder = filedialog.askdirectory(title="Wybierz folder do zapisania plików CSV")
if not download_folder:
raise Exception("Nie wybrano folderu, program zakończony.")
download_folder = os.path.normpath(download_folder)
# Ustal ścieżkę do folderu "Downloads" lub "Pobrane"
downloads_folder = os.path.join(os.path.expanduser("~"), "Downloads")
# Funkcja do pobrania wersji Chrome
def get_chrome_version():
try:
result = subprocess.run(['google-chrome', '--version'], capture_output=True, text=True)
return result.stdout.strip().split(' ')[-1] if result.returncode == 0 else None
except FileNotFoundError:
return None
# Funkcja do pobrania wersji ChromeDriver
def get_chromedriver_version():
try:
result = subprocess.run(['chromedriver', '--version'], capture_output=True, text=True)
return result.stdout.strip().split(' ')[-1] if result.returncode == 0 else None
except FileNotFoundError:
return None
# Sprawdzenie wersji i konfiguracja sterownika
chrome_version = get_chrome_version()
chromedriver_version = get_chromedriver_version()
service = Service(ChromeDriverManager().install()) if chrome_version != chromedriver_version else Service()
# Ustawienia opcji Chrome
chrome_options = Options()
# chrome_options.add_argument("--headless") # Uruchomienie w tle
chrome_options.add_experimental_option("prefs", {"download.default_directory": download_folder})
driver = webdriver.Chrome(service=service, options=chrome_options)
driver.get('https://carelink.medtronic.eu/login')
driver.maximize_window()
# # Funkcja logowania
# def login():
# try:
# WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.CLASS_NAME, 'mat-primary-button'))).click()
# load_dotenv()
# driver.find_element(By.ID, 'username').send_keys(os.getenv('CARELINK_USERNAME'))
# driver.find_element(By.ID, 'password').send_keys(os.getenv('CARELINK_PASSWORD'))
# driver.find_element(By.CSS_SELECTOR, 'input[name="actionButton"]').click()
# except Exception as e:
# print("Błąd logowania:", e)
# driver.quit()
# Funkcja logowania (dotyczy GUI)
def login():
try:
WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.CLASS_NAME, 'mat-primary-button'))).click()
driver.find_element(By.ID, 'username').send_keys(username)
driver.find_element(By.ID, 'password').send_keys(password)
driver.find_element(By.CSS_SELECTOR, 'input[name="actionButton"]').click()
except Exception as e:
print("Błąd logowania:", e)
driver.quit()
# Wybór pacjenta
def find_and_click_patient(driver, patient_surname, patient_name):
driver.get('https://carelink.medtronic.eu/clinic/patients')
patient_input = WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.ID, 'patient-users-search')))
patient_input.send_keys(patient_surname)
patients_table = driver.find_element(By.ID, 'tbl-patient-list')
time.sleep(1)
patients_table_rows = patients_table.find_elements(By.XPATH, '//*[@role="row"]')
patients_table_names = patients_table.find_elements(By.XPATH, '//mat-row/mat-cell[3]')
if len(patients_table_rows) > 2:
for idx, table_name in enumerate(patients_table_names):
if patient_name.strip().lower() == table_name.text.strip().lower():
table_name.click()
break
if len(patients_table_rows) > 1:
patients_table_rows[1].click()
else:
print("Nie znaleziono pacjenta.")
return
# Wybór zakresu lat w kalendarzu
def set_calendar(driver, target_year):
WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.XPATH, '//button[@aria-label="Choose month and year"]'))).click()
max_year_search, search_attempts = 1, 0 # Liczba lat do przeszukania i licznik prób
while search_attempts < max_year_search:
displayed_year = int(WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.XPATH, '//button[@aria-label="Choose date"]'))).text[:4])
if displayed_year > target_year:
WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.XPATH, '//button[@aria-label="Previous 24 years"]'))).click()
search_attempts += 1
else:
break
# Wybór odpowiedniego roku w kalendarzu
def select_year(driver, target_year):
for button in driver.find_elements(By.CSS_SELECTOR, "button.mat-calendar-body-cell"):
if int(button.get_attribute("aria-label")) == target_year:
if button.get_attribute("aria-disabled") != "true":
button.click()
return target_year
else:
target_year += 1
return None
# Wybór odpowiedniego miesiąca w kalendarzu
def select_month(driver, target_month, month_map, start_year, target_year):
month_range = range(target_month, 13) if target_year == start_year else range(1, 13)
for month in month_range:
for button in driver.find_elements(By.CSS_SELECTOR, "button.mat-calendar-body-cell"):
month_text = button.get_attribute("aria-label").split()[0][:3].upper()
month_num = month_map.get(month_text)
if month_num == month and button.get_attribute("aria-disabled") != "true":
button.click()
return month
return None
# Wybór odpowiedniego dnia w kalendarzu
def select_day(driver, target_day, target_month, end_date):
last_available_day = None
is_end_month = (target_month == end_date.month)
for button in driver.find_elements(By.CSS_SELECTOR, "button.mat-calendar-body-cell"):
if button.get_attribute("aria-disabled") == "true":
continue # Pomiń wyłączone dni
day_label = int((button.get_attribute("aria-label")).split()[1].replace(",", ""))
if is_end_month:
if day_label == target_day:
button.click()
return day_label
elif day_label < target_day:
last_available_day = button
elif day_label >= target_day:
button.click()
return day_label
if last_available_day:
last_available_day.click()
return int((last_available_day.get_attribute("aria-label")).split()[1].replace(",", ""))
return None # Jeśli nie znaleziono żadnego pasującego dnia
# Wybór pełnej daty w kalendarzu
def select_date(driver, start_year, target_year, target_month, target_day, end_date):
month_map = {"STY": 1, "LUT": 2, "MAR": 3, "KWI": 4, "MAJ": 5, "CZE": 6,
"LIP": 7, "SIE": 8, "WRZ": 9, "PAŹ": 10, "LIS": 11, "GRU": 12}
selected_year = select_year(driver, target_year)
if not selected_year:
print("Nie znaleziono dostępnego roku.")
driver.quit()
sys.exit()
selected_month = select_month(driver, target_month, month_map, start_year, target_year)
if not selected_month:
print("Nie znaleziono dostępnego miesiąca.")
driver.quit()
sys.exit()
selected_day = select_day(driver, target_day, target_month, end_date)
if not selected_day:
print("Nie znaleziono dostępnego dnia.")
driver.quit()
sys.exit()
# Zwracamy pełną wybraną datę jako obiekt datetime
return datetime(selected_year, selected_month, selected_day)
# Wybierz przycisk "Zastosuj" oraz pobierz CSV
def click_element(driver, element_id, timeout=10):
# Kliknij element po ID, gdy będzie klikalny
element = WebDriverWait(driver, timeout).until(EC.element_to_be_clickable((By.ID, element_id)))
element.click()
# Kolejka do komunikacji między wątkiem przetwarzania a GUI
gui_queue = queue.Queue()
# Funkcja tworząca GUI z paskami postępu
def create_progress_window():
# Główne okno
progress_window = Tk()
progress_window.title("Postęp pobierania danych z Carelink")
# Etykiety informacyjne
patient_label_var = StringVar()
patient_label = Label(progress_window, textvariable=patient_label_var)
patient_label.pack(padx=10, pady=10)
# Pasek postępu dla pacjentów
patient_progress = ttk.Progressbar(progress_window, orient="horizontal", length=300, mode="determinate")
patient_progress.pack(padx=10, pady=10)
# Pasek postępu dla konkretnego pacjenta
individual_progress = ttk.Progressbar(progress_window, orient="horizontal", length=300, mode="determinate")
individual_progress.pack(padx=10, pady=10)
# Etykieta informacyjna
individual_label_var = StringVar()
individual_label = Label(progress_window, textvariable=individual_label_var)
individual_label.pack(padx=10, pady=10)
# Wyśrodkowanie okna
progress_window.update_idletasks()
width, height = progress_window.winfo_width(), progress_window.winfo_height()
x = (progress_window.winfo_screenwidth() - width) // 2
y = (progress_window.winfo_screenheight() - height) // 2
progress_window.geometry(f"{width}x{height}+{x}+{y}")
return progress_window, patient_progress, individual_progress, patient_label_var, individual_label_var
# Inicjalizacja okna z paskami postępu
progress_window, patient_progress, individual_progress, patient_label_var, individual_label_var = create_progress_window()
# Funkcja aktualizacji GUI na podstawie komunikatów z kolejki
def update_gui():
try:
while not gui_queue.empty():
msg_type, value = gui_queue.get_nowait()
if msg_type == "update_patient_label":
patient_label_var.set(value)
elif msg_type == "update_patient_progress":
patient_progress["value"] = value
elif msg_type == "update_individual_progress":
individual_progress["value"] = value
elif msg_type == "update_individual_label":
individual_label_var.set(value)
elif msg_type == "done":
progress_window.destroy()
elif msg_type == "error":
print("Błąd podczas przetwarzania:", value)
progress_window.destroy()
progress_window.after(100, update_gui)
except Exception as e:
print("Błąd aktualizacji GUI:", e)
# Główna funkcja przetwarzania
def process_patients(df, driver):
try:
for patient_index, row in df.iterrows():
gui_queue.put(("update_patient_label", ""))
gui_queue.put(("update_individual_label", ""))
gui_queue.put(("update_patient_progress", 0))
gui_queue.put(("update_individual_progress", 0))
patient_name, patient_surname = row['Imię'], row['Nazwisko']
gui_queue.put(("update_patient_label", f"Przetwarzanie pacjenta: {patient_name} {patient_surname} ({patient_index+1}/{len(df)})"))
start_date = pd.to_datetime(row['początek ciąży']).date()
start_year, start_month, start_day = start_date.year, start_date.month, start_date.day
end_date = pd.to_datetime(row['Data porodu']).date()
find_and_click_patient(driver, patient_surname, patient_name)
WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.ID, 'pd-reports'))).click()
WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.ID, 'r-reports-days-90'))).click()
WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.XPATH, '//*[@id="date-range-button"]'))).click()
target_year, target_month, target_day = start_year, start_month, start_day
set_calendar(driver, target_year)
# Ustawienie kalendarza i wybór daty
selected_date = select_date(driver, start_year, target_year, target_month, target_day, end_date)
# Obliczenie różnicy w dniach między wybraną datą a datą końca porodu oraz liczba 90-dniowych okresów
days_difference = (end_date - selected_date.date()).days
ninety_day_periods = min(4, max(1, days_difference // 90 + 1))
# Ustal drugą datę do zaznaczenia w kalendarzu
current_highlight_date = selected_date + timedelta(days=90)
for i in range(ninety_day_periods):
# Ustawienia szczegółowe dla konkretnej pacjentki
gui_queue.put(("update_individual_label", f"Postęp dla {patient_name} {patient_surname}: okres {i + 1} z {ninety_day_periods}"))
# Sprawdź, czy to ostatnia iteracja – jeśli tak, ustaw datę docelową jako end_date
target_date = end_date if i == ninety_day_periods - 1 else current_highlight_date
target_year, target_month, target_day = target_date.year, target_date.month, target_date.day
set_calendar(driver, target_year)
selected_date = select_date(driver, start_year, target_year, target_month, target_day, end_date)
# Kliknij przycisk 'zastosuj'
click_element(driver, 'date-range-apply')
# Kliknij przycisk pobierania pliku CSV po załadowaniu strony
click_element(driver, 'r-button-data-export-csv', timeout=20)
time.sleep(12)
# Sprawdź, czy to była ostatnia iteracja
if i == ninety_day_periods - 1:
# Aktualizacja paska postępu dla pacjenta
gui_queue.put(("update_individual_progress", (i + 1)/ninety_day_periods))
gui_queue.put(("update_patient_progress", (patient_index + 1)/len(df)))
break # Przerwij pętlę po ostatniej iteracji
WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.XPATH, '//*[@id="date-range-button"]'))).click()
set_calendar(driver, target_year)
selected_date = select_date(driver, start_year, target_year, target_month, target_day, end_date)
# Aktualizuj current_highlight_date o 90 dni
current_highlight_date = selected_date + timedelta(days=90)
# Aktualizacja paska dla konkretnego okresu pacjentki
gui_queue.put(("update_individual_progress", (i + 1)/ninety_day_periods))
# Sygnał zakończenia
gui_queue.put(("done", None))
except Exception as e:
gui_queue.put(("error", str(e)))
# Funkcja uruchamiająca Selenium w osobnym wątku
def run_selenium_in_thread():
threading.Thread(target=process_patients, args=(df, driver), daemon=True).start()
# Uruchom główne funkcje
try:
login()
run_selenium_in_thread()
update_gui()
progress_window.mainloop()
except Exception as e:
print(f"Błąd podczas przetwarzania: {e}")
finally:
# Zmień opcje Chrome na domyślny folder pobierania
chrome_options = Options()
# chrome_options.add_argument("--headless") # Uruchomienie w tle
chrome_options.add_experimental_option("prefs", {"download.default_directory": downloads_folder})
# Utwórz nowy driver z zaktualizowanymi opcjami i zamknij stary driver
driver.quit() # Zamknij poprzednią sesję
driver = webdriver.Chrome(service=service, options=chrome_options)
driver.quit()