一个新闻摘要给我发邮件我真正在乎的头条

2026年7月19日 learningpython

所以我厌倦了每天早上打开四个不同的网站只是看是否有什么真的新,现在一个脚本做那部分对我并在 7 点发送我一个摘要。大多数。有大约两周的时间它默默地什么都没有做我没有注意,那是它自己的故事。

它应该拉什么

一些源(Hacker News 的 API,NRK 的 RSS feed,一些其他的),获取,解析,然后针对我真正在乎的关键词列表过滤。其他一切被扔掉,这是大部分。那是要点的全部。

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 headlines

那个版本运行很好大约一周。然后一个早上,没有摘要。

两周我没注意任何错的

Cron 不邮件你当一个脚本失败除非你设置了 MAILTO,我没有,所以一个崩溃三行到一个 for 循环只默默地结束工作什么都没告诉你。我完全是意外发现的,提到对某人摘要很方便,然后意识到我实际上一阵子没有看到一个然后去检查。

crontab -l
0 7 * * * /home/bjorn/.venv/bin/python /home/bjorn/digest.py

那里什么都没有指向实际的问题,它只说工作存在。我必须手动运行它看任何东西。

python3 digest.py
Traceback (most recent call last):
  File "digest.py", line 12, in fetch_hn_headlines
    title = item["title"]
KeyError: 'title'

我实际上 google 了什么,和我找到了什么

"python requests item some ids missing fields hacker news api" 让我到一些关于 HN API 特别的 Stack Overflow 线程,和真正的答案实际上是枯燥的一旦我看到它:一个"死"或删除的故事仍然显示在 topstories 列表中,但它的 item 查找回来没有 title 字段,有时甚至不是一个真正的对象,只是 None。我的代码假设那列表中的每个 ID 指向一个正常的,完整的故事。它没有。在那个早上的五十个 ID 中的某处是一个死的,而 item["title"] 炸掉了整个循环,它杀死了整个脚本,这意味着零头条被获取对任何源那个早上,或任何早上之后,因为崩溃发生在什么都被发送前而 cron 只默默地吃掉每个单一天之后的失败。

在实际帮助的线程上的接受的答案不是特别关于 HN API,它是一个通用模式:从不让一个批量中的一个坏项目下来整个批量。包装逐个项目的工作,记录什么失败了,保持前进。

真正在一个坏项目后生存的版本

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

一个坏项目现在得到记录和跳过而不是把整个运行下来带它。我也添加了 MAILTO 到 crontab,所以一个未来的崩溃真的到达我的收件箱而不是消失到一个我必须记得检查的日志文件里。

重用电子邮件代码

电子邮件发送的一半基本上直接从相机项目里提升,只是没有图像附件。一旦你写过 smtplib 样板一次,你没有真的想再写它。你只复制函数和改变它发送什么。

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 -l
MAILTO=bjorn@example.com
0 7 * * * /home/bjorn/.venv/bin/python /home/bjorn/digest.py

获取头条和邮件摘要

NRK feed 有它自己的小惊喜

分开从 HN 崩溃,NRK RSS 解析噎住第一次一个只有视频的项目出现在 feed 中没有文章体文本,只是一个视频嵌入。我的解析器假设每个条目有一个值得读的 description 字段。那个是一个更小的修复,一个普通的 getattr(entry, "description", "") 而不是假设属性存在,但它是相同的课两次在一个脚本中:一个 API 或 feed 告诉你数据的形状是一个建议,不是保证,而边界情况最终显示向上无论你是否为他们计划。

过滤本身比获取更难

真正的搜刮,一旦它停止崩溃,是简单的部分。决定什么算作"值得我的关注"是我保持调整的部分。关键词匹配是笨的。它会标记一篇提到"norway"一旦传递关于我根本不在乎的东西的文章,它会错过什么真的相关因为它使用了一个同义词而不是我的恰好关键词。我没有对此的好修复。可能只是一个坏问题用字符串匹配解决,完全。

其中它坐在其他东西旁边

这个和相机脚本是两件我真的建立那在一个时间表上而不是由我输入一个命令被调用的东西运行无人值守。那是一个不同的编程类型比 CLI 待做应用或 PDF 工具,更接近"写它一次,信任它保持工作",这是一个肌肉我显然之前这一年没有建立,一个那显然需要它自己的失败处理纪律一个你手动运行的脚本不真的惩罚你跳过。

关键词列表静地长大

它开始为三个字。它现在是五个,每个添加来自相同模式:我会直接在一个网站上阅读什么相关,记起摘要没有标记它,去添加无论什么字应该有抓住它。"helgeland"在一个关于渡轮时间表的本地新闻故事后被添加我只从第二手发现。它是一个关键词列表那只在反应地改进,在它已经失败一次抓住什么东西后,这不是一个伟大的过程但显然是唯一一个我真的追踪完成。

什么我会不同地做

同时获取五十个 HN 项目而不是一个接一个,这也会使整个东西少一些痛苦调试因为一个单一缓慢或悬挂请求不会停止背后的一切。concurrent.futures.ThreadPoolExecutor 坐在那里直接在标准库中而我仍然没有解决到它,主要是因为东西现在工作和"现在工作"是一个惊人的强论证反对碰触它再一次。

我也会记录一个成功的运行,不只是一个失败的,所以安静的早上有真的零匹配头条和默默地崩溃的早上有零头条不同地看起来从外面。现在两个生成完全什么都不会在我的收件箱里,而我只可以从通过去和检查日志文件来分辨它们,这是相同的错误那引起两周差距在第一个地方,只是用一个更小的爆炸半径这一次。

不管怎样。它邮件我头条。不光彩。我用咖啡读它大多数早上,这真的是全部目标,在早上它真的运行。

0 条评论

登录 后即可评论。

登录

忘记密码?

还没有账号?