forked from sgrsun3/FinNewsCollectionBot
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathfinancebot.py
More file actions
214 lines (183 loc) · 9.25 KB
/
Copy pathfinancebot.py
File metadata and controls
214 lines (183 loc) · 9.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# 福生无量天尊
from openai import OpenAI
import feedparser
import requests
from newspaper import Article
from datetime import datetime
import time
import pytz
import os
# OpenAI API Key
openai_api_key = os.getenv("OPENAI_API_KEY")
# 从环境变量获取 Server酱 SendKeys
server_chan_keys_env = os.getenv("SERVER_CHAN_KEYS")
if not server_chan_keys_env:
raise ValueError("环境变量 SERVER_CHAN_KEYS 未设置,请在Github Actions中设置此变量!")
server_chan_keys = server_chan_keys_env.split(",")
openai_client = OpenAI(api_key=openai_api_key, base_url="https://api.deepseek.com/v1")
# RSS源地址列表
rss_feeds = {
"💲 华尔街见闻":{
"华尔街见闻":"https://dedicated.wallstreetcn.com/rss.xml",
},
"💻 36氪":{
"36氪":"https://36kr.com/feed",
},
"🇨🇳 中国经济": {
# "香港經濟日報":"https://www.hket.com/rss/china", # ❌ RSS解析失败,格式问题
"东方财富":"http://rss.eastmoney.com/rss_partener.xml",
"百度股票焦点":"http://news.baidu.com/n?cmd=1&class=stock&tn=rss&sub=0",
"中新网":"https://www.chinanews.com.cn/rss/finance.xml",
"国家统计局-最新发布":"https://www.stats.gov.cn/sj/zxfb/rss.xml",
# "财新网":"https://rsshub.app/caixin/latest", # ❌ 403 Forbidden
# "新浪财经":"https://rss.sina.com.cn/finance/latest/all.xml", # ❌ 404 Not Found
},
"🇺🇸 美国经济": {
# "华尔街日报 - 经济":"https://feeds.content.dowjones.io/public/rss/WSJcomUSBusiness", # ❌ 文章内容爬取失败(403 Forbidden)
# "华尔街日报 - 市场":"https://feeds.content.dowjones.io/public/rss/RSSMarketsMain", # ❌ 文章内容爬取失败(403 Forbidden)
# "MarketWatch美股": "https://www.marketwatch.com/rss/topstories", # ❌ 文章内容爬取失败(401 Forbidden)
"ZeroHedge华尔街新闻": "https://feeds.feedburner.com/zerohedge/feed",
"ETF Trends": "https://www.etftrends.com/feed/",
"Federal Reserve Board": "https://www.federalreserve.gov/feeds/press_all.xml", # 美联储新闻/声明
},
"🌍 世界经济": {
# "华尔街日报 - 经济":"https://feeds.content.dowjones.io/public/rss/socialeconomyfeed", # ❌ 文章内容爬取失败(403 Forbidden)
"BBC全球经济": "http://feeds.bbci.co.uk/news/business/rss.xml",
"FT中文网": "https://www.ftchinese.com/rss/feed", # 英国《金融时报》中文站
"Wall Street Journal": "https://feeds.a.dj.com/rss/RSSWorldNews.xml", # 华尔街日报 - 全球新闻
"Investing.com": "https://www.investing.com/rss/news.rss", # 全球财经与投资新闻
"Thomson Reuters": "https://ir.thomsonreuters.com/rss/news-releases.xml", # 路透财经新闻
# "IMF Publications": "https://www.imf.org/en/News/rss", # ❌ 连接被关闭
},
}
# 获取北京时间
def today_date():
return datetime.now(pytz.timezone("Asia/Shanghai")).date()
# 爬取网页正文 (用于 AI 分析,但不展示)
def fetch_article_text(url):
try:
print(f"📰 正在爬取文章内容: {url}")
article = Article(url)
article.download()
article.parse()
text = article.text[:1500] # 限制长度,防止超出 API 输入限制
if not text:
print(f"⚠️ 文章内容为空: {url}")
return text
except Exception as e:
print(f"❌ 文章爬取失败: {url},错误: {e}")
return "(未能获取文章正文)"
# 添加 User-Agent 头并禁用 SSL 验证
def fetch_feed_with_headers(url):
import ssl
import urllib.request
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
}
# 创建不验证 SSL 证书的上下文
ssl_context = ssl.create_default_context()
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE
# 创建请求对象
req = urllib.request.Request(url, headers=headers)
# 使用自定义的 SSL 上下文打开 URL
with urllib.request.urlopen(req, context=ssl_context) as response:
content = response.read()
# 解析内容
return feedparser.parse(content)
# 自动重试获取 RSS
def fetch_feed_with_retry(url, retries=3, delay=5):
for i in range(retries):
try:
feed = fetch_feed_with_headers(url)
if feed and hasattr(feed, 'entries') and len(feed.entries) > 0:
return feed
except Exception as e:
print(f"⚠️ 第 {i+1} 次请求 {url} 失败: {e}")
time.sleep(delay)
print(f"❌ 跳过 {url}, 尝试 {retries} 次后仍失败。")
return None
# 获取RSS内容(爬取正文但不展示)
def fetch_rss_articles(rss_feeds, max_articles=10):
news_data = {}
analysis_text = "" # 用于AI分析的正文内容
for category, sources in rss_feeds.items():
category_content = ""
for source, url in sources.items():
print(f"📡 正在获取 {source} 的 RSS 源: {url}")
feed = fetch_feed_with_retry(url)
if not feed:
print(f"⚠️ 无法获取 {source} 的 RSS 数据")
continue
print(f"✅ {source} RSS 获取成功,共 {len(feed.entries)} 条新闻")
articles = [] # 每个source都需要重新初始化列表
for entry in feed.entries[:max_articles]:
title = entry.get('title', '无标题')
link = entry.get('link', '') or entry.get('guid', '')
if not link:
print(f"⚠️ {source} 的新闻 '{title}' 没有链接,跳过")
continue
# 爬取正文用于分析(不展示)
article_text = fetch_article_text(link)
analysis_text += f"【{title}】\n{article_text}\n\n"
print(f"🔹 {source} - {title} 获取成功")
articles.append(f"- [{title}]({link})")
if articles:
category_content += f"### {source}\n" + "\n".join(articles) + "\n\n"
news_data[category] = category_content
return news_data, analysis_text
# AI 生成内容摘要(基于爬取的正文)
def summarize(text):
completion = openai_client.chat.completions.create(
model="deepseek-chat",
messages=[
{"role": "system", "content": """
你是一名专业的财经新闻分析师,请根据以下新闻内容,按照以下步骤完成任务:
1. 提取新闻中涉及的主要行业和主题,找出近1天涨幅最高的3个行业或主题,以及近3天涨幅较高且此前2周表现平淡的3个行业/主题。(如新闻未提供具体涨幅,请结合描述和市场情绪推测热点)
2. 针对每个热点,输出:
- 催化剂:分析近期上涨的可能原因(政策、数据、事件、情绪等)。
- 复盘:梳理过去3个月该行业/主题的核心逻辑、关键动态与阶段性走势。
- 展望:判断该热点是短期炒作还是有持续行情潜力。
3. 将以上分析整合为一篇1500字以内的财经热点摘要,逻辑清晰、重点突出,适合专业投资者阅读。
"""},
{"role": "user", "content": text}
]
)
return completion.choices[0].message.content.strip()
# 发送微信推送
def send_to_wechat(title, content):
for key in server_chan_keys:
url = f"https://sctapi.ftqq.com/{key}.send"
data = {"title": title, "desp": content}
response = requests.post(url, data=data, timeout=10)
if response.ok:
print(f"✅ 推送成功: {key}")
else:
print(f"❌ 推送失败: {key}, 响应:{response.text}")
if __name__ == "__main__":
today_str = today_date().strftime("%Y-%m-%d")
# 每个网站获取最多 5 篇文章
articles_data, analysis_text = fetch_rss_articles(rss_feeds, max_articles=5)
# AI生成摘要
summary = summarize(analysis_text)
# 生成仅展示标题和链接的最终消息
final_summary = f"📅 **{today_str} 财经新闻摘要**\n\n✍️ **今日分析总结:**\n{summary}\n\n---\n\n"
for category, content in articles_data.items():
if content.strip():
final_summary += f"## {category}\n{content}\n\n"
# 推送到多个server酱key
send_to_wechat(title=f"📌 {today_str} 财经新闻摘要", content=final_summary)
# 将报告写入文件,供独立脚本发送邮件
os.makedirs("output", exist_ok=True)
with open("output/latest_report.txt", "w", encoding="utf-8") as f_txt:
f_txt.write(final_summary)
with open("output/latest_report.html", "w", encoding="utf-8") as f_html:
f_html.write(final_summary.replace("\n", "<br/>"))
# 归档保存Markdown报告,按北京时间时间戳命名(YYYYMMDD_HHMMSS)
beijing_now = datetime.now(pytz.timezone("Asia/Shanghai"))
ts_str = beijing_now.strftime("%Y%m%d_%H%M%S")
archive_dir = "output"
os.makedirs(archive_dir, exist_ok=True)
archive_path = os.path.join(archive_dir, f"report_{ts_str}.md")
with open(archive_path, "w", encoding="utf-8") as f_md:
f_md.write(final_summary)