List [CTL]
没爬过自己学校教务网站怎么能说自己会敲爬虫 : )
在此记录模拟登录cumt教务系统
Demo
正文
和许多学校相同,都是正方教务系统(ummm正方和煎蛋难兄难弟)
查看源代码
可以看到由五个js进行登录加密,为RSA加密,不了解rsa的看这里:RSA加密
提交表单
post的数据包括csrf令牌以及明文的yhm(即学号,我随便敲的),和base64加密的mm(提交了两次),即密码
csrftoken
用来防止跨站请求伪造
源代码中搜索,找到随机生成的token表单value
登录加密
查看login.js
找到获取公钥私钥的地址
cookies问题
使用requests库的requests.session()保持会话
登录逻辑:从登录页面获取csrftoken,请求login_getpublickey.html提交时间参数获取rsa密钥,对获取到的密钥base64解码,用密钥对登录密码进行rsa加密,对密文再进行base64编码,最后post
rsa加密是最麻烦的地方
看了教务系统的base64
编码js
,发现编码方式为hex串。由于使用python
标准库中的base64
会将hex串转为字节,而这里的RSA
密钥则是需要完整的hex字符串,例如标准库中a0 => YTA=
,而我需要a0 => oA==
,即将a0
看作一个字节的hex值进行编码。
故写了个base64 => hex
的算法,其实图方便可以直接把base64.js
改写为python
版,但我自己写的原因是:…那个算法我没看懂
class HB64(object):
b64byte = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
b64cpt = "="
def hex2b64(self, string):
result = ""
ptr = 0
b1 = int("111111000000000000000000", 2)
b2 = int("000000111111000000000000", 2)
b3 = int("000000000000111111000000", 2)
b4 = int("000000000000000000111111", 2)
lenth = len(string)
while ptr+6 <= lenth:
temp = int(string[ptr:ptr+6], 16)
result += self.b64byte[(temp & b1) >> 18]
result += self.b64byte[(temp & b2) >> 12]
result += self.b64byte[(temp & b3) >> 6]
result += self.b64byte[temp & b4]
ptr += 6
if lenth-ptr == 4:
temp = int(string[ptr:ptr+4], 16) << 2
result += self.b64byte[(temp & b2) >> 12]
result += self.b64byte[(temp & b3) >> 6]
result += self.b64byte[temp & b4]
result += self.b64cpt
elif lenth-ptr == 2:
temp = int(string[ptr:ptr+2], 16) << 4
result += self.b64byte[(temp & b3) >> 6]
result += self.b64byte[temp & b4]
result += self.b64cpt * 2
elif lenth-ptr == 0:
pass
else:
raise Exception
return result
def b642hex(self, string):
result = ""
ptr = 0
lenth = len(string)
b1 = int("111111110000000000000000", 2)
b2 = int("000000001111111100000000", 2)
b3 = int("000000000000000011111111", 2)
while ptr+8 <= lenth:
temp = string[ptr:ptr+4]
temp_result = 0
for cell in range(4):
temp_result += self.b64byte.index(temp[cell]) << (6 * (3 - cell))
r1 = hex((temp_result & b1) >> 16)[2:]
r2 = hex((temp_result & b2) >> 8)[2:]
r3 = hex(temp_result & b3)[2:]
if len(r1) == 1:
r1 = '0' + r1
if len(r2) == 1:
r2 = '0' + r2
if len(r3) == 1:
r3 = '0' + r3
result += r1
result += r2
result += r3
ptr += 4
if string[-1]=="=" and string[-2]=="=":
temp = string[ptr:ptr+2]
temp_result = 0
temp_result += self.b64byte.index(temp[0]) << 18
temp_result += self.b64byte.index(temp[1] >> 4) << 12
r1 = hex((temp_result & b1) >> 16)[2:]
r2 = hex((temp_result & b2) >> 8)[2:]
if len(r1) == 1:
r1 = '0' + r1
if len(r2) == 1:
r2 = '0' + r2
result += r1
result += r2
elif string[-1]=="=":
temp = string[ptr:ptr+3]
temp_result = 0
for cell in range(2):
temp_result += self.b64byte.index(temp[cell]) << (6 * (3 - cell))
temp_result += self.b64byte.index(temp[2] >> 2) << 6
r1 = hex((temp_result & b1) >> 16)[2:]
r2 = hex((temp_result & b2) >> 8)[2:]
r3 = hex(temp_result & b3)[2:]
if len(r1) == 1:
r1 = '0' + r1
if len(r2) == 1:
r2 = '0' + r2
if len(r3) == 1:
r3 = '0' + r3
result += r1
result += r2
result += r3
elif "=" not in string:
temp = string[ptr:ptr+4]
temp_result = 0
for cell in range(4):
temp_result += self.b64byte.index(temp[cell]) << (6 * (3 - cell))
r1 = hex((temp_result & b1) >> 16)[2:]
r2 = hex((temp_result & b2) >> 8)[2:]
r3 = hex(temp_result & b3)[2:]
if len(r1) == 1:
r1 = '0' + r1
if len(r2) == 1:
r2 = '0' + r2
if len(r3) == 1:
r3 = '0' + r3
result += r1
result += r2
result += r3
else:
raise Exception
return result
RSA
加密是JS
中的jsbn进行大数字运算的特定加密,有setPublicKey
方法,一样不同于python标准库,见此
参考stackoverflow文章戳我,用了github上别人写的JS
原生RSA
加密的python版程序
代码
class httpmthd():
sessions = requests.session()
time = int(time.time())
def __init__(self,user,passwd):
self.user = str(user).encode("utf8").decode("utf8")
self.passwd = str(passwd).encode("utf8").decode("utf8")
def get_public(self): #获得rsa公钥json保存在pub字典中
url = 'http://202.119.206.62/jwglxt/xtgl/login_getPublicKey.html?time='+str(self.time)
r = self.sessions.get(url)
self.pub = r.json()
def get_csrftoken(self): #提取token
url = 'http://202.119.206.62/jwglxt/xtgl/login_slogin.html?language=zh_CN&_t='+str(self.time)
r = self.sessions.get(url)
r.encoding = r.apparent_encoding
soup = BeautifulSoup(r.text,'html.parser')
self.token = soup.find('input',attrs={'id':'csrftoken'}).attrs['value']
def process_public(self,str): #处理密码,rsa加密
a = HB64()
self.exponent = a.b642hex(self.pub['exponent']) #将json中的base64加密公钥解密
self.modulus = a.b642hex(self.pub['modulus'])
rsa = RSAJS.RSAKey()
rsa.setPublic(self.modulus, self.exponent) #rsa加密
cry_data = rsa.encrypt(str)
return a.hex2b64(cry_data) #加密后的数据进行base64加密
def post_data(self): #post数据
try:
url = 'http://202.119.206.62/jwglxt/xtgl/login_slogin.html'
header = {
'Accept':'text/html,application/xhtml+xm…plication/xml;q=0.9,*/*;q=0.8',
'Accept-Encoding':'gzip, deflate',
'Accept-Language':'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
'Connection':'keep-alive',
'Content-Length':'470',
'Content-Type':'application/x-www-form-urlencoded',
'Host':'202.119.206.62',
'Referer':'http://202.119.206.62/jwglxt/xtgl/login_slogin.html?language=zh_CN&_t='+str(self.time),
'Upgrade-Insecure-Requests':'1',
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:58.0) Gecko/20100101 Firefox/58.0',
}
self.header = header
data = [
('csrftoken',self.token),
('mm',self.process_public(self.passwd)), #对密码进行加密
('mm',self.process_public(self.passwd)), #post的data数据有两个相同mm字段
('yhm',self.user)
]
self.req = self.sessions.post(url,headers = header,data = data)
ppot = r'用户名或密码不正确'
if re.findall(ppot,self.req.text):
print('用户名或密码错误,请查验..')
time.sleep(2)
exit()
except:
print('登录失败,请检查网络配置或检查账号密码...')
time.sleep(1)
exit()
放上代码地址,使用时导入login.py,调用httpmthd类即可
此处的requests库会有一个编码问题,按报错把库文件代码里某处的’latin1’改为’utf-8’就可以解决
模拟登录后顺便做了成绩获取
打算有时间了敲一个多线程暴力抢课脚本,但是很狗的是,敲完程序测试的机会就只有学期末抢课那几天,这学期敲好调试好要等到过一学期才能拿来用 ( :
好了不管这些,反正模拟登录成功后就可以为所欲为了
selenium脚本
除了利用RSA加密密码外,还可以使用selenium直接提交表单
缺点是button的click()有几率失效,网上有解决办法,不多讨论
P.s在此强烈谴责selenium喜新厌旧的行为!!!居然抛弃了PhantomJS转投火狐和chrome的无头版本,然而火狐无头实例化耗时比phantom多了不少
而且既然不支持phantom,为啥库文件里还是有phantom的包,直到运行的时候才报错说phantom已经过时了,请使用火狐或谷歌…莫名其妙.jpg
更多思路
还可以node.js本地运行js加密,或者提交到在线rsa加密网站
反正方法有很多,没有验证码也可以说对爬虫很友好了