1. 项目概述:当GPT再次接管你的午间简报

如果你和我一样,每天被海量的信息淹没,却又不想错过任何有价值的行业动态、技术更新或个人兴趣内容,那么“自动化信息筛选与整合”这个需求,你一定感同身受。今天要聊的这个项目,就是一个典型的“懒人”解决方案,或者说,是一个“效率至上”主义者的实践。它的核心很简单:利用GPT这类大语言模型的强大理解和生成能力,自动抓取、筛选、总结你关心的信息源,并在每天中午(或其他你设定的时间)准时生成一份结构清晰、重点突出的简报,推送到你的邮箱或聊天工具里。

这听起来像是RSS阅读器的升级版,但内核完全不同。传统的RSS或新闻聚合器只是信息的搬运工,把标题和摘要堆砌给你。而这个项目的灵魂在于“理解”和“提炼”。GPT模型能读懂文章,判断其与你的兴趣图谱是否匹配,并用自己的话总结出核心观点、技术要点甚至潜在影响,最终打包成一份高度个性化的“知识便当”。对于开发者、研究者、市场分析师或是任何需要持续追踪特定领域动态的人来说,这相当于雇佣了一位不知疲倦、且理解力超群的私人助理。

我最初动这个念头,是因为发现自己订阅的十几个技术博客、新闻邮件和社区更新,根本看不过来。要么是没时间点开,要么是点开了发现内容水份太大。手动筛选?效率太低。于是,一个自动化、智能化的“午间简报”(Noonification)系统就成了刚需。这个项目标题里的“Again”,恰恰点明了这个领域的现状:技术的迭代让过去难以实现的个性化信息处理,如今变得触手可及,而我们正处在利用这些新工具,重新定义个人信息流管理方式的又一个浪潮起点上。

2. 核心思路与架构设计

2.1 需求拆解:我们到底需要什么?

在动手写第一行代码之前,我们必须把模糊的需求具体化。一个高效的自动化简报系统,远不止是“定时运行一个脚本调用GPT API”那么简单。我们需要拆解出几个核心模块:

  1. 信息源管理 :系统从哪里获取信息?可能是特定网站的RSS源、API接口(如GitHub Trending、Hacker News API)、甚至是通过爬虫抓取的特定页面列表。这些源需要被分类、管理,并且要能灵活地增删改。
  2. 内容获取与预处理 :定时或触发式地从这些源拉取原始内容。这里会遇到各种问题:反爬策略、页面结构差异、信息噪音(广告、导航栏)等。预处理环节需要清洗HTML,提取出干净的标题和正文文本。
  3. 智能筛选与总结 :这是GPT的核心舞台。系统需要将预处理后的文本“喂”给模型,并给出明确的指令:“请判断这篇文章是否涉及‘后端架构’或‘机器学习模型优化’?如果是,请用三段话总结其核心创新点、技术实现路径和潜在应用场景。”这涉及到提示词工程的设计。
  4. 简报编排与格式化 :经过筛选和总结的内容,需要被组织成一份美观、易读的简报。这包括生成一个总标题、对内容进行分类(如“技术前沿”、“行业动态”、“工具推荐”)、为每条摘要配上原标题和原文链接。
  5. 交付与触发 :如何将这份简报送到用户眼前?最常见的渠道是电子邮件,也可以是Slack、Discord、Telegram等聊天工具的消息,甚至是生成一个静态网页链接。系统还需要一个可靠的调度器(如cron job, Celery)来触发每日的生成任务。

2.2 技术选型:为什么是这套组合拳?

