Minibot für Erstellung eines iCals und RSS-Feeds von einer Web1.0 Site

In Dresden gibt es die Hochschule für Musik “Carl Maria von Weber”, welche auf ihrer Website auch ihr aktuelles Programm kundtun. Wer etwas Interesse an klassischer Musik hat, hat durch diese Art der Konzerte die Gelegenheit, sehr gute Pianisten sehr preiswert (umsonst…) zu hören.

Leider bieten sie weder einen Feed noch einen Kalender an, deshalb dachte ich mir, das wär wieder ein guter Einsatz für das hpricot-Gem, ich will hier mal kurz den Ablauf skizzieren.

Rien ne va plus … ohne Gems!

1
%w[rubygems hpricot curl active_support icalendar].each { |x| require x}

Im ersten Fall hatte ich Probleme mit den Umlauten, also erst einmal alles nach UTF-8 transformieren/encodieren:

1
2
3
4
5
cal = Calendar.new  # by the way: wir machen gleich mal einen ical draus, siehe icalendar gem.

curl_object = Curl::Easy.perform("http://www.hfmdd.de/veranstaltungen/")
body = Iconv.conv("UTF-8//IGNORE","ISO-8859-1", curl_object.body_str)
doc = Hpricot(body)

Im nächsten Schritt mit Selector Gadget und Firebug herausfinden, welcher CSS-Selektor uns Zeiger auf die Contentbereiche liefert, hier z.B. “#contentbereich div” mit der Eigenschaft, dass deren margin 35em ist… Leider haben die Webdesigner bei der Site der Musikhochschule wenig Gebraucht von Klassen oder IDs gemacht.

1
2
3
4
5
6
7
8
9
elements = doc.search("#contentbereich div").select{ 
  |it| it[:style].include? "35em" if it[:style].present?
}
# present? ist das Gegenteil von .blank?

# Jetzt traversieren wir die Elemente die uebrig bleiben
elements.each do |item|
  [...]
end

Jetzt zum langweiligeren […] Teil, dem lokalen Extrahieren der Daten…
Das passiert leider etwas unsauber, da, wie man auf der Seite sehen kann, recht willkürliche Formate in den Datumsangaben gemacht wurden, hier mein bester Versuch:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
      text = item.search("div[2]").inner_html rescue text = "???"
      date = item.search(">div>b").first.inner_text.split(".")
      time = item.search(">div[1]>*")[4].to_s[0..4].split(":") rescue time = "00:00"
      begin
        new_date = DateTime.strptime("20#{date[2]}-#{date[1]}-#{date[0]}T#{time[0]}:#{time[1]}:00+0100") 
        next if new_date < Date.today
        event = cal.event
        event.start = new_date
        event.summary = text.split("<br").first[0..150]
        event.description = text  
      rescue
        puts "INVALID: 20#{date[2]}-#{date[1]}-#{date[0]}T#{time[0]}:#{time[1]}:00+0100"
        # spaeter ein logger
      end

Jetzt koennen wir den Kalendar direkt ausgeben, oder sonstwas damit machen

1
cal.to_ical

Das Ganze war eher ein Proof-of-Concept, denn wie immer, wenn man ScreenScraping betreibt, sollte man eine Art Einverständnis des Inhabers der gescrapten Seite haben (oder sie bieten wie Google und Twitter eine API an)

Notenübersichts-Bot in PHP

Vor zwei Wochen kam mir beim Überprüfen der aktuellen Notenergebnisse die Idee in den Sinn, das Ganze zu automatisieren und als Feed zur Verfügung zu stellen, um es in meinen Feedreader mit einzubinden und so immer auf dem aktuellen Stand sein zu können ;).

Der Einfachheit halber hab ich PHP/Curl genommen, da ich kurz mal in Ruby reingeschaut hatte, mir die HTTP-Bibliothek aber nicht zweckdienlich erschien.

Teil 1: Den Quelltext der Webseite holen

Dazu mal mit Firefox und LiveHTTP-Headers-Addon schauen, was man beim Login zu alles schickt. In unserem Fall muss man danach noch einen Klick auf “Notenübersicht” machen.

Das ganze dann in cURL gießen und eine cookies.txt schreibbar bereitstellen:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  $username="Meine Matrikelnummer"
  $passwort="Mein Passwort ;)"
  $ch = curl_init();
  //Variablen setzen
  $url="https://wwwqis.htw-dresden.de/qisserver/rds?state=user&type=1&category=auth.login&startpage=portal.vm";
  //$url="https://wwwqis.htw-dresden.de/qisserver/rds?state=user&amp;type=1&amp;category=auth.login&amp;startpage=portal.vm";
  $arrSubmit="username=$username&submit=%C2%A0Ok%C2%A0&password=$password";
  $cookies="cookie.txt";
//Session Optionen setzen
  curl_setopt($ch, CURLOPT_URL,$url);
  curl_setopt ($ch, CURLOPT_POST, 1);
  curl_setopt($ch, CURLOPT_POSTFIELDS, $arrSubmit);
  curl_setopt($ch, CURLOPT_HEADER, 0);
  curl_setopt($ch, CURLOPT_COOKIEJAR, $cookies);
  curl_setopt($ch, CURLOPT_COOKIEFILE, $cookies);
  curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
//Ausführen der Aktionen
  curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
  $result=curl_exec($ch);
  curl_close($ch);

In unserem Beispiel des HIS-QIS gibt es noch eine Art zweiter Session-ID, die ausgelesen werden, und mit übergeben werden muss:

1
2
  preg_match("/asi=([^\"]*)\"/",$result,$treffer);
  $asi=$treffer[1];

Dann der Zweite durchlauf mit der asi:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 $url="https://wwwqis.htw-dresden.de/qisserver/rds?state=htmlbesch&moduleParameter=Student&menuid=notenspiegel&asi=$asi";
  $ch = curl_init();
  curl_setopt($ch, CURLOPT_URL,$url);
  curl_setopt ($ch, CURLOPT_POST, 0);
  curl_setopt($ch, CURLOPT_HEADER, 0);
  curl_setopt($ch, CURLOPT_COOKIEJAR, $cookies);
  curl_setopt($ch, CURLOPT_COOKIEFILE, $cookies);
  curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
  $result=curl_exec($ch);
  //echo curl_error($ch);
  //Session beenden
  curl_close($ch);

Damit ist der Text in der Variablen $result
Extraktion der wichtigen Zeilen mit XPath

Mittels Firebug schauen, wo die Prüfungsergebnisse drinstehen und die XPaths kopieren bzw. analysieren. Damit erhalten wir eine NodeList die wir ausgeben/speichern können:

1
2
3
4
5
6
7
8
9
10
11
$Doc = new DOMDocument();
$Doc->loadHTML($result);
$Doc->preserveWhiteSpace = false;
$Doc->normalizeDocument();
$XPath = new DOMXPath($Doc);
$NodeList = $XPath->query("//tr[@bgcolor='#EFEFEF']");
foreach ($NodeList as $node)
{        
  echo $node->nodeValue;
  ...
}

Schon fast fertig, was fehlt noch?

  • Die Datumswerte auslesen, nach Sekunden umwandeln um danach die Noten danach zu sortieren.
  • Einfacher Caching Algorithmus à la “Wenn unsere cache-datei älter als 30 minuten ist, dann erstelle sie neu [durchlaufe den Algorithmus] und schreibe das Ergebnis in die Cache-Datei; andernfalls gib nur die cache-Datei aus”
  • Ausgabe als RSS-Feed, einfach mal die Spezifikation googlen ;)