想学Python。关注小编头条号,私信【学习资料】即可免费领取全套系统板Python学习教程!
尝试登录
首先我们打开学校的教务系统,随便输入,然后提交表单,在Chrome的开发者工具中打开Network,准备抓包
过滤掉css图片之类的,找到了default.aspx之类的东西
如果您学校的教育系统不使用 cookie,就会出现这种情况
我们可以发现真正的请求地址是(bdq1aj45lpd42o55vqpfgpie)/default2.aspx
然后我们发现这个URL括号里的那一串信息有点奇怪,而且每次输入的信息都不一样。数据查询后,这是 ASP.NET 不使用 Cookie 会话管理的技术。
没有 cookie 的 ASP.NET 会话管理
那么这就好办了,我们只需要在登录的时候记录这些数据就可以保持登录状态。
经过测试发现我们可以只伪造一个会话信息来一直保持登录状态,但是为了体现模拟登录的科学性,我们需要先获取会话信息。
如果您学校的教育系统使用 cookie,就会发生这种情况
服务端会返回一个cookie值,然后保存在本地asp没有验证码定义,和下面的会不一样。
获取会话信息(无 cookie)
这里我们要使用requests库,伪造header的UA信息
经过测试asp没有验证码定义,我们发现我们只访问学校的IP地址,它会自动重定向到带有会话信息的URL,所以我们先访问IP地址。
class Spider: def __init__(self, url): self.__uid = '' self.__real_base_url = '' self.__base_url = url self.__headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.62 Safari/537.36', } def __set_real_url(self): request = requests.get(self.__base_url, headers=self.__headers) real_url = request.url self.__real_base_url = real_url[:len(real_url) - len('default2.aspx')] return request
上面得到的URL是带有会话信息的URL,保存的URL格式为(bdq1aj45lpd42o55vqpfgpie)/
以这种格式保存,因为我们要访问其他地址
获取会话信息(使用 cookie)
部分学校的教务系统使用cookies。我们只需要在第一次get请求时保存cookie,然后一直使用cookie。
def get_cookie(): request = requests.get('http://xxx.xxx.xxx.xxx') #以某教务系统为例子 cookie = requets.cookie return cookie
并且在请求中使用 cookie 非常简单
只需要这个
def use_cookie(cookie): request = requests.get('http://xxx.xxx.xxx.xxx',cookie=cookie)
由于我们学校采用的是无cookie的方案,所以下面的代码不发送cookie。如果你的学校采用 cookie,你只需要像我上面做的那样发送 cookie。
而如果你的学校使用cookies,你就不必通过session信息获取地址,直接存储cookie即可。
或者可以使用请求的Session来自动管理会话信息,这样文章下面代码中的所有请求都可以改为Session请求,但是需要先在类的初始化方法中进行初始化。
def __init__(self): self.session = requests.Session()
然后我们首先访问该网站一次以获取cookie并存储它
def get(self): r = self.session.get(url,headers=headers)
更多用法可以查询文档
验证码的处理
分析r返回的文本信息
找到验证码标签的资源地址是src=”CheckCode.aspx”,我们可以直接请求然后下载验证码图片。下载图像的一种优雅方式如下
def __get_code(self): request = requests.get(self.__real_base_url + 'CheckCode.aspx', headers=self.__headers) with open('code.jpg', 'wb')as f: f.write(request.content) im = Image.open('code.jpg') im.show() print('Please input the code:') code = input() return code
以上代码将图片保存为code.jpg,Python有一个Image模块可以自动打开图片
这样验证码就显示出来了,我们可以手动输入,也可以传到编码平台。
登录数据的构建
这是上面抓到的登录帖子的数据包,
发现有信息无法解码,应该是gb2312编码,解码前检查编码
然后把不能解码的代码复制到能解码的地方
发现%D1%A7%C9%FA编解码是学生
这也对应于学生选项的登录。
学号、密码和验证码可以清楚的知道是什么信息,但是我们发现有一个__VIEWSTATE项
查一下,这是一种隐藏形式的信息,我们可以使用 BeautifulSoup 库来解析这一项数据的值
这是完整的登录数据包,
def __get_login_data(self, uid, password): self.__uid = uid request = self.__set_real_url() soup = BeautifulSoup(request.text, 'lxml') form_tag = soup.find('input') __VIEWSTATE = form_tag['value'] code = self.__get_code() data = { '__VIEWSTATE': __VIEWSTATE, 'txtUserName': self.__uid, 'TextBox2': password, 'txtSecretCode': code, 'RadioButtonList1': '学生'.encode('gb2312'), 'Button1': '', 'lbLanguage': '', 'hidPdrs': '', 'hidsc': '', } return data
登录
如果登录完成,如何判断是否登录成功?我们从登录成功返回的界面中发现有一个name的标签,而我们等待的时候也需要学生的名字,所以我们以此为依据来判断登录是否成功。
代码如下,区分验证码用户名和密码的提示信息
def login(self,uid,password): while True: data = self.__get_login_data(uid, password) request = requests.post(self.__real_base_url + 'default2.aspx', headers=self.__headers, data=data) soup = BeautifulSoup(request.text, 'lxml') try: name_tag = soup.find(id='xhxm') self.__name = name_tag.string[:len(name_tag.string) - 2] print('欢迎'+self.__name) except: print('Unknown Error,try to login again.') time.sleep(0.5) continue finally: return True
获取选课信息
下一步是获取课程选择信息。这里我们以学校公开课选课为例,点开,抓包。标题中没有什么需要注意的。我们只需要关注get发送的包裹即可。
发现有一个学号、姓名和gnmkdm。名称需要以gb2312的形式编码传输
这里我们注意到headers需要添加一个Referer项,也就是当前访问的URL,才能发出请求
def __enter_lessons_first(self): data = { 'xh': self.__uid, 'xm': self.__name.encode('gb2312'), 'gnmkdm': 'N121103', } self.__headers['Referer'] = self.__real_base_url + 'xs_main.aspx?xh=' + self.__uid request = requests.get(self.__real_base_url + 'xf_xsqxxxk.aspx', params=data, headers=self.__headers) self.__headers['Referer'] = request.url soup = BeautifulSoup(request.text, 'lxml') self.__set__VIEWSTATE(soup)
注意上面有个设置VIEWSTATE值的函数,后面在选择课程和构建数据包的时候会讲到。
模拟课程选择
随便挑一门课程,然后提交,抓包,看看发了什么数据
前三个值可以在原始网页的输入标签中找到。由于前两项为空,所以不会获取,第三项可以通过汤分析获取。由于这个操作每次被请求都会改变,我们把它写成一个每次请求完成时设置的函数。
def __set__VIEWSTATE(self, soup): __VIEWSTATE_tag = soup.find('input', attrs={'name': '__VIEWSTATE'}) self.__base_data['__VIEWSTATE'] = __VIEWSTATE_tag['value']
对于其他数据,我们可以通过搜索响应网页了解它们的用途。这里我只描述我们使用的数据。
TextBox1是搜索框数据,我们可以用这个来搜索课程,dpkcmcGrid:txtPageSize是一页显示多少数据,经过测试,服务器最多响应200条。
值得注意的是ddl_xqbs的校园数据信息,我的校园编号为2,可能不同学校设置不同,需要自己设置,也可以从网页获取
以下是基本数据包。由于我们需要使用这个基础数据包来搜索课程和选择课程,所以我们直接在init函数中添加。
self.__base_data = { '__EVENTTARGET': '', '__EVENTARGUMENT': '', '__VIEWSTATE': '', 'ddl_kcxz': '', 'ddl_ywyl': '', 'ddl_kcgs': '', 'ddl_xqbs': '2', 'ddl_sksj': '', 'TextBox1': '', 'dpkcmcGrid:txtChoosePage': '1', 'dpkcmcGrid:txtPageSize': '200', }
请登录后发表评论
注册
社交帐号登录