一.相关环境
开发工具:pycharm
开发环境:Python
操作系统:windows
二.目标数据
在百度指数首页登录,之后输入我们需要的关键词,我们需要抓取指定关键词的搜索指数趋势图中某一短时间的数据,我们首先需要登录我们的百度账号以获取cookie。
我们需要获取曲线上的每一点所对应的数据,打开开发者工具F12,在元素栏中搜索其中数据,搜索结果不到,说明数据不是网页中的,而是通过后方数据库,动态获取的。
三.获取动态数据
动态数据都是后台数据库返回的,所以我们需要找到这个文件。
打开开发者工具-->点击到网络栏-->选中XHR项-->刷新页面
XHR选项:XMLHttpRequests(XHR)请求通常会返回从服务器获取的数据。这些数据可以是各种格式,取决于服务器端和前端的配置以及应用程序的需求
我们逐个点击左侧请求的名称并查看右侧响应预览
我们可以发现在index开头的请求中包含数据,观察一下我们发现应该就是我们要的数据,其中all是指全部数据,PC是指pc端,wise是指移动端,但是data被加密了,所以我们需要通过‘JS逆向‘找到对应的解密方式。
四.JS逆向
我们分析以上请求中给我们返回的json数据
"data": {
"userIndexes": [
{
"word": [
{ }
],
"all": {
"startDate": "",
"endDate": "",
"data": ""
},
},
"pc": {
},
"wise": {
},
"type": "day"
}
],
"generalRatio": [
{}
],
"uniqid": "c195cf6d98dde7b9d7f06238796a1e42"
},
其中当我们想从data中获得数据时,必然会用到类似data.userIndexes的方式从response中来取出整个数组数据,并且最终会用all.data来获取最后一层数据。为此,我们尝试使用generalRadio,userIndexes去搜索。
Ctrl+F打开全局搜索框,搜索userIndexes(我们需要其中的数据),定位到三个JS
逐个查看后发现第一个JS就是之前我们看的数据,第二个JS中·userIndexes只有一处,是在处理请求函数中,并没有解密数据
第三个JS中userIndexes有3处,其中第一处是向后端发送axios请求(响应数据等)。第二第三处在一块,可以发现这里调用了all.data,pc,data,wise.data。我们可以看到其中有一个decrypt函数,至此咱们可以知道就是解密函数,我们可以看到dectypt函数传入了两个参数,
从其中我们可以看出其中第二个参数是从后端获取的加密数据,第一个参数暂时看不出来,因此我们搜索decrypt,并在函数中打上断点进行调试,跳转到函数内部
我们可以看到t="kAhjeN4z3WOuaD6-918652.4+370,%"这样的加密字符
搜索这个字符,我们可以发现结果显示参数是从https://index.baidu.com/Interface/ptbk?uniqid=223acdc67372b787e7619f43834b6d15返回的
打开文件的负载我们发现其中有个参数uniqid发现好几个请求都有该参数,同时在我们请求数据接口时,后台数据库也给我们返回了该参数
因此我们整理一下思路我们要获取数据因此去访问数据库,随后,数据库给我们返回了一个加密厚的数据,同时给我们返回了一个uniqid参数(此参数用于获取t参数,又叫ptbk参数),获取这个ptbk参数的url:https://index.baidu.com/Interface/ptbk?uniqid=851b6825de9a14255d199cbd73338cd2需要一个参数uniqid,我们进行GET请求,返回一个JSON数据,获取uniqid和data的url:https://index.baidu.com/api/SearchApi/index?area=0&word=[[%7B%22name%22:%22%E7%8E%AF%E5%A2%83%E6%B1%A1%E6%9F%93%22,%22wordType%22:1%7D]]&days=30
(如果要指定日期只需在word后面追加&startDate=,&endDate=,days代表多少天,area代表位置等于0就是全国)
总结:
1.通过URL获取uniqid和data
2.通过uniqid获取ptbk
3.通过ptbk和data解密
五.代码实现
因为百度指数的请求会验证用户登录,所以我们在发送请求需要携带Cookie信息,百度Cookie的核心是BDUSS,所以我们要登录账户--》开发者工具--》Cookie-->BDUSS
1,请求头和我们需要的参数
cookies = '' # 自己的cookie
search_param = {
"area": "0",
"word": [
[
{
"name": "环境污染",
"wordType": 1
}
]
],
"days": 30
}
search_url = 'https://index.baidu.com/api/SearchApi/index?'
headers = {
'Accept': 'application/json, text/plain, */*',
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
'Cipher-Text': '1716807885460_1716889185749_Nl3QA+PCPMLfYvM8U1T5ZxzmJhWSzfcSr46qfguTO1HbBO67J3CJLqUjtGlJCXGNiTVpkYh0icU2ZDkQO8g563PDL/4t+GtDE3A7xaxw8pNR5ih53NZTVWDCGGebbvonuH9whIQAnsS7IkMVf5kOyXvMpEmA80LLdpLn4tXtUSo1UK5WwoJxGNph7EvWY1JuVN5vqEvqhYdxAz7IzQ398zN7des33r7I3n9/3fTxVXa/Dx1hAJdm4h32YB4Ked60dI7Dl8U3SLFRIEjZIUiKhwx2+Dfm5SM22XwvGNCmkW6qr53R5JVo8XMpGYQZZYJr4+YnXcGn709ns/cRMHGuvHjZMxqI217Nrdsbo8nfD2n6QyfCmdngXhGpycCVMNIKJJqXQeoLX1IwF7NvcQXkwi1e+ZofFgXj50iLi3lciLlc853OY7xcURptTZQCdslP4Z7wyh3qiqciMR7sQAucTQ==',
'Connection': 'keep-alive',
'Cookie': cookies,
'Host': 'index.baidu.com',
'Referer': 'https://index.baidu.com/v2/main/index.html',
'Sec-Ch-Ua-Platform': "Windows",
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-origin',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0'
}
随后,我们向api发送get请求,获取到加密数据,同时保存好uniqid
response = requests.get(search_url, params=search_param, headers=headers)
encrypted_data = response.json()['data']
print(type(encrypted_data))
uniqid = encrypted_data['uniqid']
获得到加密数据后我们要对他进行解密
2.解密数据
为此我们要先获得解密参数
ptbk_url = f'http://index.baidu.com/Interfac/ptbk?uniqid={uniqid}'
ptbk_response = requests.get(ptbk_url, headers=headers)
ptbk = ptbk_response.json()['data']
随后我们需要改写JS中的解密函数
改写后的python版
def decrypt(ptbk, encrypted_data):
"""
:param ptbk: 解密参数
:param encrypted_data: 加密数据
:return: 解密后的数据
"""
if not ptbk:
return ""
n = len(ptbk) // 2
d = {ptbk[o]: ptbk[n + o] for o in range(n)}
decrypted_data = [d[data] for data in encrypted_data]
return ''.join(decrypted_data)
扩展
搜素关键词以及设置时间和指定省份
search_param = {
'area': '0', # 这是省份城市编号,当area = 0代表全国
'word': json.dumps([[{"name": "环境污染", "wordType": 1}]]), # 将参数值转换为 JSON 字符串,其中name是搜索关键词
# 'days': 30,
'startDate': '2024-01-03', # 输入查询开始时间
'endDate':'2024-05-30', # 输入查询结束时间
} # 我们更改参数去查询
总代码如下
import requests
import pandas as pd
import json
# from fake_useragent import UserAgentent
cookies = '' # 自己的cookie
search_param = {
'area': '0', # 这是省份城市编号,当area = 0代表全国
'word': json.dumps([[{"name": "环境污染", "wordType": 1}]]), # 将参数值转换为 JSON 字符串,其中name是搜索关键词
# 'days': 30,
'startDate': '2024-01-03', # 输入查询开始时间
'endDate':'2024-05-30', # 输入查询结束时间
} # 我们更改参数去查询
search_url = 'https://index.baidu.com/api/SearchApi/index'
headers = {
'Accept': 'application/json, text/plain, */*',
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
'Cipher-Text': '1716807885460_1716889185749_Nl3QA+PCPMLfYvM8U1T5ZxzmJhWSzfcSr46qfguTO1HbBO67J3CJLqUjtGlJCXGNiTVpkYh0icU2ZDkQO8g563PDL/4t+GtDE3A7xaxw8pNR5ih53NZTVWDCGGebbvonuH9whIQAnsS7IkMVf5kOyXvMpEmA80LLdpLn4tXtUSo1UK5WwoJxGNph7EvWY1JuVN5vqEvqhYdxAz7IzQ398zN7des33r7I3n9/3fTxVXa/Dx1hAJdm4h32YB4Ked60dI7Dl8U3SLFRIEjZIUiKhwx2+Dfm5SM22XwvGNCmkW6qr53R5JVo8XMpGYQZZYJr4+YnXcGn709ns/cRMHGuvHjZMxqI217Nrdsbo8nfD2n6QyfCmdngXhGpycCVMNIKJJqXQeoLX1IwF7NvcQXkwi1e+ZofFgXj50iLi3lciLlc853OY7xcURptTZQCdslP4Z7wyh3qiqciMR7sQAucTQ==',
'Connection': 'keep-alive',
'Cookie': cookies,
'Host': 'index.baidu.com',
'Referer': 'https://index.baidu.com/v2/main/index.html',
'Sec-Ch-Ua-Platform': "Windows",
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-origin',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0'
}
response = requests.get(search_url, params=search_param, headers=headers)
encrypted_data = response.json()['data']
print(type(encrypted_data))
uniqid = encrypted_data['uniqid']
ptbk_url = f'http://index.baidu.com/Interface/ptbk?uniqid={uniqid}'
ptbk_response = requests.get(ptbk_url, headers=headers)
ptbk = ptbk_response.json()['data']
def decrypt(ptbk, encrypted_data):
"""
:param ptbk: 解密参数
:param encrypted_data: 加密数据
:return: 解密后的数据
"""
if not ptbk:
return ""
n = len(ptbk) // 2
d = {ptbk[o]: ptbk[n + o] for o in range(n)}
decrypted_data = [d[data] for data in encrypted_data]
return ''.join(decrypted_data)
def fill_zero(data):
"""
:param data: 字符串格式的数据
:return:data为空则返回0,否则返回原数据
"""
if data == '':
return 0
else:
return data
result = pd.DataFrame(columns=['关键词', '日期', '全部', '电脑端', '移动端'])
for userIndexes_data in encrypted_data['userIndexes']:
word = userIndexes_data['word'][0]['name']
start_date = userIndexes_data['all']['startDate']
end_date = userIndexes_data['all']['endDate']
timestamp_list = pd.date_range(start_date, end_date).to_list()
date_list = [timestamp.strftime('%Y-%m-%d') for timestamp in timestamp_list]
encrypted_data_all = userIndexes_data['all']['data']
decrypted_data_all = [int(fill_zero(data)) for data in decrypt(ptbk, encrypted_data_all).split(',')]
encrypted_data_pc = userIndexes_data['pc']['data']
decrypted_data_pc = [int(fill_zero(data)) for data in decrypt(ptbk, encrypted_data_pc).split(',')]
encrypted_data_wise = userIndexes_data['wise']['data']
decrypted_data_wise = [int(fill_zero(data)) for data in decrypt(ptbk, encrypted_data_wise).split(',')]
df = pd.DataFrame(
{'关键词': word, '日期': date_list, '全部': decrypted_data_all, '电脑端': decrypted_data_pc, '移动端': decrypted_data_wise})
result = pd.concat([result, df])
result.to_csv('./result.csv', index=False)
问题及心得
1.在爬取数据过程关键词用json格式,否则可能报错
2.在逆向过程中断点可以帮助我们更好的跟踪数据