├── requirements.txt ├── images ├── image1.png └── image2.png ├── README.md ├── config.yaml ├── LICENSE ├── browser.py ├── .idea └── workspace.xml ├── .gitignore └── main.py /requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | pyyaml 3 | pycryptodome 4 | tqdm -------------------------------------------------------------------------------- /images/image1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwang1024/guahao114/HEAD/images/image1.png -------------------------------------------------------------------------------- /images/image2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwang1024/guahao114/HEAD/images/image2.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 北京市114预约挂号脚本 2 | 3 | **声明: 本软件只是方便大家挂号,请勿用于非法用途,否则后果自负** 4 | 5 | ## 环境 6 | - Python3 7 | 8 | ## 使用方法 9 | 10 | 1. 安装依赖库,例如:``` pip install --user -r requirements.txt ``` 11 | 2. 在114使用微信扫码或验证码登录,https://www.114yygh.com/ (因技术有限需登录后手动获取cookie) 12 | 3. 修改配置文件 13 | 4. 运行命令: 14 | ```python main.py``` 15 | 16 | ## 配置文件 17 | 18 | - `config.yaml` 19 | - 配置参数说明 20 | - `cmi-user-ticket`:登录后获取的cookie 21 | ![cookie](./images/image1.png) 22 | - `date`:要挂号的日期,填latest表示抢放号最新一天的号 23 | - `hospitalId`:医院ID 24 | - `firstDeptCode`:一级科室ID 25 | - `secondDeptCode`:二级科室ID 26 | ![hospitalId](./images/image2.png) 27 | - 以`协和医院-内科-内科门诊`为例,`hospitalId=H01110003,firstDeptCode=T007,secondDeptCode=133` 28 | 29 | ## Thanks 30 | - 感谢大佬[AndyXiaoyu](https://github.com/AndyXiaoyu/beijingguahao)的开源仓库,本项目据此修改完成 -------------------------------------------------------------------------------- /config.yaml: -------------------------------------------------------------------------------- 1 | # https://www.114yygh.com/ 114网址 2 | 3 | # 登录后在cookie中查 4 | "cmi-user-ticket": "" 5 | 6 | # 手机号码 7 | phoneNumber: "" 8 | 9 | # date: 挂号日期,如填写latest自动挂最新一天 10 | date: "latest" # "2023-10-15" 11 | 12 | # hospitalId: 医院id 13 | hospitalId: "H01110003" 14 | 15 | # 科室id 16 | firstDeptCode: "T007" 17 | secondDeptCode: "133" 18 | 19 | # 需要挂早上的号请填写1 需要挂下午的号请填写2 上下午均可填写0 20 | timePeriod: 21 | - "MORNING" 22 | - "AFTERNOON" 23 | # ["MORNING","AFTERNOON"] 24 | period: 25 | 26 | 27 | # patientName: 患者姓名 28 | # 若是自己挂号可为空 29 | patientName: "" 30 | 31 | cardType: "SOCIAL_SECURITY" #就诊卡类型,社保卡 32 | 33 | # 就诊卡号 34 | hospitalCardId: "" 35 | 36 | # 医保卡号 37 | medicareCardId: "" 38 | 39 | # doctorName: 医生姓名 40 | # 不填写的话默认选最好的医生 41 | # 填写后若这个医生没有号,会自动选其余号中最好的医生 42 | doctorName: 43 | - 44 | 45 | # 指定医生 46 | # false:默认不指定 47 | # true:只挂指定医生的号 48 | assign: "false" 49 | 50 | 51 | # chooseBest: 选择模式 52 | # 不填写的默认从最好的医生开始选择 53 | # 可选项为"yes" 或者 "no" 54 | chooseBest: "no" 55 | 56 | # DebugLevel: 调试等级 57 | # 支持的调试等级有 debug/info/warning/error/critical 58 | DebugLevel: "debug" 59 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 派大星星 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /browser.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 3 | 4 | 5 | import requests 6 | import yaml 7 | from yaml import Loader 8 | 9 | class Browser(object): 10 | """ 11 | 浏览器 12 | """ 13 | def __init__(self): 14 | self.session = requests.Session() 15 | self.hospital_id = '' 16 | self.cookies = {} 17 | with open('config.yaml', "r", encoding="utf-8") as yaml_file: 18 | data = yaml.load(yaml_file, Loader) 19 | self.hospital_id = data["hospitalId"] 20 | self.cookies = { 21 | "cmi-user-ticket": data["cmi-user-ticket"] 22 | } 23 | 24 | self.session.headers = { 25 | 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36', 26 | 'Content-Type': 'application/json; charset=UTF-8', 27 | 'Request-Source': 'PC', 28 | 'Referer': 'https://www.114yygh.com/hospital/' + self.hospital_id + '/home' 29 | } 30 | 31 | def load_cookies(self): 32 | self.session.cookies = requests.utils.cookiejar_from_dict(self.cookies) 33 | 34 | def get(self, url, data): 35 | """ 36 | http get 37 | """ 38 | pass 39 | response = self.session.get(url) 40 | if response.status_code == 200: 41 | self.session.headers['Referer'] = response.url 42 | return response 43 | 44 | def post(self, url, data): 45 | """ 46 | http post 47 | """ 48 | response = self.session.post(url, json=data) 49 | if response.status_code == 200: 50 | self.session.headers['Referer'] = response.url 51 | return response 52 | 53 | -------------------------------------------------------------------------------- /.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | 18 | 19 | 21 | 22 | 23 | 26 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 62 | 63 | 64 | 65 | 66 | 67 | 1696820778027 68 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ 161 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import sys 5 | import json 6 | import time 7 | import datetime 8 | import logging 9 | from tqdm import tqdm, trange 10 | from browser import Browser 11 | import yaml 12 | from yaml import Loader 13 | 14 | if sys.version_info.major != 3: 15 | logging.error("请在python3环境下运行本程序") 16 | sys.exit(-1) 17 | 18 | 19 | class Config(object): 20 | 21 | def __init__(self, config_path): 22 | try: 23 | with open(config_path, "r", encoding="utf-8") as yaml_file: 24 | data = yaml.load(yaml_file, Loader) 25 | debug_level = data["DebugLevel"] 26 | if debug_level == "debug": 27 | self.debug_level = logging.DEBUG 28 | elif debug_level == "info": 29 | self.debug_level = logging.INFO 30 | elif debug_level == "warning": 31 | self.debug_level = logging.WARNING 32 | elif debug_level == "error": 33 | self.debug_level = logging.ERROR 34 | elif debug_level == "critical": 35 | self.debug_level = logging.CRITICAL 36 | 37 | logging.basicConfig(level=self.debug_level, 38 | format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s', 39 | datefmt='%a, %d %b %Y %H:%M:%S') 40 | 41 | self.phoneNumber = data["phoneNumber"] 42 | self.date = data["date"] 43 | self.hospitalId = data["hospitalId"] 44 | self.firstDeptCode = data["firstDeptCode"] 45 | self.secondDeptCode = data["secondDeptCode"] 46 | self.timePeriod = data["timePeriod"] 47 | self.cardType = data["cardType"] 48 | self.hospitalCardId = data["hospitalCardId"] 49 | self.medicareCardId = data["medicareCardId"] 50 | self.doctorName = data["doctorName"] 51 | self.assign = data['assign'] 52 | 53 | 54 | logging.info("配置加载完成") 55 | logging.debug("手机号:" + str(self.phoneNumber)) 56 | logging.debug("挂号日期:" + str(self.date)) 57 | logging.debug("医院id:" + str(self.hospitalId)) 58 | logging.debug("上午/下午:" + str(self.timePeriod)) 59 | logging.debug("所选医生:" + str(self.doctorName)) 60 | logging.debug("是否挂指定医生:" + str(self.assign)) 61 | 62 | if not self.date: 63 | logging.error("请填写挂号时间") 64 | exit(-1) 65 | 66 | except Exception as e: 67 | logging.error(repr(e)) 68 | sys.exit() 69 | 70 | 71 | 72 | class Guahao(object): 73 | """ 74 | 挂号 75 | """ 76 | def __init__(self, config_path="config.yaml"): 77 | self.browser = Browser() 78 | self.dutys = [] 79 | self.refresh_time = '' 80 | self.duty_url = "https://www.114yygh.com/web/product/detail" 81 | self.confirm_url = "https://www.114yygh.com/web/product/confirm" 82 | self.save_url = "https://www.114yygh.com/web/order/save" 83 | self.query_hospital_url = "https://www.114yygh.com/web/hospital/detail" # 查询医院详情 84 | 85 | self.config = Config(config_path) # config对象 86 | 87 | def is_login(self): 88 | logging.info("开始检查是否已经登录") 89 | response = self.browser.get("http://www.114yygh.com/web/user/info" + "?_time=" + str(self.timestamp()), data='') 90 | try: 91 | data = json.loads(response.text) 92 | if data["resCode"] == 0: 93 | logging.debug("response data:" + response.text) 94 | return True 95 | else: 96 | logging.debug("response data: HTML body") 97 | return False 98 | except Exception as e: 99 | logging.error(e) 100 | return False 101 | 102 | def auth_login(self): 103 | """ 104 | 登录 105 | """ 106 | try: 107 | self.browser.load_cookies() 108 | if self.is_login(): 109 | logging.info("cookies登录成功") 110 | return True 111 | except Exception as e: 112 | logging.info("cookies登录失败") 113 | pass 114 | 115 | def select_doctor_one_day(self): 116 | """选择合适的大夫""" 117 | hospitalId = self.config.hospitalId 118 | timePeriod = self.config.timePeriod 119 | logging.debug("当前挂号日期: " + self.config.date) 120 | 121 | payload = { 122 | 'hosCode': hospitalId, 123 | 'firstDeptCode': self.config.firstDeptCode, 124 | 'secondDeptCode': self.config.secondDeptCode, 125 | 'target': self.config.date 126 | } 127 | duty_url = self.duty_url + '?_time=' + str(self.timestamp()) 128 | response = self.browser.post(duty_url, data=payload) 129 | logging.debug("response data:" + response.text) 130 | try: 131 | data = json.loads(response.text) 132 | if data["resCode"] == 0: 133 | for duty in timePeriod: 134 | for duty_result in data['data']: 135 | if (duty_result['dutyCode'] == duty): 136 | self.dutys = duty_result['detail'] 137 | doctor = self.select_doctor_by_vec() 138 | if doctor == 'NoDuty' or doctor == 'NotReady': 139 | continue 140 | return doctor 141 | return 'NoDuty' 142 | except Exception as e: 143 | logging.error(repr(e)) 144 | sys.exit() 145 | 146 | def select_doctor_by_vec(self): 147 | if len(self.dutys) == 0: 148 | return "NotReady" 149 | doctors = self.dutys 150 | if self.config.assign == 'true': # 指定医生 151 | for doctor_conf in self.config.doctorName: 152 | for doctor in doctors: 153 | if self.get_doctor_name(doctor) == doctor_conf: 154 | logging.info("选中:" + self.get_doctor_name(doctor)) 155 | return doctor 156 | return "NoDuty" 157 | # 按照配置优先级选择医生 158 | for doctor_conf in self.config.doctorName: 159 | for doctor in doctors: 160 | if self.get_doctor_name(doctor) == doctor_conf and doctor['totalCount'] % 2 != 0: 161 | return doctor 162 | 163 | 164 | if len(doctors) != 0: 165 | logging.info("选中:" + self.get_doctor_name(doctors[0])) 166 | return doctors[0] 167 | return "NoDuty" 168 | 169 | def get_doctor_name(self, doctor): 170 | if doctor['doctorName'] is not None: 171 | return str(doctor['doctorName']) 172 | else: 173 | return str(doctor['doctorTitleName']) 174 | 175 | 176 | def confirm(self, uniqProductKey): 177 | payload = { 178 | "dutyTime": 0, 179 | "firstDeptCode": self.config.firstDeptCode, 180 | "hosCode": self.config.hospitalId, 181 | "secondDeptCode": self.config.secondDeptCode, 182 | "target": self.config.date, 183 | "uniqProductKey": uniqProductKey 184 | } 185 | response = self.browser.post(self.confirm_url, data=payload) 186 | data = json.loads(response.text) 187 | if data["resCode"] == 0: 188 | return data["data"]["confirmToken"] 189 | 190 | def get_it(self, doctor, confirmToken): 191 | """ 192 | 挂号 193 | """ 194 | hospitalCardId = self.config.hospitalCardId 195 | card_type = self.config.cardType 196 | payload = { 197 | "cardNo": hospitalCardId, # 就诊卡号 198 | "cardType": card_type, 199 | "confirmToken": confirmToken, 200 | "uniqProductKey": doctor["uniqProductKey"], 201 | "smsCode": "", 202 | "firstDeptCode": self.config.firstDeptCode, 203 | "hosCode": self.config.hospitalId, 204 | "secondDeptCode": self.config.secondDeptCode, 205 | "dutyTime": doctor["period"][0]["dutyTime"], # 挂最早的号 206 | "treatmentDay": self.config.date, 207 | "hospitalCardId": "", 208 | "phone": self.config.phoneNumber, 209 | "orderFrom": "OTHER", 210 | "contactRelType": "CONTACT_OTHER", 211 | } 212 | response = self.browser.post(self.save_url, data=payload) 213 | logging.debug("payload:" + json.dumps(payload)) 214 | logging.debug("response data:" + response.text) 215 | 216 | try: 217 | data = json.loads(response.text) 218 | if data["resCode"] == 0: 219 | logging.info("挂号成功") 220 | return True 221 | if data["resCode"] == 8008: 222 | logging.error(data["msg"]) 223 | return True 224 | else: 225 | logging.error(data["msg"]) 226 | return False 227 | 228 | except Exception as e: 229 | logging.error(repr(e)) 230 | time.sleep(1) 231 | 232 | def timestamp(self): 233 | return int(round(time.time() * 1000)) 234 | 235 | def get_duty_time(self): 236 | """获取放号时间""" 237 | duty_time_url = self.query_hospital_url + "?_time=" + str(self.timestamp()) + "&hosCode=" + str(self.config.hospitalId) 238 | print(duty_time_url) 239 | response = self.browser.get(duty_time_url, "") 240 | ret = response.text 241 | print(ret) 242 | data = json.loads(ret) 243 | if data['resCode'] == 0: 244 | # 放号时间 245 | refresh_time = data['data']['openTimeView'] 246 | # 放号日期范围 247 | appoint_day = data['data']['bookingRange'] 248 | today = datetime.date.today() 249 | # 优先确认最新可挂号日期 250 | self.stop_date = today + datetime.timedelta(days=int(appoint_day-1)) 251 | logging.info("今日可挂号到: " + self.stop_date.strftime("%Y-%m-%d")) 252 | # 自动挂最新一天的号 253 | if self.config.date == 'latest': 254 | self.config.date = self.stop_date.strftime("%Y-%m-%d") 255 | logging.info("当前挂号日期变更为: " + self.config.date) 256 | # 生成放号时间和程序开始时间 257 | con_data_str = self.config.date + " " + refresh_time + ":00" 258 | self.start_time = datetime.datetime.strptime(con_data_str, '%Y-%m-%d %H:%M:%S') + datetime.timedelta( 259 | days=1-int(appoint_day)) 260 | logging.info("放号时间: " + self.start_time.strftime("%Y-%m-%d %H:%M")) 261 | 262 | 263 | def lazy(self): 264 | cur_time = datetime.datetime.now() + datetime.timedelta(seconds=int(time.timezone + 8 * 60 * 60)) 265 | if self.start_time > cur_time: 266 | seconds = (self.start_time - cur_time).total_seconds() 267 | logging.info("距离放号时间还有" + str(seconds) + "秒") 268 | hour = seconds // 3600 269 | minute = (seconds % 3600) // 60 270 | second = seconds % 60 271 | logging.info( 272 | "距离放号时间还有" + str(int(hour)) + " h " + str(int(minute)) + " m " + str(int(second)) + " s") 273 | 274 | sleep_time = seconds - 60 275 | if sleep_time > 0: 276 | logging.info("程序休眠" + str(sleep_time) + "秒后开始运行") 277 | if sleep_time > 3600: 278 | sleep_time -= 60 279 | for i in trange(1000): 280 | for j in trange(int(sleep_time / 1000), leave=False, unit_scale=True): 281 | time.sleep(1) 282 | else: 283 | for i in tqdm(range(int(sleep_time) - 60)): 284 | time.sleep(1) 285 | 286 | # 自动重新登录 287 | self.auth_login() 288 | 289 | def run(self): 290 | """主逻辑""" 291 | self.get_duty_time() 292 | self.auth_login() # 1. 登录 293 | self.lazy() # 2. 等待抢号 294 | while True: 295 | doctor = self.select_doctor_one_day() # 3. 选择医生 296 | if doctor == "NoDuty": 297 | # 如果当前时间 > 放号时间 + 30s 298 | if self.start_time + datetime.timedelta(seconds=30) < datetime.datetime.now(): 299 | # 确认无号,终止程序 300 | logging.info("没号了,亲~,休息一下继续刷") 301 | time.sleep(1) 302 | else: 303 | # 未到时间,强制重试 304 | logging.debug("放号时间: " + self.start_time.strftime("%Y-%m-%d %H:%M")) 305 | logging.debug("当前时间: " + datetime.datetime.now().strftime("%Y-%m-%d %H:%M")) 306 | logging.info("没号了,但截止时间未到,重试中") 307 | time.sleep(1) 308 | elif doctor == "NotReady": 309 | logging.info("好像还没放号?重试中") 310 | time.sleep(1) 311 | else: 312 | confirmToken = self.confirm(doctor["uniqProductKey"]) 313 | result = self.get_it(doctor, confirmToken) # 4.挂号 314 | if result: 315 | break # 挂号成功 316 | 317 | 318 | if __name__ == "__main__": 319 | guahao = Guahao() 320 | guahao.run() 321 | --------------------------------------------------------------------------------