1+ #!/usr/bin/env python3
2+ # -*- coding: utf-8 -*-
3+
4+ """
5+ 爬取谷歌热点新闻
6+ 使用 RSS 订阅源获取最新新闻
7+ """
8+
9+ import requests
10+ import xml .etree .ElementTree as ET
11+ import time
12+ from datetime import datetime
13+ import sys
14+
15+ def fetch_google_news (rss_url = "https://news.google.com/rss?hl=zh-CN&gl=CN&ceid=CN:zh-Hans" ):
16+ """
17+ 从 Google News RSS 订阅源获取热点新闻
18+
19+ Args:
20+ rss_url (str): Google News RSS 订阅源 URL
21+
22+ Returns:
23+ list: 新闻条目列表,每个条目包含标题、链接、发布时间等信息
24+ """
25+ print ("正在获取谷歌热点新闻..." )
26+
27+ try :
28+ # 设置请求头,模拟浏览器访问
29+ headers = {
30+ 'User-Agent' : 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1'
31+ }
32+
33+ # 发送请求获取 RSS 数据
34+ response = requests .get (rss_url , headers = headers , timeout = 10 )
35+ response .raise_for_status () # 检查请求是否成功
36+
37+ # 解析 XML
38+ root = ET .fromstring (response .content )
39+
40+ # XML 命名空间处理
41+ namespaces = {
42+ 'atom' : 'http://www.w3.org/2005/Atom' ,
43+ 'content' : 'http://purl.org/rss/1.0/modules/content/' ,
44+ 'dc' : 'http://purl.org/dc/elements/1.1/' ,
45+ 'media' : 'http://search.yahoo.com/mrss/'
46+ }
47+
48+ # 查找所有 item 元素
49+ items = root .findall ('.//item' )
50+
51+ if not items :
52+ # 尝试另一种查找方式
53+ items = root .findall ('.//channel/item' )
54+
55+ if not items :
56+ print ("未获取到新闻条目" )
57+ return []
58+
59+ print (f"成功获取到 { len (items )} 条新闻\n " )
60+
61+ # 提取新闻信息
62+ news_items = []
63+ for i , item in enumerate (items [:20 ], 1 ): # 限制显示前20条
64+ # 获取标题
65+ title_elem = item .find ('title' )
66+ title = title_elem .text if title_elem is not None else '无标题'
67+
68+ # 获取链接
69+ link_elem = item .find ('link' )
70+ link = link_elem .text if link_elem is not None else '#'
71+
72+ # 获取发布时间
73+ pub_date_elem = item .find ('pubDate' )
74+ if pub_date_elem is None :
75+ pub_date_elem = item .find ('dc:date' , namespaces )
76+ published = pub_date_elem .text if pub_date_elem is not None else ''
77+
78+ # 获取描述/摘要
79+ desc_elem = item .find ('description' )
80+ if desc_elem is None :
81+ desc_elem = item .find ('content:encoded' , namespaces )
82+ summary = ''
83+ if desc_elem is not None and desc_elem .text :
84+ summary = desc_elem .text [:200 ]
85+
86+ # 获取来源
87+ source_elem = item .find ('source' )
88+ source = source_elem .text if source_elem is not None else ''
89+
90+ # 构建新闻项
91+ news_item = {
92+ 'index' : i ,
93+ 'title' : title ,
94+ 'link' : link ,
95+ 'published' : published ,
96+ 'summary' : summary ,
97+ 'source' : source
98+ }
99+ news_items .append (news_item )
100+
101+ return news_items
102+
103+ except requests .RequestException as e :
104+ print (f"网络请求失败: { e } " )
105+ return []
106+ except ET .ParseError as e :
107+ print (f"XML 解析失败: { e } " )
108+ return []
109+ except Exception as e :
110+ print (f"获取新闻失败: { e } " )
111+ return []
112+
113+ def display_news (news_items ):
114+ """格式化显示新闻"""
115+ if not news_items :
116+ print ("没有新闻可显示" )
117+ return
118+
119+ print ("=" * 80 )
120+ print (f"📰 谷歌热点新闻 ({ datetime .now ().strftime ('%Y-%m-%d %H:%M:%S' )} )" )
121+ print ("=" * 80 )
122+
123+ for item in news_items :
124+ print (f"\n { item ['index' ]} . { item ['title' ]} " )
125+
126+ if item ['source' ]:
127+ print (f" 来源: { item ['source' ]} " )
128+
129+ if item ['published' ]:
130+ # 发布时间可能包含时间格式信息,直接显示
131+ print (f" 时间: { item ['published' ]} " )
132+
133+ if item ['summary' ]:
134+ print (f" 摘要: { item ['summary' ]} ..." )
135+
136+ print (f" 链接: { item ['link' ]} " )
137+ print ("-" * 60 )
138+
139+ def get_news_by_category ():
140+ """按类别获取新闻"""
141+ categories = {
142+ '1' : ('综合' , 'https://news.google.com/rss?hl=zh-CN&gl=CN&ceid=CN:zh-Hans' ),
143+ '2' : ('国际' , 'https://news.google.com/rss/topics/CAAqKggKIiRDQkFTRlFvSUwyMHZNRGx1YlY4U0JYQjBMVUpTR2dKQ1VpZ0FQAQ?hl=zh-CN&gl=CN&ceid=CN:zh-Hans' ),
144+ '3' : ('科技' , 'https://news.google.com/rss/topics/CAAqKggKIiRDQkFTRlFvSUwyMHZNRGRqTVhZU0JXVnVMVWRDR2dKQ1VpZ0FQAQ?hl=zh-CN&gl=CN&ceid=CN:zh-Hans' ),
145+ '4' : ('商业' , 'https://news.google.com/rss/topics/CAAqKggKIiRDQkFTRlFvSUwyMHZNRGx6TVdZU0JXVnVMVWRDR2dKQ1VpZ0FQAQ?hl=zh-CN&gl=CN&ceid=CN:zh-Hans' ),
146+ '5' : ('娱乐' , 'https://news.google.com/rss/topics/CAAqKggKIiRDQkFTRlFvSUwyMHZNREpxYW5RU0JXVnVMVWRDR2dKQ1VpZ0FQAQ?hl=zh-CN&gl=CN&ceid=CN:zh-Hans' ),
147+ '6' : ('体育' , 'https://news.google.com/rss/topics/CAAqKggKIiRDQkFTRlFvSUwyMHZNRFp1ZEdvU0JXVnVMVWRDR2dKQ1VpZ0FQAQ?hl=zh-CN&gl=CN&ceid=CN:zh-Hans' ),
148+ '7' : ('健康' , 'https://news.google.com/rss/topics/CAAqKggKIiRDQkFTRlFvSUwyMHZNR3QwTlRFU0JXVnVMVWRDR2dKQ1VpZ0FQAQ?hl=zh-CN&gl=CN&ceid=CN:zh-Hans' ),
149+ }
150+
151+ print ("\n 请选择新闻类别:" )
152+ for key , (name , _ ) in categories .items ():
153+ print (f" { key } . { name } " )
154+ print (" 0. 综合新闻 (默认)" )
155+
156+ choice = input ("\n 请输入数字选择类别 (默认: 0): " ).strip ()
157+
158+ if choice in categories :
159+ category_name , rss_url = categories [choice ]
160+ print (f"\n 正在获取「{ category_name } 」类别新闻..." )
161+ return rss_url
162+ else :
163+ print ("\n 正在获取综合新闻..." )
164+ return categories ['1' ][1 ] # 默认返回综合新闻 URL
165+
166+ def main ():
167+ """主函数"""
168+ print ("=" * 80 )
169+ print ("📰 谷歌热点新闻爬取工具" )
170+ print ("=" * 80 )
171+
172+ # 获取用户选择的类别
173+ rss_url = get_news_by_category ()
174+
175+ # 获取新闻
176+ news_items = fetch_google_news (rss_url )
177+
178+ # 显示新闻
179+ if news_items :
180+ display_news (news_items )
181+
182+ # 保存到文件选项
183+ save_option = input ("\n 是否将新闻保存到文件? (y/N): " ).strip ().lower ()
184+ if save_option == 'y' :
185+ save_to_file (news_items )
186+ else :
187+ print ("未能获取到新闻,请检查网络连接或稍后重试" )
188+
189+ def save_to_file (news_items ):
190+ """将新闻保存到文件"""
191+ timestamp = datetime .now ().strftime ("%Y%m%d_%H%M%S" )
192+ filename = f"谷歌新闻_{ timestamp } .txt"
193+
194+ try :
195+ with open (filename , 'w' , encoding = 'utf-8' ) as f :
196+ f .write ("=" * 80 + "\n " )
197+ f .write (f"谷歌热点新闻 ({ datetime .now ().strftime ('%Y-%m-%d %H:%M:%S' )} )\n " )
198+ f .write ("=" * 80 + "\n \n " )
199+
200+ for item in news_items :
201+ f .write (f"{ item ['index' ]} . { item ['title' ]} \n " )
202+
203+ if item ['source' ]:
204+ f .write (f" 来源: { item ['source' ]} \n " )
205+
206+ if item ['published' ]:
207+ f .write (f" 时间: { item ['published' ]} \n " )
208+
209+ if item ['summary' ]:
210+ f .write (f" 摘要: { item ['summary' ]} ...\n " )
211+
212+ f .write (f" 链接: { item ['link' ]} \n " )
213+ f .write ("-" * 60 + "\n \n " )
214+
215+ print (f"✓ 新闻已保存到文件: { filename } " )
216+ except Exception as e :
217+ print (f"✗ 保存文件失败: { e } " )
218+
219+ if __name__ == "__main__" :
220+ try :
221+ main ()
222+ except KeyboardInterrupt :
223+ print ("\n \n 程序被用户中断" )
224+ sys .exit (0 )
225+ except Exception as e :
226+ print (f"\n 程序执行出错: { e } " )
227+ sys .exit (1 )
0 commit comments