基于GPT与RSS构建智能午间简报系统:从架构到部署
1. 项目概述:当GPT再次接管你的午间推送
如果你每天中午都会收到一封邮件,里面塞满了你订阅的十几个科技博客、新闻简报的最新文章摘要,你会不会觉得有点信息过载?更别提这些摘要的质量参差不齐,有的冗长,有的又过于简略。这个名为“The Noonification: GPT Automated.. Again”的项目,瞄准的就是这个痛点。它的核心想法很简单:利用GPT这类大语言模型,自动抓取、整合、重写你订阅的多个RSS源内容,生成一份风格统一、精炼易读的“午间简报”,并在每天中午准时推送给你。这不是一个全新的概念,早在GPT-3时代就有类似尝试,但2023年随着GPT-4 API的开放和成本的持续下降,让这种个人级的、高质量的自动化内容聚合与再生产变得前所未有的可行和实惠。
这个项目本质上是一个“信息减负”和“体验升级”工具。它不是为了创造新内容,而是扮演一个高度智能的“编辑”角色。对于信息饥渴但又时间有限的科技从业者、内容创作者或任何需要追踪多个领域动态的人来说,它能把原本需要花费半小时浏览、筛选、阅读的零散信息,压缩成一份5分钟就能消化完的优质简报。我自己作为重度RSS用户和AI应用的实践者,在搭建和迭代这个系统的过程中,深刻体会到从简单的RSS阅读器到智能摘要流水线的转变,不仅仅是工具的升级,更是信息消费习惯的重塑。接下来,我将详细拆解如何从零构建这样一个系统,分享其中涉及的技术选型、核心实现逻辑以及我踩过的那些坑。
2. 系统架构与核心组件选型
构建一个稳定、高效且成本可控的自动化简报系统,需要一个清晰的架构。整个流程可以分解为四个核心环节:内容获取、内容处理、简报生成与格式化、以及推送交付。每个环节的技术选型都直接影响到最终简报的稳定性、质量和运行成本。
2.1 数据源获取:超越简单的RSS抓取
最基础的起点是RSS源。我们可以使用Python的 feedparser 库来解析RSS或Atom源。但真实世界的挑战马上就会出现:一些网站RSS更新不及时,一些网站为了防止爬虫会设置反爬机制,还有一些网站的内容主体并不完全包含在RSS的 description 字段中。
因此,一个健壮的获取层需要具备以下能力:
- 多源与容错解析 :使用
feedparser的同时,必须加入异常捕获和重试机制。对于解析失败的源,记录日志并跳过,避免单个源的故障导致整个流程中断。 - 内容增强抓取 :对于RSS中只提供摘要或部分内容的源(常见于Medium、某些新闻网站),需要启用“完整内容抓取”。这里我选择了
newspaper3k库。它不仅能提取文章正文,还能自动识别作者、发布时间和关键词,非常强大。from newspaper import Article def fetch_full_article(url): try: article = Article(url) article.download() article.parse() return article.text except Exception as e: print(f"抓取文章失败 {url}: {e}") return None注意 :
newspaper3k的下载成功率并非100%,对某些动态加载(大量使用JavaScript)的网站支持不佳。此时可以考虑备用方案,如使用requests-html进行简单的渲染,但这会显著增加复杂性和运行时间。 - 去重与更新检测 :必须避免同一天内重复处理同一篇文章。一个简单的方案是维护一个已处理文章的唯一标识符(如
文章URL或标题+发布时间的哈希值)缓存,缓存有效期设为24小时。每次抓取新内容时,先与缓存比对,过滤掉已处理过的条目。
2.2 处理与摘要生成引擎:GPT API的精细化调用
这是系统的核心,也是成本与质量平衡的关键。直接使用OpenAI的ChatCompletion API是最直接的方案。但如何调用,大有学问。
模型选择 :GPT-3.5-turbo和GPT-4是主要候选。GPT-4在理解长文、遵循复杂指令和生成质量上显著优于3.5,但成本也高出10-20倍。我的经验是: 对于一般的科技博客、新闻摘要,GPT-3.5-turbo-0125(或后续版本)完全够用,且性价比极高 。只有在需要对非常技术性的论文、复杂报告进行总结时,才考虑使用GPT-4。
Prompt工程 :这是决定摘要质量的生命线。一个糟糕的Prompt会产生冗余、跑题或格式混乱的摘要。经过大量测试,我总结出一个高效的Prompt结构:
你是一个专业的科技内容编辑。请根据以下文章内容,生成一段简洁的摘要。
要求:
1. 摘要长度控制在80-120字之间。
2. 提炼核心论点或新闻事实,省略具体数据、例子等细节。
3. 使用中文输出,语言流畅、客观。
4. 如果原文是教程或指南,请说明其解决的主要问题和关键步骤。
5. 如果原文是观点或评论,请提炼作者的核心立场和主要论据。
文章标题:[ARTICLE_TITLE]
文章内容:[ARTICLE_TEXT]
请开始生成摘要:
这个Prompt明确了角色、任务、具体格式要求,并对不同类型的内容做了区分引导。将 [ARTICLE_TITLE] 和截断后的 [ARTICLE_TEXT] (注意Token限制)填入即可。
成本控制技巧 :
- 内容截断 :单篇文章可能很长,消耗大量Token。在发送给API前,先对文章正文进行智能截断。例如,只取前2000个字符,或者利用
newspaper3k提取的文本,优先保留开头和结尾部分(通常包含核心信息)。 - 批量处理 :不要为每篇文章单独发起一次API调用。可以将3-5篇同类型文章(如都是AI新闻)的标题和内容组合在一个Prompt里,要求模型为每篇生成独立摘要。这能有效减少重复的系统消息Token消耗。但要注意,批量太多可能导致模型混淆或响应超时。
- 缓存摘要 :为每篇文章的摘要结果建立本地缓存(键值对:
文章URL->摘要)。这样,即使当天简报生成失败需要重跑,或者未来想将某篇文章重新编入特辑,也无需再次调用付费API。
2.3 简报组装与邮件推送
所有文章的摘要生成后,需要将它们组装成一份美观的HTML邮件。我放弃了复杂的HTML/CSS编写,转而使用 Jinja2模板引擎 。这让我能像写网页一样,设计一个邮件模板( template.html ),其中留出位置(如 {{ date }} , {{ articles }} )用于填充动态内容。
<!DOCTYPE html>
<html>
<body>
<h2>您的午间简报 {{ date }}</h2>
<p>今日为您筛选了 {{ articles|length }} 条值得关注的内容:</p>
<hr>
{% for article in articles %}
<div class="article">
<h3><a href="{{ article.url }}">{{ article.title }}</a></h3>
<p class="summary">{{ article.summary }}</p>
<p class="meta">来源:{{ article.source }} | 时间:{{ article.published }}</p>
</div>
<hr>
{% endfor %}
<footer>本简报由AI自动生成,祝您午间愉快!</footer>
</body>
</html>
然后,在Python中,将数据(日期、文章列表)渲染到这个模板中,生成最终的HTML字符串。
邮件推送服务 :我强烈推荐使用 第三方邮件发送服务 ,如SendGrid、Mailgun或Amazon SES,而不是直接用SMTP发送。原因有三:1) 送达率高,专业服务商能有效处理垃圾邮件过滤问题;2) 提供详细的发送统计和退订管理;3) 易于集成和扩展。这些服务通常提供免费的额度,对于个人项目完全足够。以SendGrid为例,集成代码如下:
import sendgrid
from sendgrid.helpers.mail import Mail, HtmlContent
def send_email_via_sendgrid(html_content, recipient):
sg = sendgrid.SendGridAPIClient(api_key=os.environ.get('SENDGRID_API_KEY'))
message = Mail(
from_email='your-newsletter@yourdomain.com',
to_emails=recipient,
subject=f'您的Noonification简报 - {datetime.today().strftime("%Y/%m/%d")}',
html_content=HtmlContent(html_content)
)
try:
response = sg.send(message)
print(f"邮件发送成功,状态码:{response.status_code}")
except Exception as e:
print(f"邮件发送失败:{e}")
2.4 调度与部署:让一切自动运转
整个脚本需要在每天中午12点自动运行。有几种成熟的方案:
- 云服务器Cron Job :如果你有一台一直运行的VPS,这是最直接的方式。在
crontab中添加一行:0 12 * * * /usr/bin/python3 /path/to/your/noonification.py。 - Serverless函数 :这是更优雅、更省钱的方案。 Vercel Serverless Functions 、 AWS Lambda 或 Google Cloud Functions 都非常适合。它们按执行次数和资源消耗计费,对于每天只运行几分钟的任务,成本几乎为零。你需要将脚本改造成一个能被云函数触发的格式(例如,一个处理HTTP请求的入口函数),并设置Cloud Scheduler(GCP)或EventBridge(AWS)定时触发它。
- GitHub Actions :如果你将代码托管在GitHub,可以利用其免费的CI/CD服务。配置一个每天定时运行的工作流(schedule),在Action中执行你的Python脚本。这是一种完全免费且易于版本管理的方案。
我最终选择了 GitHub Actions + SendGrid 的组合。代码和配置都在GitHub,安全且易于维护;SendGrid处理邮件发送,可靠且专业。整个系统没有任何需要持续付费的组件(除了GPT API调用)。
3. 核心实现步骤与代码详解
让我们把上述架构转化为具体的代码。以下是一个高度精简但功能完整的核心脚本框架,你可以在其基础上进行扩展。
3.1 环境准备与依赖安装
首先,创建一个新的项目目录,并初始化虚拟环境。
mkdir noonification && cd noonification
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
安装必要的Python包:
pip install feedparser newspaper3k openai jinja2 python-dotenv sendgrid
创建项目文件结构:
noonification/
├── config.py # 配置文件(API密钥、RSS源列表)
├── main.py # 主程序入口
├── templates/
│ └── newsletter.html # Jinja2邮件模板
├── utils.py # 工具函数(抓取、摘要生成等)
├── .env # 环境变量(勿提交至Git)
└── requirements.txt # 依赖列表
在 .env 文件中存储你的敏感信息:
OPENAI_API_KEY=sk-your-openai-key-here
SENDGRID_API_KEY=SG.your-sendgrid-key-here
RECIPIENT_EMAIL=your-email@example.com
FROM_EMAIL=newsletter@yourdomain.com
3.2 核心逻辑实现
1. 配置与数据源管理 ( config.py )
# RSS源列表,格式:{'name': '源名称', 'url': 'RSS地址'}
RSS_FEEDS = [
{'name': 'TechCrunch', 'url': 'https://techcrunch.com/feed/'},
{'name': 'Hacker News', 'url': 'https://news.ycombinator.com/rss'},
{'name': '某科技博客', 'url': 'https://example.com/feed'},
# ... 添加更多源
]
# 简报相关配置
MAX_ARTICLES_PER_RUN = 10 # 每次最多处理多少篇文章
SUMMARY_WORD_LIMIT = 100 # 摘要字数限制
2. 工具函数 ( utils.py )
import feedparser
from newspaper import Article
import hashlib
import openai
import os
from datetime import datetime, timedelta
import json
openai.api_key = os.getenv('OPENAI_API_KEY')
def fetch_feeds(feed_list):
"""抓取所有RSS源的最新文章"""
all_articles = []
for feed in feed_list:
try:
parsed = feedparser.parse(feed['url'])
for entry in parsed.entries[:5]: # 每个源取最新5篇
article = {
'title': entry.title,
'url': entry.link,
'source': feed['name'],
'published': entry.get('published', ''),
'content': entry.get('summary', '')
}
# 如果RSS内容不全,尝试抓取全文
if len(article['content']) < 200:
full_text = fetch_full_article(entry.link)
if full_text:
article['content'] = full_text
all_articles.append(article)
except Exception as e:
print(f"抓取源 {feed['name']} 失败: {e}")
return all_articles
def generate_summary_gpt(title, text):
"""调用GPT API生成摘要"""
# 截断过长的文本以节省Token
if len(text) > 3000:
text = text[:1500] + "..." + text[-1500:]
prompt = f"""你是一个专业的科技内容编辑。请根据以下文章内容,生成一段简洁的摘要。
要求:
1. 摘要长度控制在80-120字之间。
2. 提炼核心论点或新闻事实,省略具体数据、例子等细节。
3. 使用中文输出,语言流畅、客观。
4. 如果原文是教程或指南,请说明其解决的主要问题和关键步骤。
5. 如果原文是观点或评论,请提炼作者的核心立场和主要论据。
文章标题:{title}
文章内容:{text}
请开始生成摘要:"""
try:
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": prompt}],
temperature=0.5, # 较低的温度使输出更稳定、更聚焦
max_tokens=150,
)
summary = response.choices[0].message.content.strip()
return summary
except Exception as e:
print(f"为文章《{title}》生成摘要时出错: {e}")
return None
3. 主程序流程 ( main.py )
from config import RSS_FEEDS, MAX_ARTICLES_PER_RUN
from utils import fetch_feeds, generate_summary_gpt
from jinja2 import Environment, FileSystemLoader
import os
from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import Mail, HtmlContent
from datetime import datetime
def main():
print(f"[{datetime.now()}] 开始生成午间简报...")
# 1. 抓取文章
print("步骤1: 抓取RSS源...")
articles = fetch_feeds(RSS_FEEDS)
print(f"抓取到 {len(articles)} 篇原始文章。")
# 2. 去重 (基于URL哈希的简单去重)
seen = set()
unique_articles = []
for article in articles:
url_hash = hashlib.md5(article['url'].encode()).hexdigest()
if url_hash not in seen:
seen.add(url_hash)
unique_articles.append(article)
articles = unique_articles[:MAX_ARTICLES_PER_RUN] # 限制数量
print(f"去重后,选择 {len(articles)} 篇进行处理。")
# 3. 为每篇文章生成摘要
print("步骤2: 调用GPT生成摘要...")
for article in articles:
print(f" 处理: {article['title'][:50]}...")
summary = generate_summary_gpt(article['title'], article['content'])
if summary:
article['summary'] = summary
else:
article['summary'] = "(摘要生成失败)"
# 避免请求过快,轻微延迟
import time
time.sleep(0.5)
# 4. 使用Jinja2渲染HTML邮件
print("步骤3: 渲染邮件模板...")
env = Environment(loader=FileSystemLoader('templates'))
template = env.get_template('newsletter.html')
html_content = template.render(
date=datetime.now().strftime("%Y年%m月%d日"),
articles=articles
)
# 5. 通过SendGrid发送邮件
print("步骤4: 发送邮件...")
message = Mail(
from_email=os.getenv('FROM_EMAIL'),
to_emails=os.getenv('RECIPIENT_EMAIL'),
subject=f'您的Noonification简报 - {datetime.now().strftime("%Y/%m/%d")}',
html_content=HtmlContent(html_content)
)
try:
sg = SendGridAPIClient(os.getenv('SENDGRID_API_KEY'))
response = sg.send(message)
print(f"邮件发送成功!状态码: {response.status_code}")
except Exception as e:
print(f"邮件发送失败: {e}")
print(f"[{datetime.now()}] 简报生成流程结束。")
if __name__ == "__main__":
main()
3.3 模板文件示例 ( templates/newsletter.html )
这是一个极简但功能完整的模板,注重可读性而非花哨的设计。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; max-width: 700px; margin: 0 auto; padding: 20px; }
.header { border-bottom: 2px solid #4CAF50; padding-bottom: 10px; margin-bottom: 25px; }
.article { margin-bottom: 30px; padding-bottom: 20px; border-bottom: 1px solid #eee; }
.article-title { font-size: 1.3em; margin-bottom: 8px; }
.article-title a { color: #1a73e8; text-decoration: none; }
.article-title a:hover { text-decoration: underline; }
.summary { color: #555; font-size: 0.95em; }
.meta { font-size: 0.85em; color: #888; margin-top: 8px; }
.footer { margin-top: 40px; padding-top: 20px; border-top: 1px solid #ddd; font-size: 0.9em; color: #777; text-align: center; }
</style>
</head>
<body>
<div class="header">
<h1>🌞 午间简报 Noonification</h1>
<p>{{ date }} | 为您精选 {{ articles|length }} 条资讯</p>
</div>
{% for article in articles %}
<div class="article">
<h2 class="article-title"><a href="{{ article.url }}" target="_blank">{{ article.title }}</a></h2>
<p class="summary">{{ article.summary }}</p>
<p class="meta">来源:{{ article.source }} | 发布时间:{{ article.published or '未知' }}</p>
</div>
{% endfor %}
<div class="footer">
<p>本简报由AI自动生成,旨在为您节省信息筛选时间。</p>
<p>如果您不希望再收到此简报,请回复本邮件告知。</p>
</div>
</body>
</html>
4. 部署、优化与成本控制实战
将代码跑起来只是第一步,要让这个系统长期稳定、经济地运行,还需要一系列部署和优化措施。
4.1 使用GitHub Actions实现免费自动化调度
在项目根目录创建 .github/workflows/noonification.yml 文件:
name: Generate and Send Noonification
on:
schedule:
# 在UTC时间每天04:00运行(对应北京时间12:00)
- cron: '0 4 * * *'
# 也可以手动触发
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.9'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Run Noonification Script
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
SENDGRID_API_KEY: ${{ secrets.SENDGRID_API_KEY }}
RECIPIENT_EMAIL: ${{ secrets.RECIPIENT_EMAIL }}
FROM_EMAIL: ${{ secrets.FROM_EMAIL }}
run: python main.py
你需要将 OPENAI_API_KEY 、 SENDGRID_API_KEY 等添加到GitHub仓库的Settings -> Secrets and variables -> Actions中。这样,每天中午,GitHub Actions就会自动运行你的脚本,抓取新闻、生成摘要并发送邮件,完全免费。
4.2 成本监控与优化策略
GPT API是主要的成本中心。假设每天处理10篇文章,每篇文章平均消耗1000个Token(输入+输出),使用 gpt-3.5-turbo 模型(每1K Token约$0.0015),每日成本约为 10 * 1000 / 1000 * $0.0015 = $0.015 ,每月不到0.5美元。这非常便宜。但如果不加控制,成本也可能飙升。
成本控制实战技巧:
- 设置预算和告警 :在OpenAI平台设置每月使用预算和告警阈值(例如5美元)。这是防止意外消耗的最重要防线。
- 实施请求限流和重试 :在代码中加入请求延迟(
time.sleep)和指数退避重试机制,避免因网络波动导致的重复请求。 - 摘要缓存 :如前所述,将生成的摘要以
文章URL为键存储到本地文件或轻量级数据库(如SQLite)中。下次运行时,先检查缓存,避免为同一篇文章重复付费。 - 内容预过滤 :在调用昂贵的GPT API之前,先进行一层基于规则的粗筛。例如,过滤掉标题中包含“赞助”、“广告”字样的文章,或者只处理发布时间在最近24小时内的文章。
- Token使用分析 :定期查看OpenAI API的使用日志,分析哪些RSS源的文章通常更长、更耗Token。对于“性价比”低的源,可以考虑调整其内容截断策略,或者将其优先级调低。
4.3 提升摘要质量的进阶技巧
基础的Prompt能工作,但要让摘要更上一层楼,可以尝试以下方法:
- 分步摘要(Chain-of-Thought) :对于特别长或复杂的文章,可以指示GPT先提取关键要点(Bullet Points),然后再将这些要点组织成连贯的段落。这有时能产生更结构化的摘要。
请分两步处理以下文章: 第一步:提取3-5个最关键的事实或观点。 第二步:将上述要点整合成一段流畅的80字中文摘要。 - 风格定制 :你可以为不同类型的源定制不同的Prompt和语气。例如,处理严肃的学术博客时,Prompt可以要求“语气严谨、客观”;处理产品更新日志时,可以要求“突出新功能与对用户的影响”。
- 事实核对与链接保留 :在Prompt中明确要求“摘要中如涉及具体数据、名称、结论,必须与原文严格一致”,并“确保不添加原文不存在的信息”。这能减少模型“幻觉”(编造内容)的风险。
- 人工反馈循环(可选) :建立一个简单的机制,在每份简报末尾添加一个链接,让读者对某篇文章的摘要质量进行评分(如“有用”/“无用”)。收集这些数据,可以帮助你持续优化针对特定源或文章类型的Prompt。
5. 常见问题与故障排查实录
在运行这个系统的几个月里,我遇到了各种各样的问题。下面这个表格记录了一些典型问题及其解决方案,希望能帮你绕过这些坑。
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
| 收不到邮件 | 1. 发送服务(SendGrid)配置错误。 2. 邮件被归入垃圾邮件。 3. GitHub Actions运行失败。 |
1. 检查SendGrid仪表盘 :登录SendGrid,查看“Activity”标签页,是否有发送记录?是“Delivered”还是“Bounced/Dropped”?Bounced通常是因为发件人邮箱未验证。务必在SendGrid中验证 FROM_EMAIL 。 2. 检查收件箱垃圾邮件文件夹 。 3. 检查GitHub Actions运行日志 :在仓库的“Actions”标签页查看最近的工作流运行记录,是否有错误信息?常见错误是Python依赖安装失败或环境变量未正确设置。 |
| 摘要内容质量差 | 1. Prompt指令不清晰。 2. 输入给GPT的原文文本质量差(如包含大量HTML标签、导航文字)。 3. 文章本身过于技术性或晦涩。 |
1. 优化Prompt :明确角色、任务、格式、字数。要求模型“不要添加原文没有的信息”。 2. 净化输入文本 :在使用 newspaper3k 或类似库后,对提取的文本进行简单的后处理,比如移除过长的URL、重复的换行符等。 3. 尝试更换模型 :对于技术文章,可以尝试切换到 gpt-4 看是否有改善(需权衡成本)。或者,在Prompt中指定“假设读者是专业人士,可以使用必要的技术术语”。 |
| 运行速度慢 | 1. 网络问题导致抓取RSS或调用API延迟。 2. 处理的文章数量过多。 3. 未使用异步请求。 |
1. 增加超时和重试 :为 feedparser 和 requests 设置合理的超时时间(如10秒)。 2. 限制并发和总量 :使用 asyncio 和 aiohttp 进行异步抓取和API调用,可以极大提升速度。同时,严格限制每次处理的最大文章数(如10-15篇)。 3. 分析瓶颈 :在代码中添加简单的计时日志,找出是哪个环节(抓取、摘要生成、发送)最耗时。 |
| OpenAI API调用频繁失败 | 1. API密钥无效或额度用尽。 2. 请求速率超限(RPM/TPM限制)。 3. 输入Token超长。 |
1. 检查API密钥和额度 :登录OpenAI平台确认。 2. 实施速率限制 :在代码中控制请求频率,例如每两次请求之间暂停1秒。使用 tenacity 等库实现带指数退避的重试机制。 3. 检查并截断输入 :在调用API前,计算输入文本的Token长度(可用 tiktoken 库)。对于超长的文本,果断进行智能截断。 |
| 简报内容重复 | 1. 不同RSS源报道了同一新闻事件。 2. 去重逻辑有缺陷。 |
1. 改进去重逻辑 :除了URL,可以结合文章标题的相似度(如使用 difflib 或文本嵌入计算余弦相似度)进行更智能的去重。对于高度相似的标题,只保留一篇。 2. 人工设置优先级 :在配置中为每个RSS源设置优先级,当多篇文章相似时,优先保留高优先级源的内容。 |
除了上述问题,还有一个“软性”挑战: 如何保持简报的新鲜感和价值 ?运行几周后,你可能会发现内容同质化。我的建议是定期(比如每月)回顾和调整你的RSS源列表。移除那些产出质量下降或与你兴趣不再匹配的源,加入一些新的、小众但优质的博客或社区。让这个系统随着你的兴趣进化,它才能长期为你提供价值。
最后,关于这个项目的扩展方向,我个人尝试过增加一个“本周趋势”板块,让GPT分析过去一周所有摘要,提炼出几个关键主题词;也试过在周末发送一份“本周精华”特辑。这些功能让简报从一个简单的聚合工具,变得更像一个懂你的信息助手。所有的代码和配置都托管在GitHub上,修改和实验起来非常方便。这个项目的魅力就在于,它从一个简单的想法开始,通过不断迭代,最终成为了我信息流中一个不可或缺的、高度个性化的环节。
更多推荐


所有评论(0)