Mein Twitter-Account war bisher sehr inkonsequent geführt. Mal habe ich über neue Artikel getweetet und mal nicht. Das soll sich nun ändern. Ich möchte, dass ein Bot alle meine Artikel automatisch tweetet. Inspiriert wurde ich durch Kalle Hallden, der so etwas bereits mit seinen YouTube Videos gemacht hat. Das Video dazu findest du hier. Das fertige Endergebnis meines Bots, findest du auf meinem Twitter-Account.
Bevor ich über meine Artikel tweeten kann, muss ich erstmal wissen, ob etwas Neues hochgeladen wurde und was. Das erkenne ich anhand des RSS-Feeds. Dieser befindet sich bei HelloCoding hier: https://hellocoding.de/feed.xml. Falls du alle interessanten Blogs in einem Interface haben möchtest, könntest du diesen in einen RSS-Reader importieren, um keinen Artikel mehr zu verpassen. (Geheimtipp 😄)
Um den Feed zu parsen habe ich mich dazu entschieden, den Feedparser zu verwenden. Ein ganz normaler XML-Parser hätte es aber vermutlich auch getan. Da bei mir das Datum nicht automatisch geparst werden konnte, habe ich zum Einlesen des Datums noch eine weitere Bibliothek hinzugefügt: Dateparser. Diese kümmert sich um das reibungslose Parsen des Datums. Alternativ könnte man auch das feste Datumsformat zum Parsen nutzen, ich baue meinen Bot aber in diesem Fall lieber etwas resistenter, damit ich den Code bei anderen RSS Feeds mit anderem Datumsformat wiederverwenden kann.
Hier noch mal die essenziellen Bibliotheken für das Auslesen des RSS Feeds im Überblick:
# Feedparser
pip install feedparser
# Dateparser
pip install dateparser
Der Feed bzw. XML ist ein verschachteltes Informationskonstrukt, ähnlich wie HTML. Es können Informations-Tags geöffnet und geschlossen werden, es können Tags in Tags verschachtelt werden, es können aber auch unterschiedliche Tags verwendet werden. In den jeweiligen Tags sind dann die entsprechenden Informationen enthalten.
Das Herunterladen des Feeds könnte zwar via Sockets abgearbeitet werden, der Feedparser hat diese Funktion jedoch selbst integriert, was mir den entsprechenden Handlungsschritt erspart hat. Der Vorteil, es aber dennoch mit Socket selbst zu implementieren, wäre unter anderem eine bessere mögliche Fehlerabwickelung.
all_articles = feedparser.parse("https://hellocoding.de/feed.xml")
In der Variable all_articles
befindet sich nun ein Array mit allen Informationen. Neben den Head-Informationen, welche hier nicht gebraucht werden, befinden sich die Artikel im Reiter entries
. Diese können ganz normal mit einer Schleife durchgeloopt werden.
for article in all_articles["entries"]:
Nun habe ich auch Zugriff auf die verschiedenen Parameter des Artikels. Ich schreibe diese in Variablen, um sie besser lesen zu können:
for article in all_articles["entries"]:
article_name = article["title"]
article_link = article["link"]
article_author = article["author"]
article_timestamp = article["published"]
Jetzt möchte ich noch das Datum zu einem Unix-Timestamp einlesen, um das Datum vergleichen zu können. Der Dateparser gibt mir den Timestamp anschließend als Float zurück. Da ein Integer aber reicht, schneide ich die Nachkommastellen einfach ab, indem ich das Ergebnis zu einem Integer konvertiere.
article_timestamp = int(dateparser.parse(article["published"]).timestamp())
Nachdem der Autor als Variable verfügbar ist, kann geprüft werden, ob der Artikel überhaupt von mir ist. Artikel von Felix möchte ich auf meinem Twitteraccount nicht teilen. Deshalb frage ich nun ab, ob der Autor meinen Namen enthält. Jeder String hat in Python Zugriff auf magische Methoden, u.a. contains, welche nach einem Wort oder einem Teil des Satzes sucht;
if not article_author.__contains__("Riedl"):
continue
Das Autor-Tag enthält eine E-Mail-Adresse sowie den Namen. Da es sehr viel wahrscheinlicher ist, dass ein Autor ebenfalls Tim heißt statt Riedl, verwende ich hier meinen Nachnamen. Sollte mein Nachname nicht vorkommen, soll der Datensatz übersprungen werden und der nächste Datensatz überprüft werden.
Ich wurde inzwischen mehrfach darauf aufmerksam gemacht, man könne doch auch statt contains
in
verwenden. Ja, funktioniert genauso, aber ich finde in dem Fall 'contains' bei Textvergleichen ansprechender, was die Lesbarkeit betrifft, da man es eher wie einen englischen Satz lesen kann. 'In' verwende ich eher für Arrays. Aber das ist nur mein subjektives Empfinden; nutze einfach das, was dir lieber ist, funktionieren wird beides.
Um nicht jedes Mal alle via RSS-Feed übergebene Artikel zu posten, muss ich herausfinden, über welche Artikel ich bereits getweetet habe, und welche nicht.
Jedes Mal, wenn der Feedparser ausgeführt wird, wird das Datum mitgeloggt. Anhand des Timestamps kann bei der nächsten Ausführung geprüft werden, ob ein Artikel bereits gepostet wurde oder nicht. Da die Einträge nach Datum sortiert sind, kann ich nach dem Ersten bereits geposteten Artikel das Programm beenden.
Der Timestamp wird als Zahl gespeichert. Eine Datenbank für einen einzelnen Zahlenwert aufzusetzen, finde ich etwas sinnlos, ggf. muss sogar erst noch eine Datenbank installiert und gehostet werden, was mir für diesen Bot zu viel Aufwand war. Solltest du mehrere Feeds auslesen, dann ja, aber bei einem einzigen Feed reicht denke ich eine normale JSON-Datei.
Zunächst ermittle ich den aktuellen Timestamp während der Ausführung. Die Variable last_post_update
speichert den Unix-Timestamp der letzten Ausführung des Scripts, welcher in einer JSON-Datei steht. Für den Fall, dass die Datei noch nicht existiert, da das Script z.B. zum ersten Mal ausgeführt wurde, wird die Variable last_post_update
zunächst mit dem aktuellen Datum beschrieben, kann die JSON-Datei dann erfolgreich gelesen und geparst werden, wird der Wert später einfach überschrieben.
def get_last_post_timestamp() -> int:
current_timestamp = int(datetime.now().timestamp())
last_post_update = current_timestamp
Bevor die Datei ausgelesen werden kann, muss zunächst der Dateipfad ermittelt werden, unabhängig davon, von wo das Script ausgeführt wird. Mit os.path.realpath(__file__)
kann der absolute (komplette) Pfad zum Python-Script ermittelt werden. Anschließend wird aus dem Pfad mit os.path.dirname()
der Dateiname main.py
entfernt und mit data.json
ersetzt. Die Ordnerstruktur reicht für dieses Projekt völlig aus, alternativ kannst du sie aber auch z.B. in ein Unterverzeichnis deiner Wahl schieben. Der Pfad könnte optional in einer DotEnv Datei angegeben werden.
current_dir = os.path.dirname(os.path.realpath(__file__)) + "/data.json"
Ist der Pfad zur JSON-Datei ermittelt, kann diese auch ausgelesen werden. Sollte das einlesen scheitern, kannst du dein Programm mit Try-Except vor einem Absturz bewahren. Wenn du möchtest, kannst du den Fehler mitloggen, ansonsten überspringe das Exception-Handling mit Wort pass
. Bedenke, dass das Überspringen der Exception mit pass
Quick & Dirty in privaten Projekten in Ordnung ist, im kommerziellen Umfeld aber nicht.
try:
with open(current_dir, "r") as f:
last_post_update = int(json.loads(f.read())["last_post"])
except Exception as e:
pass
Nachdem der Wert ausgelesen wurde, wird anschließend der neue Timestamp geschrieben. Auch hier solltest du wieder das Schreiben der Datei gegen Fehler im professionellen Umfeld absichern, aber für meine interne Umgebung reicht das so aus.
with open(current_dir, "w") as f:
f.write(json.dumps({"last_post": current_timestamp}))
Nachdem nun klar ist, welche Artikel bereits gepostet werden dürfen und welche nicht, kann ich mich mit der Twitter API auseinandersetzen.
Um auf Twitter Tweets erstellen zu können, bediene ich mich an der offiziellen Twitter API. Dazu benötige ich einen API-Account, um darauf zugreifen zu können, sowie ein weiteres Python-Paket, um alles komfortabel tweeten zu können. Das von mir hier verwendete Paket heißt Tweepy
. Alternativ bietet Twitter aber auch die Möglichkeit an, alles RESTful abzuwickeln.
pip install tweepy
Um Zugriff auf die Twitter API zu bekommen, musst du dich im Entwicklerportal von Twitter anmelden und einen Zugang beantragen. Twitter wird dir Fragen zu deinem Projekt stellen und wie du die API nutzen möchtest. Hast du diese beantwortet, wird dir eine E-Mail zur Verifizierung geschickt. Bis die E-Mail bei dir ankommt, kann jedoch etwas Zeit vergehen. Stress dich nicht, sollte diese nicht sofort im Postfach liegen.
Ist die Bestätigung von dir bestätigt worden, hast du Zugriff zum Developer-Portal. Hier musst du ein neues Projekt angelegen:
Ist das Projekt erstellt, müssen noch die Berechtigungen der API-Keys von 'nur lesen' auf 'lesen + tweeten' geändert werden, andernfalls kann Python nicht tweeten und das Script wird mit einer Fehlermeldung beendet.
Sind die Berechtigungen gesetzt, müssen die API-Zugangsdaten neu generiert und anschließend in das Projekt integriert werden. Da es sich hierbei um höchst sensible Daten handelt, ist die Verwendung einer DotEnv-Datei ratsam. Was DotEnv-Files sind und wie man diese verwendet, habe ich dir hier erklärt.
Mit dem folgenden Code authentifiziere ich mich nun mit meinen API-Nutzerdaten bei Twitter:
# authentication of consumer key and secret
auth = tweepy.OAuthHandler("twitter_consumer_key", "twitter_consumer_secret")
# authentication of access token and secret
auth.set_access_token("twitter_access_token", "twitter_access_token_secret")
api = tweepy.API(auth)
Tweepy hat die Möglichkeit normale Text-Tweets zu erstellen, aber auch Tweets mit Anhängen, z.B. einem Bild. Ein normaler Tweet reicht mir aber völlig aus und kann so erstellt werden:
api.update_status(status="Neuer Blogartikel:\n"+article_link)
print("Neuer Blogartikel:\n"+article_link)
Nachdem das Script auf dem Server liegt, muss es nur noch automatisch in einem zeitlichen Intervall ausgeführt werden. Hierfür werden Cronjobs unter Linux verwendet. Wenn du eine ausführlichere Anleitung zu Cronjobs benötigst, kannst du diese hier nachlesen.
Um den Cronjob zu registrieren, gib in deine Linux Kommandozeile crontab -e
ein und füge folgende Zeile am Ende der Datei an. Du musst nur noch den Pfad zu deinem Script anpassen. Bitte gib den vollständigen Pfad an z.B. /home/userxyz/github/rssfeedparser/main.py
0 * * * * python3 /path/to/main.py
Fertig, Dein Python Script wird nun stündlich den RSS Feed abfragen und einen Tweet erstellen, sollte besagter Artikel zeitlich nach der letzten Ausführung veröffentlicht worden sein.
Das Projekt ist nur ein kleines nice-to-have Script, welches ich schnell für meine private Umgebung geschrieben habe. Es wurde mit Absicht einfach gehalten, damit alle Schritte für Hobby-ITler und Anfänger nachvollziehbar sind. Themen wie automatisierte Tests (z.B. Unit-Tests), CI, CD, CT, Git, Python Debugger,... sind Themen für andere Artikel 😉
Hinterlasse mir gerne einen Kommentar zum Artikel und wie er dir weitergeholfen hat beziehungsweise, was dir helfen würde das Thema besser zu verstehen. Oder hast du einen Fehler entdeckt, den ich korrigieren sollte? Schreibe mir auch dazu gerne ein Feedback!
Es sind noch keine Kommentare vorhanden? Sei der/die Erste und verfasse einen Kommentar zum Artikel "Den RSS-Feed mittels Python automatisiert tweeten"!