基于以上需求,一个经过实践检验的技术栈逐渐清晰。这里我分享一下我的选型逻辑,你可以根据自己的技术偏好和资源进行调整。

  • 后端语言与框架:Python + FastAPI Python几乎是处理此类任务的不二之选,其丰富的生态库(如 requests , beautifulsoup4 , feedparser )能轻松应对内容抓取和解析。选择FastAPI而非Django或Flask,主要是考虑到这是一个以异步任务为核心的后端服务。FastAPI原生支持 async/await ,在处理大量网络I/O(如并发抓取多个源、调用GPT API)时性能优势明显,而且它的自动生成API文档特性对后期维护也很友好。

  • 任务调度与队列:Celery + Redis 简报生成是一个典型的后台定时任务。Celery是Python领域最成熟的任务队列框架,它能将耗时的抓取、处理、AI调用任务放入后台异步执行,避免阻塞主服务。Redis则作为Celery的Broker(消息代理)和Result Backend(结果存储),轻量且高效。你可以设置一个Celery Beat定时任务,每天中午11点触发整个简报生成流程。

  • 大语言模型接口:OpenAI API (GPT-4/GPT-3.5-Turbo) 或 开源替代方案 OpenAI的API稳定、效果出色,是快速上手的首选。特别是GPT-4,在理解长文本和遵循复杂指令方面表现更佳。但成本和使用限制是需要考虑的因素。因此,架构上应将“模型调用层”抽象出来。这样,未来可以无缝切换到成本更低的GPT-3.5-Turbo,或者本地部署的开源模型(如通过Ollama部署Llama 3、Qwen等)。关键是要保证提示词模板的兼容性。

  • 数据存储:SQLite (开发) / PostgreSQL (生产) 我们需要持久化存储用户配置(订阅了哪些源)、任务历史、以及每次抓取的原始文章和生成的摘要,以便排查问题和进行简单的数据分析。开发初期用SQLite足够轻便。上线后建议换用PostgreSQL,其稳定性和对JSON字段的良好支持(用于存储灵活的摘要结果)更适合生产环境。

  • 前端与交付(可选):简单的HTML模板 + SMTP/Slack Webhook 对于MVP(最小可行产品),可以不需要独立前端。简报的格式化可以在后端用Jinja2模板引擎完成,生成HTML字符串。交付则通过 smtplib 库发送HTML邮件,或使用 requests 调用Slack的Incoming Webhook将简报发送到指定频道。如果需要更复杂的用户界面来管理订阅源,可以考虑用Vue/React写一个轻量级管理后台。

注意:成本与效率的权衡 。直接调用GPT API处理每篇文章,如果源很多,每日成本会快速累积。一个优化策略是“两级筛选”:先用简单的关键词或规则(如标题包含特定术语)进行粗筛,只将可能相关的文章送入GPT进行精读和总结。另一个策略是使用更便宜的模型(如GPT-3.5-Turbo)进行初筛,只有高置信度的文章才用GPT-4深度总结。

3. 核心模块实现细节

3.1 信息源抓取器的稳健性设计

抓取器是系统的数据入口,其稳健性直接决定简报内容的质量和系统的可靠性。你不能让一个反爬策略或网站改版就导致整个系统瘫痪。

实现要点:

  1. 多源适配器模式 :为RSS、API、通用网页爬虫分别编写适配器类。它们继承自同一个基类,实现 fetch() parse() 方法。这样,新增一种信息源类型,只需添加一个新的适配器。

    class FetcherBase:
        def fetch(self, url):
            raise NotImplementedError
        def parse(self, raw_content):
            raise NotImplementedError
    
    class RSSFetcher(FetcherBase):
        def fetch(self, feed_url):
            import feedparser
            return feedparser.parse(feed_url)
        def parse(self, feed_data):
            articles = []
            for entry in feed_data.entries:
                articles.append({
                    'title': entry.title,
                    'link': entry.link,
                    'published': entry.get('published', ''),
                    'summary': entry.get('summary', ''),
                    'content': entry.get('content', [{}])[0].get('value', '')
                })
            return articles
    
  2. 请求管理与容错 :使用 requests.Session 保持连接,并设置合理的超时(如连接超时10秒,读取超时30秒)。必须实现重试机制,对于临时性网络错误(如429状态码-请求过多,500-服务器错误)进行指数退避重试。

    from tenacity import retry, stop_after_attempt, wait_exponential
    @retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=4, max=10))
    def fetch_with_retry(url, session):
        response = session.get(url, timeout=(10, 30))
        response.raise_for_status() # 触发重试的异常之一
        return response.text
    
  3. 内容提取的降级策略 :用 BeautifulSoup lxml 解析HTML时,优先寻找特定的CSS选择器或标签(如 article , .post-content )来获取正文。如果找不到,则降级为提取整个 body 中文本密度最高的区域(可以使用 readability 算法的简化版)。始终要保存原始HTML的快照,以便在解析出错时能手动复查和调整规则。

