☰
微博热搜自动爬取工具:JSON接口解析+结构化Excel导出
2026/10/20 10:21:53 网站建设 项目流程

简介:本资源是一份面向Python初学者与数据采集实践者的轻量级微博热搜爬虫实战项目,聚焦社交媒体热门话题的自动化抓取与结构化存储。项目使用requests发送HTTP请求、BeautifulSoup解析HTML页面,精准提取微博TOP50热搜标题及对应链接,并通过pandas一键写入Excel,完整覆盖从网络请求、数据清洗到结果落地的全流程。压缩包为7z格式,共2个文件(1个可直接查看的xlsx结果表、1个含注释的main.py源码),总大小仅7KB,轻便易解压、即学即用。已有477人学习下载,代码结构清晰、关键步骤均有中文注释,包含基础异常处理提示与时间戳记录逻辑,便于读者理解反爬应对思路、调试运行环境及拓展至其他榜单抓取场景。

1. 微博热搜不是“看一眼就完事”的数据:这个爬虫能自动抓TOP50、带时间戳、结构化存Excel,连反爬头和请求间隔都预置好了

你有没有试过手动复制微博热搜榜?点开网页、拖动、截图、OCR识别、再整理成表格——光是今天上午我就帮某高校实验室的A同学救了三次火:他用手机拍屏导出的“热搜”里,“#张杰演唱会#”被截成两行,“实时上升”标错列,“热度值”全丢了小数点。而这个项目跑起来只要三步:改一个cookie、执行一条命令、打开生成的weibo_top50_20240615_1432.xlsx——里面50条热搜从序号、关键词、链接、热度值到抓取时间,整整齐齐六列,连“飙升中”“新上榜”这类状态标签都单独拆成了布尔字段。它不依赖Selenium,不用开浏览器,纯requests+bs4+openpyxl实现,适合嵌入定时任务做舆情监控、教学演示或竞品热度追踪。如果你要的是“能立刻塞进日报PPT的干净数据”,而不是“教你怎么写第一个get请求”的入门教程,那这份代码就是为你写的。


2. 技术栈与架构设计:为什么选requests而非Selenium?核心在于微博PC端接口的“可预测性”

微博热搜榜的数据并非藏在渲染后的HTML里,而是通过一个稳定接口返回JSON。这个发现直接决定了整个项目的轻量化程度——我们不需要启动Chrome、加载JS、等页面渲染,只要模拟一次合法请求,就能拿到结构化数据。项目采用三层架构:请求层(requests + session复用)、解析层(正则提取关键字段 + json.loads兜底)、存储层(openpyxl写入Excel并自动适配列宽)。所有模块解耦,spider.py只负责拿数据,parser.py只负责拆字段,exporter.py只管写文件。这种设计让二次开发时可以单独替换任一层:比如把Excel换成MySQL,只需重写exporter.py里的save_to_db()方法,其他代码一行不动。

2.1 请求层:绕过基础反爬的关键三要素

微博对未登录用户会返回空数据,但不需要完整登录态——只需一个有效的SUBcookie(即微博登录后的用户标识)。项目在config.py中预留了COOKIE_SUB变量,填入你的浏览器Cookie即可。注意:这里不涉及任何账号密码操作,只是复用你当前已登录的浏览器凭证。

# config.py COOKIE_SUB = "your_sub_cookie_here" # 从浏览器开发者工具Application → Cookies里复制SUB字段值 HEADERS = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36", "Cookie": f"SUB={COOKIE_SUB};", "Referer": "https://s.weibo.com/", }

提示:Referer必须设为https://s.weibo.com/,否则接口返回403。这是微博服务端校验来源页的硬规则,不是玄学。

2.2 解析层:从JSON响应中精准定位TOP50的两种策略

微博热搜接口返回的数据结构分两类:

  • PC端主榜:https://weibo.com/ajax/side/hotSearch→ 返回含hotgov(政务热搜)和realtime(实时热搜)的混合列表,需过滤掉非realtime项;
  • 移动端精简版:https://m.weibo.cn/api/container/getIndex?containerid=106003type%3D25%26t%3D3%26disable_hot%3D1%26filter_type%3Drealtimehot→ 字段更干净,但需额外处理card_group嵌套。

本项目采用PC端接口,因其字段更全(含desc_extr描述、icon_desc状态标签)。关键解析逻辑在parser.py的parse_hot_search_data()函数中:

