目标
需求:知乎热榜数据抓取
字段:字段:标题、链接地址、热度, 用mongodb保存
思路
前期工作
定位接口
精简请求
逆向参数
x-zse-93
多次重放测试后可以确定这是一个定值:101_3_3.0
d_c0
z_c0
这个参数和登陆接口有关,经过测试可以复用,如果选择逆向的话,需要直面点选验证码,后面会出一篇机器学习过点选的文章,到时候拿这个试手,这里笔者选择复用。
x-zse-96
定位加密位置
一共有2处,都打上断点。
分析加密逻辑
关键代码:
tq(ti).encrypt.(tg()(td))
参数:tg()(td)
总结一下:”101_3_3.0”+url的path和search部分+dc0,用+号作为连接符形成的字符串再用标准的md5加密,虽然上图显示还需要
x-zst-81
,但经过测试header里不传这个参数也可以不需要这个。
方法:(0,tq(ti).encrypt)
可以看到是_g._encrypt
对参数进行了加密,一般到了这一步有两种选择,1是使用rpc,2是继续逆向,这里选择1。
RPC
RPC是远程过程调用(Remote Procedure Call)的缩写形式,简单来说就是将本地和浏览器,看做是服务端和客户端,二者之间通过 WebSocket 协议进行通信,在浏览器中将加密函数暴露出来,在本地直接调用浏览器中对应的加密函数,从而得到加密结果,不必去在意函数具体的执行逻辑,也省去了扣代码、补环境等操作,可以省去大量的逆向调试时间。
这里使用Sekiro-RPC搭建服务器和客户端。
Sekiro-RPC的github传送门
油猴脚本注入客户端
注入客户端之前先打开本地Sekiro-RPC服务器,然后在油猴脚本新建内容如下的脚本。
// ==UserScript==
// @name zhihu
// @namespace zhihu
// @version 0.1
// @description zhihu
// @author yueqian
// @match https://www.zhihu.com/*
// @grant none
// @require http://file.virjar.com/sekiro_web_client.js?_=123
// ==/UserScript==
!(function () {
'use strict';
function guid() {
function S4() {
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
}
return (S4() + S4() + "-" + S4() + "-" + S4() + "-" + S4() + "-" + S4() + S4() + S4());
}
var client = new SekiroClient("ws://127.0.0.1:5620/business-demo/register?group=rpc&clientId=" + guid());
client.registerAction("zse96", function (request, resolve, reject) {
return resolve(window.zse96(request["param"]))
});
})();
将加密函数的引用挂到全局作用域中
python调用rpc
def get5rpc(param):
data = {
"group": "rpc",
"action": "zse96",
"param": param
}
res = requests.post(url="http://127.0.0.1:5620/business-demo/invoke", data=data, verify=False)
resp = res.json()
zse96 = '2.0_' + resp['data']
logger.debug('zse96: ' + zse96)
return zse96
传入一个’1’测试,可以正常拿到结果。
代码及运行结果
import json
import requests
from jmespath import search
from loguru import logger
import pymongo
import hashlib
class Spider():
def __init__(self):
self.mongodb = pymongo.MongoClient()
self.db = self.mongodb['spider']
self.coll = self.db['zhihuHot']
def get5rpc(self, param):
data = {
"group": "rpc",
"action": "zse96",
"param": param
}
res = requests.post(url="http://127.0.0.1:5620/business-demo/invoke", data=data, verify=False)
resp = res.json()
zse96 = '2.0_' + resp['data']
logger.debug('zse96: ' + zse96)
return zse96
def md5(self, str):
md5 = hashlib.md5()
md5.update(str.encode('utf-8'))
return md5.hexdigest()
def get_dc0(self):
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36',
'Referer': 'https://www.zhihu.com/search?type=content&q=%E7%88%AC%E8%99%AB',
}
response = requests.post('https://www.zhihu.com/udid', headers=headers)
dc0 = response.cookies.get_dict()['d_c0']
logger.debug(f'dc0: {dc0}')
return dc0
def spider(self, d_c0):
zse96 = self.get5rpc(self.md5("101_3_3.0+/api/v3/feed/topstory/hot-lists/total?limit=50&desktop=true+" + d_c0))
headers = {
"Referer": "https://www.zhihu.com/hot",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36",
"x-zse-93": "101_3_3.0",
"x-zse-96": zse96,
}
cookies = {
"d_c0": d_c0,
"z_c0": "2|1:0|10:1686573425|4:z_c0|92:Mi4xSzlXNEJnQUFBQUFBRUpkdmhfcnJGaVlBQUFCZ0FsVk5jRjkwWlFCSmdseHJOaXBhT3kyWTl5MFNDUGtIVmdET3N3|86159a1b5ed1dd64ad5f260c920e1e50350aa54f9a9d20f96dcb46ac379458dc",
}
url = "https://www.zhihu.com/api/v3/feed/topstory/hot-lists/total"
logger.debug(f'请求: {url}')
params = {
"limit": "50",
"desktop": "true"
}
response = requests.get(url, headers=headers, cookies=cookies, params=params)
logger.debug(f'下载: {response.status_code}')
return response
def parse(self, res):
data2save = []
items = search('data', res.json())
for item in items:
title = search('target.title', item)
href = search('target.url', item)
hotDegree = search('detail_text', item)
data2save.append({
'title': title,
'href': href,
'hotDegree': hotDegree,
})
logger.debug('解析: ' + json.dumps(data2save, indent=2, ensure_ascii=False))
return data2save
def save(self, data):
for i in data:
self.coll.insert_one(i)
logger.debug('保存成功!')
def main(self):
dc0 = self.get_dc0()
resp = self.spider(dc0)
data = self.parse(resp)
self.save(data)
if __name__ == '__main__':
spider = Spider()
spider.main()
这道题的解法不仅对热榜api是有效的,经过测试对知乎搜索api也同样适用,把url和param改一下即可。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达,如有问题请邮件至2454612285@qq.com。