新浪滚动新闻按日期爬虫脚本
本文参考知乎专栏文章 https://zhuanlan.zhihu.com/p/71925619
新浪的滚动新闻 链接是 https://news.sina.com.cn/roll/#pageid=153&lid=2509&k=&num=50&page=1,这个滚动新闻页如果要爬取新闻标题,#pageid=153&lid=2509&k=&num=50&page=1 是用于在 news.sina.com.cn/roll/ 这个页面内定位特定位置或执行某些客户端脚本的信息,而不是用于向服务器请求不同的内容。 # 开头的 URL 是前端 hash router生成的虚假链接,用这个构造url是请求不了的。新闻是动态加载进去的,通过像后端发请求,后端返回一个json回来,再加载出新闻页面。用f12监控网络请求,会发现选择某个日期时,都发送了这样的请求:https://feed.mix.sina.com.cn/api/roll/get?pageid=153&lid=2516&etime=1746374400&stime=1746460800&ctime=1746460800&date=2025-05-05&k=&num=50&page=1&r=0.4315126633969012&callback=jQuery111209184950013789029_1747390433128&_=1747390433138 这就是真正的后端请求
以下代码来自知乎文章。这个就是滚动新闻当天最新时刻的请求。lid填入不同的数字,相当于底下勾选不同的类型新闻。r可能是随机数。
base_url = 'https://feed.mix.sina.com.cn/api/roll/get?pageid=153&lid={}&k=&num=50&page={}&r={}'
# "2509": "全部",
# "2510": "国内",
# "2511": "国际",
# "2669": "社会",
# "2512": "体育",
# "2513": "娱乐",
# "2514": "军事",
# "2515": "科技",
# "2516": "财经",
# "2517": "股市",
# "2518": "美股",
# "2968": "国内_国际",
# "2970": "国内_社会",
# "2972": "国际_社会",
# "2974": "国内国际社会"
如果要爬取指定日期,根据我们得到的请求格式,多了etime、stime、ctime、date这几个参数
其中,etime、stime、ctime是unix时间戳。先用硬编码的链接,看看这样的请求能否得到一个和网页内容对应的返回内容。
url="https://feed.mix.sina.com.cn/api/roll/get?pageid=153&lid=2516&etime=1725984000&stime=1726070400&ctime=1726070400&date=2024-09-11&k=&num=50&page=2&r=0.08198643836947861&callback=jQuery111209184950013789029_1747390433128&_=1747390433139"
response = requests.get(url, headers=headers, timeout=15)
response是得到的响应。得到响应后,首先要根据状态判断是否正常收到了响应。这个响应有可能是html,有可能是json。要对响应内容进行解析。根据响应的内容类型(HTML、JSON、XML 等)进行解析。
response.raise_for_status() # 检查 HTTP 请求的响应状态码,如果不对,是不会继续运行的
response.encoding = 'utf-8'
# 根据输出了一下response的内容,发现是json
text = response.text
import re
import json
# 匹配被 callback 包裹的 JSON 数据(允许前有 try{...} 包裹)
match = re.search(r'jQuery\d+_\d+\((\{.*\})\)', text)
if match:
json_str = match.group(1)
data = json.loads(json_str)
print(data.keys()) # 看一下结构,比如 'result'
else:
raise ValueError("无法解析 JSONP 响应")
发现得到的数据的确和网页上指定日期的新闻标题数据一样。 能够构造出正确地请求,那么爬取数据就简单了。 ai可以很容易的发现时间戳和日期的对应关系,以及处理方式。从而写出正式爬取数据的代码。
import requests
import json
import time
from datetime import datetime, timedelta
import pandas as pd
import random
import re
# 设置关键词列表
keywords = [
"利好", "上涨", "回升", "复苏", "牛市", "突破", "获利", "回暖", "增持", "吸引",
"恐慌", "崩盘", "利空", "暴跌", "下跌", "回调", "泡沫", "熊市", "抛售", "减持",
"震荡", "波动", "调整", "观望", "分化", "不确定", "持平", "交投"
]
# 请求头
headers = {
'User-Agent': 'Mozilla/5.0',
'Accept-Language': 'zh-CN,zh;q=0.9',
}
# 用于构造URL参数的函数
def construct_url(day_ts):
etime = day_ts
stime = etime + 86400
ctime = stime
date_str = datetime.utcfromtimestamp(etime).strftime('%Y-%m-%d')
r = str(random.random())
callback = f"jQuery1112{random.randint(1000000000000, 9999999999999)}_{int(time.time()*1000)}"
ts = int(time.time() * 1000)
url = (
f"https://feed.mix.sina.com.cn/api/roll/get?pageid=153&lid=2516"
f"&etime={etime}&stime={stime}&ctime={ctime}&date={date_str}"
f"&k=&num=50&page={{page}}&r={r}&callback={callback}&_={ts}"
)
return url, date_str
# 主函数:从起始日期抓取若干天,每天至少8条符合关键词的新闻
def crawl_sina_news(start_date_str, num_days):
start_ts = int(datetime.strptime(start_date_str, "%Y-%m-%d").timestamp())
all_results = []
for day_offset in range(num_days):
day_ts = start_ts + day_offset * 86400
url_template, date_str = construct_url(day_ts)
page = 1
matched_titles = []
while len(matched_titles) < 8:
url = url_template.format(page=page)
try:
response = requests.get(url, headers=headers, timeout=15)
response.raise_for_status()
text = response.text
# 提取 JSONP 中的 JSON
match = re.search(r'jQuery\d+_\d+\((\{.*\})\)', text)
if not match:
print(f"[{date_str}] 页面{page} 无法解析 JSONP")
break
data = json.loads(match.group(1))
data_list = data.get("result", {}).get("data", [])
if not data_list:
print(f"[{date_str}] 页面{page} 没有数据")
break
for item in data_list:
title = item.get("title", "")
if any(kw in title for kw in keywords):
matched_titles.append({
"title": title,
"url": item.get("url"),
"date": date_str
})
if len(matched_titles) >= 8:
break
page += 1
time.sleep(random.uniform(0.5, 1.5))
except Exception as e:
print(f"[{date_str}] 页面{page} 请求出错: {e}")
break
print(f"[{date_str}] 获取到符合条件的标题数:{len(matched_titles)}")
all_results.extend(matched_titles)
# 返回结果为 DataFrame
return pd.DataFrame(all_results)
# 示例调用:从 2024-09-09 开始抓取 3 天
df_result = crawl_sina_news("2024-09-09", 250)
# 显示结果
df_result
代码会开源在github中。