Automatische Klausurbenachrichtigung

Einleitung

Die Klausurphase ist gerade rum und ich muss gerade auch eine Hausarbeit schreiben, dachte mir aber, dass ich vielleicht noch etwas Zeit für diesen Blogeintrag habe. Website habe ich schon. Ich hatte vor hier mal dies und jenes hochzuladen, aber heute starten wir mit etwas für Geeks. Programmierung. Ist schon spät, Zeichensetzung kann Fehler enthalten. Und die Erklärungen sind vermutlich auch nicht alle zu 100% fachlich korrekt.

Da ich selber nicht viel in der Richtung kann, habe ich mir im letzten Jahr gedacht, dass ich einfach mal mit einem kleinen Projekt anfange. Dann kommt man schnell ins Thema und widmet sich direkt den wichtigsten Fragen:

1. Welche Programmiersprache lerne ich?

2. Welches Programm lässt die Schrift so bunt aussehen und was nehme ich da am besten?

3. Brauche ich einen Mac und muss ich mich dann mit diesem in ein Café setzen und einen Chai Latte trinken, um von anderen Menschen als Programmierer wahrgenommen zu werden?

Eigentliche Sache

Also. Problem: Ich logge mich nach der Klausurphase mehrmals täglich ins Prüfungssystem der Uni ein, um zu sehen, welche Klausurnoten jetzt online sind. Das ist nicht nur nervig, weil man sich ständig einloggen muss (was gut und gerne ein paar Minuten dauern kann), sondern auch, da man in vielen Fällen keine Note sieht und das Browserfenster genervt schließt und sich denkt: Die Zeit hättste dir jetzt auch sparen können.
Klar, gibt auch so WhatsApp Gruppen, wo einer von den Kommilitonen eh immer schneller reingeschaut hat und durchgibt, dass die Noten online sind. Geht aber irgendwie nicht mehr richtig auf, wenn man im Auslandssemester war und der Rest der Freunde das Studium in Regelstudienzeit durchknallt.

Lösung: Nicht ich logge mich bei der Uni ein, sondern ein Computer könnte das für mich übernehmen. Der würde dann einmal in der Viertelstunde den Browser öffnen, sich durch das Menü klicken, die einzelnen Fächer nachschauen, die Noten mit Fachbezeichnung an meinen Telegram-Bot senden, um das Fenster dann wieder zu schließen und in 15 Minuten von vorne zu beginnen. Manche Websites verbieten dieses Vorgehen, weil man damit für viel „Traffic“ (Datenverkehr) sorgt, welchen man aufgrund von besserer Serverleistung für alle anderen Nutzer natürlich vermeiden möchte. Völlig legitim. Es gibt jedoch eine Möglichkeit. Solange die Uni nicht dieses System beim Login benutzt, welches man von diversen Websites unter reCaptcha kennt. Ein kleines Programm, was solch automatisierte Software erkennt und unterbindet.

Und wie das ganze funktioniert möchte ich euch gerne zeigen. Mein gebasteltes Skript ist mit Sicherheit nicht perfekt, funktioniert aber.

1. Programmiersprache: Python

Ganz klassisches Dings, wird viel von Google als Programmiersprache benutzt, halb YouTube basiert auf der Technik und ist easy zu lernen.

2. Visual Studio Code von Microsoft

Kostenloses Programm zur Code-Bearbeitung, auf jedem System verfügbar, Open-Source, schick und performant.

3. Code

Es gibt in jeder Programmiersprache vorgefertigte Packages, welche es dem Anwender (mir), relativ leicht machen, komplexere Programmiercodes zu vermeiden. Also was das Programm genau macht, wenn ich ihm sage, dass es eine geladene Webpage nach etwas absuchen soll (hinlänglich bekannt unter Strg + F), kann mir egal sein. Und das ist es auch. Diese Module werden zu Beginn einmal importiert:

Module importieren

#Importiere alle Module
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support import expected_conditions as EC
import requests
import time
global str
import sys
import os
from os import path
from selenium import webdriver 
from selenium.webdriver.chrome.options import Options
chrome_options = Options()
chrome_options.add_argument("--headless")