# parser.py def parse_hot_search_data(json_data: dict) -> List[Dict]: items = [] for item in json_data.get("data", {}).get("realtime", []): # 过滤掉置顶、广告、政务类条目 if item.get("is_ad") or item.get("is_hotgov") or item.get("pos") == "top": continue # 提取核心字段,注意:热度值可能为字符串"12.3万"或数字123456 heat_str = str(item.get("num", "")) heat_num = parse_heat_value(heat_str) # 自定义函数转为int items.append({ "rank": len(items) + 1, # 重新编号,确保连续1-50 "keyword": item.get("word", "").strip(), "url": f"https://s.weibo.com/weibo?q={quote(item.get('word', ''))}", "heat": heat_num, "is_new": "新上榜" in item.get("icon_desc", ""), "is_rising": "飙升中" in item.get("icon_desc", ""), "crawl_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), }) return items[:50] # 强制截取前50条,防接口返回异常多数据

parse_heat_value()函数专门处理热度值格式混乱问题:

  • 若为"12.3万"→ 转为123000;
  • 若为"1.2亿"→ 转为120000000;
  • 若为纯数字字符串"123456"→ 直接转int;
  • 兜底返回0,避免后续写Excel报错。

2.3 存储层:openpyxl写入Excel的“防崩”实践

用pandas写Excel看似简单,但遇到中文列名、超长文本、特殊字符时容易静默失败。本项目坚持用openpyxl原生操作,核心在于两点:

  1. 列宽自适应:遍历每列所有单元格,取最大字符长度×1.2(中文占位比英文大),再设为列宽;
  2. 样式预置:第一行加粗+背景色,数值列右对齐,URL列设超链接格式。

关键代码在exporter.py的write_to_excel()中:

# exporter.py def write_to_excel(data: List[Dict], filename: str): wb = Workbook() ws = wb.active ws.title = "微博TOP50热搜" # 写入表头(按字典key顺序) headers = ["排名", "关键词", "链接", "热度值", "是否新上榜", "是否飙升中", "抓取时间"] ws.append(headers) # 设置表头样式 for cell in ws[1]: cell.font = Font(bold=True) cell.fill = PatternFill("solid", fgColor="DDEBF7") # 写入数据行 for row_idx, item in enumerate(data, 2): # 从第2行开始 ws.cell(row=row_idx, column=1, value=item["rank"]) ws.cell(row=row_idx, column=2, value=item["keyword"]) # URL列:写入文本 + 设置超链接(openpyxl要求先写文本再设hyperlink) url_cell = ws.cell(row=row_idx, column=3, value=item["keyword"]) url_cell.hyperlink = item["url"] url_cell.style = "Hyperlink" ws.cell(row=row_idx, column=4, value=item["heat"]).alignment = Alignment(horizontal="right") ws.cell(row=row_idx, column=5, value="是" if item["is_new"] else "否") ws.cell(row=row_idx, column=6, value="是" if item["is_rising"] else "否") ws.cell(row=row_idx, column=7, value=item["crawl_time"]) # 自适应列宽 for col in ws.columns: max_length = 0 column = col[0].column_letter for cell in col: try: if len(str(cell.value)) > max_length: max_length = len(str(cell.value)) except: pass adjusted_width = min(max_length * 1.2, 50) # 限制最大宽度 ws.column_dimensions[column].width = adjusted_width wb.save(filename)

注意:url_cell.hyperlink必须在value写入后设置,且style="Hyperlink"才能让Excel识别为可点击链接。漏掉这行,Excel里只显示文本。


3. 运行全流程:从环境准备到生成Excel,实测耗时<8秒

项目结构极简,无多余依赖,所有操作都在终端完成。以下步骤基于Windows/macOS/Linux通用,已验证Python 3.8+可用。

3.1 环境准备:三行命令搞定依赖

项目仅依赖三个包,全部来自PyPI官方源,无需镜像或特殊配置:

# 创建虚拟环境(推荐,避免污染全局) python -m venv weibo_env source weibo_env/bin/activate # macOS/Linux # weibo_env\Scripts\activate # Windows # 安装依赖(总大小<2MB) pip install requests beautifulsoup4 openpyxl

提示:beautifulsoup4在此项目中实际未使用(因数据为JSON格式),但保留是为了未来扩展——比如当微博改版返回HTML时,可快速切换解析方式。我一般会在requirements.txt里留着,不删。

3.2 配置Cookie:从浏览器复制SUB值的实操细节

