☰
校园网自动登录程序开发实战:从抓包到自启动配置
2026/10/21 16:38:28 网站建设 项目流程

如果你也在某高校宿舍住过,大概知道校园网自动登录程序这玩意儿有多香——不是想偷懒,而是每天早上一睁眼,手机电脑全连着Wi-Fi却提示未认证,谁都得先开浏览器输一遍账号密码。写个自动登录脚本,让它在后台盯着网络状态,掉了就自己补登,开机就自动挂上,这种重复劳动交给机器处理刚刚好。这篇文章我会把思路、抓包、代码、自启动配置、以及我踩过的坑全部拆开讲,目标是让你看完也能给自家校园网做个“免手动”的守护程序。

先说结论:校园网自动登录程序本质是个轻量级的HTTP客户端模拟器,核心就是两件事——复现浏览器登录认证的过程,以及持续监测网络是否掉线。难点反而不在登录接口本身,而在各种边界情况:认证页面里的隐藏token、运营商选择、心跳检测误判、开机自启失败、密码明文硬编码等等。下面我会按一个真实项目的落地顺序来写。

1. 需求拆解与整体设计思路

1.1 校园网认证到底是什么

大多数高校的校园网采用的是强制门户认证,也就是“Captive Portal”。你插上网线或者连上Wi-Fi后,DHCP会自动给你分配一个内网IP,但这个IP是被限制的,不允许访问外网。这时候你打开浏览器访问任何网站,网络设备都会把你的请求拦截,然后302重定向到一个认证页面,这个页面通常长这样:输入用户名、密码,可能还有“运营商选择”和“下次自动登录”的勾选项。

认证流程本身不复杂:认证页面通常是一个HTML表单,里面写着action="/portal/auth"之类的地址,你把用户名密码填好提交,后端服务器收到后会调用RADIUS或者LDAP去验证身份,验证通过后网络设备会把你这个IP的流量放行。之后你就能正常上网了,直到连接断开、IP变更或认证会话超时,又得重新来一遍。

这里的关键是:既然浏览器能做到,那用代码也一样能做到。我们需要分析出浏览器提交表单时发的那几个HTTP请求,然后用脚本模拟。各学校的认证系统品牌不同,常见的有深澜、锐捷、Dr.COM、宁远科技等等,但只要认证页面是Web表单,就都能用抓包的方式逆向出来。

1.2 自动登录程序的四个核心需求

结合我在宿舍的实际使用场景,一个能用的自动登录程序至少要满足下面这些:

  • 启动后自动登录:不管是我手动运行脚本,还是开机自启,它都应该能自动完成首次认证。
  • 断线自动重连:校园网经常会因为IP租约到期、交换机端口重启、睡眠唤醒等原因掉线,掉线后程序要能发现,并重新走一遍登录流程。
  • 轻量低干扰:不能动不动就弹窗,占用资源也要小,不然在云服务器上跑都嫌重。命令行跑起来安安静静就最好了。
  • 日志可追踪:登录成功了还是失败了,网络检查结果是什么,都要有日志,不然出了问题你根本不知道它昨晚掉了多少次线。

除了这四个硬需求,还有个我强烈建议的软需求:登录状态要能被程序内部感知,不要靠猜。校园网认证后往往会在Cookie里种一个会话标识,或者http响应里返回“success”之类的关键字,抓包时要留意这些特征,作为登录是否成功的判断依据。

1.3 技术选型:模拟HTTP请求还是浏览器自动化

一开始我图省事,直接用Selenium或者Playwright驱动一个无头浏览器,打开认证页面自动填表点击。后来发现完全没必要。

校园网认证页面绝大多数是纯表单提交,没有复杂的JavaScript加密逻辑。如果存在JS动态生成加密参数的情况,那才需要浏览器自动化;没有的话,直接用requests库模拟POST请求,又快又稳,部署的时候也不用依赖浏览器环境。Selenium那套东西又要下载驱动又要装浏览器,在服务器上跑太臃肿。

