Astronomy Picture of the Day

Bei der Auflistung meiner Services habe ich meinen Astronomy Picture of the Day – Service bereits erwähnt. Hier folgt nun das, was hinter diesem Mini-Service steht.

Ich nutze schon seit Jahren den Wallpaper Cycler, um meine Bildschirme mit individuellen Hintergrundbildern zu versorgen. Mit diesem Tool kann ich Bilder überlagern, Effekte zu Objekten zufügen und vieles mehr.

Da mich das Thema Weltraum schon lange fasziniert (für mich war die Mondlandung 1969 eines der beeindruckendsten technischen Ereignisse des 20. Jahrhunderts), habe ich als Grundlage für meinen Wallpaper Cycler schon lange ein Grundset an astronomischen Bildern genutzt. Vor einiger Zeit bin ich dann aber über die „Astronomy Picture of the Day“ – Seite der NASA gestolpert. Leider werden die Bilder dort auf der Seite nicht auch als tägliches Bild unter wiederkehrendem Namen hinterlegt. Jedes Bild enthält dort noch zusätzliche Informationen. Diese sind zwar super spannend zu lesen, aber für meinen Zweck, dieses Bild als Hintergrundbild für meinen Desktop zu nutzen ungeeignet.

Auf der Suche nach einer Lösung für mein Problem habe ich mir lange Zeit mit einem simplen Shell-Skript beholfen, welches cron gesteuert das Bild herunterlädt. Leider war mein Shell-Skript alles andere als robust. So kann es zum Beispiel vorkommen, dass auf der APOD-Seite statt eines Bildes ein Video präsentiert wird. Das Shell-Skript hat hier trotzdem stur das aktuelle Bild ins Archiv verschoben, bevor es versucht hat, das neue Bild zu laden. Damit stand ich dann manchmal ohne Hintergrundbild da.

Da ich kürzlich angefangen habe, für meine privaten Sachen auch python zu lernen, war das der Startschuss, mein Shell-Skript durch ein python Skript zu ersetzen.
Ziel war es, ein Skript zu haben, welches ohne Installation periodisch auf dem Server ausgeführt werden kann. Hierbei kommt zum einen python selbst als auch ein spezielles Docker image zum Einsatz. Das python Skript bietet bestimmt noch Optimierungspotenzial. Für den Moment reicht für mich jedoch völlig aus.

import requests
from bs4 import BeautifulSoup
import urllib.parse
import os
import shutil
from datetime import date


def download_image(url, save_path):
    response = requests.get(url, stream=True)
    response.raise_for_status()
    with open(save_path, 'wb') as file:
        for chunk in response.iter_content(chunk_size=8192):
            file.write(chunk)


def get_first_image_url(html, url):
    soup = BeautifulSoup(html, 'html.parser')
    img_tag = soup.find('img')
    if img_tag:
        img_url = img_tag.get('src')
        if img_url and not img_url.startswith('data:'):
            return urllib.parse.urljoin(url, img_url)
    return None


def move_to_archive(src, target_folder):
    current_date = date.today().strftime("%Y%m%d")
    new_img_name = f"{current_date}-img.jpg"
    new_path = os.path.join(target_folder, new_img_name)
    if os.path.exists(src):
        shutil.move(src, new_path)


def create_folders():
    if not os.path.exists("data"):
        os.makedirs("data")
    if not os.path.exists("data/archive"):
        os.makedirs("data/archive")


def save_image_from_apod(url, save_path, archive_path):
    response = requests.get(url)
    response.raise_for_status()
    image_url = get_first_image_url(response.text, url)
    if image_url:
        create_folders()
        move_to_archive(save_path, archive_path)
        download_image(image_url, save_path)
        print(f"Das Bild wurde erfolgreich gespeichert: {save_path}")
    else:
        print("Es wurde kein Bild gefunden.")


apod_url = 'https://apod.nasa.gov/apod/'  # URL der HTML-Seite hier angeben
img_name = 'data/img.jpg'  # Speicherpfad für das Bild hier angeben
archive = 'data/archive'

save_image_from_apod(apod_url, img_name, archive)

Dieses Skript läuft lokal in meiner IDE wunderbar. Doch es sollte ja ohne größere Installationen auf dem Server laufen. Hier hilft mir docker weiter. Ich habe mir ein Dockerfile gebaut, welches mir ein spezielles python docker Image erstellt. In diesem Image sind genau die Abhängigkeiten enthalten, die dieses Skript benötigt.

FROM python:slim

ARG uid=1000
ARG gid=1000

RUN pip install --upgrade pip
RUN python -m pip install requests
RUN python -m pip install beautifulsoup4
RUN python -m pip install Pillow

USER ${uid}:${gid}

WORKDIR /python

CMD [ "python", "./main.py" ]

Mit diesem docker Image habe ich einmalig einen Container erstellt. Nachdem das Skript abgearbeitet ist, beendet sich dieser Container automatisch. Diesen Container rufe ich nun via cron täglich mit docker start apod_python auf. Zusätztlich ist dieser Aufruf noch via healthchecks absichert, damit ich informiert werde, wenn der cronjob mal nicht gelaufen ist.

Das gespeicherte Bild liefere ich über einen einfachen nginx-Container aus. Damit habe ich mir eine einfache Lösung geschaffen, die genau mein Problem löst. Zusätzlich ist das ganze so flexibel, dass ich mit einem ähnlichen python Skript mir auch tagesaktuell das jeweils neue doodle von Google für meine Speed Dial 2 Erweiterung laden kann.

Sternmiere
Ehemann, Vater, Nerd, Aikidoka, Bogenschütze, Technikbegeisterer, verrückter Typ — sucht es Euch das passende aus. Es gibt bestimmt noch mehr Attribute, die zu mir passen. Aber für den Moment muss das genügen.