Ich habe mit der Programmiersprache JavaScript bereits einen Internet-Speed-Test gebaut. Siehe
hier! Jedoch gibt es sporadisch immer mal wieder Browser
CORS-Probleme. Mit dem hier vorgestellten PyQt6-Python-Script gibt es diese Probleme nicht. Weiterhin
erledigt eine Python Bibliothek „speedtest-cli“
die eigentliche Messung.
Die Python Bibliothek „speedtest-cli“ wirft standardmäßig nur ein finales
Endprodukt aus. Zwischenergebnisse werden nicht geliefert. Das bedeutet, die zwei Anzeigen
(Down- & Uploade Gauge) visualisieren keine Zwischenergebnisse!
Ein Internet-Speed-Test misst die Leistung deiner aktuellen Internetverbindung, indem er Testdaten zwischen
deinem Gerät und einem speziellen Server sendet und empfängt. Dabei werden vor allem der Download, der Upload
und der Ping ermittelt. Wofür so etwas: Die Leistungsfähigkeit deines Anschlusses zu überprüfen.
Download (Herunterladen): Zeigt, wie schnell Daten aus dem Internet auf dein Gerät geladen werden. Wichtig für
Webseiten, Videos und Updates.
Upload (Hochladen): Zeigt, wie schnell Daten von deinem Gerät ins Internet gesendet werden. Wichtig für
Videogespräche und das Versenden großer Dateien.
Ping (Reaktionszeit): Misst in Millisekunden, wie lange ein Datenpaket braucht, um zum Server und zurück zu reisen.
Ein kleiner Wert ist gut für Online-Spiele.
Diese Webseite stellt für den Quellcode Download- und Kopierfunktionen zur Verfügung. Das geht nur mit JavaScript.
Wenn du mich für einen unsicheren Kantonisten hältst, verlasse diese Webseite. Alle Informationen und Angebote
sind ohne Gewähr!
#!/usr/bin/env python3
# *****************************************************************************
# Eine PyQt6 Python Applikation für Internet-Speedtest
# Installation der Abhängigkeit: pip install PyQt6 speedtest
# Start: Python pyqt6_InternetSpeed.py (c) Hans Busche Datum: 30.07.2026
# *****************************************************************************
import sys, os
import speedtest
from PyQt6.QtWidgets import QApplication, QWidget, QVBoxLayout,\
QHBoxLayout, QPushButton, QLabel
from PyQt6.QtGui import QPainter, QColor, QFont, QPen, QIcon
from PyQt6.QtCore import Qt, QTimer, QThread, pyqtSignal, QRectF
# Verzeichnis dieser Datei, damit das Icon unabhängig vom Startort gefunden wird
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
ICON_PATH = os.path.join(BASE_DIR, "Schaefer.ico")
#******************************************************************************
# Klasse Analoganzeige (Gauge) fur Down- & Upload
#******************************************************************************
class SpeedtestGauge(QWidget):
def __init__(self, title="Download", unit="Mbps",
is_download=True, parent=None):
super().__init__(parent)
self.title = title
self.unit = unit
self.current_value = 0.0
self.target_value = 0.0
self.max_value = 250.0 # Skala bis 250 Mbps
if is_download:
self.accent_color = QColor(0, 210, 255)
self.track_color = QColor(0, 70, 90)
else:
self.accent_color = QColor(236, 0, 140)
self.track_color = QColor(80, 0, 50)
self.setMinimumSize(220, 220)
self.animation_timer = QTimer(self)
self.animation_timer.timeout.connect(self.update_animation)
self.animation_timer.start(16)
def set_value(self, value):
self.target_value = max(0.0, min(self.max_value, float(value)))
def update_animation(self):
diff = self.target_value - self.current_value
if abs(diff) > 0.05:
self.current_value += diff * 0.15
self.update()
else:
self.current_value = self.target_value
def paintEvent(self, event):
painter = QPainter(self)
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
size = min(self.width(), self.height())
painter.translate(self.width() / 2, self.height() / 2)
painter.scale(size / 200.0, size / 200.0)
start_angle = -225 * 16
span_angle = 270 * 16
rect = QRectF(-85, -85, 170, 170)
pen_track = QPen(self.track_color, 10, Qt.PenStyle.SolidLine,
Qt.PenCapStyle.RoundCap)
painter.setPen(pen_track)
painter.drawArc(rect, start_angle, -span_angle)
value_ratio = self.current_value / self.max_value
active_span = int(span_angle * value_ratio)
pen_active = QPen(self.accent_color, 12, Qt.PenStyle.SolidLine,
Qt.PenCapStyle.RoundCap)
painter.setPen(pen_active)
painter.drawArc(rect, start_angle, -active_span)
painter.setPen(QColor(255, 255, 255))
font_value = QFont("Segoe UI", 24, QFont.Weight.Bold)
painter.setFont(font_value)
painter.drawText(QRectF(-80, -25, 160, 40),
Qt.AlignmentFlag.AlignCenter, f"{self.current_value:.1f}")
font_unit = QFont("Segoe UI", 10, QFont.Weight.Normal)
painter.setFont(font_unit)
painter.setPen(QColor(180, 180, 180))
painter.drawText(QRectF(-80, 15, 160, 20),
Qt.AlignmentFlag.AlignCenter, self.unit)
font_title = QFont("Segoe UI", 11, QFont.Weight.DemiBold)
painter.setFont(font_title)
painter.setPen(self.accent_color)
painter.drawText(QRectF(-80, -65, 160, 20),
Qt.AlignmentFlag.AlignCenter, self.title.upper())
#******************************************************************************
# Der Hintergrund - Thread, damit das Programm nicht einfriert
#******************************************************************************
class SpeedtestWorker(QThread):
status_signal = pyqtSignal(str)
ping_signal = pyqtSignal(float) # Neues Signal für Ping
download_signal = pyqtSignal(float)
upload_signal = pyqtSignal(float)
finished_signal = pyqtSignal()
def run(self):
try:
self.status_signal.emit("Suche besten Server...")
st = speedtest.Speedtest()
best_server = st.get_best_server()
# Ping aus den Serverdetails auslesen (wird in ms geliefert)
ping_ms = float(best_server.get("latency", 0.0))
self.ping_signal.emit(ping_ms)
# 1. Download-Test
self.status_signal.emit("Teste Download-Geschwindigkeit...")
download_speed = st.download() / 1_000_000
self.download_signal.emit(download_speed)
# 2. Upload-Test
self.status_signal.emit("Teste Upload-Geschwindigkeit...")
upload_speed = st.upload() / 1_000_000
self.upload_signal.emit(upload_speed)
self.status_signal.emit("Test erfolgreich abgeschlossen!")
except Exception as e:
self.status_signal.emit(f"Fehler: {str(e)}")
finally:
self.finished_signal.emit()
#******************************************************************************
# Das Hauptfenster
#******************************************************************************
class SpeedtestMainWindow(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("PyQt6-Internet-Speed-Test")
self.resize(550, 450)
# Icon
if os.path.exists(ICON_PATH):
self.setWindowIcon(QIcon(ICON_PATH))
self.setStyleSheet("background-color: #121214; color: white;")
main_layout = QVBoxLayout(self)
# Status-Label ganz oben
self.status_label = QLabel("Bereit für den Test")
self.status_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.status_label.setStyleSheet("font-size: 14px; color: \
#aaa; margin-top: 15px;")
main_layout.addWidget(self.status_label)
# Ping-Anzeige (Dezent zentriert unter dem Status)
self.ping_label = QLabel("Ping: -- ms")
self.ping_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.ping_label.setStyleSheet("""
font-size: 16px;
font-weight: bold;
color: #00FF66;
background-color: #1A1A1E;
padding: 6px 15px;
border-radius: 12px;
margin: 10px auto;
""")
main_layout.addWidget(self.ping_label)
# Layout für die Gauges
gauge_layout = QHBoxLayout()
self.download_gauge = SpeedtestGauge(title="Download", is_download=True)
self.upload_gauge = SpeedtestGauge(title="Upload", is_download=False)
gauge_layout.addWidget(self.download_gauge)
gauge_layout.addWidget(self.upload_gauge)
main_layout.addLayout(gauge_layout)
# Start-Button
self.start_btn = QPushButton("Speedtest starten")
self.start_btn.setStyleSheet("""
QPushButton {
background-color: #1f1f23;
border: 2px solid #333;
border-radius: 6px;
padding: 12px;
font-weight: bold;
font-size: 13px;
margin-top: 10px;
}
QPushButton:hover
{ background-color: #2a2a30; border-color: #555; }
QPushButton:disabled
{ background-color: #101012; color: #555; border-color: #222; }
""")
self.start_btn.clicked.connect(self.start_speedtest)
main_layout.addWidget(self.start_btn)
self.worker = None
def start_speedtest(self):
self.start_btn.setEnabled(False)
self.ping_label.setText("Ping: ... ms")
self.download_gauge.set_value(0)
self.upload_gauge.set_value(0)
self.worker = SpeedtestWorker()
# Signale verbinden (Neu: ping_signal)
self.worker.status_signal.connect(self.status_label.setText)
self.worker.ping_signal.connect(self.update_ping_display)
self.worker.download_signal.connect(self.download_gauge.set_value)
self.worker.upload_signal.connect(self.upload_gauge.set_value)
self.worker.finished_signal.connect(self.test_finished)
self.worker.start()
def update_ping_display(self, ping_value):
"""Formatiert und zeigt den empfangenen Ping-Wert an."""
self.ping_label.setText(f"Ping: {ping_value:.0f} ms")
def test_finished(self):
self.start_btn.setEnabled(True)
self.worker.deleteLater()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = SpeedtestMainWindow()
window.show()
sys.exit(app.exec())
#******************************************************************************
# Ende
#******************************************************************************
1️⃣ Die Klasse „SpeedtestGauge“, Custom-Widget, die Anzeige, Zeile 22 bis 96:
Dieses Widget ist für die rein optische Darstellung und die flüssige Bewegung der Nadel zuständig. Keine
starre Sprunganzeige. „update_animation“. Wenn ein neuer Wert über set_value()
reinkommt, springt die Anzeige nicht sofort dorthin. Stattdessen berechnet ein QTimer 60-mal pro Sekunde
(alle 16 Millisekunden) die Differenz zwischen dem aktuellen Zeigerstand (self.current_value)
und dem Zielwert (self.target_value). Sie nähert sich mathematisch gedämpft an,
was diese typische, geschmeidige "Tacho-Bewegung" erzeugt.
Das Zeichnen (paintEvent):
Sobald sich der Wert ändert, wird das Widget neu gezeichnet. Mit QPainter verlagern wir den Nullpunkt in die Mitte
des Widgets (translate). Zuerst wird ein grauer/dunkler Hintergrund-Bogen gezeichnet. Darüber wird ein farbiger
Bogen (drawArc) gelegt, dessen Länge exakt proportional zum aktuellen Wert im Verhältnis zum Maximalwert
(self.max_value) berechnet wird. Am Ende wird der Text (die Zahl und die Einheit)
mathematisch exakt im Zentrum platziert.
2️⃣ Die Klasse „SpeedtestWorker“, der Hintergrund-Arbeiter, die Logik, Zeile 101 bis 132:
Das ist das Herzstück für die Performance. Ein Internet-Speedtest blockiert das Skript für mehrere Sekunden, da große
Datenmengen hoch- und runtergeladen werden müssen. QThread (Multithreading). Würde man den
Speedtest im selben Thread wie das Fenster ausführen, würde das Betriebssystem melden: "Anwendung reagiert nicht mehr",
das Fenster würde einfrieren und die Animationen der Tachos würden stoppen. Durch QThread läuft der Test komplett isoliert
im Hintergrund.
Sichere Kommunikation via pyqtSignal: Ein Hintergrund-Thread darf in Qt niemals direkt die Benutzeroberfläche verändern
(das führt unweigerlich zu Abstürzen). Deshalb feuert der Thread stattdessen "Signale" (wie kleine Funksprüche) in den Raum,
sobald er neue Daten hat:
ping_signal.emit(ping_ms) sendet die Latenz.
download_signal.emit(download_speed) sendet das Ergebnis des Downloads.
status_signal.emit(...) sendet den aktuellen Textschritt für den Benutzer.
3️⃣ Die Klasse „SpeedtestMainWindow“, das Hauptfenster, der Dirigent, Zeile 137 bis 227:
Das Hauptfenster baut die Oberfläche ohne Designer auf und verbindet die Logik des Hintergrund-Threads mit den beiden
Tacho-Anzeigen. Layout-Management: Über QVBoxLayout (vertikal) und
QHBoxLayout (horizontal) werden die Elemente (Status-Text, die grüne Ping-Pille, die zwei
Gauges und der Button) sauber unter- und nebeneinander angeordnet. Sie passen sich automatisch an, wenn du das Fenster
größer oder kleiner ziehst.
Die Verbindung (Das "Verkabeln"): In der Methode start_speedtest() wird der
Hintergrund-Thread erzeugt. Nun werden die Funksprüche (Signale) des Threads mit den Methoden des Hauptfensters
verknüpft: self.worker.download_signal.connect(self.download_gauge.set_value).
PyQt6 Internet Speed. Ohne Gewähr © 2026 Hans Busche