1. 从"蹲点刷网页"到"攒请求":聊聊选课这件事的技术底子
每年一到选课季,教务系统的登录页就像被按了加速键,刷新的手速决定了一切。我不止一次听到有人抱怨,明明提前十分钟守在电脑前,页面转圈转了半分钟,等加载出来的时候热门课早就没了。问题的根源其实很朴素——浏览器渲染页面、加载脚本、绘制按钮,这些都要时间;而选课这件事真正的核心动作,只是一次带着参数的HTTP请求而已。如果能把"点按钮"这个动作,还原成"发请求",选课就从一场手速比拼,变成了一次可以精确控制的技术操作。
本文想分享的就是这样一件事:借助Postman这个接口调试工具,把教务系统网页背后的请求拆开看,理解它到底发了什么、带什么参数、返回什么结构,然后在Messages之间完成一次干净利落的选课请求。需要提前说明的是,这里的"接口文档"并不是教务系统官方提供的正式文档,而是我们在实际操作过程中,通过观察浏览器网络面板里的请求,反推整理出来的可用接口清单。
这类做法的价值在于学习本身:你能真正搞明白一个网页应用是怎么"登录—鉴权—提交—反馈"的。选课只是一个具体的应用场景。适合阅读这篇文章的人,是那些对HTTP请求、抓包、接口调试有点好奇,又希望有一个真实、有价值的场景练手的人。如果你连Postman是什么都还没概念,也没关系,我会从最基础的部分讲起,把它当成一次从浏览器到接口的完整旅程。
另外要先坦白一句:本文所有操作仅用于个人学习接口调试、理解Web应用工作原理。任何选课行为都应该在本校教务规定允许的范围内进行,不要去做超频请求、影响服务器稳定的操作,这一点在后面的实操章节里我还会反复提醒。
2. 整体方案设计与思路拆解
2.1 为什么用Postman,而不写一个自动脚本
很多人第一反应是写个Python脚本,用requests库去POST。这条路不是不能走,但在这个场景里,Postman有三个现实中很实在的优势。
第一是可视化。选课的请求通常不是一次就能跑通的,你需要反复修改参数、替换Cookie、观察返回内容。Postman把请求的每一部分——URL、Header、Body、Cookie——都拆成独立的编辑区,你改一个字段,点一下Send,立刻能看到结果,这种"改—发—看"的循环效率,是命令行脚本比不了的。脚本里出了问题,你得加print、重跑、看日志,一轮下来一两分钟就过去了。
第二是环境变量和会话保持。教务系统的选课链路里,登录态是通过Cookie维持的,而且很多学校的系统会把Cookie和IP、User-Agent这些东西绑在一起做校验。Postman内置的Cookie管理器和环境变量功能,可以让你把登录后拿到的Cookie自动带上,不用手动复制粘贴,也避免了复制漏字符导致的"明明登录了却提示未登录"。
第三是复用性强。你今天调试通了一条选课请求,可以在Postman里把它保存成一个Collection(集合),以后每次选课季直接改改参数就能用。甚至可以在Collection里把登录、查询课表、提交选课这一整条链路串起来,按顺序执行。
当然,Postman也不是万能的。它的自动化能力比纯脚本弱,如果你要做高频轮询或者复杂的条件判断,脚本仍然更合适。但在"理解接口、手动完成一次请求"这个目标上,Postman的定位刚刚好。
2.2 正方教务系统的请求链路长什么样
要拆解选课,先得理解一整个请求的完整链路。正方教务系统(业内常简称"正方")的Web结构,大致可以分成四层:登录入口层、鉴权层、业务查询层、业务提交层。
登录入口层通常是类似login_slogin.html这样的地址,用户输入学号密码,页面把密码加密后提交给鉴权接口。鉴权层是登录真正生效的地方,它校验账号密码,成功后下发一个JSESSIONID或者类似的会话Cookie,后续所有请求都要带着这个Cookie才能被服务器认作"已登录用户"。第三层是业务查询,比如查课表、查选课列表、查可选课程库存。第四层才是真正的选课提交,通常是一个POST请求,带上课程号、教学班号、志愿类型等参数。
理解这个分层很关键。因为很多新手一上来就直奔选课接口,结果发现返回"未登录"或者"参数错误",其实是前面几层的铺垫没做对。正确的思路是:先把登录链路的每个请求都摸清楚,确保会话建立成功,再去构造选课请求。这就像盖楼,地基没打好,三楼的墙肯定是歪的。
我也要提醒一句,不同学校、不同版本的正方系统,接口地址和参数名会有差异。本文给出的字段名是较为通用的一种,但你在自己学校的环境里,一定要以实际抓到的请求为准。照抄参数名是很常见的翻车原因。
3. 核心接口细节解析与Postman实操要点
3.1 登录链路:密码为什么要先取公钥
正方系统的登录做得比一般网站讲究一点,密码不是明文提交的。典型流程是这样:页面先向login_getPublicKey.html发一个GET请求,服务器返回一个公钥和它的指数;前端用加密库把用户输入的密码用这个公钥加密,再把密文提交给login_slogin.html。
这里为什么要这么设计?因为明文密码在网络上传输,如果被抓包或者被中间人截获,账号就危险了。用非对称加密,即使密文被截获,没有私钥也解不开,这就是它的逻辑。理解了这一点,你在Postman里就不能直接填明文密码了,必须走完"取公钥—加密—提交"这三步。
实际操作中,一个更省事的做法是:先在浏览器里正常登录一次,打开开发者工具的Network面板,把login_slogin.html这个请求"Copy as cURL",然后到Postman里用"Import"—"Raw text"粘贴,Postman会自动解析出URL、Header和Body。这样做的好处是密码密文、时间戳、验证码这些临时参数都是现成的,你不用自己算。
但这里有个坑:验证码。很多学校的正方系统登录时会要求验证码,验证码是和当前会话绑定的。如果你在浏览器里拿到了带验证码的请求,复制到Postman里发,往往因为Postman的Cookie和浏览器不一致而失败。所以更稳妥的方式是:在Postman里从取验证码开始,一步步建立自己的会话。
3.2 选课接口的参数构成:哪些字段是必须的
登录之后,选课请求的核心参数通常包括这几类:
| 参数类别 | 典型字段名 | 作用 | 是否可省 |
|---|---|---|---|
| 课程标识 | zzxkYzb / kch_id | 指定要选的课程或教学班 | 不可省 |
| 教学班标识 | jxbmc / jxb_id | 区分同一课程的不同班次 | 不可省 |
| 志愿类型 | zyxz / xkzy | 区分必修、选修、志愿等 | 视学校而定 |
| 操作类型 | xklc / xkly | 标记选课轮次 | 部分系统需要 |
| 会话标识 | JSESSIONID(Cookie) | 证明已登录 | 不可省 |
这里要重点说 zzxkYzb 这个字段。它在正方系统里往往是一个很长、经过编码的字符串,里面其实拼装了课程、教学班、选课轮次等多个信息。不同学期、不同轮次,这个字符串都不一样,所以你没法复用,只能每次重新从查询接口里拿。这也是为什么"写死参数"的选课脚本寿命很短——系统一升级、轮次一变,全废。
正确的做法是:先用一个查询接口拿到可选课程列表,从响应里提取出你要选的课的 zzxkYzb,再把它塞进选课请求里。这就是为什么我在上一步强调先摸清查询层。
3.3 Header里那些不能少的东西
除了Cookie,选课请求的Header里通常还有几个关键字段:
Content-Type: 正方系统多数用application/x-www-form-urlencoded,不是JSON。填错了服务器直接不解析你的Body。这一点和当下很多RESTful API不同,是正方比较"老派"的地方。Referer: 有的学校会校验来源页面,不填或者填错会被拒。User-Agent: 尤其在移动端接口上,UA决定服务器返回哪种页面结构。用浏览器的UA最稳妥。X-Requested-With: XMLHttpRequest: 标记这是AJAX请求,部分接口必须带上。
这些字段平时在浏览器里是自动带的,你手动发请求时容易漏。漏了不一定报错,但可能返回一个奇怪的HTML页面而不是JSON,让你摸不着头脑。遇到这种情况,第一反应就是回去检查Header。
4. 从零开始:用Postman完成一次完整选课请求
4.1 环境准备与请求捕获
第一步是把Postman装好。去官网下载对应系统的安装包即可,Windows、macOS、Linux都有。安装完打开,建议先建一个Workspace,再在里面建一个Collection,名字就叫"正方选课调试"。这样所有相关请求都归档在一起,不会和其他项目的请求混在同一个列表里。
第二步是捕获登录请求。打开浏览器,按F12进开发者工具,切到Network标签,勾上"Preserve log"(保留日志)。然后在教务系统里正常输入账号密码,点击登录。登录成功后,Network里会刷出一堆请求,你要找的是那个提交账号密码的POST,通常地址里带slogin。
找到后右键—Copy—Copy as cURL。回到Postman,点左上角Import,选Raw text,把cURL粘进去,点Continue—Import。Postman会把这个请求完整还原出来:URL、方法、Header、Body全都在。这时候如果你直接点Send,多数情况下能返回登录成功——但前提是会话Cookie还没过期。
这里有个我踩过的坑提醒你:浏览器里Copy as cURL带出来的Cookie,只在浏览器那次会话里有效。如果你的Postman和浏览器不是同一个时间点发请求,或者服务器做了会话轮换,就会失败。所以更推荐的做法是:在Postman里先手动发一次GET请求取验证码和公钥,把拿到的Cookie存在Postman的Cookie Jar里,再用这个会话去登录,这样链路是自洽的。
4.2 参数构造与请求发送
登录成功后,先验证会话是否有效。发一个GET请求到课表查询或用户信息接口,如果返回的是JSON而不是跳转登录页的HTML,说明会话建立成功。
接着去拿选课列表。请求地址通常是类似xsxk/zzxkyzb_cxZzxkYzb.html这样的路径,方法一般是POST,Body里带分页参数和筛选条件。返回的JSON里,你会看到一门门课程,每门课有一个 zzxkYzb 字段,这就是后面选课要用的钥匙。
拿到目标课程的 zzxkYzb 后,构造选课POST请求。Body用form-data或x-www-form-urlencoded都行,但如果是x-www-form-urlencoded,字段要手动一个个填:zzxkYzb、课程号、教学班号、志愿类型。填好后Send,如果返回的JSON里有类似"选课成功"或返回码为0,就说明成了。
这里我建议你在Postman里用Tests脚本做一次断言,比如:
pm.test("选课返回成功", function () { var jsonData = pm.response.json(); pm.expect(jsonData.flag).to.eql("1"); });这样每次发请求,Test Results面板会明确告诉你成功还是失败,不用肉眼去JSON里找字段。
4.3 把参数抽成环境变量
真正开始用的时候,你会发现每次都要手动改zzxkYzb太麻烦。这时候Postman的环境变量就派上用场了。
创建一个环境,比如叫"正方-2024秋",在里面定义变量:baseUrl、jsessionid、zzxkYzb。然后在请求里用双花括号引用它们,比如URL写成{{baseUrl}}/xsxk/zzxkyzb_cxZzxkYzb.html,Body里写zzxkYzb={{zzxkYzb}}。
更进一步,你可以在查询选课列表的那个请求的Tests里写脚本,自动把返回的第一个课程提取出来存进变量:
var jsonData = pm.response.json(); var firstCourse = jsonData.items[0]; pm.environment.set("zzxkYzb", firstCourse.zzxkYzb); pm.environment.set("courseName", firstCourse.kcmc);这样点一下查询,变量自动更新,再点选课请求,直接调用最新值,整个流程顺滑很多。这也是Postman比手动复制粘贴高效的地方。
5. 常见问题与排查技巧实录
5.1 登录后马上又提示未登录
这是最高频的问题,十有八九是Cookie没跟上。排查顺序建议这样来:
第一,检查Postman的Cookie Jar。点Send旁边的Cookies链接,看看里面有没有服务器下发的JSESSIONID。如果没有,说明你的登录请求没真正成功,或者服务器的Set-Cookie没被Postman接收。
第二,检查域名匹配。Cookie是有域限制的,如果Cookie的域是教务系统的IP或主域名,而你请求时用了另一个别名域名,浏览器和Postman都会认为不匹配,于是不发送。解决办法是在Postman的Cookie设置里手动添加域,或者干脆统一用同一个域名。
第三,如果还是不行,看看服务器是不是做了会话绑定。有的学校会把会话和User-Agent、IP绑定,你从浏览器换到Postman,UA变了,服务器就不认。这时候把Postman的UA改成和浏览器一致,通常能解决。
5.2 返回一堆HTML而不是JSON
这种情况通常是请求被服务器重定向到了登录页,或者做了UA/Referer校验。先检查Header里的Referer和User-Agent,再看Content-Type对不对。还有一种可能是URL拼错了,多一个少一个斜杠,服务器就返回了一个404页面。
排查这类问题,最快的办法是打开Postman的Console(左下角console按钮),看它实际发出的请求长什么样,和浏览器Network里抓到的对比,一眼就能看出差异。
5.3 常见返回码速查
| 现象 | 可能原因 | 处理方向 |
|---|---|---|
| 登录返回"验证码错误" | 验证码与会话不匹配 | 在同一会话内先取验证码 |
| 选课返回"不在选课时间" | 轮次参数错误或系统未开放 | 核对选课轮次字段 |
| 选课返回"人数已满" | 教学班容量到顶 | 换班次或等退课 |
| 返回302跳转登录页 | 会话失效 | 检查Cookie和过期时间 |
| 返回400参数错误 | Body字段缺失或类型不对 | 比对抓包Body逐项核对 |
| 返回500服务器错误 | 参数格式异常或系统繁忙 | 稍后重试,检查编码 |
5.4 我个人的几条实操心得
第一,不要在上课高峰或者选课开放的瞬间大批量发请求。这既不稳妥,也不厚道。调试阶段尽量在非高峰时段做,把链路跑通就够了。
第二,登录用一个自己负责的账号,不要借用别人的。别人的账号密码你不该碰,会话也容易乱。
第三,把每次调试的请求都保存成Collection里的一个版本,标注好日期和学期。下次选课季直接复用,省去重新抓包的时间。
第四,遇到返回和预期不符,先怀疑参数,再怀疑Header,最后怀疑Cookie。这三样覆盖了九成以上的问题。
6. 把抓到的接口整理成一份可用的文档
6.1 为什么值得写一份接口文档
调试完一堆请求之后,你手里其实已经攒了一份非官方的接口清单。把它整理成文档,好处是下次不用重新抓包,别人也能照着复现。这就像整理自己的工具箱,平时不觉得,真到用的时候能省下大把时间。
Postman本身支持把Collection导出成JSON文件,里面包含所有请求的URL、方法、Header、Body示例。你也可以导出为JSON接口文档模板的格式,方便分享。导出之后,可以放进自己的笔记软件里,按登录、查询、选课三个模块分节。
6.2 文档里应该记录哪些字段
一份实用的接口文档,至少要包含这几项:
- 请求地址、方法(GET/POST)
- 必需的Header(Content-Type、Referer等)
- Body字段清单,每个字段的含义和是否必填
- 一个真实的请求示例
- 一个真实的响应示例(成功和失败各一条)
尤其要记录失败响应。很多人只记成功的返回,结果遇到失败时不知道是参数问题还是系统问题。把"人数已满""不在选课时间"这类返回也记下来,排查时对比着看,效率高很多。
6.3 怎么把Postman请求导出成curl
有时候你想把调试好的请求发给同学,或者放到命令行里跑,Postman支持"Code"功能。点请求右侧的Code按钮,选cURL,它就会把当前请求完整翻译成一条curl命令:
curl -X POST "https://your-school-domain/xsxk/xkOper.html" \ -H "Content-Type: application/x-www-form-urlencoded" \ -H "Cookie: JSESSIONID=你的会话值" \ -d "zzxkYzb=课程编码&xkzy=1"把这条命令存进文档,对方复制就能用,比截图直观得多。要注意的是,里面的Cookie是会过期的,文档里要注明"Cookie需自行替换"。
7. 一点补充:这套方法还能用在哪
选课只是这套思路的一个入口。一旦你习惯了用"抓请求—看参数—用Postman复现"的方式去理解一个Web应用,很多场景都能套用:查成绩、打印证明、下载课表、抢讲座名额,本质都是同一套HTTP请求在背后跑。你甚至可以用Postman的Collection Runner做批量测试,或者结合Newman把它跑进持续集成流程,验证接口是否按预期返回。这些玩法的前提都是同一件事:先把接口本身看明白。
说到底,教务系统是个黑盒,但黑盒的边界是透明的——它在网络上跑,就一定会有请求和响应。你要做的,是用工具的视角把它照亮,而不是靠手速去撞运气。