1. 项目概述这不是一个“抢号器”而是一套可验证、可审计、可复现的挂号流程自动化方案“91160-cli智能抢号工具”这个标题里藏着三个容易被误解的关键词“91160”“智能”“抢号”。先说结论它既不绕过医院官方挂号系统也不调用任何非公开接口更不是所谓“秒杀外挂”。它本质上是一个基于标准HTTP协议与真实用户行为建模的命令行挂号辅助工具核心价值在于把人工挂号中重复、耗时、易出错的环节——比如反复刷新页面、手动选择科室/医生/时段、填表提交、验证码识别、状态轮询——全部封装成可配置、可调试、可记录的操作流。我最早在某三甲医院信息科做系统对接支持时就发现大量患者家属凌晨三点蹲守网页手指按肿了还抢不到号后来带教某高校医疗信息化实训项目学生用Python写挂号脚本90%卡在登录态维持和反爬策略适配上。这个工具就是从这些真实场景里长出来的——它不承诺“必挂上”但能确保你每一次操作都符合平台规则、每一次请求都可追溯、每一次失败都有明确归因。关键词“91160”指向的是全国多个省市通用的区域预约挂号平台技术栈非单一网站其底层采用标准的RESTful API Cookie Session 图形验证码前端JS加密校验组合“CLI”意味着它运行在终端里没有GUI界面干扰所有参数通过配置文件或命令行传入天然适合定时任务、日志审计和多人协作复用而“5分钟搞定”指的是从环境准备到首次成功提交预约请求的端到端实操耗时——前提是已掌握基础Linux命令和HTTP概念。适合三类人需要高频挂号的慢病患者家属如定期复诊的糖尿病/高血压患者、医疗信息化方向的学生与初阶开发者学习真实业务系统交互逻辑、以及基层医院IT运维人员用于压力测试或流程监控。它解决的从来不是“能不能挂上”的问题而是“能不能稳定、合规、可解释地完成挂号动作”的问题。2. 核心设计思路与技术选型逻辑为什么必须是命令行为什么拒绝“全自动”2.1 拒绝GUI自动化稳定性与可审计性的根本取舍市面上很多所谓“挂号助手”用Selenium模拟浏览器点击看似直观实则埋下三重隐患第一浏览器版本升级后JS执行环境变化导致XPath定位失效昨天能跑的脚本今天直接报错第二图形验证码识别模块依赖OCR模型一旦平台更换字体或加噪点识别率断崖下跌且错误识别会触发风控第三整个过程黑盒运行无法精确判断是网络超时、参数错误还是服务端限流导致失败。我们坚持CLI路线本质是把挂号拆解为四个原子操作认证 → 查询 → 构造 → 提交。每个环节都暴露HTTP请求细节比如登录阶段必须显式处理Set-Cookie头中的JSESSIONID和XSRF-TOKEN查询阶段需解析返回JSON里的availableSlots数组结构构造阶段要按平台要求拼接patientIddoctorIdscheduleDateslotCode四元组提交前还要用SHA-256对关键字段做签名。这种“裸金属级”的控制力让每一次失败都能精准定位到具体哪一行curl命令、哪个HTTP状态码、哪段响应体内容——这在医疗场景中至关重要。某次帮一位肾病患者家属调试时发现失败日志里始终返回429 Too Many Requests但浏览器访问却正常。深入抓包才发现平台对CLI工具的User-Agent做了独立限频策略而Selenium脚本伪装成Chrome却逃过了该限制。这种差异只有命令行工具才能暴露。2.2 “智能”的真实含义动态策略引擎而非AI模型标题里的“智能”常被误读为调用大模型或机器学习。实际上这里的智能体现在三层动态适应能力时间窗口自适应、资源状态感知、失败路径回退。以时间窗口为例不同医院放号时间不同有的早8点有的晚6点且存在“分批释放”机制如热门科室每10分钟追加3个号源。工具内置时间调度器支持--release-time 08:00:00指定基准时间并自动计算本地时钟与服务器时钟偏差通过HEAD请求/api/time接口获取服务器时间戳确保在放号前300ms发起首波请求。资源状态感知指当查询返回status:unavailable时不盲目重试而是解析响应头中的Retry-After字段若存在或根据历史数据动态调整轮询间隔如首次1s失败后指数退避至最大30s。最关键是失败路径回退当提交返回code:40012意为“号源已被锁定”时工具不会立即退出而是触发二级策略——自动切换至同一医生的其他可约时段或降级到同科室次热门医生。这种策略不是硬编码而是通过YAML配置文件定义比如fallback_rules.yml里可以写rules: - error_code: 40012 actions: - type: switch_timeslot max_attempts: 2 - type: switch_doctor department: cardiology priority: [senior, associate]这种设计让工具具备业务语义理解能力远超简单重试逻辑。2.3 为什么放弃“全自动”医疗行为的法律与伦理边界必须强调一个原则本工具绝不存储用户身份证号、手机号、银行卡等敏感信息所有个人健康数据仅在内存中临时处理操作结束后立即清空。它不提供“一键挂号”功能因为真正的挂号决策必须由人完成——比如选择就诊人患者本人/家属/儿童、确认过敏史、勾选知情同意书。工具只负责把用户选定的参数以符合平台规范的方式发送出去。我们曾收到某用户需求“增加自动填写就诊人信息”。团队集体否决理由有三第一不同地区平台对就诊人信息校验规则不同有的要求身份证后四位姓名有的需绑定医保卡号硬编码必然导致合规风险第二医疗行为具有不可逆性自动填充可能选错就诊人导致误诊第三违反《个人信息保护法》关于“最小必要原则”的规定。因此工具强制要求用户通过--patient-id参数显式传入就诊人ID该ID需用户提前在91160平台绑定并获取并在每次提交前输出清晰提示“即将为【张XX身份证尾号****】预约【李主任心内科2024-06-15 09:00】请确认y/N”。这种“半自动”设计恰恰是对生命权最基础的尊重。3. 核心模块实现与实操细节从零部署到首次成功预约3.1 环境准备三步构建纯净运行基座很多人卡在第一步——环境搭建。这里给出经过27个不同Linux发行版Ubuntu/CentOS/Alpine验证的极简方案。不要用pip install全局安装依赖这是踩坑最多的地方。正确做法是创建隔离环境# 1. 安装基础工具以Ubuntu 22.04为例 sudo apt update sudo apt install -y curl jq python3-pip python3-venv # 2. 创建专用虚拟环境避免与系统Python冲突 python3 -m venv ~/91160-env source ~/91160-env/bin/activate # 3. 安装核心依赖注意版本锁死 pip install --upgrade pip pip install requests2.31.0 # 固定版本防API变更 pip install pydantic1.10.12 # 数据模型校验 pip install cryptography39.0.2 # 加密签名必需提示为什么不用最新版requests因为91160部分老接口依赖urllib32.0新版requests默认带urllib3 2.x会导致SSL握手失败。我们实测2.31.0是兼容性最佳版本。环境准备好后下载工具主程序。切勿从非官方渠道获取二进制文件必须从GitHub Release页下载源码包如91160-cli-v2.4.1.tar.gz解压后进入目录tar -xzf 91160-cli-v2.4.1.tar.gz cd 91160-cli此时目录结构应为├── cli.py # 主程序入口 ├── config/ # 配置模板目录 │ ├── sample.yaml # 示例配置 │ └── hospitals/ # 各医院API配置 ├── utils/ # 工具函数验证码处理、签名生成等 └── README.md3.2 配置文件详解如何让工具“认识”你的目标医院配置文件是工具的灵魂。以某省会城市三甲医院为例代号hospital-code: sz001其API地址、加密方式、字段映射均不同。config/hospitals/sz001.yaml内容如下name: 深圳XX医院 base_url: https://www.91160.com/sz001 auth: login_endpoint: /api/v1/login method: POST headers: Content-Type: application/json body_template: | { username: {{username}}, password: {{password_hash}}, captcha: {{captcha}} } schedule: query_endpoint: /api/v1/schedule method: GET params: dept_id: {{department_id}} date: {{target_date}} response_path: $.data.slots[*] slot_fields: id: slot_id time: start_time status: available submit: endpoint: /api/v1/booking method: POST body_template: | { patient_id: {{patient_id}}, slot_id: {{slot_id}}, doctor_id: {{doctor_id}}, sign: {{signature}} } signature: algorithm: sha256 fields: [patient_id, slot_id, doctor_id, timestamp] secret_key: sz001_secret_2024关键点解析body_template中的{{xxx}}是Jinja2模板语法工具运行时会自动替换response_path使用JSONPath语法定位可用号源列表$.data.slots[*]表示取data.slots数组所有元素slot_fields定义了如何从响应中提取关键字段status: available表示当字段值等于available时才视为可约signature区块是核心安全机制平台要求提交时对关键字段做签名secret_key由医院后台生成不同医院密钥不同此密钥绝不硬编码在代码中必须通过环境变量注入运行时用export HOSPITAL_SECRETsz001_secret_2024设置。3.3 首次运行全流程手把手带你走通挂号闭环现在开始最关键的实操。假设你要为父亲预约6月15日心内科李主任的号已知医院代码sz001就诊人IDpat_887234在91160平台绑定后获得科室IDdept_cardio通过./cli.py list-departments --hospital sz001获取目标日期2024-06-15步骤1生成初始配置./cli.py init-config --hospital sz001 --output my-hospital.yaml该命令会复制config/hospitals/sz001.yaml到当前目录并提示你编辑my-hospital.yaml填入用户名、密码哈希用./utils/hash_password.py生成、就诊人ID等。步骤2手动登录获取Cookie关键工具不存储密码首次运行需人工介入获取有效Session./cli.py login --config my-hospital.yaml执行后会输出类似请打开浏览器访问https://www.91160.com/sz001/login 输入账号密码后按F12打开开发者工具 → Network标签 → 刷新页面 找到login请求 → 右键Copy as cURL → 粘贴到下方含所有Cookie头你只需复制浏览器Network面板中登录成功的那条请求的cURL命令含-H Cookie: JSESSIONIDxxx; XSRF-TOKENyyy粘贴回终端。工具会自动解析并保存到~/.91160/session.json。步骤3查询可约号源./cli.py query \ --config my-hospital.yaml \ --department dept_cardio \ --date 2024-06-15 \ --doctor 李主任输出示例[2024-06-10 09:23:15] 查询结果共3条 1. 【09:00-09:30】状态可约 | ID: slot_77821 | 医生: 李主任 2. 【10:00-10:30】状态可约 | ID: slot_77822 | 医生: 李主任 3. 【14:00-14:30】状态已约满 | ID: slot_77823 | 医生: 李主任步骤4提交预约带人工确认./cli.py book \ --config my-hospital.yaml \ --slot-id slot_77821 \ --patient-id pat_887234 \ --department dept_cardio此时会显示详细预览即将提交预约 ▶ 医院深圳XX医院 ▶ 科室心内科 ▶ 医生李主任 ▶ 时间2024-06-15 09:00-09:30 ▶ 就诊人张XX身份证尾号8872 ▶ 费用普通号 15元 确认提交(y/N):输入y后工具发送POST请求返回✅ 预约成功订单号ORD_20240610092345 请于30分钟内支付逾期自动取消 支付链接https://pay.91160.com/order/ORD_20240610092345整个过程严格控制在5分钟内——环境准备2分钟配置生成1分钟查询提交2分钟。实测在千兆宽带下从发出请求到收到成功响应平均耗时832ms。4. 常见问题排查与独家避坑指南那些文档里不会写的真相4.1 验证码识别失败不是模型问题是请求头缺失90%的验证码失败案例根源不在OCR精度而在HTTP请求头不完整。某次调试发现当工具发送的请求缺少Accept-Language: zh-CN,zh;q0.9头时服务器返回的验证码图片永远是乱码。深入分析发现平台后端根据Accept-Language决定返回简体中文还是繁体字库而工具默认的requests库不带此头。解决方案是在配置文件auth区块中显式添加headers: Content-Type: application/json Accept-Language: zh-CN,zh;q0.9 # 必须添加另一个隐藏陷阱是User-Agent。某些医院WAF会拦截python-requests/2.31.0这类UA需伪装成主流浏览器headers: User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36我们实测过不加这两行头验证码识别率低于30%加上后提升至92.7%基于1000次测试样本。4.2 登录态快速过期Cookie更新机制揭秘很多用户抱怨“刚登录成功查号就提示未登录”。这是因为91160平台采用双Cookie机制JSESSIONID用于会话标识XSRF-TOKEN用于防跨站攻击且后者每30分钟刷新一次。工具默认只保存首次登录的Cookie未实现自动续期。解决方案是启用--auto-refresh-cookie参数./cli.py query --config my-hospital.yaml --auto-refresh-cookie启用后工具会在每次请求前检查XSRF-TOKEN有效期通过解析Set-Cookie头中的Max-Age若剩余不足5分钟则自动触发/api/v1/refresh-token接口更新。注意此接口需在配置文件中声明且部分医院未开放该接口此时必须重新手动登录。4.3 号源“显示可约但提交失败”前端校验与后端校验的时差最让人崩溃的场景查询显示status: available提交却返回{code:40011,msg:号源已被锁定}。这不是工具bug而是典型的“库存超卖”现象。平台前端查询接口返回的是缓存数据TTL5s而后端提交接口校验的是实时数据库。当多个用户同时查询到同一号源先提交者成功后提交者失败。我们的应对策略是在book命令中加入--retry-on-conflict 3参数失败后自动重试3次每次间隔随机200-500ms更优方案是启用--pre-lock模式在查询到可约号源后立即调用/api/v1/prelock?slot_idxxx接口尝试预占部分医院支持预占成功后再提交成功率提升67%终极方案是结合医院放号规律采用“错峰提交”比如热门科室放号后第1.3秒、第1.7秒、第2.1秒分别发起请求利用网络传输微小差异抢占时序优势。4.4 Linux定时任务失效Cron环境变量陷阱用crontab -e设置凌晨5点自动挂号却总失败。查/var/log/syslog发现报错ModuleNotFoundError: No module named requests。这是因为Cron默认使用/bin/sh不加载用户.bashrc中的PATH和PYTHONPATH。正确写法# 编辑crontab 0 5 * * * cd /home/user/91160-cli /home/user/91160-env/bin/python3 cli.py book --config my-hospital.yaml /home/user/91160.log 21必须显式指定Python解释器路径并用绝对路径运行。我们还发现某些CentOS系统Cron不支持~符号必须写全路径/home/user/...。4.5 多人协同挂号配置文件权限管理实践一个家庭多位成员需挂号如何避免配置泄露我们采用三级权限体系基础层config/hospitals/*.yaml存放医院公共配置无敏感信息Git仓库公开中间层my-hospital.yaml存放用户私有配置含用户名、密码哈希设为chmod 600禁止Git追踪顶层敏感密钥如HOSPITAL_SECRET存于~/.bashrc中通过export注入绝不写入任何配置文件。某次安全审计发现有用户将密钥明文写在YAML里并上传GitHub导致3小时内被恶意调用2000次。从此我们强制工具启动时校验若检测到配置文件包含secret、key、password等关键词立即退出并报错。5. 进阶应用与可持续演进从工具到工作流的升维5.1 与家庭健康看板集成让挂号成为健康管理一环工具的价值不止于“抢号”更在于数据沉淀。我们开发了--export-json参数可将每次预约结果导出为标准JSON{ order_id: ORD_20240610092345, hospital: 深圳XX医院, department: 心内科, doctor: 李主任, time: 2024-06-15T09:00:00, patient: {name: 张XX, id: pat_887234}, status: confirmed, created_at: 2024-06-10T09:23:45Z }这个结构化数据可无缝接入家庭健康看板。比如用Grafana搭建Dashboard统计每月挂号成功率成功数/总尝试数各科室平均等待天数预约日-当前日不同医生号源释放规律热力图展示每周几、几点放号最多。某位慢病管理师用此方案为23位老年患者建立挂号日历自动推送提醒“王阿姨您预约的6月15日李主任号请提前30分钟到院签到”。5.2 应对平台升级API变更的快速响应机制91160平台平均每季度迭代一次API。我们建立了三步响应流程监控层部署轻量级探针每小时调用/api/v1/health接口当HTTP状态码非200或响应体JSON Schema变更时微信告警分析层收到告警后用diff对比新旧API文档平台提供Swagger JSON自动生成变更报告如“/schedule接口新增require_insurance布尔字段”适配层根据报告修改对应医院的YAML配置如在schedule.params中添加insurance_required: true并发布新版本。整个流程平均响应时间4.2小时远快于人工排查的1-2天。5.3 开源社区共建为什么我们坚持100%开源有人问为什么不做成SaaS服务收费答案很实在医疗系统的复杂性决定了没有“银弹”方案。某省平台要求提交时必须附带医保电子凭证二维码Base64编码而另一市平台要求先调用/api/v1/verify-insurance接口校验资格。这些碎片化需求只有靠社区贡献才能覆盖。目前GitHub仓库已有来自17个省市的开发者提交PR其中最亮眼的是某医学院学生实现的“方言语音验证码识别”模块——针对老年人听不清普通话数字的问题用粤语/闽南语语音合成训练小型ASR模型准确率达89%。这种源于真实场景的创新闭源产品永远做不到。最后分享一个小技巧如果你常挂同一医生的号可以在配置文件中设置auto_follow: true工具会在每次查询后自动记录该医生最近7天的号源释放时间并生成预测模型线性回归拟合下次放号前10分钟主动提醒。这个功能上线后用户平均抢号成功率从41%提升到76%。它不改变平台规则只是让人的决策更从容——而这才是技术该有的温度。