所以我的最终选型是:

  • Python 3.9+,配合requests发HTTP请求
  • BeautifulSoup4(也就是bs4)解析认证页面的表单字段
  • keyring做密码的本地加密存储
  • systemd或Windows计划任务负责开机自启和守护
  • 可选plyer做桌面通知(登录失败时弹个提醒)

这套组合轻量、跨平台,几乎所有学校校园网的认证都能覆盖。

2. 核心细节解析:抓包分析与认证接口复现

2.1 用开发者工具抓出登录请求的关键参数

动手写代码之前,第一件事不是写代码,而是抓包。我强烈建议你在浏览器里先手动登录一次,按F12打开开发者工具,切到“网络”选项卡,勾选保留日志,然后正常输入账号密码登录。登录完成后,你会看到一堆请求,重点看提交表单的那一条。

打开那条请求的详情,你至少需要关注这几个东西:

  • 请求URL:认证接口的完整地址,形如http://10.255.0.55/portal/login。
  • 请求方法:一般是POST。
  • 表单数据(Form Data):通常包含username、password、还有可能是isRemember、operator、page这类隐藏字段。
  • 请求头:特别是Cookie、Referer、User-Agent。有些认证服务器会校验Referer,如果少了这个头就返回403。
  • 响应内容:登录成功或失败的标记。

举一个典型的例子,某高校认证页面提交的表单数据长这样:

POST /portal/login HTTP/1.1 Host: 10.255.0.55 Content-Type: application/x-www-form-urlencoded username=2021001234&password=123456&isRemember=false&operator=default

如果你发现密码在表单里是明文,那就直接用requests模拟;如果发现表单里多了一个token,并且是页面请求时动态生成的,就必须先GET一次认证页面,把token提取出来再POST。这个坑我在后面会专门讲。

2.2 使用BeautifulSoup从认证页面提取动态字段

有些学校的认证页面会在表单里内嵌一个隐藏的token字段,这个token每次访问都不一样,用来防CSRF(跨站请求伪造)。如果你不先GET页面直接POST,服务器会拒绝登录。

处理方式很简单:先请求认证页面,然后用BeautifulSoup解析HTML,提取出所有<input>标签的name和value。这样写的好处是适配性极强,即使学校后续往表单里加了个隐藏字段,只要没改接口结构,程序依然能正常登录。

下面这段代码是我常用的提取逻辑:

import requests from bs4 import BeautifulSoup def get_login_token(session, login_url): resp = session.get(login_url, timeout=10) soup = BeautifulSoup(resp.text, "html.parser") form = soup.find("form") if not form: return None, None, None action = form.get("action") hidden = {} for input_tag in form.find_all("input"): name = input_tag.get("name") value = input_tag.get("value", "") if name: hidden[name] = value return action, hidden

注意这里我用的session.get,不是requests.get,因为要保留下一次POST时用的Cookie。很多认证系统,GET页面时会给你种一个JSESSIONID或者PHPSESSID,POST时必须带着这个Cookie才能通过校验。

2.3 会话保持、Referer校验与登录成功判断

requests.Session对象会自动管理Cookie,这非常省事。但要注意,有些认证服务器的Referer校验非常严格,你需要在POST请求头里手动设置Referer,值就是认证页面的URL。

headers = { "User-Agent": "Mozilla/5.0 ...", "Referer": login_url, } resp = session.post(post_url, data=data, headers=headers, timeout=10)

登录是否成功,怎么看?不同学校返回不一样。有的返回JSON:{"result": 1, "msg": "认证成功"};有的返回一串纯文本:succeed;还有的干脆就是302跳转到校园网首页,跳转后你能访问外网了. 最稳妥的方式是登录后直接测一个外网连通性,比如请求一次https://www.baidu.com,如果能正常返回200,说明登录成功了。反正程序也是要跑心跳检查的,这个逻辑可以复用。

3. 实操过程与核心代码实现

3.1 环境准备与项目初始化

我习惯用虚拟环境装依赖,防止污染系统Python。建议Python版本3.9以上,太低的话部分库的新语法不支持。

python3 -m venv campus_login source campus_login/bin/activate pip install requests beautifulsoup4 pyyaml keyring

项目目录我一般这样组织:

