Woher das Wetter von meiner Seite stammt

Vielleicht ist jemanden schon die Wetterinfo meines Heimatortes oben rechts auf jeder Seite aufgefallen. Im folgenden zeige ich euch, wie ich euch, wie ich diese Daten abfrage und in meinem Django-App speichere.

Der eine oder andere wird sich sicher fragen: "Was interessiert mich das Wetter irgendeines beknackten Dorfes am Rande Deutschlands?" aber das ist hier nun mal meine Homepage und hier bestimme ich. :-) Die hier vorgestellte Applikation lässt aber beliebig viele Wetterstationen abfragen so dass man theoretisch ein zweites wetter.com|de bauen könnte.

Wetterdienstdaten sind -- wenn es nicht irgendeine Flashbox halb gefüllt mit Werbung ist -- in der Regel nicht kostenlos, weather.com macht eine Ausnahme und stellt seine Daten über eine REST-Schnittstelle frei zur Verfügung. Einzige Gegenleistung: Ein Link zur Homepage sowie das Logo muss angezeigt werden.

Vorbereitung:

  1. Zu erst einmal muss man sich auf weather.com/services/ registrieren und erhält einen Link zum SDK-Paket sowie eine Partner-ID und einen Lizenzschlüssel.

  2. Des weiteren benötigt man die Wetterstations-ID des abzufragenden Ortes. Dazu geht man am einfachsten auf de.weather.com und sucht nach seinem Ort, in meinem Fall "Putbus". In der Adressleiste zeigt sich nun die Stations-ID, in meinem Fall http://de.weather.com/weather/local/GMXX4521.

  3. Nun hat man seine Stations-ID aber leider gibt nicht jede ID Daten zurück. Am besten Ihr testet es vorher einmal mit dieser URL:

    http://xoap.weather.com/weather/local/###STATIONS_ID###?cc=*&prod=xoap&unit=m&par=###PARTNER_ID###&key=###KEY###&link=xoap
    

Ich musste ein wenig knobeln und habe die nächstgrößeren Städte ausprobiert -- ohne Erfolg. Die einzige Wetterstation mit brauchbaren Daten war hier "Kap Arkona". Eigentlich klar, dort habe ich schon vor der Wetterstation des DWD gestanden. :-)

Das Model oder: Wie speichere ich die Daten in der Datenbank

Ich habe 2 Models definiert, einmal die Stationen, einmal ein Model für die Wetterdaten selbst. Damit ist es möglich beliebig viele Stationen (Hamburg, Berlin, München) abzufragen und später getrennt oder gemeinsam zu verarbeiten.

# -*- coding: utf-8 -*-

from django.db import models

class Station(models.Model):

    title = models.CharField('Titel', maxlength=250)
    slug = models.SlugField('Basename', maxlength=120,prepopulate_from=['title'])   
    description = models.TextField('Beschreibung', blank=True)    
    longitude = models.FloatField()
    latitude = models.FloatField()
    weatherdotcom_id = models.CharField(maxlength=10)

    class Admin:
        list_display = ('title', 'weatherdotcom_id')
        ordering = ['title']

    class Meta:
        verbose_name = 'Station'
        verbose_name_plural = 'Stationen'

    def __unicode__(self):
        return self.title

    def get_absolute_url(self):
        return "/wetter/%s/" % self.slug

class WeatherData(models.Model):

    station      = models.ForeignKey(Station)
    date_fetched = models.DateTimeField(auto_now_add =True)

    sunrise      = models.TimeField()
    sunset       = models.TimeField()

    temp         = models.IntegerField()
    temp_felt    = models.IntegerField()
    text         = models.CharField(maxlength =250)
    icon         = models.IntegerField()
    bar_pressure = models.FloatField()
    bar_tend     = models.CharField(maxlength =250)
    wind_speed   = models.IntegerField()
    wind_gust    = models.CharField(maxlength =250)
    wind_degree  = models.IntegerField()
    wind_text    = models.CharField(maxlength =3)
    humidity     = models.IntegerField()
    visibility   = models.FloatField()
    uv_index     = models.IntegerField()
    uv_text      = models.CharField(maxlength =250)
    dewpoint     = models.IntegerField()
    moon_icon    = models.IntegerField()
    moon_text    = models.CharField(maxlength =250)

    class Admin:
        pass

    def __unicode__(self):
        return str(self.id)

    def get_absolute_url(self):
        return "/wetter/datapoint/%s" % self.id

Das Wetter abfragen und speichern

