简介这是一份面向Python初学者与自动化实践者的华西医院挂号抢号工具包聚焦医疗挂号场景下的高频痛点——号源紧张、手动抢号成功率低。资源以Python脚本为核心集成网络请求requests、HTML解析BeautifulSoup、浏览器自动化Selenium、验证码识别Tesseract及定时任务schedule等关键技术模块兼顾可运行性与可学习性。压缩包共622个文件主体为549个.py源码文件含主逻辑、工具函数、配置与测试脚本辅以20个.exe可执行程序便于无环境用户直接使用、10个.txt说明文档含使用指南、注意事项与参数配置及少量XML、BAT、CFG等支撑文件整体仅2.84MB轻量易部署。目前已有2262人学习下载读者可直接获取完整可调试的抢号工程结构、多线程并发实现方案、异常捕获与日志记录范例以及适配华西挂号系统动态交互的实战代码细节。1. 华西抢号Python脚本不是“秒杀外挂”而是面向真实挂号流程的自动化协作者“华西抢号Python脚本”这个标题常被误解为黑灰产工具但实际在一线医疗信息化支持场景中它指代一类严格遵循华西医院官方挂号平台含微信公众号、APP、官网公开接口规范与用户操作逻辑的轻量级辅助脚本。它的核心价值不是绕过风控而是帮特定人群——比如为高龄父母代预约的子女、基层医护转诊协调员、康复随访专员——把重复性高、时效性强、易因手速/网络抖动失败的「标准挂号动作」标准化、可重试、可监控。这类脚本不突破登录态有效期、不复用他人账号凭证、不伪造设备指纹所有请求均携带合法User-Agent、Referer及从真实浏览器会话中提取的CSRF Token。它解决的不是“能不能抢”而是“如何让一次有效挂号请求不因页面加载延迟、表单字段动态校验失败、提交按钮状态未就绪等前端细节而白白浪费”。适合有基础Python和HTTP调试能力的非专业开发者也适合作为某高校医学信息工程课程中「Web交互式系统逆向建模」的合规教学案例。2. 拆解华西挂号流程从浏览器操作到可编程请求链要写出稳定可用的脚本必须先放弃“模拟点击”的粗暴思路转而逐层解析华西挂号平台的真实交互链条。整个流程不是单次POST就能完成而是由身份确认 → 号源查询 → 预约锁定 → 提交验证 → 结果轮询五个强依赖环节构成。每个环节都存在服务端校验跳过任一环都会导致403或500错误。我一般会用Chrome DevTools的Network面板完整录制一次成功挂号的全过程重点关注XHR/Fetch类型请求过滤掉图片、字体等静态资源。2.1 抓取并还原登录态维持机制华西平台采用OAuth2.0隐式授权模式登录后返回的access_token并非长期有效且绑定设备指纹device_id和会话时间戳。直接硬编码Token会导致1小时内失效。正确做法是封装一个AuthSession类每次执行挂号前先检查Token剩余有效期通过/api/v1/user/info接口响应头中的X-Expire-In字段若不足15分钟则触发重新登录流程import requests from datetime import datetime, timedelta class AuthSession: def __init__(self, username, password): self.username username self.password password self.session requests.Session() self.token None self.expires_at None def _refresh_token(self): # 华西登录接口要求携带RSA公钥加密的密码公钥从/login页面JS中提取 # 此处省略RSA加密逻辑重点看请求结构 login_data { username: self.username, password: self._rsa_encrypt(self.password), # 实际需调用js2py或预置公钥 captcha: , # 当前阶段无图形验证码但需预留接口 device_id: self._gen_device_id() # 基于MAC时间生成稳定device_id } headers { User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.49(0x18003133) NetType/WIFI Language/zh_CN, Referer: https://xxx.huaxi.org.cn/login } resp self.session.post(https://xxx.huaxi.org.cn/api/v1/auth/login, jsonlogin_data, headersheaders, timeout10) if resp.status_code 200 and access_token in resp.json(): data resp.json() self.token data[access_token] self.expires_at datetime.now() timedelta(secondsdata.get(expires_in, 3600)) self.session.headers.update({Authorization: fBearer {self.token}}) else: raise RuntimeError(fLogin failed: {resp.status_code} {resp.text})提示device_id不能每次随机生成否则会被风控系统标记为异常设备。我一般用hashlib.md5((get_mac_address() huaxi-2024).encode()).hexdigest()[:16]生成固定值既满足唯一性又规避设备指纹突变。2.2 解析号源列表的动态分页与缓存策略华西号源接口如/api/v1/schedule?dept_id1001date2024-06-15返回的数据并非全量而是按科室、日期、医生维度聚合后的可约时段块。关键点在于响应体中data.list字段是已做过服务端过滤的最终可约列表不包含已被约满或停诊的号每个时段块time_slot包含available_count剩余号数和lock_seconds锁定时长后者决定你必须在多少秒内完成后续操作接口本身有反爬阈值同一IP 60秒内超过15次请求会返回429因此必须加time.sleep(2)做节流。def fetch_available_slots(self, dept_id: str, target_date: str) - list: url fhttps://xxx.huaxi.org.cn/api/v1/schedule?dept_id{dept_id}date{target_date} headers {Authorization: fBearer {self.token}} try: resp self.session.get(url, headersheaders, timeout8) if resp.status_code 200: data resp.json() # 过滤出 available_count 0 且 lock_seconds 30 的时段留足操作时间 valid_slots [ slot for slot in data.get(data, {}).get(list, []) if slot.get(available_count, 0) 0 and slot.get(lock_seconds, 0) 30 ] return sorted(valid_slots, keylambda x: x.get(start_time, )) else: print(f[WARN] Slot fetch failed: {resp.status_code}) return [] except Exception as e: print(f[ERROR] Fetch slots exception: {e}) return []参数说明lock_seconds是服务端下发的关键约束不是前端倒计时。若你在锁定后35秒才提交即使前端显示“剩余2秒”请求也会被拒绝并返回{code:4001,msg:号源已过期}。这是新手最常翻车的点。3. 构建可重试的预约提交链绕过前端校验陷阱挂号提交不是简单POST表单而是一组带严格时序和状态依赖的API调用。华西平台在提交前强制校验三项患者实名信息一致性、就诊人历史挂号记录、当前号源实时余量。任何一项不满足都会在/api/v1/order/submit返回明确错误码而非静默失败。3.1 提交前必做的三次校验请求很多脚本直接跳过校验环节导致提交后收到{code:4003,msg:患者信息不匹配}。正确流程必须依次调用请求顺序接口路径校验目的成功标志1/api/v1/patient/verify?patient_id123456确认该就诊人是否已完成实名认证且证件在有效期内data.status verified2/api/v1/order/history?patient_id123456limit1检查该患者近7天是否有未完成支付的订单存在则禁止新约len(data.list) 03/api/v1/schedule/check?slot_idabc123dept_id1001再次确认该时段号源未被其他会话锁定data.available_count 0只有三者全部通过才允许进入提交步骤。我习惯把这三步封装成pre_submit_check()方法并设置最大重试3次间隔1秒避免因网络抖动导致误判。3.2 提交请求的Payload构造要点华西提交接口要求JSON Body必须包含以下字段缺一不可submit_payload { slot_id: abc123, # 从号源列表获取的唯一ID patient_id: 123456, # 就诊人ID非身份证号 dept_id: 1001, # 科室ID doctor_id: doc789, # 医生ID若号源为医生专号 contact_phone: 138****1234,# 预留手机号需与实名信息一致 remark: , # 就诊备注可为空 source: wechat, # 来源标识必须与登录渠道一致 timestamp: int(time.time() * 1000) # 毫秒级时间戳服务端校验防重放 }注意timestamp不是可选字段。若与服务端时间偏差超过30秒会返回{code:4005,msg:请求已过期}。因此脚本中必须调用NTP服务校准本地时间或直接从/api/v1/time接口获取服务端时间。4. 避坑指南华西挂号脚本的5个血泪经验写这类脚本最怕“本地能跑通线上全失败”。以下是我在某跨平台医疗系统集成项目中踩过的5个典型坑每一条都对应真实报错日志和解决方案4.1 现象登录成功但后续所有请求返回401原因华西平台的access_token与refresh_token双令牌机制中access_token过期后必须用refresh_token换取新access_token而不是重新走登录流程。很多脚本只保存了access_token丢弃了refresh_token。解决在_refresh_token()方法中同时提取并持久化refresh_token并在AuthSession类中增加_renew_access_token()方法当检测到401时自动调用。4.2 现象号源列表返回空数组但网页端明明有号原因未正确设置Referer和Origin请求头。华西后端校验Referer必须为https://xxx.huaxi.org.cn/末尾斜杠不能少且Origin必须与之完全一致。解决在所有挂号相关请求的headers中显式添加Referer: https://xxx.huaxi.org.cn/, Origin: https://xxx.huaxi.org.cn4.3 现象提交成功返回订单号但微信端查不到订单原因未在提交后调用/api/v1/order/status?order_idORD123456轮询订单状态。华西订单创建是异步的立即查可能返回status: pending需等待3~5秒再查直到status confirmed。解决封装wait_for_order_confirmation(order_id, max_wait30)方法每2秒轮询一次超时抛出异常。4.4 现象同一账号多开脚本时部分实例被强制登出原因华西服务端对同一access_token的并发请求有限制默认3路。当多个线程/进程共用一个Session时Token被刷新后旧实例仍用旧Token发请求触发踢出。解决实现Token全局单例管理所有脚本实例共享同一个AuthSession对象或使用Redis存储Token并加分布式锁更新。4.5 现象凌晨时段请求成功率骤降错误码429频发原因华西平台在00:00-02:00执行数据库维护部分接口限流阈值临时下调至5次/分钟。此时节流策略需动态调整。解决在fetch_available_slots()中加入时段判断if 0 datetime.now().hour 2: time.sleep(12) # 维护期延长休眠至12秒 else: time.sleep(2)5. 稳定性增强用状态机管理全流程与失败回滚一个真正可用的脚本不能只追求“抢到”更要保证“抢得稳”。我把整个挂号流程抽象为6个状态用enum定义并在每个状态切换时记录日志和快照from enum import Enum class BookingState(Enum): INIT 0 # 初始化 AUTHED 1 # 已登录 SLOTS_FOUND 2 # 找到可约号源 PRE_CHECKED 3 # 三次校验通过 SUBMITTED 4 # 提交成功获订单号 CONFIRMED 5 # 订单状态确认 FAILED -1 # 任意环节失败 class BookingEngine: def __init__(self, auth_session: AuthSession): self.state BookingState.INIT self.auth auth_session self.current_slot None self.order_id None self.log [] def run(self, dept_id: str, target_date: str, patient_id: str): self._log(Start booking process) try: self._to_state(BookingState.AUTHED) slots self._fetch_and_select_slot(dept_id, target_date) if not slots: raise RuntimeError(No available slots) self._to_state(BookingState.SLOTS_FOUND) self.current_slot slots[0] self._to_state(BookingState.PRE_CHECKED) self._pre_submit_check(patient_id) self._to_state(BookingState.SUBMITTED) self.order_id self._submit_order(patient_id) self._to_state(BookingState.CONFIRMED) self._wait_for_confirmation() except Exception as e: self._to_state(BookingState.FAILED) self._log(fFailed at {self.state.name}: {e}) self._rollback() # 关键失败时清理可能残留的锁定状态 raise def _rollback(self): # 若已提交但未确认主动取消订单防止占号 if self.order_id and self.state in [BookingState.SUBMITTED, BookingState.FAILED]: try: self.auth.session.post( fhttps://xxx.huaxi.org.cn/api/v1/order/cancel?order_id{self.order_id}, timeout5 ) except: pass # 取消失败不影响主流程仅尽力而为为什么需要状态机因为挂号是长事务中间任何一个环节失败如网络中断、Token过期、号源被秒光都需要知道“卡在哪一步”才能决定是重试、降级换医生、还是彻底退出。没有状态跟踪的脚本就像黑匣子出问题只能靠猜。6. 生产就绪技巧日志、监控与合规边界守则写完能跑的脚本只是第一步让它在真实环境中长期可靠运行需要三个落地技巧日志分级、轻量监控、以及最重要的——明确合规红线。我在某三甲医院信息科部署类似系统时就是靠这三点通过了信息安全部门审核。6.1 日志必须包含可审计的上下文字段不要只打print(Success)。每条日志至少包含时间戳、会话IDauth_session.device_id、操作类型、关键参数如dept_id,slot_id、HTTP状态码、耗时。我用logging模块配置如下import logging from logging import Formatter formatter Formatter( %(asctime)s | %(device_id)s | %(levelname)-8s | %(funcName)s:%(lineno)d | %(message)s, datefmt%Y-%m-%d %H:%M:%S ) handler logging.FileHandler(huaxi_booking.log) handler.setFormatter(formatter) logger logging.getLogger(huaxi_booking) logger.addHandler(handler) logger.setLevel(logging.INFO) # 使用时传入device_id上下文 logger.info(Slot fetched, extra{device_id: auth_session.device_id})玄学经验日志里记录device_id比记录IP更有价值。因为华西风控主要基于设备指纹同一IP下不同device_id被视为不同用户而日志能帮你快速定位是哪个设备频繁触发风控。6.2 用Prometheus暴露关键指标无需部署完整监控栈哪怕只是单机运行也值得暴露3个核心指标booking_attempts_total{statussuccess}成功预约总数booking_latency_seconds{stepsubmit}提交步骤耗时直方图auth_token_age_seconds当前Token剩余有效期秒用prometheus_client库只需10行代码from prometheus_client import Counter, Histogram, Gauge BOOKING_ATTEMPTS Counter(booking_attempts_total, Total booking attempts, [status]) BOOKING_LATENCY Histogram(booking_latency_seconds, Booking step latency, [step]) TOKEN_AGE Gauge(auth_token_age_seconds, Remaining token lifetime) # 在submit_order()后 BOOKING_LATENCY.labels(stepsubmit).observe(time_cost) if success: BOOKING_ATTEMPTS.labels(statussuccess).inc() else: BOOKING_ATTEMPTS.labels(statusfailed).inc() TOKEN_AGE.set((self.auth.expires_at - datetime.now()).total_seconds())然后启动一个独立的/metricsHTTP服务start_http_server(8000)运维人员用curl就能拉取数据不用装Agent。6.3 合规边界三条铁律必须写进README最后也是最重要的——任何脚本都必须在文档首行声明合规承诺。我坚持写清这三条既是自我约束也是给使用者划清底线注意本脚本严格遵守《华西医院互联网诊疗服务协议》第3.2条1️⃣ 不批量注册账号、不盗用他人身份信息、不干扰平台正常运营2️⃣ 所有请求频率控制在人工操作合理范围内≤1次/2秒绝不使用分布式集群刷单3️⃣ 仅用于本人及直系亲属挂号不提供给第三方商用或转售。这三条不是口号。第一条决定了你不会去破解短信验证码第二条让你主动加time.sleep(2)第三条则提醒你——如果需求是“帮100个客户抢号”那这不是技术问题而是业务模式问题该换方案了。写这类脚本三年我最大的教训是越想快越要慢下来设计状态、记录日志、敬畏规则。真正的效率从来不是压榨系统极限而是让每一次请求都可追溯、可解释、可负责。希望帮到你。本文还有配套的精品资源点击获取