cover image for post 'Tutorial - TakeTV Streamingtermine in Kalender schreiben'

Tutorial - TakeTV Streamingtermine in Kalender schreiben

TakeTV ist eine von vielen Seiten, die ihre Termine im Web veröffentlichen, allerdings in einem für Kalender unlesbaren Format. Mit etwas Pythoncode kann dem Abhilfe geschafft werden. In einem ersten Schritt werden die Termine von der Webseite gelesen; im zweiten Schritt diese dann in eine iCalendar-Datei geschrieben, welches von den meisten Kalendarprogrammen gelesen werden kann.

Schritt 1: Streamdaten von Webseite laden

Die Streamingtermine auf TakeTV werden durch HTML in folgendem Format repräsentiert:

<div class="ym-grid stream_item  own">
    <div class="column_left">
        <span class="datum">29.10</span>
        <span class="time">19:00h</span>
    </div>
    <div class="column_right">
        <h2>
            EPS WINTER CUP #4
        </h2>
    </div>
</div>

<div class="ym-grid stream_item   even">
    <div class="column_left">
        <span class="datum">31.10</span>
        <span class="time">15:30h</span>
    </div>
    <div class="column_right">
        <h2>
            ATC - MOUSESPORTS VS LIQUID
        </h2>
    </div>
</div>

Das heisst:

  • Die Begegnung steht zwischen <h2>-Tags.
  • Das Datum steht zwischen <span>-Tags der Klasse “datum” und stehen vor dem Titel der Begegnung.
  • Die Zeit steht zwischen <span>-Tags der Klasse “time” und erscheinen ebenfalls vor der Begegnung.

Die drei Angaben können in Python ohne Nachinstallation von Modulen ausgelesen werden, z.B. mit einer Regular Expression, davon wird jedoch aus gutem Grund abgeraten. Eine hervorragende Alternative ist Scrapy. Scrapy benötigt aber insbesondere auf Windows viele Schritte bei der Installation, setzt Grundkenntnisse der eleganten XPath-Sprache voraus und ist für dieses Projekt wahrscheinlich zu viel des Guten. Im Folgenden verwende ich deshalb das ebenfalls sehr empfehlenswerte Beautiful Soup, welches ohne viel Einarbeitung zum Ziel führt.

1.1 Webseite lesen und Parsen

Als erstes wird die Webseite mit den Streamingdaten aufgerufen, der HTML-Inhalt gelesen und durch Beautiful Soup geparsed. Für den Webseitenaufruf nutze ich requests, gleiches kann aber mit urllib erreicht werden.

import requests
from bs4 import BeautifulSoup

## Webseite aufrufen
r = requests.get('http://www.taketv.net/streams')
## Den HTML-Inhalt speichern
html = r.text
## Den HTML-Text durch Beautiful Soup parsen
doc = BeautifulSoup(html)

1.2 Begegnungen auslesen

Wie erwähnt stehen die Begegnungen zwischen <h2>-Überschriften. Es sind gleichzeitig die einzigen <h2>-Tags auf der Seite, so dass bloss alle <h2>-Tags auf der Seite auszulesen sind um an die Matchups zu kommen. Die getText()-Methode liefert schliesslich den Text zwischen den Tags:

# Iteriere über alle h2-Tags = Begegnungen
    for h2 in doc.find_all('h2'):
        # Inhalt der h2-Tags ist die Begegnung.
        who = h2.getText()

1.3 Datum und Zeit auslesen

Das Datum und die Zeit stehen in -Tags vor der Begegnung. Über die findPrevious()-Methode kann Beautiful Soup rückwärts nach Elementen mit bestimmten Kriterien suchen. Wir brauchen die -Tags mit Attributmit Wert datum resp. time. Erneut sind wir nur am Text zwischen den Tags interessiert:

# Datum und Zeit auslesen
        date = h2.findPrevious('span', {'class':'datum'}).getText()
        time = h2.findPrevious('span', {'class':'time'}).getText()

1.4 Datum und Zeit formatieren

Das iCalendar-Format verlangt nach Zeitpunkte in einem genau definierten Format. Um dieses Format später zu erzeugen, werden Datum und Zeitangabe interpretiert und als Pythons datetime-Variablen gespeichert. Wir verbinden zuerst das Datum und die Zeit zu einem String mit ‘{0} {1}’.format(date,time). Als Ergebnis erhalten wir daraus z.B. 25.10 13:00h. Diese Datumsformat konvertieren wir dann mit der strptime-Methode und dem Format ‘%d.%m %H:%Mh’ (%d steht dabei für den Tag, %m für den Monat, etc.). Da die Jahresangabe fehlt, setzen wir dieses manuell auf das aktuelle Jahr (führt beim Jahreswechsel unweigerlich zu Problemen, der Einfachheit halber ignorieren wir diese Unschönheit hier). Als letztes brauchen wir für iCalendar noch die Zeitzone, diese erhalten wir über das Modul pytz:

