手把手教你用Python爬虫抓取行业网站新闻,结合大模型API自动生成摘要,打造你的AI资讯助手。完整代码+详细注释,小白也能学会!
前言
做技术和商业的人都知道,信息差就是金钱。
每天花大量时间刷各种行业网站、公众号、新闻客户端,找有价值的内容——这个过程既耗时又累人。
如果有一个工具,能自动帮你:
- 定时抓取你关心的行业资讯
- 自动过滤重复内容
- 用大模型生成简洁摘要
- 推送到你的邮箱或微信
今天,这个工具我们可以自己动手做。
一、项目整体架构
┌─────────────────────────────────────────────────────────────────┐
│ AI资讯助手架构图 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 新闻网站 │ │ 公众号 │ │ 行业论坛 │ │
│ │ RSS/HTML/API │ │ 第三方API │ │ 网页抓取 │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ └───────────────────┼───────────────────┘ │
│ ↓ │
│ ┌─────────────────┐ │
│ │ 数据采集层 │ │
│ │ Python爬虫 │ │
│ └────────┬────────┘ │
│ ↓ │
│ ┌─────────────────┐ │
│ │ 数据处理层 │ │
│ │ 去重 / 清洗 / 分类 │ │
│ └────────┬────────┘ │
│ ↓ │
│ ┌─────────────────┐ │
│ │ AI摘要层 │ │
│ │ 大模型API调用 │ │
│ └────────┬────────┘ │
│ ↓ │
│ ┌─────────────────┐ │
│ │ 通知推送层 │ │
│ │ 邮件/微信/钉钉 │ │
│ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
二、环境准备
2.1 依赖安装
# 创建虚拟环境
python -m venv venv
source venv/bin/activate # Linux/Mac
# venv\Scripts\activate # Windows
# 安装依赖
pip install requests beautifulsoup4 lxml feedparser
pip install python-dotenv langchain-openai
pip install schedule aioschedule # 定时任务
pip install apscheduler # 更强大的定时任务
2.2 配置文件
# .env 文件
# 大模型API配置(可根据需要换成通义、文心等)
OPENAI_API_KEY=sk-your-api-key
OPENAI_API_BASE=https://api.openai.com/v1
OPENAI_MODEL=gpt-4o-mini
# 邮件配置(可选)
SMTP_SERVER=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=your-email@gmail.com
SMTP_PASSWORD=your-app-password
# 订阅源配置
RSS_SOURCES=techcrunch,36kr,ithome
三、数据采集层:多源爬虫实现
3.1 RSS订阅源抓取(最简单)
"""
rss_crawler.py - RSS订阅源抓取
支持:36氪、爱范儿、IT之家、少数派等主流科技媒体
"""
import feedparser
import hashlib
from datetime import datetime
from typing import List, Dict
import requests
from bs4 import BeautifulSoup
class RSSNewsCollector:
"""RSS新闻采集器"""
# 预设的RSS订阅源
RSS_SOURCES = {
"36氪": "https://36kr.com/feed",
"爱范儿": "https://www.ifanr.com/feed",
"IT之家": "https://www.ithome.com/rss/",
"少数派": "https://sspai.com/feed",
"科技媒体": "https://www.techcrunch.com/feed/",
"量子位": "https://www.qbitai.com/feed",
"机器之心": "https://arrings.machinethat.net/feed",
}
def __init__(self):
self.seen_hashes = set() # 用于去重
def get_content_hash(self, title: str, link: str) -> str:
"""生成内容唯一标识"""
content = f"{title}{link}"
return hashlib.md5(content.encode()).hexdigest()
def fetch_rss(self, source_name: str, url: str) -> List[Dict]:
"""抓取单个RSS源"""
articles = []
try:
feed = feedparser.parse(url)
print(f"[{source_name}] 抓取到 {len(feed.entries)} 条内容")
for entry in feed.entries:
# 生成唯一标识
article_hash = self.get_content_hash(entry.title, entry.link)
# 去重检查
if article_hash in self.seen_hashes:
continue
self.seen_hashes.add(article_hash)
article = {
"title": entry.title,
"link": entry.link,
"source": source_name,
"published": entry.get("published", datetime.now().isoformat()),
"summary": entry.get("summary", ""),
"hash": article_hash
}
articles.append(article)
except Exception as e:
print(f"[{source_name}] 抓取失败: {e}")
return articles
def collect_all(self) -> List[Dict]:
"""采集所有订阅源"""
all_articles = []
for source_name, url in self.RSS_SOURCES.items():
articles = self.fetch_rss(source_name, url)
all_articles.extend(articles)
print(f"\n总计采集到 {len(all_articles)} 条新闻(去重后)")
return all_articles
# 使用示例
if __name__ == "__main__":
collector = RSSNewsCollector()
news = collector.collect_all()
for item in news[:3]:
print(f"- {item['source']}: {item['title']}")
3.2 网页爬虫:抓取无RSS的网站
"""
web_crawler.py - 通用网页爬虫
适用于没有RSS的网站,使用BeautifulSoup解析
"""
import requests
from bs4 import BeautifulSoup
from datetime import datetime
from typing import List, Dict, Optional
import time
import random
from urllib.parse import urljoin
class WebNewsCrawler:
"""网页新闻爬虫"""
# 常用请求头,模拟浏览器
HEADERS = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
}
def __init__(self, timeout: int = 10):
self.session = requests.Session()
self.session.headers.update(self.HEADERS)
self.timeout = timeout
def fetch_page(self, url: str) -> Optional[str]:
"""获取网页内容"""
try:
# 随机延时,避免请求过快
time.sleep(random.uniform(1, 3))
response = self.session.get(url, timeout=self.timeout)
response.raise_for_status()
response.encoding = response.apparent_encoding
return response.text
except Exception as e:
print(f"获取页面失败 {url}: {e}")
return None
def parse_article(self, html: str, base_url: str) -> List[Dict]:
"""解析新闻列表页"""
soup = BeautifulSoup(html, "lxml")
articles = []
# 常见的文章列表选择器(根据网站调整)
selectors = [
"article",
".article-item",
".news-item",
".post-item",
"div[class*='article']",
"div[class*='news']",
]
for selector in selectors:
items = soup.select(selector)
if items:
for item in items[:20]: # 限制每页抓取数量
title_elem = item.select_one("h2,h3,h4,a")
link_elem = item.select_one("a[href]")
date_elem = item.select_one("time,.date,.time")
if title_elem and link_elem:
title = title_elem.get_text(strip=True)
href = link_elem.get("href", "")
# 补全相对链接
full_url = urljoin(base_url, href)
# 过滤非新闻链接
if any(x in href for x in [".pdf", ".jpg", ".png", "javascript"]):
continue
article = {
"title": title,
"link": full_url,
"source": base_url,
"published": date_elem.get_text(strip=True) if date_elem else "",
"fetched_at": datetime.now().isoformat()
}
articles.append(article)
break
return articles
def crawl_site(self, url: str, max_pages: int = 3) -> List[Dict]:
"""抓取整个网站"""
all_articles = []
for page in range(1, max_pages + 1):
page_url = url if page == 1 else f"{url}?page={page}"
html = self.fetch_page(page_url)
if html:
articles = self.parse_article(html, url)
all_articles.extend(articles)
print(f"第{page}页: 抓取到 {len(articles)} 条")
return all_articles
# 使用示例
if __name__ == "__main__":
crawler = WebNewsCrawler()
# 抓取示例网站(请根据需要修改)
articles = crawler.crawl_site("https://example.com/news")
print(f"总计: {len(articles)} 条")
四、AI摘要生成:大模型API调用
4.1 摘要生成器实现
"""
summarizer.py - 使用大模型生成新闻摘要
支持:OpenAI GPT、通义千问、文心一言等主流模型
"""
import os
from typing import List, Dict
from dataclasses import dataclass
from langchain_openai import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
from langchain.output_parsers import PydanticOutputParser
from pydantic import BaseModel, Field
# Pydantic模型定义输出格式
class NewsSummary(BaseModel):
"""新闻摘要数据结构"""
key_points: List[str] = Field(description="3-5个核心要点,每个不超过20字")
summary: str = Field(description="50字以内的简要总结")
category: str = Field(description="分类:技术/商业/政策/人物/其他")
sentiment: str = Field(description="情感倾向:正面/中性/负面")
tags: List[str] = Field(description="3-5个标签词")
class NewsSummarizer:
"""AI新闻摘要生成器"""
# 摘要提示词模板
SUMMARY_PROMPT = """
你是一个专业的科技新闻分析师。请根据以下新闻内容,生成结构化的摘要。
## 新闻标题
{title}
## 新闻链接
{link}
## 新闻内容
{content}
## 要求
1. 提取3-5个核心要点,每个要点不超过20个字
2. 生成50字以内的简要总结
3. 判断内容分类(技术/商业/政策/人物/其他)
4. 判断情感倾向(正面/中性/负面)
5. 提供3-5个标签词
请用中文回答。
"""
def __init__(self, model: str = "gpt-4o-mini"):
api_key = os.getenv("OPENAI_API_KEY")
api_base = os.getenv("OPENAI_API_BASE", "https://api.openai.com/v1")
self.llm = ChatOpenAI(
model=model,
api_key=api_key,
base_url=api_base,
temperature=0.3 # 摘要任务用较低的temperature
)
self.parser = PydanticOutputParser(pydantic_object=NewsSummary)
def summarize_article(self, title: str, link: str, content: str = "") -> Dict:
"""生成单篇文章摘要"""
# 构建提示词
prompt = ChatPromptTemplate.from_template(self.SUMMARY_PROMPT)
messages = prompt.format_messages(
title=title,
link=link,
content=content or "(无详细内容)"
)
try:
# 调用大模型
response = self.llm.invoke(messages)
# 解析输出
result = self.parser.parse(response.content)
return {
"title": title,
"link": link,
"key_points": result.key_points,
"summary": result.summary,
"category": result.category,
"sentiment": result.sentiment,
"tags": result.tags
}
except Exception as e:
print(f"摘要生成失败: {e}")
return {
"title": title,
"link": link,
"key_points": [],
"summary": "摘要生成失败",
"category": "其他",
"sentiment": "中性",
"tags": []
}
def batch_summarize(self, articles: List[Dict], max_concurrent: int = 3) -> List[Dict]:
"""批量生成摘要(带并发控制)"""
results = []
for i, article in enumerate(articles):
print(f"[{i+1}/{len(articles)}] 处理: {article['title'][:30]}...")
summary = self.summarize_article(
title=article.get("title", ""),
link=article.get("link", ""),
content=article.get("summary", "")
)
results.append(summary)
return results
# 使用示例
if __name__ == "__main__":
summarizer = NewsSummarizer()
test_article = {
"title": "OpenAI发布GPT-5,性能提升10倍",
"link": "https://example.com/gpt5",
"summary": "OpenAI在今日发布了最新的GPT-5模型..."
}
result = summarizer.summarize_article(**test_article)
print(result)
4.2 兼容通义千问/文心一言
"""
multi_model.py - 多模型兼容封装
可以轻松切换不同的AI服务提供商
"""
from enum import Enum
from typing import Dict, List
from langchain_openai import ChatOpenAI
from langchain_community.chat_models import Tongyi
import os
class AIModel(Enum):
"""支持的AI模型"""
GPT4 = "gpt-4o"
GPT4_MINI = "gpt-4o-mini"
QWEN = "qwen-plus"
WENXIN = "ernie-4.0-8k"
class AISummarizerFactory:
"""AI摘要生成器工厂"""
@staticmethod
def create(model_type: AIModel, **kwargs):
"""创建指定类型的AI摘要生成器"""
if model_type == AIModel.GPT4 or model_type == AIModel.GPT4_MINI:
return ChatOpenAI(
model=model_type.value,
api_key=os.getenv("OPENAI_API_KEY"),
base_url=os.getenv("OPENAI_API_BASE"),
temperature=kwargs.get("temperature", 0.3)
)
elif model_type == AIModel.QWEN:
# 通义千问
return ChatOpenAI(
model="qwen-plus",
api_key=os.getenv("DASHSCOPE_API_KEY"), # 通义API Key
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
temperature=kwargs.get("temperature", 0.3)
)
elif model_type == AIModel.WENXIN:
# 文心一言(需要使用百度云SDK)
from langchain_community.chat_models import ErnieBotChat
return ErnieBotChat(
model_name="ernie-4.0-8k",
ernie_api_key=os.getenv("ERNIE_API_KEY"),
secret_key=os.getenv("ERNIE_SECRET_KEY")
)
raise ValueError(f"不支持的模型类型: {model_type}")
五、定时任务与通知推送
5.1 定时任务调度器
"""
scheduler.py - 定时任务调度器
每天定时执行资讯采集和摘要生成
"""
import schedule
import time
import logging
from datetime import datetime
from rss_crawler import RSSNewsCollector
from summarizer import NewsSummarizer
from notifier import NewsNotifier
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
filename='news_daily.log'
)
def daily_task():
"""每日任务:采集 + 摘要 + 推送"""
print(f"\n{'='*50}")
print(f"开始执行每日资讯任务 - {datetime.now()}")
print('='*50)
try:
# 1. 采集新闻
print("\n[步骤1] 采集新闻...")
collector = RSSNewsCollector()
articles = collector.collect_all()
if not articles:
print("没有采集到新内容")
return
# 2. 生成摘要
print("\n[步骤2] 生成AI摘要...")
summarizer = NewsSummarizer()
summaries = summarizer.batch_summarize(articles)
# 3. 推送通知
print("\n[步骤3] 推送通知...")
notifier = NewsNotifier()
notifier.send_daily_digest(summaries)
print(f"\n✅ 任务完成!处理了 {len(summaries)} 条资讯")
except Exception as e:
logging.error(f"任务执行失败: {e}", exc_info=True)
print(f"❌ 任务执行失败: {e}")
# 调度配置
def setup_schedule():
"""配置定时任务"""
# 每天早上8点执行
schedule.every().day.at("08:00").do(daily_task)
# 工作日早上8点30分执行(可选)
# schedule.every().mondy.at("08:30").do(daily_task)
# schedule.every().tuesday.at("08:30").do(daily_task)
# ...
print("📅 定时任务已配置:")
print(" - 每天 08:00 执行一次")
# 启动调度器
print("\n🚀 启动调度器...")
while True:
schedule.run_pending()
time.sleep(60)
if __name__ == "__main__":
# 测试运行(不等待定时)
# daily_task()
# 启动定时调度
setup_schedule()
5.2 通知推送模块
"""
notifier.py - 通知推送模块
支持:邮件、微信(企业微信/Server酱)、钉钉机器人
"""
import os
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from typing import List, Dict
import requests
class NewsNotifier:
"""资讯通知推送器"""
def __init__(self):
self.smtp_server = os.getenv("SMTP_SERVER")
self.smtp_port = int(os.getenv("SMTP_PORT", 587))
self.smtp_user = os.getenv("SMTP_USER")
self.smtp_password = os.getenv("SMTP_PASSWORD")
self.recipients = os.getenv("RECIPIENT_EMAILS", "").split(",")
# Server酱配置(微信推送)
self.serverchan_key = os.getenv("SERVERCHAN_KEY")
# 钉钉机器人
self.dingtalk_token = os.getenv("DINGTALK_TOKEN")
self.dingtalk_secret = os.getenv("DINGTALK_SECRET")
def format_daily_digest(self, summaries: List[Dict]) -> str:
"""格式化每日摘要"""
html = """
<html>
<head>
<style>
body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
h1 { color: #333; border-bottom: 2px solid #007bff; padding-bottom: 10px; }
.article { margin: 20px 0; padding: 15px; border-left: 4px solid #007bff; background: #f8f9fa; }
.title { font-size: 18px; font-weight: bold; margin-bottom: 10px; }
.summary { color: #666; margin-bottom: 10px; }
.tags { color: #007bff; font-size: 12px; }
.key-points { margin: 10px 0; padding-left: 20px; }
.key-points li { margin: 5px 0; }
</style>
</head>
<body>
<h1>📰 AI科技资讯日报</h1>
<p>今日共为您整理 {count} 条重要资讯</p>
""".format(count=len(summaries))
for item in summaries[:10]: # 最多显示10条
key_points_html = "".join(f"<li>{p}</li>" for p in item.get("key_points", []))
html += f"""
<div class="article">
<div class="title">
[{item.get('category', '其他')}] {item.get('title', '')}
</div>
<div class="summary">{item.get('summary', '')}</div>
<ul class="key-points">
{key_points_html}
</ul>
<div class="tags">
{' '.join(f'#{tag}' for tag in item.get('tags', []))}
</div>
<p><a href="{item.get('link', '')}">阅读原文 →</a></p>
</div>
"""
html += """
<hr>
<p style="color: #999; font-size: 12px;">
本报告由AI资讯助手自动生成 |
<a href="https://yunduancloud.icu">访问博客了解更多</a>
</p>
</body>
</html>
"""
return html
def send_email(self, subject: str, html_content: str):
"""发送邮件"""
if not self.smtp_user or not self.recipients:
print("邮件配置不完整,跳过邮件发送")
return
msg = MIMEMultipart("alternative")
msg["Subject"] = subject
msg["From"] = self.smtp_user
msg["To"] = ", ".join(self.recipients)
msg.attach(MIMEText(html_content, "html"))
try:
with smtplib.SMTP(self.smtp_server, self.smtp_port) as server:
server.starttls()
server.login(self.smtp_user, self.smtp_password)
server.sendmail(self.smtp_user, self.recipients, msg.as_string())
print("✅ 邮件发送成功")
except Exception as e:
print(f"❌ 邮件发送失败: {e}")
def send_serverchan(self, title: str, content: str):
"""通过Server酱发送微信通知"""
if not self.serverchan_key:
print("Server酱未配置,跳过微信通知")
return
url = f"https://sctapi.ftqq.com/{self.serverchan_key}.send"
data = {
"title": title,
"desp": content
}
try:
requests.post(url, data=data)
print("✅ Server酱推送成功")
except Exception as e:
print(f"❌ Server酱推送失败: {e}")
def send_daily_digest(self, summaries: List[Dict]):
"""发送每日摘要"""
# 格式化内容
html = self.format_daily_digest(summaries)
plain_text = "\n".join([
f"- [{item.get('category')}]{item.get('title')}: {item.get('summary')}"
for item in summaries[:10]
])
subject = f"📰 AI科技资讯日报 | {len(summaries)}条重要资讯"
# 发送邮件
self.send_email(subject, html)
# 发送微信(Server酱)
if summaries:
first_summary = summaries[0]
msg = f"📰 今日资讯速递\n\n{first_summary.get('title')}\n{first_summary.get('summary')}\n\n共{len(summaries)}条,点击查看全文 →"
self.send_serverchan(subject, msg)
# 使用示例
if __name__ == "__main__":
notifier = NewsNotifier()
# 测试发送
test_data = [{
"title": "测试新闻标题",
"link": "https://example.com",
"summary": "这是一条测试新闻的摘要内容",
"category": "技术",
"tags": ["AI", "新闻"]
}]
# notifier.send_daily_digest(test_data)
六、完整运行示例
# 1. 配置环境变量
export OPENAI_API_KEY="sk-your-key"
export SMTP_SERVER="smtp.gmail.com"
export SMTP_PORT="587"
export SMTP_USER="your-email@gmail.com"
export SMTP_PASSWORD="your-app-password"
# 2. 运行程序
python main.py
# 3. 或者使用定时模式
python scheduler.py
# 4. 查看日志
tail -f news_daily.log
效果预览:
2026-04-10 08:00:00 - INFO - 开始执行每日资讯任务
2026-04-10 08:00:01 - INFO - [36氪] 抓取到 30 条内容
2026-04-10 08:00:02 - INFO - [IT之家] 抓取到 25 条内容
2026-04-10 08:00:03 - INFO - ...
2026-04-10 08:00:30 - INFO - 总计采集到 45 条新闻(去重后)
2026-04-10 08:00:31 - INFO - [1/45] 处理: OpenAI发布GPT-5...
2026-04-10 08:00:35 - INFO - [2/45] 处理: 腾讯云新出...
...
2026-04-10 08:15:00 - INFO - ✅ 邮件发送成功
2026-04-10 08:15:01 - INFO - ✅ Server酱推送成功
七、进阶优化建议
| 优化方向 | 实现方式 | 效果 |
|---|---|---|
| 增量抓取 | 记录上次抓取位置 | 避免重复抓取相同内容 |
| 分布式爬虫 | 使用Scrapy-Redis | 提升抓取速度 |
| 智能去重 | 用embedding计算相似度 | 更精准的去重 |
| 自动分类 | 训练文本分类模型 | 自动打标签 |
| 情感分析 | 接入专门的情绪分析API | 更精准的情感判断 |
| 多语言翻译 | 接入翻译API | 覆盖英文资讯 |
八、总结
本文完整实现了一个AI资讯助手系统:
- 数据采集:RSS订阅源 + 通用网页爬虫
- 去重处理:基于内容hash的智能去重
- AI摘要:调用大模型API生成结构化摘要
- 定时任务:每天自动执行
- 多端推送:邮件 + 微信通知
这套方案的成本极低:
- API调用:GPT-4o-mini 处理50条新闻约 $0.1
- 爬虫:免费
- 推送:免费
如果你有更多需求,可以在此基础上添加:
- 自定义订阅源
- 关键词过滤
- 自动生成思维导图
- 对接更多通知渠道
完整代码已整理好,欢迎访问我的博客获取更多实战内容!
关于作者
长期关注大模型应用落地与云服务器实战,专注技术在企业场景中的落地实践。
个人博客:yunduancloud.icu —— 持续更新云计算、AI大模型实战教程,欢迎访问交流。