3.2 与GPT协作的提示词工程

这是项目的“智能”核心。如何与GPT对话,决定了摘要的质量和相关性。

一个高效的提示词结构通常包括:

  1. 角色设定 :让GPT进入状态。“你是一位资深的[领域,如科技]编辑,擅长从长篇技术文章中提炼核心价值。”
  2. 清晰的任务指令 :“请阅读以下文章,并完成以下任务:”
  3. 具体步骤
    • “1. 判断这篇文章的主题是否属于以下我关心的范畴:[列出关键词,如‘分布式系统’, ‘Python性能优化’, ‘AI伦理’]。如果不相关,直接回复‘不相关’。”
    • “2. 如果相关,请用不超过150字总结文章的核心论点或技术要点。”
    • “3. 提取文章中提到的最有启发性的一个观点或一个具体的技术实现细节。”
    • “4. 指出这篇文章可能对哪种类型的读者最有价值(如:后端工程师、数据科学家初学者)。”
  4. 输出格式约束 :“请严格按照以下JSON格式回复: {“relevant”: true/false, “summary”: “...”, “insight”: “...”, “audience”: “...”}
  5. 提供原文 :“文章标题:{title}\n文章正文:{content_preview}” (注意:需截断过长的正文以节省token,例如只取前3000字符)。

实操心得:

  • 温度参数 :设置 temperature=0.2 左右,让输出更稳定、可预测,适合这种事实性总结任务。
  • 处理长文 :如果文章极长,可以尝试“分而治之”。先让GPT总结各部分,再总结这些总结。但更经济的做法是,在预处理时就用算法提取关键段落(如TF-IDF找高频句,或文本分割模型),只将关键部分送入GPT。
  • 缓存结果 :对同一篇文章的URL进行哈希,将GPT的总结结果缓存起来(缓存时间24小时)。避免因重复抓取或重试导致不必要的API调用,这是控制成本的关键。

3.3 任务编排与异步处理

使用Celery来编排整个工作流,确保系统的可扩展性和可靠性。

一个典型的Celery任务流可能如下:

  1. task_fetch_all_sources :这是一个由Celery Beat触发的周期性任务。它从数据库读取所有活跃的信息源配置,然后为 每个源 动态发起一个子任务。这里使用Celery的 group chord 来实现并发。

    @app.task
    def task_fetch_all_sources():
        sources = Source.get_active_sources()
        # 为每个源创建一个抓取子任务
        job = group(task_fetch_one_source.s(source.id) for source in sources)
        result = job.apply_async()
        # 可以在这里链接下一个处理阶段
        return result.id
    
  2. task_fetch_one_source :执行具体的抓取和解析,将获取到的文章列表存入数据库的“原始文章”表,状态标记为 pending 。然后为 每篇新文章 发起一个处理任务。

    @app.task(bind=True, max_retries=3)
    def task_fetch_one_source(self, source_id):
        source = Source.get_by_id(source_id)
        fetcher = get_fetcher_for_source(source.type)
        try:
            articles = fetcher.fetch_and_parse(source.url)
            for article in articles:
                # 去重检查后存入数据库
                if not Article.exists(article['link']):
                    article_obj = Article.create(raw_data=article, status='pending')
                    # 发起AI处理任务
                    task_process_one_article.delay(article_obj.id)
        except Exception as exc:
            self.retry(exc=exc, countdown=60) # 失败后重试
    
  3. task_process_one_article :这个任务承载核心AI处理逻辑。它获取文章内容,构造提示词,调用GPT API,解析返回的JSON,将最终摘要存入数据库,状态更新为 processed irrelevant

  4. task_generate_daily_digest :在每天所有文章处理完毕后(可以通过监听上述任务完成的事件,或简单地在固定时间点执行),这个任务被触发。它查询当天所有状态为 processed 的摘要,按主题分类,用Jinja2模板渲染成HTML简报,然后调用邮件发送任务。

踩坑提醒 :Celery任务一定要做好 幂等性 设计。特别是抓取任务,要确保网络超时重试不会导致同一篇文章被重复创建和处理。可以在数据库层面为文章URL设置唯一索引,或者在任务逻辑中先检查是否存在。