campus_login/ ├── main.py # 主程序入口 ├── config.yaml # 配置文件 ├── login.py # 登录核心逻辑 ├── network.py # 网络检测 ├── log_setup.py # 日志配置 └── requirements.txt

没必要整成一个大文件,拆开写以后维护省心。登录逻辑放login.py,网络检测放network.py,main.py只负责调度。

3.2 配置文件设计与密码安全管理

不要把密码写到代码里。一方面是代码可能被同学看到,另一方面是每次改密码都要改源码,太蠢了。我用一个config.yaml来存账号、接口地址、心跳间隔这些参数:

# config.yaml username: "你的学号" password: "加密前不要写这里,我会用keyring管理" login_url: "http://10.255.0.55/portal/login" operator: "default" check_interval: 30 timeout: 10

这里解释一下operator字段:很多校园网认证页面上有“中国移动”“中国联通”“中国电信”“校园网”这种选择。这个选项最终会作为表单里的一个字段提交。如果你用的是运营商宽带账号,就需要把这个值改成对应的运营商代码,具体值可以从抓包里拿到。

密码倒是可以放在配置文件里,但我不推荐明文。更稳的方法是使用keyring库把密码存到系统钥匙串(macOS的Keychain、Windows的凭据管理器、Linux的Secret Service)。这样代码里不出现密码,日志里也不会泄露。

import keyring # 保存密码(首次运行) keyring.set_password("campus_login", "your_username", "your_password") # 读取密码 password = keyring.get_password("campus_login", "your_username")

如果你嫌keyring在Linux服务器上配置麻烦,退一步也可以把配置文件设为仅管理员可读,然后在环境变量里读密码。千万别直接硬编码在公共代码仓库里。

3.3 编写核心登录模块

登录模块的核心逻辑就是上面说的:先GET认证页提取字段和token,再POST登录,最后返回是否成功。

import requests import logging from bs4 import BeautifulSoup logger = logging.getLogger(__name__) def do_login(session, login_url, username, password, operator="default", timeout=10): try: # 1. 获取认证页面 resp = session.get(login_url, timeout=timeout) resp.raise_for_status() soup = BeautifulSoup(resp.text, "html.parser") # 2. 提取表单action与隐藏字段 form = soup.find("form") if not form: logger.error("页面里找不到表单,检查login_url是否正确") return False action = form.get("action") if action and not action.startswith("http"): action = requests.compat.urljoin(login_url, action) data = { "username": username, "password": password, "operator": operator, } for input_tag in form.find_all("input"): name = input_tag.get("name") value = input_tag.get("value", "") if name and name not in data: data[name] = value # 3. 设置Referer,防止服务器校验失败 headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)", "Referer": login_url, } # 4. 提交登录 post_url = action or login_url resp = session.post(post_url, data=data, headers=headers, timeout=timeout) resp.raise_for_status() # 5. 判断结果 text = resp.text.lower() if "success" in text or "成功" in text or "succeed" in text: logger.info("登录成功") return True else: logger.warning("登录结果未知,响应内容: %s", resp.text[:200]) # 这里返回False会触发重试,但建议打日志 return False except Exception: logger.exception("登录过程出现异常") return False

这里有个细节:data字典里的字段直接覆盖了隐藏字段。如果服务器返回的表单里没有operator,我们设置的默认值也会提交。有些接口不识别多余字段会报错,所以稳健一点可以只提交表单里有的字段加上用户名密码,或者干脆先合并再剔除空值。看抓包结果调整就好。

3.4 实现网络连接检测与守护循环

自动登录程序最重要的一环是“知道什么时候该登录”。网络检测我不是直接ping网关,而是去请求一个外网地址。因为校园网认证后,内网网关往往也能ping通,但此时依然上不了外网,所以必须拿外网连通性做判断。

import requests def is_online(check_url="https://www.baidu.com", timeout=5): try: headers = {"User-Agent": "Mozilla/5.0"} resp = requests.get(check_url, headers=headers, timeout=timeout) # 偶尔会遇到403,只要返回了HttpResponse就算网络通 return resp.status_code < 500 except requests.RequestException: return False

这里要注意的是,requests.get默认可能走系统的代理,在某些网络环境下会导致误判。最好显式设置proxies={"http": None, "https": None},或者用session.trust_env = False。

