Python模拟登陆学信网查询四六级成绩,告别准考证号丢失

发布时间:2026/10/14 16:35:55

Python模拟登陆学信网查询四六级成绩,告别准考证号丢失 简介这份Android示例项目实现了模拟登录学信网并查询四六级成绩适合正在学习网络编程与数据解析的Android开发者。项目完整展示了从构造登录请求、携带Cookie维持会话到解析返回HTML并提取成绩信息的全过程涉及HttpURLConnection/OkHttp网络层、Jsoup页面解析、Gson数据处理及登录状态管理同时包含EditText输入、按钮事件与结果展示等经典UI交互。压缩包共50个文件以class、xml、java源码为主辅以jar库含jsoup-1.7.1与android-support-v4、apk安装包及资源文件整体仅1.46MB结构紧凑便于快速导入学习。已有737人学习过这份源码关注用户登录态处理与异常流控的同学可重点参考其网络层设计还能从中掌握HTTPS安全传输、权限声明及try-catch容错等实战细节。1. 一份能自动查四六级的模拟登陆源码解决了“准考证号丢失”这个真实痛点每年四六级出分季最尴尬的瞬间不是没过而是准考证号找不到了。这套Android模拟登陆学信网查询四六级源码核心逻辑是把“登录学信网→发起成绩查询→解析结果页”这条链路用Python脚本自动化。它不是手机App成品而是一套可复现的模拟登陆实现不依赖OCR识别验证码、不用模拟器人工点击、更不需要逆向App抓包——一个requests会话加几次HTTP请求就能把成绩捞出来。适合三类人弄丢准考证号又急着查分的考生、想拆解模拟登陆与Cookie机制的学习者、需要批量核对成绩的教务助理。2. 学信网模拟登陆的认证链路与技术选型先把账算清在下载源码动手跑之前得先把这条认证链路讲清楚。学信网账号体系走的是统一身份认证(CAS风格)用户先在passport登录页提交身份证号和密码偶尔还要过一个人机验证码认证通过后服务端种下一组会话Cookie再带着一个ticket参数通过302跳转到学信档案(my.chsi.com.cn)的service地址。整个过程是三次握手式的缺了任何一段都会出现“明明登录成功、但查询接口还报未授权”。2.1 认证链路拆解GET预取、POST提交与302跟随模拟登陆的第一课不是写POST而是先GET登录页。很多人的实现翻车就翻在直接往登录接口怼参数——统一认证页里往往藏着一两个hidden字段(比如CAS令牌这类东西)漏掉任何一项都会被告知“请求来源不合法”。常见做法是先抓登录页把input[typehidden]里的键值对原样取出来再叠加上你手输的身份证号、密码、验证码字段作为POST表单原样回发。import requests from lxml import html LOGIN_BASE https://account.chsi.com.cn/passport/login # service 参数指向学信档案的CAS回调地址登录成功后必须跳到这 ARCHIVE_SERVICE https://my.chsi.com.cn/archive/j_spring_cas_security_check session requests.Session() session.headers[User-Agent] ( Mozilla/5.0 (Linux; Android 12; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Mobile Safari/537.36 ) # 1) 先拉登录页提取隐藏表单字段 resp session.get(LOGIN_BASE, params{service: ARCHIVE_SERVICE}, timeout10) doc html.fromstring(resp.text) form {node.get(name): node.get(value) for node in doc.xpath(//input[typehidden])} # 2) 追加用户凭据 form[username] 你的身份证号 form[password] 你的密码 form[captcha] # 无验证码时留空有验证码则后续填充这段代码的逻辑是先构造一个有移动端UA的全局Session再GET一次登录页取出CAS留下的hidden字段。之所以要单拉一次是因为很多统一认证页会把令牌绑定在会话里你不预先GET一次它后面校验来源时就拿不到有效token。参数方面params里的service必须与登录后跳转目标一致否则CAS回调校验会失败。UA建议固定成一台真实机型的字符串学信网对空UA和常见Python库UA的拦截概率明显更高。提交阶段要用allow_redirectsFalse这是为了观察302的Location去向而不是让requests自动吞掉跳转。LOGIN_POST https://account.chsi.com.cn/passport/login def do_login(session, form): resp session.post(LOGIN_POST, dataform, allow_redirectsFalse, timeout10) # 认证成功服务器返回302并给出CAS回调地址 if resp.status_code 302: location resp.headers.get(Location) if location: session.get(location, timeout10) # 跟随到学信档案 return True # 失败可能是验证码错误或凭据无效 if 验证码 in resp.text: print(验证码不对需要重新取一次登录页) return False关键点在于手动跟随Location而不是让requests自动跳。原因有两个一是有些反爬逻辑会检查Referer手动缓存一下302地址能更清晰地控制请求头二是调试时你能直观看到这次登录到底被踢到哪个service。若这里返回的状态码不是302八成是用户名密码错或验证码过期——先看返回页面里嵌着的错误提示比瞎改UA省时间。2.2 为什么用Python做核心而不是纯Kotlin/Java这批源码大概率是Python脚本为主体、Android侧用Termux或封装App来调用而不是用OkHttp硬写。原因很实在模拟登陆涉及会话保持、Cookie解析、HTML结构定位、验证码人工介入Python的requests和lxml在这个领域几乎是标配而Java/Kotlin生态里最强的Jsoup在动态表单与复杂回调场景下要写的胶水代码多得多。维度Python(requestslxml)Kotlin(OkHttpJsoup)会话与CookieSession自动管理需要手动维护CookieJar表单与重定向allow_redirects控制也要写拦截器HTML解析XPath/正则都很顺手Jsoup选择器可用但容错差调试速度改一行重跑编译打包循环长在Android上的落地方式Termux或后端封装直接集成进App在Android设备上跑这套脚本最省事的落地方式是装TermuxPython环境就在手机里脚本原样运行如果目标是做一个能点按钮的App更常见的做法是把这个Python脚本包一层Flask接口App只发HTTP请求拿结果。直接全用Kotlin重写也不是不行但维护成本会把“查个分”这件事变成“修一个App”不划算。2.3 为什么不依赖OCR识别验证码网上很多模拟登陆教程会拿深度学习OCR去认那四位数字字母实际效果是60%的场景顺利、40%的时间在翻车——学信网的验证码字体扰动、噪点、干扰线一换识别准确率立刻打回原形。这套源码的聪明之处在于默认不跟验证码硬刚而是走“登录态持久化”首次登录时人工看一眼验证码输入一次登进去后把Cookie落到本地json缓存之后的查询全部带着本地Cookie走完全绕开验证码。这也顺便解决了“模拟登陆查四六级”场景里真正的高频痛点考完试的同学只需要人肉登一次、把Cookie留在机器上后面每次出分季节就能直接跑查询脚本不用再回忆密码和验证码。代码逻辑上就是查数据前先读本地Cookie文件判断是否还有效失效才重新触发完整登录。3. 源码三件套直读登录器、查询器与HTML解析器把这套源码拆开看核心就是三个文件/模块登录器负责拿Session查询器负责拼参数打成绩接口解析器负责从HTML里把分数抠出来。三者各干各的接口之间只传字符串和字典这样哪怕学信网改版也只需要动解析器一个文件。下面按模块逐个过。3.1 登录器模块Session构造、登录与Cookie落盘登录器对外就暴露一个login()和load_or_login()。前者走完整的POST表单流程后者先检查本地cookie缓存能用就直接复用。完整起见我把验证码图片下载也放进来——验证码必须用同一个Session下载前面2.1说过这是最容易翻车的一步。import json from pathlib import Path COOKIE_FILE Path(chsi_cookies.json) def load_or_login(session, identity, password): # 有本地Cookie先试着复用 if COOKIE_FILE.exists(): session.cookies.update(json.loads(COOKIE_FILE.read_text())) probe session.get(https://my.chsi.com.cn/archive/, timeout8) if 退出登录 in probe.text: return session COOKIE_FILE.unlink() # 会话失效删掉再重登 # 重新走完整登录流程 resp session.get(LOGIN_BASE, params{service: ARCHIVE_SERVICE}, timeout10) doc html.fromstring(resp.text) form {i.get(name): i.get(value) for i in doc.xpath(//input[typehidden])} form.update({username: identity, password: password}) captcha_src doc.xpath(//img[contains(id,captcha)]/src) if captcha_src: img session.get(LOGIN_BASE captcha_src[0], timeout10) Path(captcha.png).write_bytes(img.content) form[captcha] input(打开 captcha.png 输入四位验证码: ) else: form[captcha] if do_login(session, form): COOKIE_FILE.write_text(json.dumps(session.cookies.get_dict())) return session这里load_or_login把“要不要登一次”的决策做成幂等逻辑Cookie存在就先用探测页验证页面里出现“退出登录”字样说明会话活着否则清掉缓存强制重登。验证码用input()人工输入是刻意保留的兜底设计——识别模型再强也扛不住验证码换样式与其赌OCR不如让人看一眼。Cookie落盘用的是session.cookies.get_dict()重新登录后旧Cookie文件会被新会话的Cookie覆盖这是合理的不用手动清理。3.2 查询器模块准考证号查询与免准考证号查询四六级成绩查询在学信网体系里有两条路。第一条是常规的CET查询接口输入15位准考证号加姓名就能拿成绩单页第二条是登录学信档案后从学信档案的成绩单入口直接按身份证维度调取历史成绩这条路不需要准考证号——对弄丢准考证号的人来说这是源码里价值最高的部分。CET_QUERY_URL https://cet.neea.edu.cn/cet/query ARCHIVE_SCORE_URL https://my.chsi.com.cn/archive/cjcx/ def query_by_ticket(session, zkzh, name): 常规查询准考证号姓名 params {zkzh: zkzh, xm: name} resp session.get(CET_QUERY_URL, paramsparams, timeout15) return resp.text def query_from_archive(session): 免准考证号查询登录态有效时学信档案直接吐成绩列表 resp session.get(ARCHIVE_SCORE_URL, timeout15) return resp.text常规接口的params就是准考证号和姓名两个字段字段名和官网表单保持一致最稳妥不要画蛇添足加pageSize之类不存在的参数。免准考证号那条路依赖学信档案页面结构如果返回结果里一个“成绩单”链接都没出现那就是登录没生效先回头查登录器别在查询器里瞎调。3.3 解析器模块XPath定位容器正则抠数字学信网成绩页属于历史遗留HTMLclass命名不统一、层级嵌套混乱只用XPath经常踩到“今天能抓到明天抓不到”的坑。我一般会两层配合XPath负责把整块成绩卡片切出来再用正则从卡片文本里提取总分和听、读、写译三项。import re from lxml import html def parse_scores(page_source: str) - list: doc html.fromstring(page_source) cards doc.xpath(//div[contains(class, score-card)]) if not cards: # 结构改版时的快速失败 return [] results [] for card in cards: text .join(html.tostring(card, encodingunicode).split()) total re.search(r总分[^\d]{0,10}(\d{2,3}), text) listening re.search(r听力[^\d]{0,10}(\d{2,3}), text) reading re.search(r阅读[^\d]{0,10}(\d{2,3}), text) writing re.search(r写作[^\d]{0,10}(\d{2,3}), text) results.append({ total: int(total.group(1)) if total else None, listening: int(listening.group(1)) if listening else None, reading: int(reading.group(1)) if reading else None, writing: int(writing.group(1)) if writing else None, }) return results正则里[^\d]{0,10}是为了容忍“总分”和数字之间可能夹着空格、冒号或标签碎片。注意先把页面用 join碾平再正匹配是为了一次性吸收换行符和多余空白避免跨标签匹配失败。如果返回空列表第一反应不是改正则而是先dump一页HTML到本地看结构——正则解千愁的前提是结构还在老轨道上。4. 跑通全流程环境配置、模拟登陆与首次查分前面讲的是模块逻辑这一章直接按操作顺序走一遍环境、登录、查询、打印结果。全程在手机Termux和电脑上都能跑命令行步骤完全一致区别只在依赖安装那段。4.1 依赖安装版本怎么锁才不翻车requests和lxml两个库就够不要为“性能”盲目装scrapy、playwright这类重武器。在Android Termux里先装Python再装包pkg update pkg install python python -m pip install requests lxml电脑端建议建个虚拟环境再装避免污染系统Python。版本上我没有刻意锁死但建议requests固定在2.31以上、lxml固定在4.9以上——这两个版本序列对移动端UA和HTTPS TLS握手兼容性最稳。装完先跑一句查询验证环境python -c import requests, lxml; print(requests.__version__, lxml.__version__)如果这里报module not found多半是pip装到了别的Python解释器上。Termux下常见坑是系统装了两套Python解决办法是直接用python -m pip install而不是裸pip。4.2 实操模拟登陆一次把Cookie留到本地写一个最小可跑的main.py把登录和查询串起来。简化结构逻辑和前面模块一一对应import argparse import json import requests def main(): parser argparse.ArgumentParser(description学信网四六级查询) parser.add_argument(--identity, requiredTrue, help身份证号) parser.add_argument(--password, default, help学信网密码已登录可留空) parser.add_argument(--zkzh, default, help准考证号不填则走学信档案免证查询) parser.add_argument(--name, default, help姓名与准考证号配对使用) args parser.parse_args() session requests.Session() if args.password: # 有密码走完整登录把Cookie落盘 load_or_login(session, args.identity, args.password) else: # 没密码就尝试加载本地Cookie失效会强制要求密码 load_or_login(session, args.identity, PLACEHOLDER_FORCE_RELOGIN) if args.zkzh: page query_by_ticket(session, args.zkzh, args.name) else: page query_from_archive(session) print(json.dumps(parse_scores(page), ensure_asciiFalse, indent2)) if __name__ __main__: main()运行顺序是先跑一次带--identity和--password的登录流程让程序把Cookie写到chsi_cookies.json之后再用--password 跑查询程序发现本地Cookie有效就直接走查询。首次登录时终端会卡在input(打开 captcha.png 输入四位验证码: )那儿你需要另开一个终端打开captcha.png看图输入后回车就通了。注意命令行里出现明文密码会留在shell历史里正式使用建议把密码放到环境变量或配置文件里脚本内用os.environ读取。4.3 查询实操两条查询路径的参数差异有准考证号时python main.py --identity 你的身份证号 --password 你的密码 --zkzh 准考证号 --name 姓名没准考证号时则去掉--zkzhpython main.py --identity 你的身份证号 --password 你的密码第二条走学信档案路径返回的是该身份证名下所有历史四六级成绩解析器会逐条输出。输出是带缩进的JSON总分、听力、阅读、写作与翻译一目了然。到这里“模拟登陆→查询→解析”的完整闭环就算通了。第一次跑通之后你会发现后续再查分根本不需要重复登录——这就是Cookie持久化带来的效率。5. 避坑手册验证码会话、IP风控与页面结构漂移这套流程跑顺了很快但每个人几乎都会在下面几条里至少踩一次。每一条都是实操现场的真实教训按“现象→原因→解决”写清楚。5.1 人工输入的验证码总是提示错误现象盯着验证码图看了半天输入后返回页面还是“验证码不正确”连续试三次都这样。原因最常见的是验证码图片与POST请求不在同一个会话里。有人是先打开浏览器登录页看图再把验证码填到脚本里——那个验证码令牌已经绑定在浏览器的Session上脚本的Session根本没拿到对应凭据。解决用脚本的Session把验证码图片下载到本地再输入。这样验证码所属会话与POST会话严格一致成功率立刻拉到95%以上。也就是3.1那段的逻辑session.get(LOGIN_BASE captcha_src[0])把图片二进制写到本地确认后回车提交。captcha_img doc.xpath(//img[contains(id,captcha)]/src)[0] img_resp session.get(LOGIN_BASE captcha_img, timeout10) Path(captcha.png).write_bytes(img_resp.content) captcha input(打开 captcha.png 输入四位验证码: )逻辑说明先通过XPath取验证码图片地址再用同一个Session下载图片到本地打开查看保证图片令牌和提交的Cookie对齐。这是模拟登陆里最容易忽略的会话一致性问题没有之一。5.2 连续查询几个准考证号后接口返回403或安全验证页现象单查一个成绩完全OK连续查五个后突然返回403或者被要求滑块验证。原因学信网/考试院接口对同一Session在短时间内的请求次数有限速连续高频请求直接触发风控。这不是账号被冻结只是临时限流过段时间会恢复。解决在查询循环里加随机间隔单位是秒而非毫秒。import random, time for zkzh in ticket_list: results.append(parse_scores(query_by_ticket(session, zkzh, name))) time.sleep(random.uniform(3, 7))逻辑说明随机sleep的作用是让请求时间分布接近真人操作而不是固定间隔——固定间隔同样能被风控识别成脚本特征。参数3到7是根据经验得出的区间过短无保护作用过长批量任务会拖太久。5.3 Cookie昨天还能用今天突然报“请先登录”现象本地chsi_cookies.json明明还在运行load_or_login也走了session.cookies.update但查询结果还是被重定向到登录页。原因学信网的会话Cookie有时效跨天或长时间闲置后服务端会使会话过期。本地文件里有Cookie不代表服务端还认。解决load_or_login里的探测不能只靠“文件存在”判断必须发一个轻量请求看响应特征。最常见做法是访问学信档案首页并检查里面是否有“退出登录”字样有就继续用没有就删掉缓存强制重新登录。这也是3.1节那段探测逻辑的由来。另外检查一下设备系统时间——曾经遇到过手机时区不对导致Cookie签名校验失败时间校正后就恢复了。5.4 解析器返回空列表但浏览器里能正常看到成绩现象同一个身份证在浏览器访问学信档案能看到成绩列表但脚本parse_scores返回[]页面源码也存下来了。原因页面结构改版class名换了XPath表达式匹配不到容器节点。这种情况90%是前端结构调整10%是登录态半失效导致页面里加载的是空壳模板。解决把存下来的HTML拖进浏览器DevTools搜“score”或“总分”关键字符串找到新的容器class并更新XPath。更新完解析器后一定要保留一份当时页面的HTML快照做回归测试——改一次XPath就回归一次能省掉后面无数轮翻车。# 保存快照便于解析异常时取证 Path(page_snapshot.html).write_text(page, encodingutf-8)代码说明这段快照保存逻辑应该挂在查询和解析之间一旦解析异常立刻就能拿现场去DevTools排查而不是去线上重放。排查完再决定改XPath还是改正则别盲改。6. 进阶把一次性模拟登陆脚本封装成可维护的查询工具到这一步登录、查询、解析都能跑通了但脚本还是一次性的——参数靠命令行传结果靠print输出。真正能长期用的工具应该把这三件事封装成类再输出结构化JSON。把脚本里的函数收拢成一个ChsiClient类登录态和查询方法都挂在实例上。class ChsiClient: def __init__(self, identity, password, cookie_filechsi_cookies.json): self.identity identity self.password password self.session requests.Session() self.cookie_file Path(cookie_file) def login(self): load_or_login(self.session, self.identity, self.password) def cet_query(self, zkzh, name): return parse_scores(query_by_ticket(self.session, zkzh, name)) def archive_query(self): return parse_scores(query_from_archive(self.session))这样一个对象就能同时服务命令行和一个简单的Flask接口。命令行侧用argparse已经够用如果后续要接App只需要在这个类外面套一个Flask路由把JSON结果原样返回。结构化输出比print重要——成绩数据要能进Excel或数据库JSON是最低摩擦的形式。验证方法也固定下来跑一个已知准考证号考生的成绩把脚本返回的总分、听力、阅读、写作与翻译四项数值和官网页面逐项对照数值完全一致才算这次改动通过。我从那以后每次改完解析器或登录逻辑都强制把这个回测流程走一遍——先信任脚本再用已知数据打脸自己确认没误伤再落地。这套习惯救过我太多次尤其是有一次学信网把成绩单页从表格改成卡片布局我的新XPath就是靠回测才发现少解析了一项。希望帮到你。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/10/14 16:30:48

