1. 项目简介
百度指数是由百度提供的一个用于测量特定关键词或搜索词在百度搜索引擎上的搜索量和搜索趋势的工具。它通过分析用户在百度上的搜索行为和搜索量,提供了关键词的搜索指数和相关数据,以反映关键词的热度和受欢迎程度。通过百度指数,用户可以了解到特定关键词在一段时间内的搜索趋势、地域分布以及与其他关键词的对比情况。
不少社会研究分析都用到了百度指数,百度指数提供了大量的搜索行为数据,反映了人们在百度搜索引擎上的兴趣、关注和需求。这些数据可以帮助我们了解社会话题、事件和趋势,从而进行相关的社会科学研究。
你肯定见过像下面这样的动态排序图,那么就可以通过百度指数获取某一日期区间的数据,做出如某时间段内多个品牌热度对比的动态排序图。
那么直接开始正题,我们来到百度指数官网:百度指数
可以看到百度官方也贴心的给出了统计图表,便于观察数据,鼠标光标会显示具体某一天的数据,这说明我们可以获取到具体到天
的数据。
同时我们可以选择多个关键词选择时间区间进行对比,另外还可设定数据地区,用户设备种类等,接下来我们具体分析。
2. 分析思路
2.1. 抓包
老规矩,首先按F12
打开开发者工具,查看网络请求
很容易发现,接口地址为:https://index.baidu.com/api/SearchApi/index?
查看请求标题后发现这是一个GET请求,负载数据直接拼串在接口链接后面即可。
接着我们分析响应的数据:
"startDate"
和"endDate"
对应年份区间;
pc
wise
all
对应不同设备的数据;
data
也就是我们需要的数据,但是很显然加密了,因此我们后续还需解密。
2.2. 解析数据
那么,假设我们得到了这组数据,即一个字典格式的json
文件,我们可以将其格式化保存,并读取各个键值对数据。
但问题在于data
中的数据:kwwQkkoQwFkQkwoQwFGQwFGQwF-Q
很显然是加密的,我们即便获取了这样的数据也无法阅读,但是网页上显示到的数据又是正常显示,这说明网页后端必然存在解密的js代码。
我们回过头来继续观察:
观察上面的结果,我们惊喜的发现,每个json文件中必然包含着一个uniqid
这样一个特殊值,而恰巧网页又以这个唯一值为载体发送了一次请求。
这个请求返回了一个键值对,"data":"J,y8vIBmLWlCuw901+3-5%9,8.2746"
我们暂且将其命名为ptbk,这变量名称可能由token,key之类组成,猜测其作用为解码字,密钥之类。
我们紧接着查看这个请求,并在网页JS源代码中搜索,果不其然,发现了相关踪迹:
而decrypt就是解密的意思!紧接着我们顺藤摸瓜,搜索decrypt,结果我们就发现了其解密函数。
看不懂?没关系,我们有Chatgpt,我们跟它说明原委,让它翻译就好了。
(另外,互联网上也有大佬已经开源了现成的解决方案。)
2.3. 数据存储
既然数据能够到手,我们接下来的工作就是将数据保存,我们可以将结果保存到json中,用来进行可视化展示处理。也可以存入Excel文件中,首列通过data库输出每天的日期,第二列开始为关键词的数据,如下图所示。
但这里其实有很多问题尚未解决,如:
- 如果data返回值为空怎么办?
- 如果某些日期中数据缺失,如何对应到正确日期中?
- 遇到润年如何处理?
- data数据获取有无上限?
- ……
这些都是我们需要去解决的细节,细节处理与考虑周全很大程度上都影响了代码质量,最终影响数据准确性。
3. 具体实现
3.1. 数据获取
和一般的网站爬虫无异,我们还是使用request库,并配置headers进行伪装。
def get_index_data(keys,year):
words = [[{"name": keys, "wordType": 1}]]
words = str(words).replace(" ", "").replace("'", "\"")
startDate = f"{year}-01-01"
endDate = f"{year}-12-31"
url = f'http://index.baidu.com/api/SearchApi/index?area=0&word={words}&startDate={startDate}&endDate={endDate}'
# 请求头配置
headers = {
"Connection": "keep-alive",
"Accept": "application/json, text/plain, */*",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36",
"Sec-Fetch-Site": "same-origin",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Dest": "empty",
"Cipher-Text": "1698156005330_1698238860769_ZPrC2QTaXriysBT+5sgXcnbTX3/lW65av4zgu9uR1usPy82bArEg4m9deebXm7/O5g6QWhRxEd9/r/hqHad2WnVFVVWybHPFg3YZUUCKMTIYFeSUIn23C6HdTT1SI8mxsG5mhO4X9nnD6NGI8hF8L5/G+a5cxq+b21PADOpt/XB5eu/pWxNdwfa12krVNuYI1E8uHQ7TFIYjCzLX9MoJzPU6prjkgJtbi3v0X7WGKDJw9hwnd5Op4muW0vWKMuo7pbxUNfEW8wPRmSQjIgW0z5p7GjNpsg98rc3FtHpuhG5JFU0kZ6tHgU8+j6ekZW7+JljdyHUMwEoBOh131bGl+oIHR8vw8Ijtg8UXr0xZqcZbMEagEBzWiiKkEAfibCui59hltAgW5LG8IOtBDqp8RJkbK+IL5GcFkNaXaZfNMpI=",
"Referer": "https://index.baidu.com/v2/main/index.html",
"Accept-Language": "zh-CN,zh;q=0.9",
'Cookie': Cookie}
res = requests.get(url, headers=headers)
res_json = res.json()
需要注意的是,请求头配置中务必填写自己的Cookie,如果Cookie缺失或错误,直接导致数据获取失败!!
至于如何获取Cookie,下面是一种简单的手动处理方式:
- 打开百度指数官网并登录
- 随意搜索某一关键词,按F12打开开发者工具,切换到网络模块
- 点击一个请求信息,查看请求头即可看到自己的Cookie
(为了自身账号安全,请务必保管好自己的Cookie,切勿发送到公开场合)
紧接着我们利用软件发送请求,获取json文件以及uniqid解码字。
if res_json["message"] == "bad request":
print("抓取关键词:"+keys+" 失败,请检查cookie或者关键词是否存在")
else:
# 获取特征值
data = res_json['data']
# print(data)
uniqid = data["uniqid"]
url = f'http://index.baidu.com/Interface/ptbk?uniqid={uniqid}'
res = requests.get(url, headers=headers)
# 获取解码字
ptbk = res.json()['data']
#创建暂存文件夹
os.makedirs('res', exist_ok=True)
filename = f"{keys}_{year}.json"
file_path = os.path.join('res', filename)
with open(file_path, 'w', encoding='utf-8') as json_file:
json.dump(res_json, json_file, ensure_ascii=False, indent=4)
return file_path,ptbk
3.2. 格式化数据
在获取到data数据和ptbk解码字后,我们即可调用解码函数进行翻译,获取原始数据。
# 解码函数
def decrypt(ptbk, index_data):
n = len(ptbk)//2
a = dict(zip(ptbk[:n], ptbk[n:]))
return "".join([a[s] for s in index_data])
但是在测试过程中我们发现,data值的获取具有上限,如果一次性获取跨年数据,则数据大概率残缺。
关键的一步来了!我们按年份逐次进行多次请求,并将每年的结果拼接成完整的数据列表!
在这一过程中,我们要确保每年的数据数量为365个,润年为366个,若数量缺失则用0从左侧补足。
由此,我们能确保拼接而成的数据列表能与日期一一对应。
def reCode(file_path,ptbk):
# 读取暂存文件
with open(file_path, 'r', encoding='utf-8') as file:
res = json.load(file)
data = res['data']
li = data['userIndexes'][0]['all']['data']
startDate = data['userIndexes'][0]['all']['startDate']
year_str = startDate[:4] # 使用切片取前四个字符,即年份部分
try:
# 将年份字符串转换为整数
year = int(year_str)
# 根据年份判断是否为闰年
if (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0):
year = 366
else:
year = 365
except :
year =365
if li =='':
result = {}
name = data['userIndexes'][0]['word'][0]['name']
tep_all = []
while len(tep_all) < year:
tep_all.insert(0, 0)
result["name"] = name
result["data"] = tep_all
else:
ptbk = ptbk
result = {}
for userIndexe in data['userIndexes']:
name = userIndexe['word'][0]['name']
index_all = userIndexe['all']['data']
try:
index_all_data = [int(e) for e in decrypt(ptbk, index_all).split(",")]
tmp_all = index_all_data
except:
tmp_all = []
while len(tmp_all) < year:
tmp_all.insert(0, 0)
result["name"] = name
result["data"] = tmp_all
return result
注意,由于我们需要获取特征值,上面的代码中,我们创建了res
文件夹存放第一次请求的结果,将数据暂存,防止数据丢失。
在经过上面的代码后,我们即可得到一个字典数据:result
其基本格式为:{"name":"关键词","data":[123,123,123,432,453,634]}
3.3. 数据保存
最后,我们只需要对数据进行保存,如前所述,我们将其保存到Excel文件中。
为此,我们需要创建Excel表格并进行读写操作。
#创建日期表格
def create_excel(start_year, end_year):
workbook = openpyxl.Workbook()
sheet = workbook.active
# 设置第一行的标题
sheet['A1'] = '日期'
# 计算日期范围
start_date = datetime(start_year, 1, 1)
end_date = datetime(end_year, 12, 31)
# 逐行填充日期
current_date = start_date
row = 2 # 从第二行开始
while current_date <= end_date:
sheet[f'A{row}'] = current_date.strftime('%Y-%m-%d')
current_date += timedelta(days=1)
row += 1
# 保存 Excel 文件
filename = f'百度指数数据-{start_year}-{end_year}.xlsx'
workbook.save(filename)
return filename
首先我们按照提供的日期区间,创建首列日期数据,并将我们获取的数据写入表格中,若获取了多个关键词,则会出现在同一个表格中。
#为文件写入数据
def write_to_excel(file_name, name, data,i):
try:
# 打开 Excel 文件
workbook = openpyxl.load_workbook(file_name)
# 获取默认的工作表(第一个工作表)
sheet = workbook.active
# 将名称写入第一行第i列
sheet.cell(row=1, column=i, value=name)
# 将数据写入从第二行开始的第i列
for index, value in enumerate(data, start=2):
sheet.cell(row=index, column=i, value=value)
# 保存文件
workbook.save(file_name)
if len(data) != 0 :
print(f"关键词-{name}-写入成功!有效数据共{len(data)-data.count(0)}个")
except Exception as e:
print(f"发生错误: {e}")
程序的可交互性与健壮性也是非常重要的,异常处理与提示也是基本功。
最后就是我们的主函数,我们将上面的所有函数调用,run起来!
def main(keys,startDate,endDate):
filename = create_excel(startDate, endDate)
print(filename+"创建成功!")
data = []
i = 2
for key in keys:
for year in range(startDate, endDate + 1):
print(f"正在处理第{year}年,请耐心等待……")
try:
file_path = get_index_data(key, year)[0]
ptbk = get_index_data(key, year)[1]
res = reCode(file_path,ptbk)
name = res["name"]
temp = res["data"]
data = data + temp
except:
continue
# print(data)
write_to_excel(filename,name,data,i)
i = i +1
data = []
print("程序运行结束!")
相关参数:
# 要搜索的关键词,可以输入一个列表
keys = ["蜜雪冰城","喜茶","原神"]
# 获取的时间区间,若只获取某一年份,则二者相同
# 注意!年份区间下限为2011年,不建议选择太早年份
startDate = 2018
endDate = 2023
if Cookie == "":
Cookie = input("请输入你的Cookie,若错误则无法运行:")
elif startDate < 2011:
print("请注意初始年份限制!!!")
else:
main(keys,startDate,endDate)
4. 小结
本项目实现了百度指数的获取与解码,格式化输出为表格,支持日期选择,多个关键词爬取。
但仅仅为简单实现,代码还有很多值得改进之处,欢迎大家反馈完善。
已知问题:
- 不支持自定义具体的日期,如
2021-5-06~2022-7-11
,但可以通过获取完整数据并截取解决 - 展示的数据为手机端+PC端所有数据,未进行区分
- 展示的数据为全国范围内数据,未提供精确到省份与城市
- 输出结果类型单一,只有表格形式,不方便数据对接
Future
- 提供精确到省份与城市的参数
- 区分手机端、PC端数据
- 提供咨询指数数据
- 将结果用echart库进行可视化展示
我是凌小添,一枚热衷于分享的Coder,这里有好玩有趣的项目,各类实用技巧,让小白也能感受科技的乐趣,我致力于提供有价值、易懂的内容,让天下没有难学的技术!