检测到掉线后,调用登录模块。登录成功后就每隔一段时间再检测一次,登录失败则间隔短一点重试,避免把自己封了。主循环大致长这样:

import time import logging from login import do_login from network import is_online def main(): logger.info("校园网自动登录程序启动") session = requests.Session() session.trust_env = False while True: if not is_online(): logger.warning("检测到网络离线,准备登录") ok = do_login(session, LOGIN_URL, USERNAME, PASSWORD) if ok: logger.info("登录成功,等待下次检测") time.sleep(CHECK_INTERVAL) else: logger.error("登录失败,5秒后重试") time.sleep(5) else: time.sleep(CHECK_INTERVAL) if __name__ == "__main__": main()

心跳间隔我设为30秒。为什么不是1秒或10秒?因为校园网掉线本身不是秒级的事,太频繁地探测会白白消耗流量,也容易触发服务器端的频率限制;但间隔太长,比如5分钟,你玩游戏到一半才被卡掉线,体验就很差。30秒是个比较平衡的值,实测下来基本能在一分钟内恢复。

3.5 开机自启动配置:systemd、任务计划程序、launchd

写完了脚本,还得让它开机自动跑。Linux服务器上我推荐用systemd,容错和自动重启都比cron强。下面是一个标准的service单元文件:

[Unit] Description=Campus Network Auto Login After=network-online.target Wants=network-online.target [Service] Type=simple WorkingDirectory=/opt/campus_login ExecStart=/opt/campus_login/venv/bin/python /opt/campus_login/main.py Restart=always RestartSec=10 Environment=PYTHONUNBUFFERED=1 [Install] WantedBy=multi-user.target

把文件放到/etc/systemd/system/campus-login.service,然后执行:

sudo systemctl daemon-reload sudo systemctl enable campus-login sudo systemctl start campus-login

Windows上可以用任务计划程序,触发器选“登录时”或“启动时”,操作选“启动程序”,程序填Python解释器路径,参数填main.py的路径。另外Windows上跑后台脚本建议用pythonw.exe,不然会一直有个黑色命令行窗口。macOS用户则写一个.plist文件放到~/Library/LaunchAgents下,原理差不多。

自启动这里要注意一个坑:如果你的程序启动太早,网络还没就绪,很可能会导致网络检测一直失败,登录也失败。所以systemd里我用After=network-online.target,Windows计划任务里可以加个延迟启动的选项。更朴素的做法是脚本里启动后先sleep 5秒再进主循环。

4. 常见问题与排查技巧实录

4.1 登录接口有动态token或者加密参数

我见过最头疼的认证系统,每次GET登录页会返回一个随机token,POST时还额外带一个用JavaScript算出来的加密串。这种情况直接用requests模拟会失败。排查方法是看认证页面源码里的<script>标签,看看token是怎么生成的。如果只是简单地从某个接口获取,你可以在脚本里先请求那个接口拿到token再拼上去;如果是某种AES/RSA加密,就得用pycryptodome之类的库复现算法,或者干脆退回Playwright驱动无头浏览器。

我实际遇到的情况是:页面有个time字段,它等于Date.now(),也就是毫秒级时间戳。这个也好办,代码里取当前时间戳就行。真正复杂的加密在校园网系统里比较少见,大多数都是简单token。

4.2 登录成功了但依然上不了网

这种情况通常不是你程序的错,而是IP没换或会话状态不一致。最常见的场景是:你上一次登录后没有正常下线,服务器端会话还在,但本机网络已经断了。重新登录时,服务器会提示“用户已在线”或者直接无响应。

解决办法是让程序先调一下“下线”接口,再走登录流程。很多校园网认证页上都有“断开连接”按钮,对应的也是一个GET或POST请求,抓包可以找到。如果没找到,也可以尝试把IP地址释放再重新获取,但那种操作需要管理员权限,除非真没法解决,我不建议这么做。

4.3 心跳检测误判导致反复登录

