News per Mail
Dafür gibt es bestimmt schon andere Lösungen, aber ich wollte auch einfach ein bisschen rumprobieren.
Ich verwende hierfür im Prinzip folgende Dinge:
- einen Rechner 🤓 (konkret einen Raspberry Pi, der immer läuft)
- Puppeteer
- nodemailer
- Ein Postfach, das ich noch bei Strato habe (zum Versenden von Mails per SMTP)
- Cronjobs
Der Ablauf sieht im Prinzip so aus:
- zu einer bestimmten Zeit am Tag wird ein JS-Programm gestartet (über einen Cronjob)
- im JS-Programm gehe ich mit Puppeteer auf die Newsseite meiner Wahl, hole mir über QuerySelector usw. die Headlines, Teaser usw. die ich brauche und mache mir ein Array aus Objekten daraus. Dieses hole ich mir wieder in mein JS-Programm zurück.
- Dann baue ich aus den Inhalten die Mail zusammen und schicke diese per nodemailer an meine Mail-Adresse.
Diese Mail wird 4-mal am Tag verschickt. Ich kann dann kurz in meine Mails schauen, checken, ob die Welt bald untergeht und welchen neuen Hype es in der Tech-Welt gibt bzw. wann ich meinen Job an ein LLM verliere und dann (hoffentlich) produktiv weiterarbeiten, ohne vom Browser in das nächste Rabbithole gezogen zu werden.
Puppeteer #
Puppeteer ist im Prinzip ein Browser, den man ohne Fenster betreiben kann (sog. headlessly) und per Programmiersprache steuern kann (API).
Dadurch ergeben sich viele coole Use-Cases. Man kann sich im Prinzip alle möglichen Dinge, die man im Browser macht, bspw. automatisieren.
Ich habe ihn hier verwendet, um automatisch News-Webseiten zu besuchen und dort die aktuellen "Schlagzeilen" inkl. Teaser rauszuziehen, diese in eine E-Mail zu verpacken und mir dann selbst zuzuschicken.
Ich zeige das mal am Beispiel meiner Webseite. Damit das funktioniert, muss man NodeJS und NPM installiert haben.
Dann muss man Puppeteer im Projekt installieren, also npm install puppeteer
.
Für alle, die noch nie etwas mit NodeJS gemacht haben:
- Neuen Ordner erstellen
- Terminal darin öffnen
- Dann im Terminal
npm init -y
eingeben (initialisiert sozusagen ein NodeJS-Projekt) - Dann Puppeteer installieren über
npm install puppeteer
- JS-Datei erstellen, bspw.
index.js
. - Etwas Einfaches reinschreiben, bspw.
console.log("Hello World");
- Dann im Terminal
node index.js
und man sollteHello World
im Terminal sehen
Um auf meiner Webseite die Schlagzeilen und Teaser zu holen, könnte man folgenden Code verwenden:
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
const page = await browser.newPage();
// Sagt Chrome, dass es auf diese URL gehen soll
await page.goto('https://maxkistner.com');
// stellt die Fenstergröße ein (interessant für bestimmte Layouts)
await page.setViewport({ width: 1080, height: 1024 });
// Die Funktion, die `page.evaluate` erhält, ist JS, das im Kontext
// der Seite ausgeführt wird, also in unserem Headless-Browser.
const teasers = await page.evaluate(() => {
// holt sich alle Posts
const teasers = document.querySelectorAll(".postlist-item");
const infos = (Array.from(teasers)).map((teaser) => {
// holt sich alle Titel
const title = teaser.querySelector(".postlist-link").innerText;
// holt sich alle Teaser
const description = teaser.querySelector(".teaser").innerText;
// macht ein Objekt daraus
return { title, description };
});
// gibt das Objekt zurück
return infos;
});
await browser.close();
console.log(teasers)
Wenn man das dann in der Konsole ausgibt, sieht das so aus (... habe ich eingefügt, damit die Lauflänge nicht zu groß wird):
[
{
title: 'News per Mail',
description: 'Während der Corona-Pandemie habe ...'
},
{
title: 'Willkommen!',
description: 'Ich habe mal gelesen, dass Wissen...'
}
]
Damit hat man eigentlich alles, um daraus eine E-Mail zu bauen.
E-Mail verschicken mit Nodemailer #
Auch nodemailer ist ein npm-Package. Die Installation läuft damit genauso wie bei Puppeteer
(npm i nodemailer
)
und auch die vorbereitenden Schritte sind gleich wie oben.
Eine einfache Mail kann man dann so versenden:
import nodemailer from 'nodemailer';
function sendMail(subject, text) {
const transporter = nodemailer.createTransport(
{
host: "<host>", // aus der Doku des Anbieters
port: 465, // aus der Doku des Anbieters
secure: true,
auth: {
user: '<user', // aus der Doku des Anbieters
pass: '<pw>' // aus der Doku des Anbieters
},
logger: true,
transactionLog: false,
allowInternalNetworkInterfaces: false
},
{
from: 'Mir <mail@example.com>'
}
);
const message = {
to: 'Dir <mail@example.com>',
subject,
html: `<p>${text}</p>`
};
transporter.sendMail(message, (error, info) => {
// Falls es einen Fehler gab, hat `error` einen Wert und
// wir fallen in dieses if
if (error) {
console.log('Error occurred');
console.log(error.message);
return process.exit(1);
}
console.log('Nachricht versendet.');
console.log(nodemailer.getTestMessageUrl(info));
});
}
sendMail("Test Mail", "Das ist eine Testmail.");
Und dann in der Konsole wieder node index.js
und man sollte kurz darauf eine Mail erhalten.
Wenn wir jetzt das Ergebnis vom Skript mit Puppeteer noch etwas zu HTML verwurschteln und das dann an die Funktion oben übergeben, können wir uns schon eine Mail mit aktuellen News zusenden.
Jetzt fehlt noch das automatische Versenden zu einem gewissen Zeitpunkt.
Cronjobs zum automatischen Versenden von Mails #
Cronjobs können auf Linux eingesetzt werden, um wiederkehrende Aufgaben zu erledigen.
Die Erstellung eines Cronjobs ist relativ einfach:
- Terminal öffnen
crontab -e
eingeben- Cronjob erstellen
- Datei speichern und schließen
- Warten bis Zeitfenster des Cronjobs aktiv ist
"Cronjob erstellen" ist natürlich trotzdem ein bisschen schwieriger, aber auch nicht so schlimm. Um die Zeit einzustellen, gibt es hier einfach eine bestimmte Syntax. Am Ende muss man dann noch das Skript eintragen, das ausgeführt werden soll. Bei mir soll bspw. um 7:00 Uhr, 12:15 Uhr, 17:00 Uhr und 20:15 Uhr das Skript laufen:
00 07 * * * . ~/cron-jobs/cronjob.env.sh; cd ~/_dev/news-scrape/out-tsc/src; $(which node) index.js >> ~/cron-jobs/log.log 2>&1
15 12 * * * . ~/cron-jobs/cronjob.env.sh; cd ~/_dev/news-scrape/out-tsc/src; $(which node) index.js >> ~/cron-jobs/log.log 2>&1
00 17 * * * . ~/cron-jobs/cronjob.env.sh; cd ~/_dev/news-scrape/out-tsc/src; $(which node) index.js >> ~/cron-jobs/log.log 2>&1
15 20 * * * . ~/cron-jobs/cronjob.env.sh; cd ~/_dev/news-scrape/out-tsc/src; $(which node) index.js >> ~/cron-jobs/log.log 2>&1
Was bedeutet jetzt das hier genau? ⬇️
. ~/cron-jobs/cronjob.env.sh; cd ~/_dev/news-scrape/out-tsc/src; $(which node) index.js >> ~/cron-jobs/log.log 2>&1
Dieser erste Part hier . ~/cron-jobs/cronjob.env.sh;
führt die Datei cronjob.env.sh
im Ordner
~/cron-jobs/cronjob.env.sh
aus (~/
steht für den Nutzerordner des aktuellen Users).
Der Inhalt dieser Datei sieht so aus:
#!/bin/bash
# Quelle: https://gist.github.com/simov/cdbebe2d65644279db1323042fcf7624
# NVM_DIR finden man in der .bashrc des Nutzers - Nutzername (bei mir pi) entsprechend anpassen!
export NVM_DIR="/home/pi/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
Diese Datei ist nicht zwingend notwendig. Ich verwende nvm zum Verwalten meiner NodeJS-Version.
Das bedeutet, dass sich meine NodeJS-Installation nicht immer an einem fixen Ort befindet, sondern je
nach Version, die gerade gewählt ist, an einer anderen Stelle liegt. Den Pfad zur aktuellen NodeJS-Version
kriegt man im Terminal über diesen Aufruf hier $(which node)
, ABER das klappt nicht ohne Weiteres, wenn
der Cronjob läuft. Durch Ausführen der Datei oben liefert $(which node)
aber den korrekten Pfad
zurück und sollte das eigentlich auch tun, egal welche NodeJS-Version ausgewählt ist.
cd ~/_dev/news-scrape/out-tsc/src
wechselt in den Ordner, indem sich das JS befindet und über $(which node) index.js
klebt man sozusagen den aktuellen NodeJS-Pfad mit dem Skript, das man
ausführen will, zusammen. So kann man das Skript über NodeJS laufen lassen.
Der letzte Teil des Skripts hier >> ~/cron-jobs/log.log 2>&1
leitet alle Ausgaben der Aufrufe des Skripts
in die Datei log.log
um. Das erleichtert die Fehlersuche, falls etwas nicht funktioniert.
Interessanterweise habe ich gerade in die Datei reingeschaut, weil manchmal Mails nicht zugestellt wurden
und siehe da: In irgendeiner Schlagzeile in der Mail stand scheinbar Text drin, der dem SPAM-Filter von
Strato nicht gefiel, weshalb dann die Mail nicht gesendet werden durfte. Wie ich das verhindern soll, weiß ich noch nicht.
Fertig #
Das ist eigentlich alles. Hiermit kann man, wie gesagt, coole Sachen machen.
Eine Sache, die ich auch noch in meinem Skript laufen lasse, ist das Abrufen des Wetterberichts. Der wird aber auf der Seite, auf der ich ihn mir hole, über ein cooles Widget dargestellt, weshalb das Rausziehen von Text nicht so zielführend wäre.
Puppeteer erlaubt es aber auch, Screenshots von Elementen zu machen. So könnte der Code hierfür aussehen:
const weatherWidget = await page.waitForSelector('#id-eines-widgets');
if (!weatherWidget) {
console.error("selector not found")
return;
};
await weatherWidget.screenshot({
path: __dirname + '/weather.png',
});
Dieses Bild schicke ich mir dann einfach als eigebetteten Anhang mit.
Ich hoffe das hier hilft irgendjemandem weiter. Falls sich jemand das anschaut und Fragen hat, Hilfe braucht oder Anregungen hat: einfach eine Mail schicken.
- Vorheriger: Willkommen!