Hier ist also unser kleines Paket zum Strg + F’en einer Seite: selenium. Ein sogenannter Webscraper. Außerdem importieren wir auch noch einen webdriver, das ist quasi ein Add-On für unseren Browser, damit der auch automatisch gesteuert werden kann. Dann noch time, damit man auch Wartepausen und Co. im Script einbauen kann. Das Modul requests ist zum anpingen (abrufen) von Websites. Das brauchen wir später für den Telegram-Bot. Dann noch so kryptische Sachen, nicht weiter relevant. Unten geben wir dann noch an, dass wir den Browser nicht mit Bild öffnen wollen, sondern das Ganze im Hintergrund stattfinden soll – headless eben.

Auf der Seite des akademischen Instituts

#Öffne den Browser und öffne die URL
driver = webdriver.Chrome('/usr/lib/chromium-browser/chromedriver',options=chrome_options)
driver.get("https://psso.th-koeln.de")

Jetzt wird es spannender. Wir sagen dem Programm, wo es die kleine Instanz zur automatischen Bedienung von Chrome findet und öffnen die Seite einer beliebigen Uni. Das hier ist die Seite vom Studierendenservice der TH Köln.

#Klicke dich durch die Seite
asdf = driver.find_element_by_id("asdf")
asdf.send_keys("NUTZERNAME")
fdsa = driver.find_element_by_id("fdsa")
fdsa.send_keys("PASSWORT")
loginForm = driver.find_element_by_name("submit")
loginForm.click()
Prue = driver.find_element_by_link_text("Prüfungsverwaltung")
Prue.click()
Noten = driver.find_element_by_link_text("Notenspiegel")
Noten.click()
Absch = driver.find_element_by_link_text("Abschluss BA Bachelor-Studiengang")
Absch.click()
i = driver.find_element_by_xpath('//*[@title="Leistungen für Betriebswirtschaftslehre (Ba.)  (PO-Version 3)  anzeigen"]')
i.click()

So, jetzt etwas komplizierter. Wenn man in Chrome ist, kann man F12 klicken, damit sich die Entwickleroptionen öffnen. Wenn man dann die Seite nach Login-Feldern, Buttons oder Ähnlichem untersucht, bekommt man eine ID. Darauf gehe ich hier nicht im Detail ein, hier nur zwei kleine Tipps:

Das Symbol ganz links ist der Corsor. Damit könnt ihr auf Elemente einer Website klicken.
Da ist sie schon: Die gesuchte ID.

Das Skript sucht sich jetzt also anhand der ID asdf = driver.find_element_by_id("asdf") das Login-Feld und sendet diesem Login-Feld nun den Nutzernamen asdf.send_keys("NUTZERNAME"). Passwort: same procedure. Dann kommen ein paar Bottons aus dem Menü. Suchen: Noten = driver.find_element_by_link_text("Notenspiegel") und bitte klicken: Noten.click(). So sind wir am Ende dieses Codes auf der Seite, wo die Noten stehen. Jetzt nur noch auslesen und verarbeiten.

Auslesen

#Liest die Tabelle nach den Notenwerten aus
Note1 = driver.find_element_by_xpath("/html/body/div/div[6]/div[2]/table[2]/tbody/tr[10]/td[6]").text
Note2 = driver.find_element_by_xpath("/html/body/div/div[6]/div[2]/table[2]/tbody/tr[12]/td[6]").text
Note3 = driver.find_element_by_xpath("/html/body/div/div[6]/div[2]/table[2]/tbody/tr[9]/td[6]").text
Note4 = driver.find_element_by_xpath("/html/body/div/div[6]/div[2]/table[2]/tbody/tr[32]/td[6]").text
Note5 = driver.find_element_by_xpath("/html/body/div/div[6]/div[2]/table[2]/tbody/tr[34]/td[6]").text
Note6 = driver.find_element_by_xpath("/html/body/div/div[6]/div[2]/table[2]/tbody/tr[2]/td[5]").text

a = Note1
b = Note2
c = Note3
d = Note4
e = Note5
f = Note6

