ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

用Python打造宿舍电费监控系统:爬虫、存储与自动告警实战

2026/9/11 9:45:54 拓冰建站 浏览量
用Python打造宿舍电费监控系统:爬虫、存储与自动告警实战 简介面向高校宿舍管理场景的Python电费监控程序旨在解决宿舍用电数据采集、统计与可视化展示问题适合Python学习者、校园信息化开发者及有课设需求的学生参考。代码包共7个文件以5个Python脚本为核心分别实现电费账户ID获取、房间遍历查询、用电监控及代理请求等关键功能另有1个HTML前端页面用于结果展示1个Markdown文档介绍项目背景与使用方法压缩包仅10KB轻量精炼、便于阅读。目前已有90人学习下载。资源基于一个完整项目整理各脚本分工明确、逻辑清晰可直接运行或二次开发。通过学习该程序读者能掌握requests请求、数据解析、定时任务与Web页面渲染的联动实现理解小型监控系统的整体架构对课程设计、宿舍管理工具开发乃至Python综合应用都有实际参考价值。1. 为什么宿舍电费监控值得自己写一套大学宿舍的电费查询入口通常分散在校内网页、微信小程序或后勤App里没有一个统一渠道会主动提醒你余额告急。断电往往发生在深夜等发现时空调已经停了只能摸黑给手机充电等第二天去充值。与其每天手动打开查询页面刷余额不如写一个 Python 定时任务把电量抓下来、存进本地数据库低于阈值自动发消息提醒。这套逻辑本质上就是一个轻量级的监控告警系统涉及 HTTP 请求、数据解析、持久化存储、阈值判定和通知推送覆盖了绝大多数 Python 自动化项目的核心链路。这个方案适合有一定 Python 基础、想练手爬虫和定时任务的人也适合懒得天天查电费、想在宿舍低成本搭一套可用工具的在校生。整个过程不需要高端硬件一台能跑 Python 的电脑或 Linux 小主机就够了。成本低、见效快且换到其它校园场景门禁余额、健身房预约、图书馆座位时采集层和数据层的代码基本可以平移复用。2. 摸清电费查询接口先解决数据怎么来的问题2.1 查询入口的三种形态以及各自的应对思路宿舍电费查询系统在不同学校差异极大但抽象下来无非三种形态校内网页版查询通常是 HTML 表单提交或 AJAX 请求返回 JSON。微信公众号或小程序内嵌的 H5 页面底层仍是 HTTP API但可能在请求头里带 token。第三方聚合应用如完美校园、易校园接口加密程度较高需要动态签名。最稳妥的第一步是用浏览器开发者工具F12抓包找到真正返回密钥数据的那个 XHR 请求而不是去解析 HTML 页面。因为很多页面上的数字是异步加载的直接 requests 拉页面源码经常拿不到电量值。找到请求后把 URL、请求方法、请求头、参数复制出来在 Python 里用 requests 库原样复现一遍能通就说明接口可用。2.2 一个最小可用的采集器先跑通再管异常在写完整的监控程序之前先做一个只执行一次的手动抓取脚本确认接口通、字段对。下面这段代码以常见的 JSON API 为例演示最基础的采集流程import requests import json # 从浏览器开发者工具里复制的完整 URL url https://your-campus.edu/api/dorm/electricity # 浏览器请求头里挑这几个复制的就够了Cookie 是登录态关键 headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64), Cookie: sessionidyour-session-id, Referer: https://your-campus.edu/dorm/ } # 有些接口用 GET 带查询参数有些用 POST 提交 JSON params { room_id: 5-423, type: room } resp requests.get(url, headersheaders, paramsparams, timeout10) resp.raise_for_status() # 4xx/5xx 直接抛异常 data resp.json() print(json.dumps(data, ensure_asciiFalse, indent2))这段代码里有几个参数值得说明timeout10是必需的宿舍网络不稳时没有超时上限的请求可能卡住整个任务raise_for_status()会在接口返回 404 或 500 时直接抛出异常避免拿错误页面当正常数据存库headers里的User-Agent和Referer是为了尽量模仿浏览器行为部分校园系统会对非浏览器请求做拦截。如果打印出来发现数据嵌套很深别急着写解析逻辑。先把 JSON 的层级结构看清了再决定是直接索引取值还是用jsonpath这类库做模糊匹配。常见做法是先把原始响应保存一份到本地文件防止后续程序改坏了回不去现场。2.3 遇到加密参数怎么办不是所有学校系统都这么直白。有的接口需要sign签名参数有的 token 是登录后从别的接口拿的。我的建议是分三步排查请求头里是否缺了必要字段尤其是Authorization或X-Token这通常需要登录后动态获取不适合硬编码。整个请求是否有前置步骤比如先访问一次 H5 页拿到合法 token再带 token 请求真正接口。可以让 Python 脚本先模拟一次登录或者 curl 一下前置接口。签名算法本身是否为前端全动态计算。如果发现在 JavaScript 里动态拼接可以考虑用pyexecjs或node子进程执行同一段签名逻辑而不是自己去逆算法。这里有个容易被忽略的细节Cookie 往往有过期时间对监控程序来说必须保证程序在任何一次运行失败时能感知到 Cookie 时效问题。我一般会让程序在返回数据里识别“请重新登录”或跳转登录页的标记遇到这种情况就把告警发出来而不是静默重试。3. 数据落地和阈值判定SQLite 存储与告警触发3.1 为什么要用 SQLite 而不是直接写文件采集到的电量是一个随时间变化的数值天然适合表结构存储。方案上有三个选择直接追加写入 JSON 或 CSV简单但查询趋势时要全量读再过滤数据量大后效率低。部署 MySQL 或 PostgreSQL宿舍环境没必要额外增加安装和维护成本。SQLite单文件、零配置、Python 内置sqlite3模块支持最适合这种个人级监控任务。SQLite 在大约 100MB 数据量以下表现足够好电费半小时一条的记录攒一年也就一万多条完全在舒适区内。而且 SQLite 文件可以随时拷贝备份扔到网盘或者传到手机上都行。数据库只需一张表核心字段是记录时间、宿舍房间号、剩余电量千瓦时、余额如果有、原始响应备注。一般不需要复杂索引给读取时间加一个普通索引足够。3.2 建表、写入和查询的完整代码下面的代码实现了建表和插入监控数据的完整逻辑。要确保脚本在初次运行时自动建表不需要提前手工创建。import sqlite3 from datetime import datetime DB_PATH dorm_electricity.db def init_db(): conn sqlite3.connect(DB_PATH) cursor conn.cursor() cursor.execute( CREATE TABLE IF NOT EXISTS electricity_records ( id INTEGER PRIMARY KEY AUTOINCREMENT, room TEXT NOT NULL, record_time TEXT NOT NULL, remaining_power REAL NOT NULL, balance REAL, remark TEXT ) ) cursor.execute( CREATE INDEX IF NOT EXISTS idx_record_time ON electricity_records (record_time) ) conn.commit() conn.close() def save_record(room, power, balanceNone, remark): conn sqlite3.connect(DB_PATH) cursor conn.cursor() cursor.execute( INSERT INTO electricity_records (room, record_time, remaining_power, balance, remark) VALUES (?, ?, ?, ?, ?), (room, datetime.now().strftime(%Y-%m-%d %H:%M:%S), power, balance, remark) ) conn.commit() conn.close() def query_recent(room, limit7): conn sqlite3.connect(DB_PATH) cursor conn.cursor() cursor.execute( SELECT record_time, remaining_power FROM electricity_records WHERE room ? ORDER BY record_time DESC LIMIT ?, (room, limit) ) rows cursor.fetchall() conn.close() return rows这里的要点是参数化查询。另外REAL类型用于存储浮点电量值精度对电费监控够用AUTOINCREMENT可以防止历史记录被删后 ID 复用虽然这不是必须但能避免一些数据迁移上的小麻烦。每次写入前不必手动检查表是否存在因为init_db()在脚本入口调用一次即可。3.3 阈值判定的两个关键参数阈值本身和防抖窗口只存数据不告警毫无意义。但阈值判定不能简单写成“电量小于 X 就告警”因为接口返回可能存在瞬时抖动或解析错误一旦误判就会深夜给宿舍群发消息很容易吵到室友。常见的做法是双重判定当前电量低于设定阈值比如 5 度。连续 N 次采样或者持续超过 M 小时都低于该值才触发告警。第二种条件能过滤掉偶发的瞬时异常。整套判定逻辑用状态来描述比用散落的 if 语句更清晰。class PowerMonitor: def __init__(self, room, low_threshold5.0, consecutive_count3): self.room room self.low_threshold low_threshold self.consecutive_count consecutive_count self.low_count 0 def check(self, power): if power 0: # 负数通常代表欠费属于紧急状态直接告警 return critical if power self.low_threshold: self.low_count 1 else: self.low_count 0 if self.low_count self.consecutive_count: return warning return ok这个监控器的设计逻辑比较好地处理了几个边界场景负数直接进入紧急状态因为欠费停电和余额不足是两回事连续计数保持在一个对象内部不会在外部重复记录状态阈值和连续次数都可以在构造时指定便于为不同宿舍设置不同参数。触发告警后还需要额外一个机制告警后不重复通知。否则脚本每 30 分钟跑一次、每次都在阈值以下消息会轰炸手机。可以用一个简单的is_notified标志位或者读取上次成功通知的时间戳间隔超过一定时长如 12 小时才再发一条。3.4 告警推送服务商邮箱是低门槛方案告警渠道没有绝对选型但实际用于宿舍场景的基本三选一邮件 SMTP最通用无需额外安装依赖使用 Python 标准库 smtplib适合推送历史趋势图。微信通知第三方渠道很多但不是标准协议需要注册或 token 失效风险适合有额外需求的用户。钉钉/企业微信机器人如果有校园组织账号可以直接用没有的话需要个人注册入门门槛比邮件高一些。考虑到这套程序的使用人群是学生保持低门槛相对重要。下面是基于 QQ 邮箱或 163 邮箱发送告警邮件的模型需要在邮箱设置里开启 SMTP 授权码。import smtplib from email.mime.text import MIMEText from email.header import Header def send_alert(subject, content): smtp_server smtp.qq.com smtp_port 465 sender_email your-accountqq.com auth_code your-smtp-auth-code receiver_email targetexample.com msg MIMEText(content, plain, utf-8) msg[Subject] Header(subject, utf-8) msg[From] sender_email msg[To] receiver_email with smtplib.SMTP_SSL(smtp_server, smtp_port, timeout30) as server: server.login(sender_email, auth_code) server.send_message(msg)注意SMTP_SSL一般对应 465 端口SMTP加starttls对应 587 端口不同邮件服务商的默认方式有差异写配置时要注意区分。平时测试可以先把告警逻辑跑一次再决定是否正式启用避免真正需要告警时才发现发信配置有问题。4. 可视化面板用 Flask 把历史电量拉成趋势图4.1 展示层的目的是发现规律不只是看当前数字命令行直接print电量数据适合调试但不适合长期使用。人眼从一大片数字里找趋势很吃力但折线图能一眼看出某个时段电量下降特别快、是不是最近空调开得太猛、周末用电和平时差别有多大。选择 Flask 做 Web 展示原因很简单它可以只提供一个静态页面加两个 API 端口依赖轻、模板不用太复杂跟数据库共用同一个 Python 进程时数据流转最顺。ECharts 的可视化图表库通过 CDN 引入即可不需要额外安装前端构建工具链。4.2 提供一个查询接口返回最近 N 天的趋势数据后端只需要一个接口给定宿舍房间号返回最近 7 天的电量快照。前端拿到数据后用 ECharts 画折线图即可。from flask import Flask, jsonify, request import sqlite3 app Flask(__name__) def query_history(room, days7): conn sqlite3.connect(DB_PATH) conn.row_factory sqlite3.Row cursor conn.cursor() cursor.execute( SELECT record_time, remaining_power FROM electricity_records WHERE room ? AND record_time datetime(now, ?) ORDER BY record_time ASC , (room, f-{days} days)) rows [dict(row) for row in cursor.fetchall()] conn.close() return rows app.route(/api/electricity) def api_electricity(): room request.args.get(room, 5-423) days int(request.args.get(days, 7)) return jsonify(query_history(room, days)) app.route(/) def index(): return open(templates/index.html, encodingutf-8).read() if __name__ __main__: app.run(host0.0.0.0, port8080, debugFalse)这里值得说的参数是host0.0.0.0它让宿主机上其他设备比如手机也能通过内网 IP 访问这个页面而不仅是 localhost。debugFalse在生产运行时不开启调试模式避免调试器暴露真实代码路径。datetime(now, -7 days)是 SQLite 内置的时间范围过滤语法比传入 Python 字符串更直接。前端页面用 ECharts 并不复杂最重要的是理解它的数据流。fetch拿到后端 JSON 后把时间塞进 xAxis 的 data再把电量数值塞进 series 的 data其余样式按需调整即可。前后端分离的程度越小越容易维护。4.3 显示数据时要处理两个常见问题前端直接展示数据时会发现两个麻烦采集中断导致数据点缺失折线图会出现断档。不同时间点的电量可能都是满值比如刚充完电图会显得太平看不出下降。针对第一点前端通过connectNulls: false让缺失部分断开更直观针对第二点可以在展示时追加一条余额或日增耗电量的辅助折线让用户看到一天用掉多少而不只是存量。在后端补齐这条计算逻辑会比前端拼接方便得多。5. 部署、定时调度与排查让脚本自己跑起来5.1 定时策略怎么定才对定时轮询频率不宜过高。学校查询接口谈不上多大的吞吐压力但过于频繁的请求既会给服务器增加负担又可能触发反爬策略。半小时一次是比较平衡的频率可以覆盖断电前的紧急告警需求也不会造成数据冗余或接口拦截。Windows 平台用任务计划程序Linux 或树莓派用 crontabmacOS 可以挂 launchd。对于绝大多数宿舍场景一个 cron 表达式就可以完成任务。下面是一个达到同样目的的 Linux crontab 示例*/30 * * * * /usr/bin/python3 /home/yourname/dorm_monitor.py /home/yourname/monitor.log 21这个表达式的含义是每 30 分钟执行一次监控脚本并把所有标准输出和错误输出追加到monitor.log。21粗糙但有效排查问题全靠这个日志。如果你使用 Windows 做定时任务别忘了在任务计划里的程序路径填上python.exe的完整路径工作目录设置到脚本所在文件夹否则相对路径会全部失效。5.2 脚本自身的异常兜底监控程序跑久了之后最常见的崩溃原因是网络、Cookie 过期和学校系统改版。理想情况下定时任务应在任何异常时通过告警渠道通知你而不是默默死在日志里。import traceback def main(): try: data fetch_data(room_id) save_record(room_id, data[power], balancedata.get(balance)) monitor.check(data[power]) if monitor.status in (warning, critical): send_alert(room_id, f当前余电 {data[power]} 度) except Exception: send_alert(电费监控程序异常, traceback.format_exc()) if __name__ __main__: init_db() main()把traceback.format_exc()塞进邮件内容失败那一刻的堆栈信息就保留下来了。这一点价值极高很多问题发生在你没盯屏幕的时候只有日志和告警才能还原现场。5.3 排查时看日志的具体指标跑起来之后日志里重点盯三类指标。第一类是请求耗时。校园网高峰期可能出现超过 30 秒的响应这会拖慢一轮轮询需要针对性地调整timeout到合理值。第二类是数据结构变化。如果学校后台加了一个字段或改了 JSON 结构你解析代码会直接抛 KeyError 或 IndexError日志会在第一时间暴露问题。第三类是告警次数是否合理。如果一个月内连续触发超过 10 次电量低告警说明阈值设得太高或者实际用电太快需要用可视化趋势图确认是哪种情况再决定调整阈值还是优化提醒逻辑。5.4 一个验证脚本自洽性的技巧回放历史数据在正式跑监控之前用一个已经积累好数据的数据库文件从所有历史记录中模拟逐条注入监控类这样可以在不等待真实采样的前提下验证阈值判定和告警逻辑在各类边界值0 度、负数、突然跳变的 999 度下表现是否正常。这本质上是一种用真实数据做回归测试的做法能把很多逻辑问题在部署前就消灭掉。本文还有配套的精品资源点击获取