今天打算将一个学校学生自己做的查询老师给分情况的网站爬了(因为实在联系不上原网站作者,迫不得已出此下策),这是一个根据输入的信息,如教师名称、课程名称、课程号等查询信息并返回的网站,为了覆盖到本学校全部的课程,我首先去学校的教务系统通过课表查询系统轻松拿到了全部课程的信息(没有任何反爬措施),根据全部课程的课程号,我开始设计爬取查给分网站的代码。
我首先想当然的打开dev tools,将需要的headers
全部写入config.json
,然后直接requests.get
,最后发现什么也没有拿到,原来headers
里有一个anthorization
的字段,而这个字段里的signature
会随着请求信息,cookie
以及时间变动,说明接下来到了JS逆向的环节了。
这并不是一个好消息,因为我几乎没有相关的经验,我首先再dev tools的源代码一栏中搜索signature, date, secret等信息,结果一无所获。然后我查看了页面元素,发现表单提交里的input元素里有这样一个属性:
<input type="button" id="submitbutton" value="提交" onclick="login(1,true)">
我又到源代码中去搜索login这个函数,不出意外,0个匹配。
这属于超出我贫瘠的知识范围的情况了,不得已求出openai,我得到了这样的回复:
>如果你在网站的开发者工具中搜索不到login函数,有几个可能的原因:
>1. 函数不是在页面加载时直接定义的:如果login函数是在异步加载或动态生成的代码中定义的,它可能不会在页面初始加载时出现在开发者工具的源代码中。你可以尝试在网页加载完成后再次搜索。
>2. 函数可能被压缩或混淆:有些网站会对JavaScript代码进行压缩或混淆,以减少文件大小或保护代码。这可能会导致函数名发生变化,使其难以在源代码中直接搜索到。
>3. 函数可能是从外部资源加载的:login函数可能是从其他文件或外部资源(如CDN)加载的,这样它就不会出现在当前页面的源代码中。你可以尝试追踪网页中加载的脚本文件,查找是否有包含login函数的文件。
dev tools获得不了?我当即放弃了逆向加密算法的尝试,转而看看有没有其他漏洞,事实证明,这是一个错误的决定。
我尝试控制请求的全部header信息与一个真实发送的请求一致,仅仅替换request url param中的课程号。这个策略一开始起效了,但我随即又遇到了问题:
1. 这个api对于x-date
这个字段有校验,与x-date当前时间差距稍远的请求会被拒绝
2. 这个服务器对于过于频繁的请求会直接拒绝,我不得不再两个请求之间添加time.sleep
这就导致了我的一套真实signature是能供我发送600左右个请求,而总共的课程号大约有5000个。虽然只要这样重复操作不到10次就可以完成任务,但这样一点都不DRY,有违我的个人原则。
我又将目光放在了逆向js上,摸索dev tools的过程中我才以外发现原来可以直接在html上打断点(是的我这个铸币这都不知道),然后终于通过再submit input上打断点我找到了login这个函数。
我迅速将这个函数添加到了我的代码中,随即又有一个问题出现:这段代码中使用了CryptoJS这个库,而PyExecJS提供的运行环境貌似并不能调用外部库。
铸币的我首先尝试自创hmacsha1
编码代码,又踩了几个坑之后直接从crypto-js.js源代码中摘出了模组相关代码复制粘贴进了login
函数所在的文件,历经千辛万苦,终于完成了我人生中第一个逆向。(鼓掌)
最后的代码也十分的朴实无华:
import json
import requests
import execjs
import time
with open('course_number.txt', 'r') as f:
numbers = f.readlines()
data = open('scores.json', 'a+', encoding='utf-8')
scores = []
with open('login_cp.js', 'r', encoding='utf-8') as f:
jscode = f.read()
context = execjs.compile(jscode)
for i, number in enumerate(numbers):
number = number.strip()
print(number)
result = context.call('login', number, 1)
new_url = result['url']
headers = result['headers']
try:
time.sleep(0.3) # avoid request rate detection
response = requests.get(new_url, headers=headers)
json_response = response.json()
total = int(json_response['total'])
if total > 10:
tot = int(total / 10 + 1)
for i in range(2, tot + 1):
time.sleep(0.3)
result = context.call('login', number, i)
new_url = result['url']
headers = result['headers']
response = requests.get(new_url, headers=headers)
extra = response.json()
print(extra)
scores = scores + extra['score']
print(i, json_response)
scores = scores + json_response['score']
except:
continue
data.write(json.dumps({"score": scores}, indent=4 ,ensure_ascii=False))
只有python的代码,如果对于whu的查询给分网站感兴趣并且想要相关js脚本可以联系作者。
虽然事后看来感觉自己十分的铸币,这个逆向实际上也非常简单,只需要找到login这个函数就可以解决问题,但是这次的逆向经历对我而言还是意义非凡,故作此留念。