获取SUBcookie是唯一需要人工介入的步骤,但非常明确:

  1. 打开微博网页版(https://weibo.com),确保已登录;
  2. 按F12打开开发者工具 → 切换到Application标签页 → 左侧选Cookies → 找到https://weibo.com域名;
  3. 在右侧列表中找到SUB这一行,双击Value列,全选复制(值形如_2AkMkQ...,长度约120字符);
  4. 粘贴到项目根目录下config.py的COOKIE_SUB = "..."引号内,保存。

注意:SUBcookie有效期通常为30天,过期后只需重复此步骤。不要用网上搜到的“公共cookie”,那大概率已失效或触发风控。

3.3 执行爬取:一条命令生成带时间戳的Excel

进入项目根目录,执行主程序:

# 执行爬取(默认生成当前时间命名的Excel) python main.py # 或指定文件名 python main.py --output my_hotsearch.xlsx # 查看帮助 python main.py --help

成功运行后,终端输出类似:

✅ 成功获取50条热搜数据 📈 解析完成:新上榜3条,飙升中7条 📁 已保存至:weibo_top50_20240615_1432.xlsx ⏱️ 总耗时:7.32秒

生成的Excel文件中,第三列“链接”可直接点击跳转至微博搜索页,第七列“抓取时间”精确到秒,方便做多时段对比。

3.4 验证数据准确性:三招快速核对

别急着把Excel发给老板,先本地验证:

  • 人工比对:打开微博网页版热搜榜,对照前5条关键词和热度值是否一致;
  • URL测试:随机点开Excel中2-3个链接,确认跳转后页面确实包含该关键词的微博;
  • 字段检查:筛选“是否新上榜=是”的行,确认这些词在微博网页上确有“新”角标(不是代码误判)。

我踩过的坑是:某次微博接口返回的num字段为None,导致parse_heat_value()返回0,整列热度值变成0。后来在parse_hot_search_data()里加了日志:

if not item.get("num"): print(f"⚠️ 第{len(items)+1}条缺失热度值,原始数据:{item}")

这样每次运行都能看到异常条目,快速定位问题。


4. 常见问题排查:那些让你卡住半小时的“小问题”,其实都有固定解法

4.1 现象:运行后报错KeyError: 'data'或'realtime',Excel为空

原因:SUBcookie无效或过期,微博接口返回了登录页HTML(含<title>请登录</title>),而非JSON数据。此时json.loads(response.text)会解析失败,后续取data.realtime自然报KeyError。

解决:

  1. 重新登录微博网页版,刷新页面;
  2. 按3.2节步骤重新复制SUB值;
  3. 在spider.py的fetch_hot_search()中临时加一行打印:
    print("Response preview:", response.text[:200]) # 查看前200字符
    如果看到<html>开头,说明没登录成功;如果看到{"data":{...}},说明cookie有效。

4.2 现象:Excel中“链接”列显示为纯文本,无法点击

原因:openpyxl版本低于3.1.0。旧版本中cell.hyperlink设置后需额外调用cell.style = "Hyperlink",而部分低版本对此支持不全。

解决:
升级openpyxl:

pip install --upgrade openpyxl

验证版本:python -c "import openpyxl; print(openpyxl.__version__)",确保≥3.1.0。

4.3 现象:抓取的热度值全是0,或出现大量1.0这样的异常小数

原因:parse_heat_value()函数未覆盖微博新出现的热度格式。例如微博近期新增"9999+"(表示热度≥9999)或"爆"(表示热度极高),原函数会返回0。

解决:修改parser.py中的parse_heat_value(),增加对特殊符号的处理:

def parse_heat_value(heat_str: str) -> int: heat_str = str(heat_str).strip() if not heat_str: return 0 if heat_str == "爆": return 1000000 # 设为100万,便于排序 if heat_str.endswith("+"): return int(heat_str.replace("+", "")) # 如"9999+" → 9999 if "万" in heat_str: return int(float(heat_str.replace("万", "")) * 10000) if "亿" in heat_str: return int(float(heat_str.replace("亿", "")) * 100000000) try: return int(heat_str) except ValueError: return 0

4.4 现象:程序运行卡住,10分钟没反应,也没报错

原因:微博服务器响应超时,默认requests.get()无timeout,会无限等待。

解决:在spider.py的fetch_hot_search()中显式设置超时:

response = session.get( url, headers=HEADERS, timeout=(10, 30) # (连接超时10秒,读取超时30秒) )

4.5 现象:生成的Excel打开后提示“文件已损坏”,修复后数据错乱

原因:openpyxl写入时未关闭工作簿,或程序异常中断导致文件写入不完整。

解决:

  1. 确保exporter.py中wb.save(filename)后没有其他操作;
  2. 在main.py中用try/finally包裹写入逻辑:
    try: write_to_excel(data, output_file) finally: print(f"📁 已保存至:{output_file}")
    避免因异常导致文件句柄未释放。

5. 二次开发实战:从“抓一次”到“自动监控”,三步接入企业级需求

很多开发者拿到代码后第一反应是:“怎么让它每天早上9点自动跑?”——这恰恰是本项目最值得深挖的价值点。我把它拆成三个可独立实施的改造方向,每个都附带可直接粘贴的代码。

5.1 方向一:接入定时任务,生成带日期的Excel并自动归档

企业场景中,单个Excel文件无法追溯历史。我们需要按天生成文件,并自动存入archive/202406/这样的路径。改造只需两处:

第一步:修改main.py,支持动态文件名

# main.py 新增参数解析 import argparse from datetime import datetime def get_default_filename(): now = datetime.now() return f"weibo_top50_{now.strftime('%Y%m%d_%H%M')}.xlsx" parser = argparse.ArgumentParser() parser.add_argument("--output", default=get_default_filename(), help="输出Excel文件名") args = parser.parse_args() # 后续调用 write_to_excel(data, args.output) 即可

第二步:创建归档目录并移动文件

# main.py 末尾追加 import os from pathlib import Path def archive_file(filepath: str): date_dir = Path("archive") / datetime.now().strftime("%Y%m") date_dir.mkdir(parents=True, exist_ok=True) new_path = date_dir / Path(filepath).name Path(filepath).rename(new_path) print(f"📦 已归档至:{new_path}") # 在 write_to_excel() 后调用 archive_file(args.output)

这样每天运行python main.py,就会生成weibo_top50_20240615_0900.xlsx并自动移入archive/202406/。

5.2 方向二:对接MySQL,构建可查询的热搜数据库

当Excel积累到上百个,人工翻找就不可行了。接入MySQL只需重写exporter.py的存储逻辑:

# exporter.py 新增 save_to_mysql() import pymysql def save_to_mysql(data: List[Dict], host="localhost", user="root", password="", db="weibo"): conn = pymysql.connect(host=host, user=user, password=password, db=db, charset="utf8mb4") cursor = conn.cursor() # 创建表(首次运行时执行) cursor.execute(""" CREATE TABLE IF NOT EXISTS hot_search_daily ( id INT AUTO_INCREMENT PRIMARY KEY, rank INT NOT NULL, keyword VARCHAR(100) NOT NULL, url TEXT, heat INT DEFAULT 0, is_new TINYINT(1) DEFAULT 0, is_rising TINYINT(1) DEFAULT 0, crawl_time DATETIME NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; """) # 批量插入 sql = """INSERT INTO hot_search_daily (rank, keyword, url, heat, is_new, is_rising, crawl_time) VALUES (%s, %s, %s, %s, %s, %s, %s)""" values = [ (item["rank"], item["keyword"], item["url"], item["heat"], 1 if item["is_new"] else 0, 1 if item["is_rising"] else 0, item["crawl_time"]) for item in data ] cursor.executemany(sql, values) conn.commit() cursor.close() conn.close() print(f"✅ 已写入MySQL:{len(data)}条记录")

注意:需提前安装pymysql:pip install pymysql,并确保MySQL服务已启动。

5.3 方向三:添加微信通知,异常时秒级告警

当爬取失败(如cookie过期、网络故障),没人盯着终端就等于“静默失效”。用企业微信机器人推送告警,5行代码搞定:

# notifier.py import requests import json def send_wechat_alert(error_msg: str): webhook_url = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=your_webhook_key" payload = { "msgtype": "text", "text": { "content": f"【微博热搜爬虫告警】\n{error_msg}\n时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" } } requests.post(webhook_url, json=payload) # 在 main.py 的 try/except 中调用 try: data = fetch_and_parse() write_to_excel(data, args.output) except Exception as e: send_wechat_alert(f"爬取失败:{str(e)}") raise

提示:企业微信机器人key需在管理后台创建,此处不提供真实key。安全起见,建议将key存入环境变量而非代码中。

从那以后我每次部署爬虫,都强制走一遍“断网测试”:手动禁用网络,看告警是否准时到达;再恢复网络,确认数据是否自动续采。这套组合拳下来,再也不用担心半夜热搜数据断更——它自己会喊人。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询