#Liest die Namen der zugehörigen Klausuren
KlausurText1 = driver.find_element_by_xpath("/html/body/div/div[6]/div[2]/table[2]/tbody/tr[10]/td[2]").text
KlausurText2 = driver.find_element_by_xpath("/html/body/div/div[6]/div[2]/table[2]/tbody/tr[12]/td[2]").text
KlausurText3 = driver.find_element_by_xpath("/html/body/div/div[6]/div[2]/table[2]/tbody/tr[30]/td[2]").text
KlausurText4 = driver.find_element_by_xpath("/html/body/div/div[6]/div[2]/table[2]/tbody/tr[32]/td[2]").text
KlausurText5 = driver.find_element_by_xpath("/html/body/div/div[6]/div[2]/table[2]/tbody/tr[34]/td[2]").text

#Liest aktuellen Schnitt aus
Schnitt = driver.find_element_by_xpath("/html/body/div/div[6]/div[2]/table[2]/tbody/tr[2]/td[6]").text

Uff. Ja, die Noten stehen hier in so Tabellen. Wenn man sich angemeldet hat erscheint eine neue Zeile auf der Seite. Vorne steht dann der Name der Klausur (hier im Skript KlausurText1-6). Weiter hinten in der Tabelle stehen dann die Noten (Note1-6). An einer Stelle steht auch der aktuelle Schnitt, den ließt die letzte Code-Zeile aus. Der Code a = Note 1 verkürzt nur den Namen der (zugegebenermaßen schon recht kurzen) Variable Note1-6. Das ist nur so, damit wir später nicht so viele Zeilen Code haben, wenn es an die Logik des Programmes geht.

Ja, man muss das mit der ID (von oben) hier mit jeder einzelnen Zelle machen, welche man überprüfen möchte. Also pro Klausur mindestens die Note und den Namen der Klausur und bei Bedarf auch noch den aktuellen Schnitt. Hier in abgewandelter Form mit dem xpath anstelle der ID. Aber da fällt mir in Zukunft bestimmt was leichteres ein.

Zusammengefasst: Das Programm sucht nach Noten, Klausurbeschreibungen und dem Schnitt und hat diese Werte nun in den Variablen gespeichert (das sind immer die Wörter vor dem Gleichheitszeichen wie z.B. a, b, oder auch KlausurText1 oder auch Schnitt). Diese formt das Programm mit .text auch gleich in eine Textform um.

Die Logik

An der Stelle würde ich euch empfehlen, dies keinem „echten Programmierer“ zu zeigen, da der Code vermutlich maximal ineffizient ist. Ist nicht das hübscheste Stück Code, aber klappt (wahrscheinlich 😉 ).

Noch eine Sache vorab: Woher weiß das Programm eigentlich, ob es mir schon eine Benachrichtigung zur Klausur geschickt hat? Ich meine, wenn keine Klausurnote da war, sendet es mir keine Nachricht. Wenn aber eine da war, sendet es mir jedes mal eine, wenn das Programm wieder startet. Dann hat man schon ne schlechte Klausur geschrieben und das wird einem noch unter die Nase gerieben. Viertelstündlich. Hier der gesamte Code und unten dann noch einmal aufgedröselt:

try:
 f1 = open("Klausur1.txt")
except IOError:
 if not a:
  pass
 else:
  requests.get("TELEGRAM_API UND DIE ENTSPRECHENDE CHAT ID&text=Deine Klausur im Fach {} wurde mit der Note {} abgeschlossen. Dein neuer Schnitt ist somit {}.".format(KlausurText1, variableTable1, Schnitt))
  with open('Klausur1.txt', 'w') as fp:
   pass
else:
 f1.close()
finally:
   pass

Also: Das Programm soll, wenn es eine Benachrichtigung gesendet hat, nie wieder eine Benachrichtigung zur gleichen Note senden. Dafür erstellt es nach der Benachrichtigung eine Datei auf dem Computer: „Klausurname.txt“.

  with open('Klausur1.txt', 'w') as fp:
   pass

Und wenn das Programm (bzw. jede einzelne Klausurüberprüfung in den Programm; ergo 6) startet, schaut das Programm erstmal nach, ob es diese Datei gibt. Wenn ja, Datei schließen und fertig. Wenn nein? Weitermachen.

