详解百度指数搜索指数js逆向

一、相关环境

开发工具:PyCharm 2022.3.1 (Community Edition)

开发环境:Python 3.9

操作系统:Windows10

二、目标数据

百度指数首页登录,然后输入一个关键词进入搜索指数页面,以下操作全是基于该页面进行。

我们要抓取的是指定关键词的搜索指数趋势图中某一段时间的数据,这里以python的搜索指数为例:

需要获取曲线上每一个这样的点对应的数据。打开开发者工具,在元素栏中Ctrl+F搜索其中一个数据,搜索结果为0条,说明网页源代码中不存在需要的数据,而是动态获取的。

三、获取动态数据

打开开发者工具->切换到网络栏->选中XHR项->刷新页面

逐个点击左侧请求的名称并查看右侧的响应预览,发现index开头的请求中包含数据,观察一下发现应该是我们需要的数据,但是data被加密了,那么下一步需要js逆向找到加密方式。

四、JS逆向

我们分析上述请求返回的json数据,其形式大致如下:

{
  "data": {
    "generalRatio": [
      {}
    ],
    "userIndexes": [
      {
        "word": [],
        "all": {
          "data": "",
          "endDate": "",
          "startDate": ""
        },
        "pc": {},
        "wise": {},
        "type": "day"
      }
    ],
    "uniqid": "xxxxxxxxxx"
  }
}

当前端拿到这样的加密数据来解密时,必然要用到类似data.generalRatio、data.userIndexes这样的方式来取出整个数组;更进一步考虑,前端会使用all.data来获取最后一层数据。由于编译过的js中整个数据一般会赋值给单字母,而all和data这种单词又过于常见,因此我们的思路是先用generalRatio、userIndexes去搜索,没有结果的话再用all.data去搜索。

Ctrl+Shift+F调出全局搜索框,搜索userIndexes(因为我们找的是userIndexes中的数据),定位到两个js。

逐个查看后发现第一个js中userIndexes只有一处,是在处理请求的函数中。第二个js中userIndexes有三处,第一处是向后端发送axios请求,说明我们的目标就在这个js中;第二处和第三处在一起,可以很明显得发现这里在构造趋势图的数据,除了赋值操作之外,我们可以看到有一个decrypt函数,且分别用它操作了all.data、pc.data、wise.data,至此我们可以确定这个函数就是解密函数。

decrypt函数传入了两个参数,第二个参数可以看出是从后端获取的加密数据,第一个参数暂时看出来是,我们在其中一个decrypt函数处打断点进行调试,跳转至decrypt函数内部时,可以看到第一个参数是"-GlAwgjW3yBdeQm7,%5+940831-6.2"这样的加密字符串,在网络栏中搜索一下看是不是从后端传过来的,结果显示这个参数是https://index.baidu.com/Interface/ptbk?uniqid=47ec7818050d810e4d02951afcbd38c7的返回。

再搜索一下参数uniqid,发现好几个请求都有该参数返回且值相同,同时请求数据的接口也返回了该参数。

最后整理一下思路:

程序 api/SearchApi/index接口 Interface/ptbk接口 登录 请求数据 加密数据、uniqid 参数:uniqid 解密参数ptbk 通过解密参数ptbk解密数据 程序 api/SearchApi/index接口 Interface/ptbk接口

五、代码实现

导入所需的包
import requests
import pandas as pd
from fake_useragent import UserAgentnt
获取Cookie

百度指数的请求会验证用户是否登录,所以发起请求前需要获取Cookie信息,百度Cookie的核心是BDUSS,只能自己登录账号->开发者工具->应用程序->Cookie->BDUSS然后复制值。

在前面拼接BDUSS=即可构造Cookie

cookies = 'BDUSS=XXXXXXXXXX'
请求数据
1.请求头及参数构造

网络栏查看api/SearchApi/index请求,有三个参数:

1.area:省份代码,默认0表示全国

2.word:由关键词构造的二维数组,形如[[{“name”: “python”, “wordType”: 1}]]

3.days:返回数据是最近多少天,默认30

然后复制请求头,将Cookie传入

search_param = {'area': '0', 'word': '[[{"name": "python", "wordType": 1}]]', 'days': '60'}
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',
    'Cipher-Text': '1672733200884_1672792253933_1NLQa+cgc5N2JSNoinHAaMmrDrPtwqHL6D2NHONACx//1P+9YXcg/erBma8ucj43shvH2VsAi3Dzlo9cFfqA3k/PmqixjXJEslJCwNzCzNCVHs+/y7su33mGAxAtFWXrl55rYxzEJNGi4xM6jb4UUibTrVbOl46gKWq/7PVKAIzRyrJbxQP9pKmxECIpO12JbXFrA3leOj8xDZk69P1O/tNU6lD8eMPylUrgCp5k89c9EAD+Q4lgHhsZpTktcKTzKSbrJ5/l0GYNxNS96gEpS/0BnesBc6X52rqE7K4fNzrxm5cfgwbCJx/2+1ayhkI2gUMNDabQ1dnR0hr/NyWxeh7nYvxqarQHsZ+cu3XCt5uEHE4aAPgcXTfDgMsCQOrtMfDGKuxX5PiMDzDODjxSn8cDFRnJ+RMvfPjIIfq2P4k=',
    'Connection': 'keep-alive',
    'Cookie': cookie,
    'Host': 'index.baidu.com',
    'Referer': 'https://index.baidu.com/v2/main/index.html',
    'Sec-Fetch-Dest': 'empty',
    'Sec-Fetch-Mode': 'cors',
    'Sec-Fetch-Site': 'same-origin',
    'User-Agent': str(UserAgent().random)
}
2.发送请求,解析数据

使用requests发送get请求,获取加密数据,同时保存uniqid

response = requests.get(search_url, params=search_param, headers=headers)
encrypted_data = response.json()['data']
uniqid = encrypted_data['uniqid']
解密数据
1.获取解密参数
ptbk_url = f'http://index.baidu.com/Interface/ptbk?uniqid={uniqid}'
ptbk_response = requests.get(ptbk_url, headers=headers)
ptbk = ptbk_response.json()['data']
2.改写解密函数

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)

顺带把填充零的函数写了

def fill_zero(data):
    """
    :param data: 字符串格式的数据
    :return:data为空则返回0,否则返回原数据
    """
    if data == '':
        return 0
    else:
        return data
3.解析数据并保存到csv
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.同时搜索多个关键词

在word参数中添加多个关键词,最多五个,超过五个只会返回前五个的结果

'word': '[[{"name": "python", "wordType": 1}],[{"name": "java", "wordType": 1}],[{"name": "c", "wordType": 1}],[{"name": "c++", "wordType": 1}],[{"name": "go", "wordType": 1}]'
2.指定时间段

将days参数修改为startDate、endDate这两个参数

'startDate': '2022-12-01',
'endDate': '2022-12-31'
3.指定省份

可以只搜索某一个省份的数据,将area参数改为相应省份的代码即可,如北京市的代码为911,其他省份的代码可以在步骤四的js文件中找到:

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值