大家好我是小小明,今天给大家演示如何使用python直接采集百度指数的数据。
百度指数(Baidu Index) 是以百度海量网民行为数据为基础的数据分析平台,它能够能够告诉用户:某个关键词在百度的搜索规模有多大,一段时间内的涨跌态势以及相关的新闻舆论变化,关注这些词的网民是什么样的,分布在哪里,同时还搜了哪些相关的词。
百分十先生分享过如何使用uiautomation采集百度指数:百度指数 如何批量获取?
不过个人感觉这方法好像有点杀鸡用牛刀,对于网页使用selenium完全足以,当然对于专门针对selenium进行反爬检测的网页就需要特殊修改。
本文不演示如何使用UI自动化工具采集百度指数,为了采集更简单将直接读取并解析接口。
关于uiautomation,PC端的UI自动化可以查看教程:Windows桌面程序自动化控制之uiautomation模块全面讲解
打开百度指数发现查看指数必须要先登录,比如我们对比一个python和Java最近一周的指数:
当鼠标移动到每天的坐标上时会显示当天的数据,例如:
如果我们采用UI自动化的方式,至少得模拟移动到每天的坐标。
打开开发者工具,重新查询发现获取数据的接口:
实际的指数数据就存储在这个data字段中,但是以某种加密方式加密了。
然后注意第二个接口的某个参数与当前接口返回的数据某个值一致。
此时我全局搜索decrypt
,找到了加密函数:
此时打上断点重新搜索,可以看到传入该函数的t参数与ptbk接口返回的值一致:
说明我们只需要将这段js翻译为python来解密加密数据即可。
下面我们总结一下指数数据获取的思路:
-
通过index接口获取uniqid和加密后的指数数据userIndexes
-
通过ptbk接口传入uniqid获取密钥key
-
通过解密函数根据密钥key解密userIndexes
下面我们分别用代码来实现,首先获取指数数据:
import requests
import json
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",
"Referer": "https://index.baidu.com/v2/main/index.html",
"Accept-Language": "zh-CN,zh;q=0.9",
'Cookie': cookie,
}
words = '[[{"name":"python","wordType":1}],[{"name":"java","wordType":1}]]'
start = '2021-11-15'
end = '2021-11-21'
url = f'http://index.baidu.com/api/SearchApi/index?area=0&word={words}&area=0&startDate={start}&endDate={end}'
res = requests.get(url, headers=headers)
data = res.json()['data']
data
cookie需要在登录后复制粘贴获取,就是请求中的这段字符串(直接复制粘贴即可):
结果:
{'userIndexes': [{'word': [{'name': 'python', 'wordType': 1}],
'all': {'startDate': '2021-11-15',
'endDate': '2021-11-21',
'data': 'WQ3Q-nWQ.yGnWQ.y3nW3yQsnWW.Q-nysXV3ny.-VG'},
'pc': {'startDate': '2021-11-15',
'endDate': '2021-11-21',
'data': 'y3yVXny3yWyny3GWWny3QyVnyQG33nXGsQn-..G'},
'wise': {'startDate': '2021-11-15',
'endDate': '2021-11-21',
'data': 'XWVXnXQ-XnX3XWnX-WynX3X3n--XynsQyG'},
'type': 'day'},
{'word': [{'name': 'java', 'wordType': 1}],
'all': {'startDate': '2021-11-15',
'endDate': '2021-11-21',
'data': '-XW.n-ssXnXG3GnXG..nXyyGnVQyWn.QQQ'},
'pc': {'startDate': '2021-11-15',
'endDate': '2021-11-21',
'data': '.VVVn.3Xsn.XX3n.-VWn.sW3nQG-snWVWQ'},
'wise': {'startDate': '2021-11-15',
'endDate': '2021-11-21',
'data': 'QW.XnQW-WnQG3VnQyXQnQQ-VnQWW.nWsyG'},
'type': 'day'}],
'generalRatio': [{'word': [{'name': 'python', 'wordType': 1}],
'all': {'avg': 21565, 'yoy': -24, 'qoq': 7},
'pc': {'avg': 12470, 'yoy': -32, 'qoq': 3},
'wise': {'avg': 9095, 'yoy': -10, 'qoq': 12}},
{'word': [{'name': 'java', 'wordType': 1}],
'all': {'avg': 8079, 'yoy': -23, 'qoq': 11},
'pc': {'avg': 4921, 'yoy': -33, 'qoq': 6},
'wise': {'avg': 3157, 'yoy': '-', 'qoq': 18}}],
'uniqid': '5f0a123915325e28d9f055409955c9ad'}
这些数据中,wise表示移动端,all表示pc端+移动端。userIndexes是指数详情数据,generalRatio是概览数据。
下面我们只关心各个关键字的整体表现。
下面我们获取uniqid
并获取ptbk:
uniqid = data['uniqid']
res = requests.get(
f'http://index.baidu.com/Interface/ptbk?uniqid={uniqid}', headers=headers)
ptbk = res.json()['data']
ptbk
'LV.7yF-s30WXGQn.65+1-874%2903,'
下面我将下面这段Js代码翻译为python:
decrypt: function(t, e) {
if (t) {
for (var n = t.split(""), i = e.split(""), a = {}, r = [], o = 0; o < n.length / 2; o++)
a[n[o]] = n[n.length / 2 + o];
for (var s = 0; s < e.length; s++)
r.push(a[i[s]]);
return r.join("")
}
}
python代码:
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])
然后我们遍历每个关键字解密出对应的指数数据:
for userIndexe in data['userIndexes']:
name = userIndexe['word'][0]['name']
index_data = userIndexe['all']['data']
r = decrypt(ptbk, index_data)
print(name, r)
python 23438,23510,23514,24137,22538,17964,15860
java 8925,8779,9040,9055,9110,6312,5333
检查实际网页中的数据发现确实一致:
那么我们就可以轻松获取任意指定关键字的指数数据。下面我将其整体封装一下,完整代码为:
基础版本代码
import requests
import json
from datetime import date, timedelta
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",
"Referer": "https://index.baidu.com/v2/main/index.html",
"Accept-Language": "zh-CN,zh;q=0.9",
'Cookie': cookie,
}
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])
def get_index_data(keys, start=None, end=None):
words = [[{"name": key, "wordType": 1}] for key in keys]
words = str(words).replace(" ", "").replace("'", "\"")
today = date.today()
if start is None:
start = str(today-timedelta(days=8))
if end is None:
end = str(today-timedelta(days=2))
url = f'http://index.baidu.com/api/SearchApi/index?area=0&word={words}&startDate={start}&endDate={end}'
print(words, start, end)
res = requests.get(url, headers=headers)
data = res.json()['data']
uniqid = data['uniqid']
url = f'http://index.baidu.com/Interface/ptbk?uniqid={uniqid}'
res = requests.get(url, headers=headers)
ptbk = res.json()['data']
result = {}
result["startDate"] = start
result["endDate"] = end
for userIndexe in data['userIndexes']:
name = userIndexe['word'][0]['name']
tmp = {}
index_all = userIndexe['all']['data']
index_all_data = [int(e) for e in decrypt(ptbk, index_all).split(",")]
tmp["all"] = index_all_data
index_pc = userIndexe['pc']['data']
index_pc_data = [int(e) for e in decrypt(ptbk, index_pc).split(",")]
tmp["pc"] = index_pc_data
index_wise = userIndexe['wise']['data']
index_wise_data = [int(e)
for e in decrypt(ptbk, index_wise).split(",")]
tmp["wise"] = index_wise_data
result[name] = tmp
return result
测试一下:
get_index_data(["python", "java"])
{'startDate': '2021-11-15',
'endDate': '2021-11-21',
'python': {'all': [23438, 23510, 23514, 24137, 22538, 17964, 15860],
'pc': [14169, 14121, 14022, 14316, 13044, 9073, 8550],
'wise': [9269, 9389, 9492, 9821, 9494, 8891, 7310]},
'java': {'all': [8925, 8779, 9040, 9055, 9110, 6312, 5333],
'pc': [5666, 5497, 5994, 5862, 5724, 3087, 2623],
'wise': [3259, 3282, 3046, 3193, 3386, 3225, 2710]}}
结果非常不错。
查看指定区域的百度指数
如果我们希望查看指定地区的指数就需要关心area参数,经过一番查找找到了各支持地址的地址码为:
location_code = {
'济南': 1, '贵阳': 2, '黔南': 3, '六盘水': 4, '南昌': 5, '九江': 6, '鹰潭': 7,
'抚州': 8, '上饶': 9, '赣州': 10, '重庆': 11, '包头': 13, '鄂尔多斯': 14, '巴彦淖尔': 15,
'乌海': 16, '阿拉善盟': 17, '锡林郭勒盟': 19, '呼和浩特': 20, '赤峰': 21, '通辽': 22, '呼伦贝尔': 25,
'武汉': 28, '大连': 29, '黄石': 30, '荆州': 31, '襄阳': 32, '黄冈': 33, '荆门': 34, '宜昌': 35, '十堰': 36,
'随州': 37, '恩施': 38, '鄂州': 39, '咸宁': 40, '孝感': 41, '仙桃': 42, '长沙': 43, '岳阳': 44, '衡阳': 45,
'株洲': 46, '湘潭': 47, '益阳': 48, '郴州': 49, '福州': 50, '莆田': 51, '三明': 52, '龙岩': 53, '厦门': 54,
'泉州': 55, '漳州': 56, '上海': 57, '遵义': 59, '黔东南': 61, '湘西': 65, '娄底': 66, '怀化': 67, '常德': 68,
'天门': 73, '潜江': 74, '滨州': 76, '青岛': 77, '烟台': 78, '临沂': 79, '潍坊': 80, '淄博': 81, '东营': 82,
'聊城': 83, '菏泽': 84, '枣庄': 85, '德州': 86, '宁德': 87, '威海': 88, '柳州': 89, '南宁': 90, '桂林': 91,
'贺州': 92, '贵港': 93, '深圳': 94, '广州': 95, '宜宾': 96, '成都': 97, '绵阳': 98, '广元': 99, '遂宁': 100,
'巴中': 101, '内江': 102, '泸州': 103, '南充': 104, '德阳': 106, '乐山': 107, '广安': 108, '资阳': 109,
'自贡': 111, '攀枝花': 112, '达州': 113, '雅安': 114, '吉安': 115, '昆明': 117, '玉林': 118, '河池': 119,
'玉溪': 123, '楚雄': 124, '南京': 125, '苏州': 126, '无锡': 127, '北海': 128, '钦州': 129, '防城港': 130,
'百色': 131, '梧州': 132, '东莞': 133, '丽水': 134, '金华': 135, '萍乡': 136, '景德镇': 137, '杭州': 138,
'西宁': 139, '银川': 140, '石家庄': 141, '衡水': 143, '张家口': 144, '承德': 145, '秦皇岛': 146, '廊坊': 147,
'沧州': 148, '温州': 149, '沈阳': 150, '盘锦': 151, '哈尔滨': 152, '大庆': 153, '长春': 154, '四平': 155,
'连云港': 156, '淮安': 157, '扬州': 158, '泰州': 159, '盐城': 160, '徐州': 161, '常州': 162, '南通': 163,
'天津': 164, '西安': 165, '兰州': 166, '郑州': 168, '镇江': 169, '宿迁': 172, '铜陵': 173, '黄山': 174,
'池州': 175, '宣城': 176, '巢湖': 177, '淮南': 178, '宿州': 179, '六安': 181, '滁州': 182, '淮北': 183,
'阜阳': 184, '马鞍山': 185, '安庆': 186, '蚌埠': 187, '芜湖': 188, '合肥': 189, '辽源': 191, '松原': 194,
'云浮': 195, '佛山': 196, '湛江': 197, '江门': 198, '惠州': 199, '珠海': 200, '韶关': 201, '阳江': 202,
'茂名': 203, '潮州': 204, '揭阳': 205, '中山': 207, '清远': 208, '肇庆': 209, '河源': 210, '梅州': 211,
'汕头': 212, '汕尾': 213, '鞍山': 215, '朝阳': 216, '锦州': 217, '铁岭': 218, '丹东': 219, '本溪': 220,
'营口': 221, '抚顺': 222, '阜新': 223, '辽阳': 224, '葫芦岛': 225, '张家界': 226, '大同': 227, '长治': 228,
'忻州': 229, '晋中': 230, '太原': 231, '临汾': 232, '运城': 233, '晋城': 234, '朔州': 235, '阳泉': 236,
'吕梁': 237, '海口': 239, '万宁': 241, '琼海': 242, '三亚': 243, '儋州': 244, '新余': 246, '南平': 253,
'宜春': 256, '保定': 259, '唐山': 261, '南阳': 262, '新乡': 263, '开封': 264, '焦作': 265, '平顶山': 266,
'许昌': 268, '永州': 269, '吉林': 270, '铜川': 271, '安康': 272, '宝鸡': 273, '商洛': 274, '渭南': 275,
'汉中': 276, '咸阳': 277, '榆林': 278, '石河子': 280, '庆阳': 281, '定西': 282, '武威': 283, '酒泉': 284,
'张掖': 285, '嘉峪关': 286, '台州': 287, '衢州': 288, '宁波': 289, '眉山': 291, '邯郸': 292, '邢台': 293,
'伊春': 295, '大兴安岭': 297, '黑河': 300, '鹤岗': 301, '七台河': 302, '绍兴': 303, '嘉兴': 304, '湖州': 305,
'舟山': 306, '平凉': 307, '天水': 308, '白银': 309, '吐鲁番': 310, '昌吉': 311, '哈密': 312, '阿克苏': 315,
'克拉玛依': 317, '博尔塔拉': 318, '齐齐哈尔': 319, '佳木斯': 320, '牡丹江': 322, '鸡西': 323, '绥化': 324,
'乌兰察布': 331, '兴安盟': 333, '大理': 334, '昭通': 335, '红河': 337, '曲靖': 339, '丽江': 342, '金昌': 343,
'陇南': 344, '临夏': 346, '临沧': 350, '济宁': 352, '泰安': 353, '莱芜': 356, '双鸭山': 359, '日照': 366, '安阳': 370,
'驻马店': 371, '信阳': 373, '鹤壁': 374, '周口': 375, '商丘': 376, '洛阳': 378, '漯河': 379, '濮阳': 380, '三门峡': 381,
'阿勒泰': 383, '喀什': 384, '和田': 386, '亳州': 391, '吴忠': 395, '固原': 396, '延安': 401, '邵阳': 405, '通化': 407,
'白山': 408, '白城': 410, '甘孜': 417, '铜仁': 422, '安顺': 424, '毕节': 426, '文山': 437, '保山': 438, '东方': 456,
'阿坝': 457, '拉萨': 466, '乌鲁木齐': 467, '石嘴山': 472, '凉山': 479, '中卫': 480, '巴音郭楞': 499, '来宾': 506,
'北京': 514, '日喀则': 516, '伊犁': 520, '延边': 525, '塔城': 563, '五指山': 582, '黔西南': 588, '海西': 608,
'海东': 652, '克孜勒苏柯尔克孜': 653, '天门仙桃': 654, '那曲': 655, '林芝': 656, 'None': 657, '防城': 658,
'玉树': 659, '伊犁哈萨克': 660, '五家渠': 661, '思茅': 662, '香港': 663, '澳门': 664, '崇左': 665, '普洱': 666,
'济源': 667, '西双版纳': 668, '德宏': 669, '文昌': 670, '怒江': 671, '迪庆': 672, '甘南': 673, '陵水黎族自治县': 674,
'澄迈县': 675, '海南': 676, '山南': 677, '昌都': 678, '乐东黎族自治县': 679, '临高县': 680, '定安县': 681, '海北': 682,
'昌江黎族自治县': 683, '屯昌县': 684, '黄南': 685, '保亭黎族苗族自治县': 686, '神农架': 687, '果洛': 688, '白沙黎族自治县': 689,
'琼中黎族苗族自治县': 690, '阿里': 691, '阿拉尔': 692, '图木舒克': 693
}
那么前面的方法可以修改为:
import requests
import json
from datetime import date, timedelta
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",
"Referer": "https://index.baidu.com/v2/main/index.html",
"Accept-Language": "zh-CN,zh;q=0.9",
'Cookie': cookie,
}
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])
def get_index_data(keys, start=None, end=None, location=None):
words = [[{"name": key, "wordType": 1}] for key in keys]
words = str(words).replace(" ", "").replace("'", "\"")
today = date.today()
if start is None:
start = str(today-timedelta(days=7))
if end is None:
end = str(today-timedelta(days=1))
if location is None:
area = 0
else:
area = location_code.get(location)
url = f'http://index.baidu.com/api/SearchApi/index?area={area}&word={words}&startDate={start}&endDate={end}'
res = requests.get(url, headers=headers)
data = res.json()['data']
uniqid = data['uniqid']
url = f'http://index.baidu.com/Interface/ptbk?uniqid={uniqid}'
res = requests.get(url, headers=headers)
ptbk = res.json()['data']
result = {}
result["startDate"] = start
result["endDate"] = end
for userIndexe in data['userIndexes']:
name = userIndexe['word'][0]['name']
tmp = {}
index_all = userIndexe['all']['data']
index_all_data = [int(e) for e in decrypt(ptbk, index_all).split(",")]
tmp["all"] = index_all_data
index_pc = userIndexe['pc']['data']
index_pc_data = [int(e) for e in decrypt(ptbk, index_pc).split(",")]
tmp["pc"] = index_pc_data
index_wise = userIndexe['wise']['data']
index_wise_data = [int(e)
for e in decrypt(ptbk, index_wise).split(",")]
tmp["wise"] = index_wise_data
result[name] = tmp
return result
测试获取成都最近一周的数据:
get_index_data(["python", "java"], location="成都")
结果:
{'startDate': '2021-11-23',
'endDate': '2021-11-29',
'python': {'all': [1535, 1572, 1467, 1599, 1248, 1050, 1584],
'pc': [878, 867, 865, 820, 585, 519, 865],
'wise': [657, 705, 602, 779, 663, 531, 719]},
'java': {'all': [695, 694, 694, 738, 527, 476, 808],
'pc': [436, 447, 425, 383, 250, 213, 457],
'wise': [259, 247, 269, 355, 277, 263, 351]}}
获取深圳10月份的指数数据:
get_index_data(["python", "java"], '2021-10-01', '2021-10-31', "深圳")
结果:
{'startDate': '2021-10-01', 'endDate': '2021-10-31', 'python': {'all': [863, 807, 697, 687, 768, 953, 1110, 1399, 1415, 1278, 1620, 1529, 1524, 1571, 1520, 1270, 1134, 1496, 1731, 1692, 1439, 1524, 1308, 1212, 1666, 1595, 1702, 1578, 1552, 1266, 1170], 'pc': [546, 512, 437, 430, 476, 620, 741, 981, 951, 831, 1049, 976, 979, 957, 967, 852, 740, 997, 976, 956, 932, 945, 849, 751, 1022, 995, 993, 975, 939, 848, 754], 'wise': [317, 295, 260, 257, 292, 333, 369, 418, 464, 447, 571, 553, 545, 614, 553, 418, 394, 499, 755, 736, 507, 579, 459, 461, 644, 600, 709, 603, 613, 418, 416]}, 'java': {'all': [374, 366, 326, 337, 347, 416, 518, 806, 694, 502, 747, 719, 786, 709, 725, 531, 446, 938, 1125, 1019, 910, 850, 501, 588, 939, 855, 839, 798, 712, 564, 437], 'pc': [193, 170, 161, 175, 173, 223, 309, 582, 495, 313, 523, 485, 555, 498, 504, 309, 257, 644, 656, 600, 591, 549, 316, 341, 665, 575, 571, 574, 498, 375, 251], 'wise': [181, 196, 165, 162, 174, 193, 209, 224, 199, 189, 224, 234, 231, 211, 221, 222, 189, 294, 469, 419, 319, 301, 185, 247, 274, 280, 268, 224, 214, 189, 186]}}