4. 部署与运维实战

4.1 环境配置与依赖管理

使用 Docker docker-compose 进行容器化部署,是保证环境一致性的最佳实践。

一个典型的 docker-compose.yml 骨架:

version: '3.8'
services:
  redis:
    image: redis:7-alpine
    container_name: noonification-broker
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data

  db:
    image: postgres:15-alpine
    container_name: noonification-db
    environment:
      POSTGRES_USER: noon_user
      POSTGRES_PASSWORD: ${DB_PASSWORD} # 从.env文件读取
      POSTGRES_DB: noonification
    volumes:
      - postgres_data:/var/lib/postgresql/data
    ports:
      - "5432:5432"

  backend:
    build: ./backend
    container_name: noonification-api
    depends_on:
      - redis
      - db
    environment:
      DATABASE_URL: postgresql://noon_user:${DB_PASSWORD}@db/noonification
      REDIS_URL: redis://redis:6379/0
      OPENAI_API_KEY: ${OPENAI_API_KEY}
    volumes:
      - ./backend:/app # 开发时挂载代码
    ports:
      - "8000:8000"
    command: uvicorn main:app --host 0.0.0.0 --port 8000 --reload

  celery_worker:
    build: ./backend
    container_name: noonification-worker
    depends_on:
      - redis
      - db
      - backend
    environment: # 共享相同的环境变量
      DATABASE_URL: postgresql://noon_user:${DB_PASSWORD}@db/noonification
      REDIS_URL: redis://redis:6379/0
      OPENAI_API_KEY: ${OPENAI_API_KEY}
    volumes:
      - ./backend:/app
    command: celery -A app.celery_app worker --loglevel=info --concurrency=4

  celery_beat:
    build: ./backend
    container_name: noonification-beat
    depends_on:
      - redis
      - db
    environment:
      DATABASE_URL: postgresql://noon_user:${DB_PASSWORD}@db/noonification
      REDIS_URL: redis://redis:6379/0
    volumes:
      - ./backend:/app
    command: celery -A app.celery_app beat --loglevel=info

关键点:

  • 敏感信息 :所有密码、API密钥必须通过环境变量( .env 文件)注入,绝不可硬编码在代码或Compose文件中。
  • 数据持久化 :为Redis和PostgreSQL定义命名卷( volumes ),确保容器重启后数据不丢失。
  • 网络 :服务间通过Docker Compose创建的网络,使用服务名(如 db , redis )进行通信,无需暴露所有端口到宿主机。

4.2 监控、日志与错误处理

一个无人值守的系统,必须有完善的眼睛和耳朵。

  1. 结构化日志 :使用 structlog 或配置Python标准 logging 模块,输出JSON格式的日志。记录每个关键步骤:任务开始/结束、API调用耗时与状态、遇到的异常等。这便于后续用ELK(Elasticsearch, Logstash, Kibana)或Loki+Grafana进行日志聚合与分析。

    import logging
    logger = logging.getLogger(__name__)
    # 在任务中记录
    logger.info(f“开始处理文章 {article_id}”, extra={“article_id”: article_id, “task”: “process_article”})
    try:
        result = call_gpt_api(prompt)
        logger.info(“GPT API调用成功”, extra={“usage”: result.usage, “duration”: duration})
    except openai.error.RateLimitError as e:
        logger.error(“触发OpenAI速率限制”, exc_info=True, extra={“retry_after”: e.retry_after})
        raise self.retry(exc=e, countdown=e.retry_after)
    
  2. 健康检查与监控 :为FastAPI后端添加 /health 端点,检查数据库和Redis连接。使用Prometheus客户端库暴露应用指标(如任务队列长度、API调用次数、各阶段耗时)。通过Grafana制作仪表盘,监控系统健康度。

  3. 错误预警 :将Celery任务的失败重试次数设置为3-5次。如果任务最终失败(重试耗尽),应将其移入一个“死信队列”,并触发警报(如发送邮件到管理员,或通过Webhook推送到监控群)。可以使用Flower来监控Celery集群的状态。

4.3 成本控制与优化策略

