本章内容包含python模拟登录中国海洋大学教务系统(青果)- 百度智能云识别验证码 - 进入选课页面。
系列文章目录
第一章 python模拟登录中国海洋大学教务系统(青果) 第二章 爬取学期所有专业课至excel 第三章 课表排课
前言
提示:本人是个菜鸟,没系统学过python、html等,对以下所有知识点只会应用,不懂原理,大家可以去看看这两位大佬的技术文章。
python爬虫模拟登录学校教务系统(青果教务系统)并查询个人成绩——王森ouc python-scrapy模拟登陆网站–登陆青果教务管理系统(一)——耿子666
本文是借鉴了以上两位大佬的文章,能够跑出结果,但我不是很懂原理,会在网上整理总结,仅供参考。
一、模拟登录
这个前两个大佬都做的很优秀了,我一开始只会f12,查看一下要提交的表单,然后发现提交表单的key(不知道专业术语是啥)会变,我就歇菜了。 就下面这个? , _uydvv,_pydvv,它们是由_u,_p和你输入的验证码拼接而成的
之后查资料的时候,发现竟然有大佬已经写了我想写的前半部分内容,整挺好,然后踩在大佬的肩膀上,修修改改也写了一天,才基本实现了所有内容。 现在我就带着大家再挨个走一遍所有的路程,出发!
1.分析提交表单
(海大的教务系统需要连校园网或VPN) 1.使用Google Chrome打开教务网站http://jwgl.ouc.edu.cn/cas/login.action
2.按F12 - 进入开发者工具 点击Network - 网络
chrome开发者工具最常用的四个功能模块:元素(ELements)、控制台(Console)、源代码(Sources),网络(Network)。
勾选Preserve log - 保留请求日志
谷歌开发者工具里面这个preserve log :保留请求日志,跳转页面的时候勾选上,可以看到跳转前的请求,也可适用于chrome开发者工具抓包的问。
3.随便输入一些账户密码验证码 可以看到点击验证码框或验证码的时候,会有名为genValidateCode?..的请求来获取新的验证码图片。
点击该请求(查看请求的详细内容)- 点击preview(预览)-可以看到验证码图片 再点击Headers Request Method是Get
Request URL: http://jwgl.ouc.edu.cn/cas/genValidateCode?dateTime=Fri%20Jul%2023%202021%2010:35:41%20GMT+0800%20(%D6%D0%B9%FA%B1%EA%D7%BC%CA%B1%BC%E4)
复制该url进行打开,可以看到并不是原来的验证码图片,每次刷新后验证码都不一样
经过验证,该验证码与dateTime并没有关系,目前我还不知道原理,请大佬指教
通过该url可以获得验证码,暂且不管该验证码是否是我们需要的正确的验证码
4.点击提交,出现名为logon.action的请求,点击观察其Headers内容
在写请求的时候,一般形式应该是(url,表单数据,请求头):
response = request.post(url, data, headers)
这些在Headers内容里都能找到,然后就是用代码构造这些内容:
import requests
url = 'http://jwgl.ouc.edu.cn/cas/logon.action'
data = {
'_u'+randnumber: 'MTgwOTAwMTIwMjg7OzkyODY2QTM4NUI4NDBCMkQzQjFGNEI5MzgwMDI2Qzg3',
'_p'+randnumber: 'e045dced42fea99c1434f62903a9e564',
'randnumber': 'y7jz',
'isPasswordPolicy': '1',
'txt_mm_expression': '12',
'txt_mm_length': '11',
'txt_mm_userzh': '0',
}
headers = {
'Host': 'jwgl.ouc.edu.cn',
'Origin': 'http://jwgl.ouc.edu.cn',
'Referer': 'http://jwgl.ouc.edu.cn/cas/login.action',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36',
'X-Requested-With': 'XMLHttpRequest',
'Upgrade-Insecure-Requests': '1',
}
r = session.post(url, data=data, headers=headers)
request对象的生命周期是针对一个客户端(说确切点就是一个浏览器应用程序)的一次请求,当请求完毕之后,request里边的内容也将被释放掉 。 而session的生命周期也是针对一个客户端,但是却是在别人设置的会话周期内(一般是20-30分钟),session里边的内容将一直存在,即便关闭了这个客户端浏览器 session也不一定会马上释放掉的。 ——参考request和session的区别
在这里因为在登录后,还要跳转到选课页面,需要保存登录信息,所以用session
另外好像可以#创建cookie,利用cookie实现持久化登录,感兴趣的可以瞅瞅
会话(Session)跟踪是Web程序中常用的技术,用来跟踪用户的整个会话。常用的会话跟踪技术是Cookie与Session。Cookie通过在客户端记录信息确定用户身份,Session通过在服务器端记录信息确定用户身份。 ——参考Cookie和Session详解
表单信息不能直接复制,需要先观察一下
_uy7jz: MTgwOTAwMTIwMjg7OzkyODY2QTM4NUI4NDBCMkQzQjFGNEI5MzgwMDI2Qzg3
_py7jz: e045dced42fea99c1434f62903a9e564
randnumber: y7jz
isPasswordPolicy: 1
txt_mm_expression: 12
txt_mm_length: 11
txt_mm_userzh: 0
观察得到: 1.前两项的key是_u,_p+输入的验证码,value是加密过的,看不出内容 2.根据变量名猜测其含义,见注释 3.账户密码没有直接显示出来
所以要去源代码里找相关信息: 1.定位账号密码代码的位置 2.在控制台里按CTRL+shift+F打开全局搜索,搜索yhmm 3.点击第二个 4.观察代码:前几行都是获取输入,后几行就是对这些信息的加密,我们需要在代码里对信息进行同样的加密
password = hex_md5(hex_md5(password)+hex_md5(randnumber.toLowerCase()));
var p_username = "_u"+randnumber;
var p_password = "_p"+randnumber;
username = base64encode(username+";;"+_sessionid);
var params = p_username+"="+username+"&"+p_password+"="+password+"&randnumber="+randnumber+"&isPasswordPolicy="+passwordPolicy+
"&txt_mm_expression="+txt_mm_expression+"&txt_mm_length="+txt_mm_length+"&txt_mm_userzh="+txt_mm_userzh;
加密语句??对应
password = md5(password.encode('utf-8')).hexdigest()
randnumber = md5(randnumber.lower().encode('utf-8')).hexdigest()
password = md5((password + randnumber).encode('utf-8')).hexdigest()
data['randnumber'] = randnumber
p_username = '_u' + randnumber
p_password = '_p' + randnumber
data[p_username] = username
data[p_password] = password
_sessionid = session.cookies.get_dict()['JSESSIONID']
username = base64.b64encode(username.encode('utf-8') + b';;' + str(_sessionid).encode('utf-8'))
???以上,账号、密码、验证码、url、data、headers都已分析过
2.百度智能云识别验证码
没有用过百度智能云的可以看看:百度智能云-文字识别SDK-识别验证码Demo
import requests
from aip import AipOcr
from bs4 import BeautifulSoup
headers = {
'Host': 'jwgl.ouc.edu.cn',
'Origin': 'http://jwgl.ouc.edu.cn',
'Referer': 'http://jwgl.ouc.edu.cn/cas/login.action',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36',
'X-Requested-With': 'XMLHttpRequest',
'Upgrade-Insecure-Requests': '1',
}
def randnumber_ocr(image):
APP_ID = ''
API_KEY = ''
SECRET_KEY = ''
client = AipOcr(APP_ID, API_KEY, SECRET_KEY)
text = client.basicAccurate(image)
if text['words_result_num'] == 1:
return text['words_result'][0]['words'].strip()
else:
return ''
def logon():
url = 'http://jwgl.ouc.edu.cn/cas/logon.action'
session = requests.Session()
randnumber = ''
while len(randnumber) != 4:
r = session.get('http://jwgl.ouc.edu.cn/cas/genValidateCode', headers=headers)
randnumber = randnumber_ocr(r.content).replace(' ','')
if __name__ == '__main__':
logon()
3.代码
import requests
from aip import AipOcr
import base64
from hashlib import md5
import json
import re
from bs4 import BeautifulSoup
headers = {
'Host': 'jwgl.ouc.edu.cn',
'Origin': 'http://jwgl.ouc.edu.cn',
'Referer': 'http://jwgl.ouc.edu.cn/cas/login.action',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36',
'X-Requested-With': 'XMLHttpRequest',
'Upgrade-Insecure-Requests': '1',
}
data = {
'randnumber': 'gqzd'
}
def randnumber_ocr(image):
APP_ID = ''
API_KEY = ''
SECRET_KEY = ''
client = AipOcr(APP_ID, API_KEY, SECRET_KEY)
text = client.basicAccurate(image)
if text['words_result_num'] == 1:
return text['words_result'][0]['words'].strip()
else:
return ''
def logon():
url = 'http://jwgl.ouc.edu.cn/cas/logon.action'
username = ''
password = ''
session = requests.Session()
randnumber = ''
while len(randnumber) != 4:
r = session.get('http://jwgl.ouc.edu.cn/cas/genValidateCode', headers=headers)
randnumber = randnumber_ocr(r.content).replace(' ','')
password = md5(password.encode('utf-8')).hexdigest()
randnumber = md5(randnumber.lower().encode('utf-8')).hexdigest()
password = md5((password + randnumber).encode('utf-8')).hexdigest()
data['randnumber'] = randnumber
p_username = '_u' + randnumber
p_password = '_p' + randnumber
data[p_username] = username
data[p_password] = password
_sessionid = session.cookies.get_dict()['JSESSIONID']
username = base64.b64encode(username.encode('utf-8') + b';;' + str(_sessionid).encode('utf-8'))
r = session.post(url, data=data, headers=headers)
info = json.loads(r.text)
status = info['status']
if status == '401':
print('验证码错误')
return
elif status == '200':
pass
else:
print(info['message'])
return
print('登录成功')
if __name__ == '__main__':
logon()
二、进入选课页面
登录成功后,就要进入选课页面,此部分与一内容基本相同且更简单,可以自己尝试分析一下
1.选择选课学年学期-年级-专业——点击检索 2. 出现新请求,一般我们需要的都是document类型的,点击查看其详细信息 3. Request URL: http://jwgl.ouc.edu.cn/taglib/DataTable.jsp?tableId=6146 Request Method: POST
Referer: http://jwgl.ouc.edu.cn/student/wsxk.kcbcx.html?menucode=JW130414
FormData initQry: 0 xktype: 2 #选课类型 xh: 1*********8 #你的学号 xn: 2021 #学年 xq: 1 #学期-夏0-秋1-春2 nj: 20** #你的年级 zydm: 0**0 #你的专业代码 items: xnxq: 2021-1 #学年-学期 kcfw: Specialty #课程范围 sel_nj: 2019 #选择的年级2018-2020 sel_zydm: 0011 #选择的专业代码 sel_schoolarea: sel_cddwdm: sel_kc: kcmc:
经过观察,表单信息都很简单,构造data,headers需要有Referer
Referer 是 HTTP 请求header 的一部分,当浏览器(或者模拟浏览器行为)向web 服务器发送请求的时候,头信息里有包含 Referer 。它就是表示该请求的来源。 ——参考什么是HTTP Referer?
def get_score(session, sel_nj):
url = 'http://jwgl.ouc.edu.cn/taglib/DataTable.jsp?tableId=6146'
formdata={
'initQry': '0',
'xktype': '2',
'xh': '1**********',
'xn': '2021',
'xq': '1',
'nj': '20**',
'zydm': '0**0',
'items': '',
'xnxq': '2021-1',
'kcfw': 'Specialty',
'sel_nj': sel_nj,
'sel_zydm': '0011',
'sel_schoolarea': '',
'sel_cddwdm': '',
'sel_kc': '',
'kcmc': ''
}
headers['Referer'] = 'http://jwgl.ouc.edu.cn/student/wsxk.kcbcx.html?menucode=JW130414'
r = session.post(url,data=formdata, headers=headers)
soup = BeautifulSoup(r.text, 'html.parser')
table_node = soup.find_all('td')
for table in table_node:
print (table.text)
session.close()
三、完整代码及代码效果
import requests
from aip import AipOcr
import base64
from hashlib import md5
import json
import re
from bs4 import BeautifulSoup
headers = {
'Host': 'jwgl.ouc.edu.cn',
'Origin': 'http://jwgl.ouc.edu.cn',
'Referer': 'http://jwgl.ouc.edu.cn/cas/login.action',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36',
'X-Requested-With': 'XMLHttpRequest',
'Upgrade-Insecure-Requests': '1',
}
data = {
'randnumber': 'gqzd'
}
def randnumber_ocr(image):
APP_ID = ''
API_KEY = ''
SECRET_KEY = ''
client = AipOcr(APP_ID, API_KEY, SECRET_KEY)
text = client.basicAccurate(image)
if text['words_result_num'] == 1:
return text['words_result'][0]['words'].strip()
else:
return ''
def get_score(session):
url = 'http://jwgl.ouc.edu.cn/taglib/DataTable.jsp?tableId=6146'
formdata={
'initQry': '0',
'xktype': '2',
'xh': '1**********',
'xn': '2021',
'xq': '1',
'nj': '20**',
'zydm': '0**0',
'items': '',
'xnxq': '2021-1',
'kcfw': 'Specialty',
'sel_nj': '2019',
'sel_zydm': '0011',
'sel_schoolarea': '',
'sel_cddwdm': '',
'sel_kc': '',
'kcmc': ''
}
headers['Referer'] = 'http://jwgl.ouc.edu.cn/student/wsxk.kcbcx.html?menucode=JW130414'
r = session.post(url,data=formdata, headers=headers)
soup = BeautifulSoup(r.text, 'html.parser')
table_node = soup.find_all('td')
for table in table_node:
print (table.text)
session.close()
def logon():
url = 'http://jwgl.ouc.edu.cn/cas/logon.action'
username = ''
password = ''
session = requests.Session()
randnumber = ''
while len(randnumber) != 4:
r = session.get('http://jwgl.ouc.edu.cn/cas/genValidateCode', headers=headers)
randnumber = randnumber_ocr(r.content).replace(' ','')
data['randnumber'] = randnumber
p_username = '_u' + randnumber
p_password = '_p' + randnumber
password = md5(password.encode('utf-8')).hexdigest()
randnumber = md5(randnumber.lower().encode('utf-8')).hexdigest()
password = md5((password + randnumber).encode('utf-8')).hexdigest()
_sessionid = session.cookies.get_dict()['JSESSIONID']
username = base64.b64encode(username.encode('utf-8') + b';;' + str(_sessionid).encode('utf-8'))
data[p_username] = username
data[p_password] = password
r = session.post(url, data=data, headers=headers)
info = json.loads(r.text)
status = info['status']
if status == '401':
print('验证码错误')
return
elif status == '200':
pass
else:
print(info['message'])
return
print('登录成功')
get_score(session)
if __name__ == '__main__':
logon()
运行效果:
四、遇到的问题
KeyError: 'words_result_num'
没有填写APP_ID、API_KEY 、SECRET_KEY
print(tds[1].string + ": " + tds[16].string)
TypeError: can only concatenate str (not "NoneType") to str
爬取错误,tds里没有内容
if table.attrs['style'] != "display:none;":
KeyError: 'style'
TypeError: 'NoneType' object is not subscriptable
table = soup.find('td')
print (table)
print (table['id'])
print (table['style'])
print (table.id)
print (table.style)
<td id=“tr2_cddw” name=“cddw” style=“display:none;”>计算机科学与技术系</td> 这样一个table,其style=“display:none;”,说明其是隐藏的,没有必要爬取,但是有的table有style,有的没有 所以要怎么添加判断呢 TT 感谢指教
总结
知识点十分多啊,整理了两天,收获也很多 \OoO/
码字不易,感谢点赞评论收藏关注~
|