Så jeg ble lei av å åpne fire forskjellige sider hver morgen bare for å se om noe faktisk var nytt, så nå gjør et script den delen for meg og sender meg et sammendrag på e-post klokken 7. For det meste. Det var en periode på omtrent to uker der det stille ikke gjorde noe som helst, og jeg merket det ikke, som er sin egen historie.
hva den skal hente
En håndfull kilder (Hacker News' API, NRKs RSS-feed, et par andre), hentet, parset, så filtrert mot en liste med nøkkelord jeg faktisk bryr meg om. Alt annet blir kastet, som er det meste. Det er liksom hele poenget.
import requests
KEYWORDS = ["postgres", "sveltekit", "linux", "norway", "helgeland"]
def fetch_hn_headlines():
ids = requests.get("https://hacker-news.firebaseio.com/v0/topstories.json").json()[:50]
headlines = []
for story_id in ids:
item = requests.get(f"https://hacker-news.firebaseio.com/v0/item/{story_id}.json").json()
title = item["title"]
if any(k in title.lower() for k in KEYWORDS):
headlines.append((title, item["url"]))
return headlinesDen versjonen kjørte fint i omtrent en uke. Så, en morgen, intet sammendrag.
de to ukene jeg ikke merket at noe var galt
Cron sender deg ikke e-post når et script feiler med mindre du har satt MAILTO, noe jeg ikke hadde, så et krasj tre linjer inn i en for-løkke bare stille avslutter jobben og ingenting varsler deg. Jeg fant det ut helt tilfeldig, ved å nevne til noen at sammendraget var praktisk, og så innse at jeg faktisk ikke hadde sett et på en stund, og gikk for å sjekke.
crontab -l0 7 * * * /home/bjorn/.venv/bin/python /home/bjorn/digest.pyIngenting der peker på det faktiske problemet, det sier bare at jobben eksisterer. Jeg måtte kjøre den manuelt for å se noe som helst.
python3 digest.pyTraceback (most recent call last):
File "digest.py", line 12, in fetch_hn_headlines
title = item["title"]
KeyError: 'title'det jeg faktisk googlet, og det jeg fant
"python requests item some ids missing fields hacker news api" tok meg til et par Stack Overflow-tråder om spesifikt HN-API-et, og det faktiske svaret var nesten kjedelig så snart jeg så det: en "død" eller slettet historie dukker fortsatt opp i topstories-listen, men oppslaget av den kommer tilbake helt uten et title-felt, noen ganger ikke engang et ekte objekt, bare None. Koden min antok at hver ID i den listen peker på en normal, komplett historie. Det gjør den ikke. Et sted i de femti ID-ene den morgenen var det en død en, og item["title"] sprengte hele løkken, som drepte hele scriptet, som betydde at null overskrifter ble hentet for noen kilde den morgenen, eller noen morgen etterpå, siden krasjet skjedde før noe ble sendt, og cron bare stille spiste feilen hver eneste dag etter det.
Det aksepterte svaret på tråden som faktisk hjalp handlet ikke spesifikt om HN-API-et, det var et generelt mønster: la aldri ett dårlig element i en batch ta ned hele batchen. Pakk inn arbeidet per element, logg hva som feilet, fortsett.
versjonen som faktisk overlever et dårlig element
import logging
import requests
logging.basicConfig(filename="/home/bjorn/digest.log", level=logging.WARNING)
KEYWORDS = ["postgres", "sveltekit", "linux", "norway", "helgeland"]
def fetch_hn_headlines():
ids = requests.get("https://hacker-news.firebaseio.com/v0/topstories.json", timeout=10).json()[:50]
headlines = []
for story_id in ids:
try:
item = requests.get(
f"https://hacker-news.firebaseio.com/v0/item/{story_id}.json", timeout=10
).json()
title = item.get("title")
if not title:
continue
except (requests.RequestException, AttributeError) as e:
logging.warning("skipping story %s: %s", story_id, e)
continue
if any(k in title.lower() for k in KEYWORDS):
headlines.append((title, item.get("url", "")))
return headlinesÉtt dårlig element blir nå logget og hoppet over i stedet for å ta ned hele kjøringen med seg. Jeg la også til MAILTO i crontaben, slik at et fremtidig krasj faktisk når innboksen min i stedet for å forsvinne inn i en loggfil jeg må huske å sjekke.
gjenbruk av e-postkoden
E-post-sende-halvparten er i praksis rett løftet fra kameraprosjektet, bare uten bildevedlegget. Når du først har skrevet smtplib-boilerplate én gang, vil du egentlig ikke skrive den igjen. Du kopierer bare funksjonen og endrer hva den sender.
import smtplib
from email.message import EmailMessage
def send_digest(headlines):
body = "\n".join(f"- {title} ({url})" for title, url in headlines)
msg = EmailMessage()
msg["Subject"] = f"morning digest: {len(headlines)} headlines"
msg["From"] = "digest@example.com"
msg["To"] = "bjorn@example.com"
msg.set_content(body or "Nothing matched today.")
with smtplib.SMTP_SSL("smtp.example.com", 465) as server:
server.login("digest@example.com", "app-password-not-real")
server.send_message(msg)crontab -lMAILTO=bjorn@example.com
0 7 * * * /home/bjorn/.venv/bin/python /home/bjorn/digest.py
NRK-feeden hadde sin egen lille overraskelse
Helt separat fra HN-krasjet, kvalt NRKs RSS-parsing seg selv første gang et rent videoelement dukket opp i feeden uten noen artikkeltekst, bare en videoinnbygging. Parseren min antok at hver oppføring hadde et description-felt verdt å lese. Det var en mye mindre fiks, en enkel getattr(entry, "description", "") i stedet for å anta at attributtet finnes, men det er den samme leksjonen to ganger i ett script: en API eller en feed som forteller deg formen på dataene er et forslag, ikke en garanti, og kantsakene dukker opp til slutt uansett om du planla for dem eller ikke.
selve filtreringen var vanskeligere enn hentingen
Den faktiske skrapingen, når den først sluttet å krasje, var den enkle delen. Å avgjøre hva som teller som "verdt oppmerksomheten min" er delen jeg fortsatt finjusterer. Nøkkelordmatching er dumt. Den vil flagge en artikkel som nevner "norway" én gang i forbifarten om noe jeg overhodet ikke bryr meg om, og den vil gå glipp av noe genuint relevant fordi den brukte et synonym i stedet for mitt eksakte nøkkelord. Jeg har ingen god fiks for dette. Kan hende det bare er et dårlig problem å løse med strengmatching, punktum.
hvor det plasserer seg ved siden av alt annet
Denne og kamerascriptet er de to tingene jeg har bygget som faktisk kjører ubemannet på et skjema i stedet for å bli kalt av at jeg skriver en kommando. Det er en annen type programmering enn CLI-gjøremålsappen eller PDF-verktøyene, nærmere "skriv den én gang, stol på at den fortsetter å fungere," som er en muskel jeg tydeligvis ikke hadde bygget før i år, og en som tilsynelatende trenger sin egen feilhåndteringsdisiplin som et script du kjører manuelt egentlig ikke straffer deg for å hoppe over.
nøkkelordlisten har stille vokst
Den startet som tre ord. Den er fem nå, og hver tillegg kom fra det samme mønsteret: jeg leste noe relevant direkte på en av sidene, husket at sammendraget ikke hadde flagget det, og gikk og la til hvilket ord som enn burde ha fanget det. "helgeland" ble lagt til etter en lokal nyhetssak om fergeruten som jeg bare fikk vite om andrehånds. Det er en nøkkelordliste som bare forbedres reaktivt, etter at den allerede har feilet å fange noe én gang, noe som ikke er en flott prosess men tilsynelatende den eneste jeg faktisk følger opp.
hva jeg ville gjort annerledes
Hente de femti HN-elementene samtidig i stedet for ett om gangen, noe som også ville gjort hele greia mindre smertefull å feilsøke siden en enkelt treg eller hengende forespørsel ikke ville stanset alt bak den. concurrent.futures.ThreadPoolExecutor ligger rett der i standardbiblioteket og jeg har fortsatt ikke kommet meg til det, mest fordi tingen fungerer nå og "fungerer nå" er et overraskende sterkt argument mot å røre den igjen.
Jeg ville også logget en vellykket kjøring, ikke bare en feilet en, slik at en stille morgen med genuint null matchende overskrifter og en stille krasjet morgen med null overskrifter ikke ser identiske ut utenfra. Akkurat nå produserer begge nøyaktig ingenting i innboksen min, og jeg kan bare skille dem fra hverandre ved å gå og sjekke loggfilen, som er den samme feilen som forårsaket det topukers gapet i utgangspunktet, bare med en mindre skadeomkrets denne gangen.
Uansett. Den sender meg overskrifter på e-post. Ikke glamorøst. Jeg leser det med kaffe de fleste morgener, som genuint var hele målet, de morgenene den faktisk kjører.