# Zeit und Datum (letzteres ohne Jahresangabe) von String nach datetime konvertieren
when_dt = datetime.strptime('{0} {1}'.format(date,time), '%d.%m %H:%Mh')
# Manuell das Jahr auf das aktuelle Jahr setzen
when_dt = when_dt.replace(year=datetime.now().year)        
# Zeitzone (benötigt 'import pytz')
berlin = pytz.timezone('Europe/Berlin')

1.5 Angaben in Datenstruktur speichern

Als Letztes speichern wir alle Angaben eines Termins in einem Dictionary event, und legen diese in einer Liste events ab. Folgende fehlende Angaben werden ausserdem noch ergänzt:

  • location: als Ort der Veranstaltung wird fix der Link zu TwitchTV verwendet.
  • created: der Zeitpunkt, an dem der Kalendereintrag generiert wurde, hier nehmen wir die aktuelle Zeit.
  • end: der Zeitpunkt, an dem der Event endet. Wir schätzen die Länge der Übertragung auf 2.5 Stunden.

Python snippet:

event = {
'summary': 'TakeTV - {0}'.format(who),        
'location': 'www.twitch.tv/taketv',        
'created': datetime.utcnow().replace(tzinfo=pytz.utc),
'start' : when_dt.replace(tzinfo=berlin),
'end' : (when_dt+timedelta(hours=2,minutes=30)).replace(tzinfo=berlin),
}
events.append( event )

Zusammenfassug

Hier die komplette Funktion, um die Streamingdaten von der Webseite zu holen:

import requests
from bs4 import BeautifulSoup
from datetime import datetime, timedelta
import pytz

def get_stream_events():    
    events = []
    r = requests.get('http://www.taketv.net/streams'
    html = r.text
    doc = BeautifulSoup(html)
    entries = []    
    for h2 in doc.find_all('h2'):
        who = h2.getText().encode('utf-8')        
        date = h2.findPrevious('span', {'class':'datum'}).getText()
        time = h2.findPrevious('span', {'class':'time'}).getText()
        when_dt = datetime.strptime('{0} {1}'.format(date,time), '%d.%m %H:%Mh')
        when_dt = when_dt.replace(year=datetime.now().year)        
        berlin = pytz.timezone('Europe/Berlin')
        event = {
        'summary': 'TakeTV - {0}'.format(who),        
        'location': 'www.twitch.tv/taketv',        
        'created': datetime.utcnow().replace(tzinfo=pytz.utc),
        'start' : when_dt.replace(tzinfo=berlin),
        'end' : (when_dt+timedelta(hours=2,minutes=30)).replace(tzinfo=berlin),
        }
        events.append( event )
    return events

Schritt 2: Daten in iCal Format schreiben

Die generierten Events werden mit dem Python Modul icalendar ins eine Kalendardatei geschrieben.

2.1: Kalender erstellen

Als Erstes wird der Kalender erstellt, eine ID gesetzt und die Version von iCal geschrieben.

from icalendar import Calendar, Event

cal = Calendar()
cal.add('proid', 'TakeTV Streaming Dates')
cal.add('version', '2.0')

2.2: Events hinzufügen

Nun werden die Events hinzugefügt. Dabei sind vordefinierte Felder zu verwendent, z.B. dtstart für den Startzeitpunkt des Events:

for e in events:
    event = Event()
    event.add('summary', e['summary'])        
    event.add('dtstart',e['start'])
    event.add('dtend',e['end'])
    event.add('dtstamp',e['created'])
    event.add('location', e['location'])
    event['uid'] = "{0}#{1}".format(e['start'],e['summary'])
    cal.add_component(event)

Zusammenfassug

Hier die ganze Funktion:

from icalendar import Calendar, Event

def create_calendar(events):
    cal = Calendar()
    cal.add('proid', 'TakeTV Streaming Dates')
    cal.add('version', '2.0')
    
    for e in events:
        event = Event()
        event.add('summary', e['summary'])        
        event.add('dtstart',e['start'])
        event.add('dtend',e['end'])
        event.add('dtstamp',e['created'])
        event.add('location', e['location'])
        event['uid'] = "{0}#{1}".format(e['start'],e['summary'])
        cal.add_component(event)
    return cal

Schritt 3: iCal schreiben und auf Webserver laden

Der erstellte Kalender (aus create_calendar() wird nun in eine Datei geschrieben:

FILENAME = 'taketv.ics'

with open(FILENAME, 'wb') as f:
    f.write(ical.to_ical())

Die Datei kann nun, falls ein eigener Webserver vorhanden ist, auf diesen hochgeladen werden und so von überall abonniert und abgerufen werden. Hier z.B. über FTP:

import ftplib
session = ftplib.FTP('ftp.server','username','password')
session.cwd('files')

with open(FILENAME,'rb') as file:
    session.storbinary('STOR {0}'.format(FILENAME), file)

Endresultat

Der gesamte Code ist auf GitHub zu finden. Das Skript wird momentan alle 10 Minuten ausgeführt und das Ergebnis auf diese Domain hochgeladen:. Edit: Schaue aktuell nicht mehr auf TakeTV Starcraft und habe den Kalender abgeschaltet, das Skript sollte aber noch funktionieren.