1. 项目概述:百度指数关键词趋势采集实战
作为一名长期深耕Python爬虫领域的开发者,我经常需要获取各类搜索引擎的关键词趋势数据。百度指数作为国内权威的搜索热度指标,其数据对于市场分析、舆情监测和SEO优化都具有重要价值。今天我将分享一套经过实战检验的百度指数采集方案,这个方案已经稳定运行了两年多,累计采集了超过50万条关键词数据。
不同于简单的静态页面爬取,百度指数采用了动态数据加载和加密传输机制,需要处理以下几个核心难点:
- 动态Token生成机制
- 数据加密传输与解密
- 反爬虫策略应对
- 高频请求的稳定性保障
这个项目适合有一定Python基础,想要提升爬虫实战能力的开发者。即使你是爬虫新手,只要跟着我的步骤操作,也能在2小时内完成整个采集系统的搭建。
2. 技术选型与整体架构设计
2.1 为什么选择这套技术栈?
在多次迭代后,我最终确定了以下技术组合:
- 请求库:Requests + aiohttp(同步+异步混合)
- 解析库:BeautifulSoup + PyExecJS
- 存储方案:MySQL + CSV双备份
- 调度控制:APScheduler
选择这样的组合主要基于三个考量:
- 开发效率:BeautifulSoup比lxml更易上手,适合快速开发
- 性能平衡:同步请求保证稳定性,异步提升批量采集效率
- 容错机制:数据库+文件双存储避免单点故障
2.2 系统架构流程图
整个采集系统分为四个核心模块:
code复制1. 认证模块 → 2. 数据获取模块 → 3. 解密模块 → 4. 存储模块
每个模块都设计了重试机制和异常处理,确保单点故障不会导致整个流程中断。特别在认证环节,我们实现了Token自动刷新机制,解决了百度指数每小时更新Token带来的中断问题。
3. 环境准备与依赖安装
3.1 基础环境配置
建议使用Python 3.8+版本,这个版本在异步IO和加密库兼容性方面表现最好。以下是必须安装的依赖包:
bash复制pip install requests aiohttp beautifulsoup4 pyexecjs pymysql apscheduler cryptography
对于Windows用户,还需要额外安装Node.js环境(v14+),因为PyExecJS需要调用Node来执行百度指数的前端解密逻辑。
3.2 数据库准备
创建MySQL数据表时,建议采用以下结构:
sql复制CREATE TABLE `baidu_index` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`keyword` varchar(255) NOT NULL,
`date` date NOT NULL,
`index_value` int(11) NOT NULL,
`region` varchar(50) DEFAULT NULL,
`device_type` enum('pc','mobile','all') DEFAULT 'all',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_keyword_date` (`keyword`,`date`,`region`,`device_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
这个表结构设计考虑了三个关键点:
- 联合唯一索引防止重复数据
- 支持按地区/设备类型细分统计
- 使用utf8mb4编码避免特殊字符存储问题
4. 核心实现:认证与请求层
4.1 获取认证Cookie
百度指数需要先通过验证才能获取数据。我们模拟登录流程获取关键Cookie:
python复制def get_auth_cookie():
session = requests.Session()
login_url = "https://index.baidu.com/v2/login.html"
# 关键请求头
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)...",
"Referer": "https://index.baidu.com/v2/main/index.html"
}
response = session.get(login_url, headers=headers)
# 提取关键Cookie
auth_cookie = "; ".join([f"{k}={v}" for k,v in session.cookies.items()])
return auth_cookie
注意:百度指数会检测User-Agent的完整性,建议使用真实浏览器的完整UA字符串,而不仅仅是简单的"python-requests"。
4.2 动态Token生成机制
百度指数的API请求需要携带动态生成的token,这个token每小时变化一次。我们通过分析前端代码找到了生成逻辑:
python复制import execjs
def generate_token():
with open('baidu_token.js', 'r', encoding='utf-8') as f:
js_code = f.read()
ctx = execjs.compile(js_code)
return ctx.call('generateToken')
对应的baidu_token.js文件需要从百度指数前端代码中提取关键函数。这里有个小技巧:在浏览器控制台执行JSON.stringify(window.xxx)可以快速获取前端加密函数。
5. 数据获取与解密处理
5.1 构造API请求
百度指数的核心数据接口需要以下参数:
python复制params = {
"word": json.dumps([{"name": keyword, "wordType": 1}]),
"area": region_code, # 0为全国
"startDate": start_date.strftime("%Y-%m-%d"),
"endDate": end_date.strftime("%Y-%m-%d"),
"token": current_token,
"type": "day" # 可改为week/month
}
特别要注意word参数的格式,即使只有一个关键词也需要用数组包裹,且必须包含wordType字段。
5.2 解密数据流程
获取到的数据是经过加密的,需要经过三层处理:
- Base64解码:将返回的密文解码为二进制
- AES解密:使用动态生成的密钥解密
- Gzip解压:最终得到JSON格式的原始数据
解密核心代码:
python复制from Crypto.Cipher import AES
import gzip
def decrypt_data(encrypt_data, key):
# 第一层:Base64解码
decoded_data = base64.b64decode(encrypt_data)
# 第二层:AES解密
iv = decoded_data[:16]
cipher = AES.new(key, AES.MODE_CBC, iv)
decrypted = cipher.decrypt(decoded_data[16:])
# 第三层:Gzip解压
return gzip.decompress(decrypted).decode('utf-8')
实测发现:百度指数使用的AES密钥每小时变化一次,但可以通过同一个token接口获取,建议将token和密钥的获取放在同一个函数中。
6. 数据存储与优化策略
6.1 批量插入优化
当采集大量关键词时,建议使用批量插入提升效率:
python复制def batch_insert(data_list):
sql = """INSERT INTO baidu_index
(keyword, date, index_value, region, device_type)
VALUES (%s, %s, %s, %s, %s)
ON DUPLICATE KEY UPDATE index_value=VALUES(index_value)"""
with connection.cursor() as cursor:
cursor.executemany(sql, data_list)
connection.commit()
使用ON DUPLICATE KEY UPDATE可以避免重复数据导致插入失败,同时更新可能变化的指数值。
6.2 文件备份策略
除了数据库存储,建议同时保存原始JSON文件:
python复制def save_to_file(data, keyword):
filename = f"data/{keyword}_{datetime.now().strftime('%Y%m%d')}.json"
os.makedirs(os.path.dirname(filename), exist_ok=True)
with open(filename, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
这种双备份设计带来两个好处:
- 原始数据可追溯
- 数据库异常时可以从文件恢复
7. 反爬虫策略应对方案
7.1 请求频率控制
百度指数对高频请求有严格限制,建议采用以下策略:
python复制import random
import time
def safe_request(url, max_retry=3):
for _ in range(max_retry):
try:
time.sleep(random.uniform(1, 3)) # 随机延迟
response = requests.get(url, headers=headers)
if response.status_code == 200:
return response
except Exception as e:
print(f"Request failed: {e}")
time.sleep(5) # 失败后等待更久
return None
7.2 IP代理池集成
当采集量较大时,建议使用代理IP轮询:
python复制proxies = [
{"http": "http://ip1:port", "https": "http://ip1:port"},
# ...多个代理IP
]
def get_with_proxy(url):
proxy = random.choice(proxies)
try:
return requests.get(url, proxies=proxy, timeout=10)
except:
return None
重要提示:不要使用免费代理IP,百度指数能识别大多数公共代理,建议使用高质量的付费代理服务。
8. 完整代码结构与运行示例
8.1 项目目录结构
code复制baidu_index_crawler/
├── core/
│ ├── auth.py # 认证模块
│ ├── fetcher.py # 数据获取
│ ├── parser.py # 数据解析
│ └── storage.py # 存储模块
├── utils/
│ ├── proxy.py # 代理管理
│ └── logger.py # 日志记录
├── config.py # 配置文件
└── main.py # 主程序
8.2 示例运行代码
python复制from core.auth import get_auth_cookie
from core.fetcher import fetch_index_data
from core.parser import parse_index_data
keywords = ["Python", "机器学习", "数据分析"]
start_date = "2023-01-01"
end_date = "2023-12-31"
for keyword in keywords:
raw_data = fetch_index_data(keyword, start_date, end_date)
if raw_data:
clean_data = parse_index_data(raw_data)
save_to_database(clean_data)
9. 常见问题与解决方案
9.1 Token失效错误
现象:返回"token invalid"错误
解决方案:
- 检查系统时间是否准确(误差需在30秒内)
- 重新获取token并重试
- 确保token生成逻辑与当前版本一致
9.2 数据解密失败
排查步骤:
- 确认AES密钥是否正确
- 检查返回数据是否完整(可能被截断)
- 验证Gzip解压是否正常
9.3 请求频率限制
优化方案:
- 增加请求间隔时间
- 使用更多优质代理IP
- 分时段采集(避开高峰期)
10. 进阶优化方向
对于需要大规模采集的用户,可以考虑以下优化:
- 分布式采集:使用Celery + Redis搭建分布式任务队列
- 增量采集:记录最后采集日期,只获取新数据
- 数据可视化:集成Matplotlib自动生成趋势图
- 异常预警:设置监控指标,自动报警异常情况
我在实际项目中发现,通过合理的任务调度,单台服务器每天可以稳定采集约5万个关键词的数据(需使用10个以上优质代理IP轮询)。