如何解决谷歌新闻RSS爬虫重定向问题?
背景与问题
在开发新闻爬虫项目时,我们经常需要从谷歌新闻获取数据。传统的做法是直接调用谷歌搜索API,但这种方式存在明显缺陷:
- 容易触发验证码机制
- 频繁出现429错误(请求过多)
- 需要消耗大量代理资源
相比之下,谷歌新闻RSS提供了一个更优雅的解决方案:数据量大、稳定可靠、不消耗代理资源。然而,它也带来了一个新的技术挑战:无限重定向问题。
问题分析:无限重定向的原因
谷歌新闻RSS返回的URL并非真实的新闻链接,而是经过编码的重定向链接。当爬虫尝试访问这些链接时,会遇到无限重定向到原地址的问题,无法获取到真实的新闻内容。
传统解决方案(已失效)
早期的解决方案是通过正则表达式从响应文本中提取真实链接:
def process_response(self, request, response, spider):
if "https://news.google.com/rss/articles" in response.url or "https://news.google.com/articles" in response.url:
regex = r"rel=\"nofollow\">([http|https]{1,}://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|])"
search_result = re.findall(regex, response.text)
问题:随着谷歌新闻架构的更新,这种方法已经不再有效。
现代解决方案:googlenewsdecoder
通过开发者工具分析谷歌新闻的网络请求,我发现了新的解码机制。幸运的是,社区已经有开发者将解决方案封装成了googlenewsdecoder包。
快速开始
from googlenewsdecoder import new_decoderv1
def main():
source_urls = [
"https://news.google.com/rss/articles/CBMiyAFBVV85cUxPcEZDc3JhSEZJdnJESmxicTZFNjltbFNZb2p6UFFxanZFRTVBQ0hJdndIY25kYVU1d2RFbGdFbVU5N2lZRjlGOXhsSUZzM1Ezb1lwaWhkMXFyei1YUGdKd3FXRUlheE51NloyRmVOdTJoWXZ0dGxvLVNQM3lNaEk0TURNZGFTMUp4YWVPX1ZXTk85UmRzSWdvZGF5N012SXFDdzJkM05UZHNXVlFWN3Y3NVB0N1hXSXpiQjRfS2hXTXZHY0psV2UyVNIByAFBVV85cUxPcEZDc3JhSEZJdnJESmxicTZFNjltbFNZb2p6UFFxanZFRTVBQ0hJdndIY25kYVU1d2RFbGdFbVU5N2lZRjlGOXhsSUZzM1Ezb1lwaWhkMXFyei1YUGdKd3FXRUlheE51NloyRmVOdTJoWXZ0dGxvLVNQM3lNaEk0TURNZGFTMUp4YWVPX1ZXTk85UmRzSWdvZGF5N012SXFDdzJkM05UZHNXVlFWN3Y3NVB0N1hXSXpiQjRfS2hXTXZHY0psV2UyVA?oc=5&hl=en-US&gl=US&ceid=US:en"
]
for url in source_urls:
try:
decoded_url = new_decoderv1(url)
if decoded_url.get("status"):
print("解码成功:", decoded_url["decoded_url"])
else:
print("解码失败:", decoded_url["message"])
except Exception as e:
print(f"发生错误: {e}")
if __name__ == "__main__":
main()
输出示例:
解码成功: https://worldsoccertalk.com/amp/news/cristiano-ronaldo-loses-his-third-final-with-al-nassr-how-does-that-compare-to-lionel-messi/
技术原理深度解析
谷歌新闻URL编码结构
谷歌新闻的URL通常采用以下格式:
https://news.google.com/articles/{base64_encoded_string}
https://news.google.com/read/{base64_encoded_string}
其中{base64_encoded_string}是经过Base64编码的原始URL信息。
解码算法演进
早期解码算法(版本1)
最初的解码器采用直接Base64解码方式:
def decode_google_news_url(source_url: str) -> str:
"""早期版本的解码函数"""
url = urlparse(source_url)
path = url.path.split("/")
if (url.hostname == "news.google.com" and
len(path) > 1 and path[len(path) - 2] == "articles"):
base64_str = path[len(path) - 1]
decoded_bytes = base64.urlsafe_b64decode(base64_str + "==")
decoded_str = decoded_bytes.decode("latin1")
# 移除协议特定的前缀和后缀
prefix = bytes([0x08, 0x13, 0x22]).decode("latin1")
if decoded_str.startswith(prefix):
decoded_str = decoded_str[len(prefix):]
suffix = bytes([0xD2, 0x01, 0x00]).decode("latin1")
if decoded_str.endswith(suffix):
decoded_str = decoded_str[:-len(suffix)]
# 处理长度字节
bytes_array = bytearray(decoded_str, "latin1")
length = bytes_array[0]
if length >= 0x80:
decoded_str = decoded_str[2:length + 1]
else:
decoded_str = decoded_str[1:length + 1]
return decoded_str
解码步骤:
- 提取Base64编码字符串
- 进行URL安全的Base64解码
- 移除特定的协议前缀(0x08, 0x13, 0x22)
- 移除特定的后缀(0xD2, 0x01, 0x00)
- 根据长度字节提取实际的URL
现代解码算法(版本2)
随着谷歌新闻系统的升级,简单的Base64解码已无法处理所有URL。新版本采用了更复杂的三步解码机制:
步骤1:提取Base64字符串
def get_base64_str(self, source_url: str) -> dict:
"""从源URL中提取Base64编码字符串"""
url = urlparse(source_url)
path = url.path.split("/")
if (url.hostname == "news.google.com" and
len(path) > 1 and path[-2] in ["articles", "read"]):
return {"status": True, "base64_str": path[-1]}
return {"status": False, "message": "无效的谷歌新闻URL"}
步骤2:获取解码参数
def get_decoding_params(self, base64_str: str) -> dict:
"""获取解码所需的签名和时间戳参数"""
url = f"https://news.google.com/articles/{base64_str}"
response = requests.get(url, proxies=self.proxies)
parser = HTMLParser(response.text)
data_element = parser.css_first("c-wiz > div[jscontroller]")
if not data_element:
return {"status": False, "message": "无法找到必需的DOM元素"}
return {
"status": True,
"signature": data_element.attributes.get("data-n-a-sg"),
"timestamp": data_element.attributes.get("data-n-a-ts"),
"base64_str": base64_str,
}
步骤3:API解码
def decode_url(self, signature: str, timestamp: str, base64_str: str) -> dict:
"""通过谷歌内部API进行最终解码"""
url = "https://news.google.com/_/DotsSplashUi/data/batchexecute"
payload = [
"Fbv4je",
f'["garturlreq",[["X","X",["X","X"],null,null,1,1,"US:en",null,1,null,null,null,null,null,0,1],"X","X",1,[1,1,1],1,1,null,0,0,null,0],"{base64_str}",{timestamp},"{signature}"]',
]
headers = {
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
"User-Agent": "Mozilla/5.0 (compatible; GoogleNewsDecoder/1.0)",
}
response = requests.post(
url,
headers=headers,
data=f"f.req={quote(json.dumps([[payload]]))}",
proxies=self.proxies,
)
# 解析响应数据
parsed_data = json.loads(response.text.split("\n\n")[1])[:-2]
decoded_url = json.loads(parsed_data[0][2])[1]
return {"status": True, "decoded_url": decoded_url}
实际应用建议
1. 错误处理
在生产环境中,务必添加完善的错误处理机制:
def safe_decode_google_news_url(url):
"""安全的URL解码函数"""
try:
result = new_decoderv1(url)
if result.get("status"):
return result["decoded_url"]
else:
logging.error(f"解码失败: {result.get('message', '未知错误')}")
return None
except Exception as e:
logging.error(f"解码过程中发生异常: {str(e)}")
return None
2. 批量处理
对于大量URL的批量处理,建议添加适当的延时和重试机制:
import time
from typing import List, Dict
def batch_decode_urls(urls: List[str], delay: float = 0.5) -> List[Dict]:
"""批量解码URL"""
results = []
for i, url in enumerate(urls):
result = safe_decode_google_news_url(url)
results.append({
"original_url": url,
"decoded_url": result,
"success": result is not None
})
# 添加延时避免被限制
if i < len(urls) - 1:
time.sleep(delay)
return results
3. 性能优化
对于高频使用场景,可以考虑添加缓存机制:
from functools import lru_cache
@lru_cache(maxsize=1000)
def cached_decode_url(url: str) -> str:
"""带缓存的URL解码"""
return safe_decode_google_news_url(url)
总结
谷歌新闻RSS爬虫的重定向问题通过googlenewsdecoder包得到了有效解决。该包提供了:
- 简单易用的API接口
- 高度可靠的解码算法
- 良好的兼容性,支持多种URL格式
- 持续更新,适应谷歌新闻的变化
对于需要从谷歌新闻获取数据的开发者来说,这是一个不可多得的工具。建议在生产环境中使用时,注意添加适当的错误处理、限流和缓存机制。
参考资源
- googlenewsdecoder GitHub仓库 - 项目源码
- PyPI包页面 - 安装和文档