基于jQuery UI的虚拟键盘实现:布局、焦点与避坑指南

简介:VirtualKeyboard是一款基于jQuery UI的虚拟键盘插件,面向需要优化网页表单输入体验的Web开发者。它模拟真实键盘提供字母、数字、符号等布局,支持jQuery UI多主题切换,并针对触摸屏操作做了专门优化;在密码输入等…

2026/10/14 17:46:22

Android输入法遮挡解决方案:布局动态上浮实战

1. 项目概述:为什么“输入法遮挡布局”是Android开发里最常被低估的体验雷区 “Android开发——如何优雅的将布局置于输入法之上”,这个标题看似只讲一个UI细节,实则直击移动端表单交互中最普遍、最顽固、也最容易被开发者用“临时方案”糊弄…

2026/10/14 17:46:22

Serverless在大规模数据处理中的实战落地与避坑指南

1. 这不是“免运维”的童话,而是大规模数据处理的现实杠杆“Serverless 在大规模数据处理的实践”——这个标题里藏着一个被严重误读的词:Serverless。很多人第一反应是“不用管服务器了?那我是不是可以躺平了?”我带过三个不同行…

2026/10/14 17:41:21

Azure DevOps Pipeline从零搭建持续集成:环境、YAML与避坑实践

聊到持续集成,很多团队第一反应是Jenkins,第二反应是GitLab CI。但我这几年在几个不同规模的团队里实践下来的感受是,如果项目本身准备深度使用Azure生态,或者团队已经是.NET、Java、Python混着写,那Azure DevOps的Pip…

2026/10/14 11:31:03

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/14 17:41:07

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/14 2:32:55

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/14 0:00:33

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:33

macOS/iOS高精度计时:mach_absolute_time原理与实战

1. 项目概述:为什么mach_absolute_time是 macOS/iOS 时间测量的“黄金标准”在 macOS 和 iOS 开发中,我见过太多人用CFAbsoluteTimeGetCurrent()或NSDate.timeIntervalSinceReferenceDate做性能打点,结果在高精度场景下被系统时钟跳变、NTP 同…

2026/10/14 0:00:33

SCORM课件演示与测试全解析:从API通信到自动化验证

简介:这是一套遵循SCORM标准的交互式课件演示与测试资源,面向E-Learning课程开发者、LMS平台测试人员及在线教学从业者,用于验证课件在不同学习管理系统中的识别、传递与跟踪能力。压缩包共28个文件,约12.07MB,以swf动…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