做高校科研那会儿,我接过一个死命令:抓取200所高校过去5年的百度指数日数据。要求一周交付。
第一反应是“写个爬虫分分钟”。结果呢?被封IP、验证码、数据错乱、采集到一半崩溃……你猜怎么着?折腾了两周,最后乖乖买了择云软件的批量采集工具。但这段踩坑经历,倒让我把百度指数采集的技术细节摸了个透。
今天不聊学术,就聊批量采集百度指数到底难在哪,以及我们怎么用工程手段解决它。
为什么高校研究绕不开百度指数批量采集
先摆结论:百度指数是社会关注度的量化代理指标,尤其适合做时间序列分析和地域对比。
我们团队当时研究的是“双一流”高校的网络关注度与生源质量的关系。需要的数据维度包括:
- 每日搜索指数(2018-2023)
- 地域分布(省份级)
- 人群属性(年龄、性别)
关键问题是:百度指数官方不提供批量导出API。单个关键词的日数据可以在网页上看到,但200个关键词 × 5年 × 365天 = 36.5万个数据点。手工点?不现实。
所以批量采集工具成了刚需。市面上的方案无非三种:
- 开源爬虫框架(如Selenium、Puppeteer)—— 自己写,但容易挂
- 第三方数据服务(如百度指数API付费接口)—— 贵,且数据延迟
- 专业批量采集软件(如择云软件的百度指数批量采集工具)—— 省心,但得花钱
我们一开始选方案1,因为穷。结果发现这是一个典型的“看似简单实则深渊”的项目。
技术复现:百度指数批量采集的三大核心难点
难点一:反爬机制——不是简单的Cookie登录
百度指数的数据接口是这样:https://index.baidu.com/api/SearchApi/index?area=0&word=清华大学&days=365
你以为带个Cookie就能拿数据?太天真。
百度做了几层防护:
- Referer校验:请求必须来自百度指数页面
- User-Agent轮换:同一UA高频请求直接封
- 请求间隔限制:单IP每秒超过3次就触发验证码
- 参数加密:
words参数在URL中会动态生成sign签名
⚠️ 踩坑警告:我们第一版用requests库直接请求,跑了不到100个关键词,IP就被封了24小时。换代理IP池?免费的代理质量差,付费的又增加成本。
难点二:数据解析——JSON中的嵌套陷阱
百度指数返回的JSON结构长这样:
{
"data": {
"userIndexes": {
"清华大学": {
"index": [
{"date": "2020-01-01", "value": 12345},
{"date": "2020-01-02", "value": 11890}
]
}
}
}
}
看着简单?但实际返回里还有relatedWords、avgIndex、trend等嵌套字段。更坑的是:当关键词无数据时,返回的index数组为空,但value字段可能为null。
我们一开始没做空值校验,导致数据库里写入了大量None,后期清洗数据花了3天。
难点三:长时间采集的任务状态管理
这是最容易被低估的难点。
批量采集200个关键词 × 5年数据,如果每个关键词请求间隔2秒(为了防封),总耗时就是:
200 × 2s = 400秒 ≈ 7分钟。
但实际远比这个长,因为:
- 百度指数接口单次最多返回365天数据,要5年就得请求5次
- 地域分布数据需要单独请求另一个接口
- 网络波动导致请求超时重试
所以实际耗时在30分钟到2小时之间。在这期间,程序可能因为:
- 内存溢出(数据积压)
- 网络断开
- 百度临时封禁
- 电脑休眠
——导致中途崩溃。然后你得从头再来。
这就是为什么商业工具必须有断点续爬功能。
我们的解决方案:自研轻量级采集框架
在连续崩溃3次后,我们决定重构。核心设计思路是:状态持久化 + 分层容错。
架构示意(ASCII版)
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 任务队列 │─────▶│ 请求调度器 │─────▶│ 代理中间件 │
│ (Redis/DB) │ │ (限流/重试) │ │ (IP轮换) │
└─────────────┘ └─────────────┘ └─────────────┘
│
▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 数据清洗 │◀─────│ 响应解析器 │◀─────│ 百度指数API│
│ (去重/补全) │ │ (JSON处理) │ │ │
└─────────────┘ └─────────────┘ └─────────────┘
关键代码片段:带重试的请求封装
我们用Python的tenacity库做重试控制,配合fake_useragent轮换UA。
from tenacity import retry, stop_after_attempt, wait_exponential
import requests
from fake_useragent import UserAgent
class BaiduIndexFetcher:
def __init__(self, cookie_str):
self.session = requests.Session()
self.session.headers.update({
"Cookie": cookie_str,
"Referer": "https://index.baidu.com/",
"User-Agent": UserAgent().random
})
@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=2, max=10))
def fetch_daily_data(self, keyword, start_date, end_date):
"""
获取单个关键词的日指数数据
重试策略:失败后等待 2^n 秒(n=1,2,3),最多重试3次
"""
url = "https://index.baidu.com/api/SearchApi/index"
params = {
"word": keyword,
"area": 0,
"startDate": start_date,
"endDate": end_date
}
try:
resp = self.session.get(url, params=params, timeout=10)
resp.raise_for_status()
data = resp.json()
# 校验数据有效性
if data.get("status") != 0:
raise ValueError(f"API返回错误状态: {data.get('message')}")
# 提取指数数组,处理空值
index_list = data.get("data", {}).get("userIndexes", {}).get(keyword, {}).get("index", [])
if not index_list:
# 返回空数组表示该时段无数据,不视为异常
return []
return [{"date": item["date"], "value": item["value"] or 0} for item in index_list]
except requests.exceptions.RequestException as e:
# 网络错误触发重试
raise RuntimeError(f"请求失败: {e}") from e
except KeyError as e:
# JSON解析错误,记录日志后重试
raise RuntimeError(f"数据结构异常: {e}") from e
逐行解释:
@retry装饰器:失败后等待2、4、8秒再重试,避免被百度封禁area=0表示全国数据,如果要地域维度需要调另一个接口- 空
index_list时返回[]而不是抛异常,因为有些冷门关键词确实没数据 value or 0:当value为null时转成0,保证数据完整性
断点续爬的状态表设计
我们用SQLite维护任务状态,关键字段:
CREATE TABLE crawl_tasks (
keyword TEXT PRIMARY KEY,
start_date TEXT,
end_date TEXT,
last_success_date TEXT, -- 最近成功采集到的日期
retry_count INTEGER DEFAULT 0,
status TEXT CHECK(status IN ('pending', 'running', 'done', 'failed'))
);
每次请求前查询last_success_date,如果已经有部分数据就只请求缺失的日期段。这样即使程序崩溃,重启后也能从断点继续。
💡 经验:断点续爬这个功能,看着不起眼,但在批量任务中能省下80%的重复劳动。择云的工具在这方面做得很好,它的“续爬”机制还会自动检测数据完整性,比我们自建的粗糙版本强不少。
真实性能对比:自研 vs 商业工具
我们拿同样50个关键词(每个拉5年日数据)做对比:
| 指标 | 自研框架(优化后) | 择云批量采集工具 |
|---|---|---|
| 总耗时 | 18分钟 | 9分钟 |
| 被封次数 | 2次(自动解封) | 0次 |
| 数据缺失率 | 3.2%(需二次补采) | 0.5% |
| 开发成本 | 3人天 | 0 |
| 维护成本 | 高(百度更新接口需改代码) | 低(厂商适配) |
说白了自己写胜在灵活,但商业工具的稳定性和效率确实碾压。尤其当研究需要采集上千个关键词时,自研方案的边际维护成本直线上升。
给高校研究者的三条实用建议
1. 选关键词别贪多
我们一开始选了500个关键词,结果采集了3天,后期分析时发现很多词搜索量极低(日指数<100),数据噪声太大,根本没法建模。
建议:先用百度指数官网的“趋势对比”功能(最多5个词)做预实验,看哪些词的波动与你的研究假设相关,再批量采集。
2. 地域数据要单独处理
百度指数的地域分布接口返回的是各省份占比,不是绝对值。如果你要做跨区域对比,需要结合全国总指数反推各省的绝对搜索量。
这个计算逻辑挺绕的,我们当时写了个转换函数:
def calc_region_abs(province_ratio, national_index):
"""province_ratio: 0-1之间的浮点数"""
return round(national_index * province_ratio / 100, 2)
注意province_ratio的精度是百分比,但接口返回的是小数还是整数?不同时期不一样。百度改过格式,所以要做好兼容。
3. 数据清洗别偷懒
采集下来的原始数据常有:
- 节假日指数为0(实际是百度不更新)
- 个别日期缺失(网络丢包)
- 异常尖峰(可能是爬虫触发了反爬导致返回了测试数据)
我们的清洗规则:
- 缺失日期用线性插值补全
- 超过均值±3倍标准差的值标记为异常,人工复核
最后说两句
批量采集百度指数这件事,技术门槛不算高,但坑多且细。如果你只是为了做研究,时间有限,我真心建议直接买成熟工具——择云那款我们用了半年,没出过大问题,云CK支持也能保证长期任务的稳定性。
但如果你想练手,或者对反爬技术感兴趣,自己造轮子也是极好的学习路径。两种选择没有对错,只看你的目标是“出成果”还是“学技术”。
我们团队后来把采集的数据用来训练就业舆情预测模型,MAPE做到22.9%,投了篇核心期刊。工具只是手段,数据背后的洞察才是价值所在。
祝各位采集顺利,少踩我踩过的坑。