Um die Daten von weather.com abzufragen und zu speichern habe ich eine Datei sync.py die im selben Verzeichnis wie die Models liegt. Da das Wetter bei meiner Station zu jeder vollen Stunde aktualisiert wird, lasse ich die sync.py zu jeder halben Stunde über einen Cronjob aufrufen. Das macht dann pro Tag 24 neue Werte.

Als einziges zusätzliches Paket verwende ich das wunderbare Beautiful Soup um mich durch den XML-Dom zu hangeln und die Werte auszulesen.

Vergesst in der Datei nicht die Partner-ID, den Key sowie die Pfade zu eurem Django-App anzupassen.

# -*- coding: UTF-8 -*-

WEATHERDOTCOM_PARTNER_ID = '1234567890'
WEATHERDOTCOM_KEY = '012345679abcdefg'

if __name__ == '__main__':

    import sys, os
    import urllib

    import time
    import datetime

    from BeautifulSoup import BeautifulStoneSoup

    sys.path.append('/home/vhosts/')
    os.environ['DJANGO_SETTINGS_MODULE'] = 'myproject.settings'

    from myproject.apps.weather.models import Station, WeatherData

    for station in Station.objects.all():

        WEATHERDOTCOM_URL = "http://xoap.weather.com/weather/local/%s?cc=*&prod=xoap&unit=m&par=%s&key=%s" % (station.weatherdotcom_id, WEATHERDOTCOM_PARTNER_ID, WEATHERDOTCOM_KEY)

        xml = urllib.urlopen(WEATHERDOTCOM_URL).read()
        soup = BeautifulStoneSoup(xml)

        weather = {}

        weather['station']      = Station.objects.get(pk=station.id)

        '''
        weather.com sendet für Sonnenauf- und Untergang ein Zeitfeld,
        wobei der Stunde keine Null vorangestellt ist (bspw. 8:45 AM).

        Um den Zeitpunkt auslesen zu können, muss ggf. diese 0 erst
        vorangestellt werden.
        '''
        if(len(str(soup.loc.sunr.contents[0]).split(':')[0]) < 2):
            sunrise = "0"+str(soup.loc.sunr.contents[0])
        else:
            sunrise = str(soup.loc.sunr.contents[0])

        if(len(str(soup.loc.suns.contents[0]).split(':')[0]) < 2):
            sunset = "0"+str(soup.loc.suns.contents[0])
        else:
            sunset = str(soup.loc.sunr.contents[0])

        weather['sunrise'] = datetime.datetime(*time.strptime(sunrise, "%I:%M %p")[0:5])
        weather['sunset'] = datetime.datetime(*time.strptime(sunset, "%I:%M %p")[0:5])

        '''
        'Visibility' kennzeichnet die weather.com-DTD als "Float",
        es kann aber auch das Wort "Unlimited" enthalten. In meinem
        Fall setze ich "Unlimited" daher auf "-1".
        '''
        if(soup.cc.vis.contents[0] == 'Unlimited'):
            weather['visibility']   = float(-1)
        else:
            weather['visibility']   = soup.cc.vis.contents[0]

        '''
        Der Rest ist Fleißarbeit
        '''
        weather['temp']         = soup.cc.tmp.contents[0]
        weather['temp_felt']    = soup.cc.flik.contents[0]
        weather['text']         = soup.cc.t.contents[0]
        weather['icon']         = soup.cc.icon.contents[0]
        weather['bar_pressure'] = soup.cc.bar.r.contents[0]
        weather['bar_tend']     = soup.cc.bar.d.contents[0]
        weather['wind_speed']   = soup.cc.wind.s.contents[0]
        weather['wind_gust']    = soup.cc.wind.gust.contents[0]
        weather['wind_degree']  = soup.cc.wind.d.contents[0]
        weather['wind_text']    = soup.cc.wind.t.contents[0]
        weather['humidity']     = soup.cc.hmid.contents[0]    
        weather['uv_index']     = soup.cc.uv.i.contents[0]
        weather['uv_text']      = soup.cc.uv.t.contents[0]
        weather['dewpoint']     = soup.cc.dewp.contents[0]
        weather['moon_icon']    = soup.cc.moon.icon.contents[0]
        weather['moon_text']    = soup.cc.moon.t.contents[0]

        '''
        Ein neues WeatherData-Object wird erzeugt und gespeichert.
        '''
        w = WeatherData(**weather)
        w.save()

Ausgabe

Da die Daten nun im Model liegen und im Feld date_fetched der jeweilige Zeitpunkt verfügbar ist, lassen sich die Daten wunderbar z.B. mit den Generic Views anzeigen.

Viel Spass mit dieser Anregung und es wäre schön, wenn Ihr in den Kommentaren berichten würdet, wo und wie ihr euer Wetter eingebaut habt.