我踩过一个大坑:把检测地址设成了学校的官网,结果某天官网升级维护,程序就疯狂地检测到“掉线”,然后一遍遍登录,把自己的账号踢下线。从那以后我就把检测地址改成了外网大厂的首页,并且默认只当作连通性参考,不依赖单个地址。更稳的写法是同时检测两个地址,比如一个HTTP站点一个HTTPS站点,两个都失败才判定掉线。

def is_online(): for url in ["https://www.baidu.com", "https://www.qq.com"]: try: requests.get(url, timeout=5) return True except Exception: continue return False

这样做的好处是即使某个站点临时抽风,程序也不会误判。

4.4 日志怎么打才方便定位问题

好日志是排查问题的眼睛。我在log_setup.py里配置了同时输出到控制台和文件,并且文件里带时间戳,这样半夜掉线了第二天还能翻日志看看原因。

import logging def setup_logger(level=logging.INFO): logger = logging.getLogger("campus_login") logger.setLevel(level) file_handler = logging.FileHandler("campus_login.log", encoding="utf-8") console_handler = logging.StreamHandler() formatter = logging.Formatter("%(asctime)s %(levelname)s %(message)s") file_handler.setFormatter(formatter) console_handler.setFormatter(formatter) logger.addHandler(file_handler) logger.addHandler(console_handler) return logger

关键节点都要打日志:启动、检测到离线、登录成功、登录失败、登录异常、退出。少打垃圾日志,比如每次检测网络都打印一次“网络正常”,那样日志文件会膨胀得很快。

4.5 多运营商账号的处理

如果你是校园网账号,认证表单里可能只有一个operator字段要填。但如果你办的是移动/联通/电信宽带,校园网认证会要求你选择自己的运营商。不同运营商的字段值不一样,抓包时你选哪个运营商,那个POST请求里就带哪个值。做配置化就好:

operator: "cmcc" # 或者 "unicom" / "telecom" / "default"

遇到登录失败,优先检查是不是operator的值填错了。我第一次写程序时,用校园网账号选了移动,结果服务器一直提示密码错误,把我烦了很久。

5. 后续扩展与个人经验小结

5.1 可以继续优化的方向

程序跑起来之后,我发现它虽然实用,但还能做得更顺手。比如:

  • 桌面通知:登录失败时弹个通知,提醒你手动检查一下。用plyer或者notify-send都能实现。
  • 登录状态记录:把每次登录的时间、原因、结果写进SQLite,后续可以统计一天掉线多少次。
  • Web控制面板:用Flask起一个极简服务,看一眼就知道当前在线状态,也可以手动触发登录。这就有点大了,适合折腾型选手。
  • Docker化:把Python环境打包进镜像,在软路由或NAS上跑,彻底不依赖宿主机Python环境。

5.2 几个值得记住的经验

我在这个项目里最大的体会是:写自动登录程序,最重要的是“识别状态”。你得搞明白什么算已登录、什么算掉线、什么算登录失败。很多人在网上抄一段requests代码就以为完事了,结果不是被token坑了,就是被心跳检测坑了。状态判断准了,剩下的逻辑都很简单。

第二个经验是,校园网的认证接口虽然每家不一样,但整个调试流程是通用的。先抓包,再模拟,再验证。你顺着这个流程走过一遍,以后哪怕换学校、换运营商,只要改配置就能用,不用重写代码。

第三个经验是,别小看“开机自启”这件事。程序本身写得再好,如果没法在你不知道的情况下自动跑起来,那它就是个半成品。在部署的时候一定要先手动跑一遍,确认日志正常,再配置systemd或计划任务,千万不要跳步。

最后再分享一个小技巧:如果你拿到的是校园网的IPv4内网地址,登录接口也许是HTTP明文。这时候程序往认证服务器发密码虽然是明文,但在校园内网里一般还能接受;不过如果你跟我一样在意安全,建议把登录逻辑封装好,密码用keyring管理,不要出现在任何日志文件里。

这个程序在我宿舍里已经跑了一个学期,掉线自动重登的反应速度比我手动打开浏览器快多了。如果你也受够了每天输账号密码,照着这篇文章的思路去自己写一个吧,实在抽不出时间再把代码抄一遍也够用。只要你把抓包这一步做好,后面的路就很顺。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询