新浪滚动新闻按日期爬虫脚本

本文参考知乎专栏文章 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中。