Python模拟登录教务系统全流程:从抓包到成绩查询

发布时间:2026/10/12 14:59:23
Python模拟登录教务系统全流程:从抓包到成绩查询 搞自动化的人都知道学校里的教务系统是最让人又爱又恨的东西。爱的是所有课程、成绩、选课都在上面恨的是它几乎没有一个开放API所有操作都得在浏览器里手动点就连查个平时分也要输入学号、密码、验证码再点三次确认。于是“Python模拟登录教务网”就成了一个绕不开的话题。本文以某高校教务网为样例把完整的模拟登录流程拆开讲清楚从抓包分析、验证码处理、密码加密还原到会话保持、成绩查询每一步都会给出可直接运行的代码和避坑记录。无论你是刚接触网络请求的新手还是写过一些爬虫想摸清学校系统套路的开发者这篇都能派上用场。1. 项目概述与整体思路拆解很多人以为模拟登录教务网是个很高深的事其实拆开之后就是一次普通的HTTP会话你用浏览器输入账号密码、验证码点击登录浏览器把表单数据打包成POST请求发给服务器服务器校验通过后在响应里设置Cookie浏览器记录下来之后你访问成绩、课表页面都会带着这个Cookie服务器一看是“已登录用户”才把数据返回给你。Python要做的就是把这个过程原样复刻一遍建一个Session对象用它去请求登录页、下载验证码、提交表单、保存Cookie再去访问数据页面。整个过程里最麻烦的不是HTTP本身而是三个细节验证码怎么识别、密码怎么加密、Session怎么保持。这三个点解决掉剩下的都是体力活。1.1 教务网登录的本质一次带状态的会话把教务网登录类比成去健身房办卡你带着身份证、手机号去前台账号密码前台给你一张临时入场二维码验证码你扫码确认是真人后办卡成功拿到一张会员卡Cookie。之后每次进健身房只要刷会员卡就行不用再把身份证掏出来再验证一遍。HTTP协议本身是无状态的服务器不记得谁是谁全靠Cookie这个“会员卡”来认人。所以模拟登录的核心就是两件事第一把账号密码验证码正确地交上去第二把服务器发的Cookie妥善保存并且每次请求都带上。requests.Session就是干这个用的它会自动帮你管理Cookie比纯手写Cookie头要省心太多。实际写代码的时候我不建议一上来就对着代码抄而是先在浏览器里完整走一遍登录流程把每一步的请求看清楚。因为不同学校的教务网字段名、接口路径、加密算法都不同网上抄来的代码大概率第一天就跑不通。只有自己抓包才能拿到最准确的信息。1.2 为什么是Python requests而不是Selenium我知道很多人会问用Selenium模拟浏览器登录不是更省事吗确实Selenium直接驱动真实浏览器登录几乎不用关心验证码和加密看起来完美。但用久了你会发现三个痛点启动慢每次都要拉起一个完整浏览器资源占用高跑一次脚本吃掉几百兆内存依赖重还要维护浏览器驱动版本。更关键的是一旦你想并发查很多个账号Selenium几乎无法支撑。requests就不一样轻量、底层、可控。它把HTTP请求暴露给你让你能清楚地看到每一个参数是怎么构造的Cookie是怎么流转的。虽然绕过验证码和加密要做更多工作但换来的是速度和稳定性。而且理解了请求层面的模拟对后续做数据解析、批量任务、异常重试都有帮助。如果只是临时登录一次查成绩那可以用Selenium图省事但如果你想写一个长期运行、自动监控成绩或者选课余量的脚本我强烈建议用requests做。这套方案的执行效率高部署到服务器上也方便不用装图形界面。2. 登录链路分析与核心技术细节2.1 先抓包把浏览器的登录请求“拆开”看动手写代码之前一定要做的第一步是抓包。我用的是Chrome的开发者工具你也可以用Edge、Firefox逻辑都一样。操作过程如下打开教务网登录页按F12打开开发者工具切到Network网络面板。勾选Preserve log保留日志并清空已有请求然后再刷新页面这样能看到登录页加载时的完整请求列表。在页面上输入一个测试账号和密码注意密码随便填一个就行不要用真实密码验证码可以先不看。点击登录按钮立刻在Network面板中找到那条新增的POST请求通常它的名称是login、userLogin、doLogin之类。点击这条请求先看Headers面板记录它的Request URL、请求方法、Content-Type、User-Agent和Referer。再看Payload在旧版Chrome里叫Form Data或Request记下所有表单字段名比如username、password、captcha、rememberMe等等。这一步是整个项目的基石。我见过很多同学跳过抓包直接套模板结果登录接口的字段名不一样代码跑一个小时也登录不进去。把抓包数据保存下来后续构造请求时一一对应基本不会出大问题。有一点要特别提一下很多教务网的登录接口是HTTP而不是HTTPS或者HTTPS证书并不规范。如果是HTTP那直接在Network里看数据明文清晰如果是HTTPS且报证书错误可以在requests里设置verifyFalse并且用urllib3关闭InsecureRequestWarning警告这在后面的代码里会体现。2.2 验证码最大的拦路虎验证码几乎是所有模拟登录教程里第一个劝退点。教务网的验证码五花八门有的是四位数字有的是四位字母数字混合有的还要旋转图片甚至有的加了噪点和干扰线。处理方式大致有三种我按推荐程度从高到低给你排一下。第一种是手动输入。你写一个脚本先用requests把验证码图片下载下来用PIL打开并用系统默认图片查看器展示然后input()等待用户输入。脚本拿到验证码后继续POST登录。这种方式虽然不能全自动但是极其稳定识别率100%也最简单。我平时写个人用的脚本90%都是这个方案。启动之后扫一眼图片输入4个字符脚本就自动往下跑了。第二种是OCR识别。如果你实在想全自动可以用pytesseract配合PIL对验证码图片做处理。多数教务网验证码是白底黑字可以用灰度化、二值化、去噪点来提升识别率。但说实话OCR对不同字体的鲁棒性一般干扰线一多识别率就会掉到60%以下。如果验证码是那种几何扭曲很强的OCR基本白搭。第三种是打码平台。把验证码图片扔给第三方识别接口付费几厘钱一张准确率能到95%以上。这个思路适合大批量登录的场景但需要注意不要踩到平台合规的边界。个人学习用我建议优先手动输入简单可靠不说还能让你把精力放在登录的核心逻辑上。还有个小细节验证码图片的URL一般会带一个随机时间戳参数比如/captcha?t1717000000这是为了防止浏览器缓存。requests请求验证码时也建议带上当前时间戳并且用同一个Session去下载否则验证码和登录请求对不上。2.3 密码加密别看花眼教务网登录时前端JS通常会把密码做一层加密再传上去直接传明文密码的系统现在已经很少了。最常见的加密算法是MD5有的会先MD5一次再拼接一个固定盐值或验证码二次加密。要搞清楚具体算法最直接的方式是看前端JS源码。打开教务网登录页在页面上右键查看源代码搜索password、md5、encrypt等关键词找到登录按钮绑定的JS函数。看懂它如何对password赋值然后用Python复现同样的逻辑。大多数情况下hashlib.md5(password.encode(utf-8)).hexdigest()就够用了。也有更复杂的系统用AES或RSA加密比如先用RSA公钥把密码加密成密文再提交。这种你手动用Python的Crypto库也能实现但稍微繁琐。我建议遇到这种情况时直接用execjs调用网页里的原生JS加密函数把JS文件下载下来用Node.js环境执行让它返回加密结果。这个方法虽然依赖JS运行环境但能最大程度还原前端行为省去你分析算法的时间。我还见过一种套路密码字段的值不是加密后的密码而是“加密密码验证码混淆后的结果”服务器校验时再把验证码部分拆出来。这种深度定制方案没有通用代码只能靠抓包和阅读JS一步步还原。记住一个原则一切以你抓到包里的真实提交值为准不要臆测。2.4 Session与Cookie登录态的生命线保持登录态的关键就是Session。在requests里s requests.Session()创建了一个会话对象之后的每一次get和post都会自动携带之前响应中服务器设置的Cookie。这相当于你在浏览器里一直保持登录状态。这里最常踩的坑是有人自己用requests.get拿到一个Cookie字典然后拼到headers里提交结果登录成功了但查询成绩时却提示未登录。原因往往是登录接口返回的Cookie不止一个或者某个Cookie的Path属性限制只能在部分路径下生效。用Session就不存在这个问题它会根据Set-Cookie里的Path和Domain规则自动处理。另外很多教务网在登录成功后首页会返回一个跳转页面或者接口直接返回一个JSON告诉你status: success。无论哪种形式你都要确认登录是否真的成功再决定要不要继续做后续请求。判断标准不要只看响应状态码是不是200因为登录失败时也可能是200页面里嵌着“密码错误”几个字。更稳妥的方式是检查响应文本里是否包含特定关键词比如“欢迎”“退出”“修改密码”或者直接访问一个需要登录才能访问的页面看返回数据是否符合预期。3. 完整实操从登录到查询成绩3.1 环境准备在开始之前建议先创建一个虚拟环境避免依赖污染。我这里用的库只有四个requests、Pillow、beautifulsoup4、pandas。其中pandas不是必须的只是最后解析表格和存文件时方便一点。安装命令如下pip install requests pillow beautifulsoup4 pandas如果你打算用OCR方案再执行pip install pytesseract并且需要单独安装Tesseract OCR引擎。但正如前面说的手动输入验证码更省心所以OCR这块不是必须的。3.2 第一步获取Session和验证码新建一个login.py文件先导入依赖并创建Session设置一个真实浏览器的User-Agent。某些教务网会拦截不正常的UA这个头不能省。import requests import time import hashlib from PIL import Image # 教务网地址按实际情况替换这里用保留域名做示例 BASE_URL https://jwc.example.edu.cn session requests.Session() session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 })访问登录页让Session先拿到初始化Cookie。有的系统这一步会种一个名为JSESSIONID或者ASP.NET_SessionId的Cookie是后续登录必须携带的。login_page_url f{BASE_URL}/login resp session.get(login_page_url, verifyFalse) print(登录页状态码, resp.status_code)请求验证码图片并把它保存到本地。注意验证码URL里的时间戳参数很多系统不刷新页面时验证码是缓存不变的这不影响使用关键是要用同一个Session去拿。captcha_url f{BASE_URL}/captcha?timestamp{int(time.time())} captcha_resp session.get(captcha_url, verifyFalse) with open(captcha.png, wb) as f: f.write(captcha_resp.content) # 弹出系统图片查看器人工看验证码 img Image.open(captcha.png) img.show() captcha input(请查看屏幕上的验证码图片输入其中的字符)这一步会阻塞等待输入。我把整个脚本设计成交互式的虽然不能全自动但非常稳定。如果你在服务器上跑没有图形界面可以把img.show()改成输出图片到文件然后远程查看或者用OCR。3.3 第二步构造登录请求并提交接下来构造表单数据。假设抓包得到的字段名是username、password、captcha密码加密方式是MD5。很多教务网的密码加密逻辑是“MD5(明文密码)”少数会有盐值你需要以抓包结果为准。username input(请输入学号) plain_password input(请输入密码) md5_password hashlib.md5(plain_password.encode(utf-8)).hexdigest() login_data { username: username, password: md5_password, captcha: captcha, } login_url f{BASE_URL}/login login_resp session.post(login_url, datalogin_data, verifyFalse) login_resp.encoding utf-8这里要注意如果登录响应是JSON直接用login_resp.json()解析如果是HTML则用文本判断。不同学校的成功标志不一样我用关键词匹配的方式做验证if 欢迎 in login_resp.text or 退出 in login_resp.text: print(登录成功) else: print(登录失败请检查账号、密码、验证码或加密方式)如果登录失败把login_resp.text前500个字符打印出来看看页面提示的是什么。常见的是“验证码错误”“密码错误”也有的是“非法请求”或“session过期”这类多半是请求头或Cookie的问题。3.4 第三步验证登录态并访问数据页登录成功后不要急着关掉脚本先访问一个需要登录才能看的接口确认Session里的Cookie真的有效。比如个人中心、成绩查询入口。profile_url f{BASE_URL}/student/profile profile_resp session.get(profile_url, verifyFalse) profile_resp.encoding utf-8 print(个人信息页状态码, profile_resp.status_code) print(是否包含姓名关键词, 姓名 in profile_resp.text)如果这一步返回的还是未登录页面说明登录流程没有完全走通。可能是登录成功后的某个跳转请求没有跟上比如登录接口返回302重定向你需要让Session跟着跳转这通常allow_redirectsTrue会默认处理。也可能是登录成功后服务器种了一个新Cookie你要确认Session保存了它。3.5 第四步查询成绩并解析表格查询成绩往往不是直接GET一个固定URL就行有些系统需要先POST学期参数比如year2024term1。这个必须抓包确认。我这里以最常见的GET方式为例grade_url f{BASE_URL}/student/grade-list grade_resp session.get(grade_url, params{term: 2024-1}, verifyFalse) grade_resp.encoding utf-8 from bs4 import BeautifulSoup soup BeautifulSoup(grade_resp.text, html.parser) table soup.find(table) # 根据实际情况选择表格 rows table.find_all(tr) import pandas as pd data [] for row in rows[1:]: cells row.find_all(td) data.append([cell.get_text(stripTrue) for cell in cells]) df pd.DataFrame(data) print(df.head()) df.to_csv(grades.csv, indexFalse, encodingutf-8-sig)很多老教务网的表格结构是嵌套的find(table)拿到的可能是最外层的布局表格里面套着数据表格。遇到这种情况建议先用print(table.prettify())看一眼结构再用类名或文本内容定位实际数据行。3.6 封成一个简单的类登录和查询逻辑写到函数里当然可以但如果以后还要加课表查询、选课余量监控我建议封装成一个类。这样每个功能一个方法维护起来很顺手。class EduSystem: def __init__(self, base_url): self.session requests.Session() self.base_url base_url self.session.headers.update({User-Agent: Mozilla/5.0 ...}) self.logged_in False def login(self, username, password): # 访问登录页 self.session.get(f{self.base_url}/login, verifyFalse) # 下载验证码 cap_resp self.session.get(f{self.base_url}/captcha, verifyFalse) with open(captcha.png, wb) as f: f.write(cap_resp.content) Image.open(captcha.png).show() captcha input(请输入验证码) pwd hashlib.md5(password.encode(utf-8)).hexdigest() data {username: username, password: pwd, captcha: captcha} resp self.session.post(f{self.base_url}/login, datadata, verifyFalse) if 欢迎 in resp.text: self.logged_in True return True return False def get_grade(self, term): if not self.logged_in: raise RuntimeError(请先登录) resp self.session.get(f{self.base_url}/grade, params{term: term}, verifyFalse) resp.encoding utf-8 return resp.text封装之后脚本入口只需要几行edu EduSystem(https://jwc.example.edu.cn) if edu.login(20240001, your_password): html edu.get_grade(2024-1) print(成绩页面拉取成功长度, len(html))4. 常见问题与排查技巧4.1 登录永远失败先按顺序排查这几项如果真的登录不进去别急着改代码按下面这个顺序排查基本能覆盖90%的问题检查验证码是否输错。尤其是字母O和数字0、字母I和数字1很容易看错。检查密码加密是不是MD5可以用自己抓包的数据对比一下。比如在浏览器里故意输错密码Network里看POST的password值如果它和你的MD5结果一致那说明算法对了如果不一致去看看前端JS到底做了什么操作。检查表单字段名是否一致。有的系统字段名是user而不是username或者密码字段叫pass、pwd这个只能靠抓包确认。检查请求头是否缺少必要字段。有些教务网校验Referer如果为空会直接拒绝请求。可以在Session的headers里加上Referer: 登录页地址。检查是否漏带某个参数。比如rememberMe、loginType、deviceId有些系统即使不填也能过但有些系统填了才能过。我见过最典型的翻车案例是账号密码验证码全对但登录接口要求必须带上一个从登录页种下的csrf_token参数结果整个代码里完全没有这个字段服务器每次都返回“请求无效”。所以抓包时Payload里出现的每一个字段都要想办法找到它的来源。4.2 验证码总识别错误怎么办如果OCR识别率低先检查图片是否做过预处理。简单验证码可以这样操作灰度化、二值化、去掉小噪点。from PIL import Image import pytesseract img Image.open(captcha.png).convert(L) img img.point(lambda x: 255 if x 128 else 0) text pytesseract.image_to_string(img, config--psm 7 -c tessedit_char_whitelistabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789) print(OCR识别结果, text)但说句实在话如果验证码稍微加点扭曲或者干扰线OCR就是不行。这时候别跟它较劲切回手动输入或者直接上打码平台。手动输入虽然要等一步但整个流程的终点是“成功登录”没必要在验证码上卡死自己。另外提醒一下验证码是有时效性的一般一分钟后失效。如果脚本从下载验证码到提交之间耗时太长服务器也会报验证码过期。所以流程设计成先输入验证码再填密码然后一次性提交尽量不要让脚本停在某个步骤超过10秒。4.3 Session失效和Cookie过期问题登录成功后过几分钟再查询突然发现变成未登录状态。最常见的原因是脚本被网络请求卡住Session的存活时间超过了服务器的空闲超时值另一种可能是你没有带上服务器的其他Cookie比如JSESSIONID在登录后会被更新你需要继续用同一个Session对象发请求而不是新建Session。还有一个很多人忽略的点有些教务网在登录成功后会主动Set-Cookie一个新的JSESSIONID如果你用独立变量保存了旧的Cookie再手动塞回Headers里那个旧ID已经失效自然就会被踢出来。用requests.Session就不会有这个问题它会自动更新。检查Cookie的配置也不难直接用session.cookies.get_dict()打印当前所有Cookie一眼就能看出有没有JSESSIONID的变化。4.4 教务系统升级后怎么办教务网每年都可能升级一次。升级之后最常变的几个点依次是登录URL变动、表单字段加新参数、密码加密算法改变、验证码类型改变。发现登录代码失效后不要闷头改代码先把浏览器抓包重新做一遍比对新旧请求的差异。我自己的习惯是每次升级后会保留一份完整的抓包对比文档把登录接口的URL、请求头、请求体、响应格式全部记录下来。有了这份文档即使代码不能跑也能快速定位是哪个环节变了。就算管理员临时改个加密算法你也能通过历史记录轻易发现规律。4.5 网页乱码和编码问题老教务网很喜欢GBK或GB2312编码直接用默认的UTF-8解析会看到一堆乱码。解法很简单在拿到响应后先猜测编码import chardet detected chardet.detect(grade_resp.content) grade_resp.encoding detected[encoding]或者更省事直接用response.encoding gbk试试。如果解析出来的表格文字还是不对那就换成gb18030这个编码是GBK的超集兼容性更好。5. 边界意识与实际使用建议5.1 能做什么不能做什么模拟登录教务网这个技术本身是中立的关键看用它来做什么。写这个脚本的初衷我认为应该是方便自己查询成绩、自动监控课表、或者在选课系统开放时提醒自己登录去抢课。这些场景只访问你自己的账号数据对服务器压力可控属于正常的技术学习与生活便利。但有些事绝对不能碰。比如用脚本批量抓取全校学生的学号、照片、成绩信息使用撞库方式尝试他人账号绕过验证码限制做高频自动化操作或者对教务系统发起大流量请求导致服务不稳定。这些行为不仅容易被学校有关部门盯上还涉嫌违法违规一旦被追究后果很严重。技术能力是用来解决问题的不是用来惹事的这一点我每次写完脚本都要提醒自己。如果真的需要大量数据做教学或科研最正规的做法是联系学校信息中心申请官方数据接口或者走校内流程。很多学校其实是有数据开放渠道的只是学生不知道而已。绕开权限私自爬数据风险远大于收益。5.2 怎样让脚本跑得更稳模拟登录脚本跑起来容易长期稳定运行难。有几个经验是我踩坑后积累的。第一控制请求频率。哪怕你只是查自己的成绩也不要写个死循环每秒钟刷一次。建议每次请求间隔至少2到5秒我一般在查询前加一个随机延迟import time import random time.sleep(random.uniform(1, 3))第二做好异常处理。网络波动、服务器超时、响应结构变化都是常态。我在关键请求上都加try/except并把错误日志写入文件防止脚本半夜默默崩掉还不知道。第三写清楚日志。登录成功、会话失效、验证码错误这类信息用print和logging一起输出。日志是排查问题最快的抓手。第四不要把密码硬编码在代码里。现在很多学校要求密码定期更换你用明文写在脚本里不仅容易泄露换密码后还得改代码。我建议从环境变量或本地配置文件读取配置文件记得加进.gitignore不要上传到代码仓库。5.3 我的一点经验这类模拟登录脚本说实话写完的那一刻最有成就感但你得做好后续维护的心理准备。学校系统一变脚本就可能全线失效所以关键步骤的抓包记录一定要留好。另外我一直坚持不在脚本里加入任何批量拉取他人数据的功能哪怕技术上能实现也很容易给自己惹上不必要的麻烦。最后分享一个非常实用的小技巧登录成功后先手工在浏览器里把成绩页、课表页、个人信息页的请求都抓一遍把URL和参数记下来一次性把代码写好再跑。不要登录之后还不知道数据页长什么样临时去抓包那样中间很容易因为编码、参数、分页问题反复试错。先观察再动手脚本一次写成的概率会大很多。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询