GPT API的成本是项目运行的主要开销,必须精打细算。

  • 用量监控 :在每次调用GPT API后,记录其返回的 usage 字段(prompt_tokens, completion_tokens)。在数据库中建立每日/每周用量统计表,密切监控趋势。
  • 模型选型 :对于简单的相关性筛选,完全可以使用更便宜的 gpt-3.5-turbo 。只有在需要深度理解、复杂推理或生成高质量总结时,才调用 gpt-4 。可以在提示词中让模型自己判断是否需要“升级”处理。
  • 缓存策略 :如前所述,对文章摘要进行缓存。甚至可以缓存“不相关”的判断结果,避免重复判断。
  • 摘要长度控制 :在提示词中明确限制总结的字数(如“用80字以内总结”),并设置API的 max_tokens 参数,防止模型生成过于冗长的内容,浪费token。
  • 备用方案 :考虑集成开源模型。在本地或云端GPU服务器上部署一个中等参数量的开源模型(如7B-13B参数的模型),用于处理对时效性要求不高或敏感性较低的内容摘要。将成本高的商业API作为“精品”通道。

5. 进阶思考与扩展方向

当基础系统稳定运行后,你可以从“能用”向“好用”、“聪明”进化。

5.1 个性化推荐与兴趣学习

最初的系统依赖用户手动维护的兴趣关键词列表。更智能的方式是让系统学习用户的偏好。

  1. 隐式反馈 :在生成的简报中,为每条摘要添加“感兴趣”或“不感兴趣”的按钮(在邮件中可以是链接到后端API的URL)。用户点击后,系统记录这条反馈。
  2. 兴趣模型 :将用户标记为“感兴趣”的文章的摘要、标题甚至原始内容向量化(使用OpenAI的Embeddings API或开源的sentence-transformers模型)。定期(如每周)计算这些向量的中心点或聚类,形成用户的“兴趣向量”。
  3. 个性化筛选 :在文章筛选阶段,除了基于规则的关键词匹配,额外计算新文章的向量与用户“兴趣向量”的余弦相似度。只有相似度超过阈值或综合评分较高的文章,才会被送入后续流程。这样,系统能逐渐发现你潜在的新兴趣点。

5.2 多模态与信息富化

简报不一定只能是文字。

  • 图文并茂 :在抓取阶段,可以尝试提取文章中的头图或关键图表。在生成简报时,将其嵌入到HTML邮件中,提升可读性。
  • 音频简报 :利用TTS(文本转语音)API,将每日简报的文本内容转换为一段5分钟的音频文件,作为播客推送到用户的音乐APP或专用播放器里,适合通勤时间收听。
  • 知识图谱关联 :对摘要中出现的实体(公司名、技术名、人名)进行命名实体识别,并尝试链接到已有的知识库(如维基百科)。在简报中,可以为这些实体添加简单的悬浮注释或链接,帮助用户建立更立体的知识网络。

5.3 从个人工具到可分享服务

如果你觉得这个工具对他人也有价值,可以考虑将其产品化。

  1. 多用户支持 :改造数据库架构,引入用户模型。每个用户有自己的订阅源集合、兴趣模型和简报发送设置。
  2. Web管理界面 :开发一个前端,让用户可以方便地添加/删除信息源(甚至通过搜索或推荐来发现源)、调整兴趣标签、预览和调整简报格式、查看历史简报。
  3. 模板市场 :允许用户创建和分享自己的简报模板(如“极客风”、“商务简报风”、“简约列表风”),甚至分享自己精心配置的“源订阅包”(如“AI研究前沿精选包”、“东南亚科技创投包”)。
  4. 协作功能 :允许团队共享订阅源和生成的简报,并添加批注讨论,让信息消化从个人行为变为团队协作。

构建这样一个系统,最大的收获不仅仅是得到了一份定制的每日简报,更是在过程中深入理解了从数据获取、处理到智能应用的全链路。你会对网络请求的复杂性、任务队列的可靠性设计、AI能力的边界与成本,以及如何构建一个健壮的自动化服务有第一手的体会。它开始是一个解决个人痛点的脚本,但完全可以成长为一个充满可能性的平台。最重要的是,它让你从信息的被动接收者,转变为信息的主动管理者。

Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