try:
 f1 = open("Klausur1.txt")

Wir haben hier also verschiedene Logikarme: Try: Versuche die Datei „Klausur1.txt“ zu öffnen. Falls das gelingt, gehe zu else und schließe die Datei wieder. Das f1 ist hier nichts anderes als eine Variable, wie die oben Kennengelernten. Das verkürzt das Ganze zu: f1.close(). Finally: Ja so endet das ganze, dass pass[t] das Programm aber. Das muss nur am Ende einer try-Logik stehen.

Zweiter Fall: Try: Versuche die Datei „Klausur1.txt“ zu öffnen. Falls das nicht gelingt und du einen IOError hast, dann: Checke ob die Variable a (wir erinnern uns: nichts anders als unsere Klausurnote bzw. wenn nichts online ist ein leeres Feld) = 0 ist. Das heißt if not a. Wenn die Klausurnote gleich 0 ist, pass. Also gehe direkt zur Finally-Schleife und beende die Überprüfung.

Wenn dies aber nicht der Fall ist, also a (unsere Note) nicht = 0 ist, mache else. Und else „requestet“ eine URL (nichts anderes als der Telegram-Bot), die zur Folge hat, dass wir zu unserer Chat-ID (unserer Telegram-Nummer) eine Nachricht gesendet bekommen. Diese Nachricht statten wir dann jetzt noch mit den nötigen Infos aus.

Wie du deinen Telegram-Bot einrichtest (kostenlos und spaßig), kannst du hier sehen. Diese API (Automates Programming Interface) trägst du dann mit samt deiner Chat-ID (also einfach einen langen Link) da ein, wo aktuell „TELEGRAM_API UND DIE ENTSPRECHENDE CHAT ID“ steht.

requests.get("TELEGRAM_API UND DIE ENTSPRECHENDE CHAT ID&text=Deine Klausur im Fach {} wurde mit der Note {} abgeschlossen. Dein neuer Schnitt ist somit {}.".format(KlausurText1, a, Schnitt))

Ab dem Teil &text= kannst du nun beliebig mit den Formulierungen spielen. Die geschweiften Klammern werden dann mit den oben abgerufenen Variablen gefüllt. Also: Deine Klausur im Fach (Ah, er meint die Variable KlausurText1, da sie da hinten bei .format(X, Y, Z) an erster Stelle steht. … wurde mit der Note {} (Okay, Variable a (das ist unsere Note von oben)).. und so weiter.

Wenn dieser Logik-Prozess abgeschlossen ist, erstellt das Programm noch unsere kleine Text-Datei, damit wir die Benachrichtigung nur einmal bekommen.

with open('Klausur1.txt', 'w') as fp:
   pass

Dieser Code, mit entsprechend angepassten Textdateien und Variablen, existiert dann genau sechs mal. Einmal pro Klausur. Also nur in dem Beispielfall, man kann das beliebig erweitern. Zum Schluss wird der Browser noch geschlossen:

#Schließen des Browserfensters
driver.close()
driver.quit()

Aber dein PC läuft doch gar nicht den ganzen Tag, oder? Genau deswegen gibt es so kleine Dinger wie den Raspberry Pi. Der verbraucht extrem wenig Strom und ist so groß wie eine Checkkarte. Ein bisschen Overkill, wenn da nur dieses Skript drauf läuft, jo. Aber da läuft auch noch eine kleine Hausautomation mit Temperatursteuerung der Heizung drauf und zwei, drei andere Dinge. Aber dazu ein andern mal.

So, jetzt gehe ich schlafen. Kommende Woche erkläre ich euch, wie ich das alles mit der Erfindung und Entwicklung des Bitcoin gemacht habe und was man da so beachten muss 🙂

PS: Damit das Skript viermal pro Stunde startet, gibt es auf diesem Minicomputer crontab. Hier könnt ihr mehr dazu lesen.

1 Kommentar zu „Automatische Klausurbenachrichtigung“

Kommentar verfassen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

de_DEDeutsch