基于Selenium与ddddocr的Web自动化抢票脚本开发实战
发布时间:2026/9/3 8:19:33 作者:尧图编辑部 阅读量:1,286

简介本资源是一套面向东南大学在校学生及Python自动化开发者的技术实践工具包专为解决人文与科学素养系列讲座预约抢票中验证码识别、短信双因素认证、多校区优先级调度等高频痛点而设计。脚本基于PythonSelenium构建集成ddddocr实现图形验证码自动识别并支持手机短信验证码的动态提取与填入同时内置校区智能匹配逻辑优先推送用户所属校区活动。压缩包共1045个文件955KB含1037张验证码样本图jpg用于模型训练与测试3个核心Python脚本含主程序、OCR调用、短信解析模块2个配置说明txt1份使用文档docx及1个README.md结构清晰、开箱即用。目前已有51人学习下载读者可直接复用完整自动化流程代码、获取真实场景下的验证码样本集、掌握Selenium与ddddocr协同开发范式并参考短信认证对接与校区策略嵌入的工程化实现思路。1. 项目缘起与核心痛点又到了一年一度选课、抢讲座票的时候东南大学的同学们想必对那个熟悉的预约系统界面又爱又恨。爱的是学校提供了丰富的人文与科学素养讲座资源恨的是每次开放预约票都在几秒钟内被“秒杀”一空。手动刷新、拼手速、拼网速结果往往是网页卡死验证码看不清最后只能看着“已约满”三个字兴叹。这种经历相信不少同学都深有体会。我最初也是“抢票失败大军”中的一员直到有一次为了听一场心仪已久的讲座我决定不再把希望寄托于飘忽不定的运气和网络。作为一名计算机相关专业的学生我琢磨着能不能用技术手段来解决这个“老大难”问题于是这个“全自动智能预约抢票脚本”的想法便诞生了。它的目标非常明确模拟一个真实用户的完整操作流程自动完成从登录、识别验证码、选择校区和讲座到最终提交预约的全过程将人力从重复、紧张的机械操作中解放出来把抢票的成功率提升到一个新的高度。这个项目不仅仅是一个简单的“刷票器”它集成了几个关键技术点来应对实际抢票环境中的复杂挑战使用Selenium进行高仿真的Web自动化操作以绕过简单的反爬机制引入ddddocr这个强大的开源OCR库来破解图形验证码这一常见障碍甚至还考虑了双因素认证短信验证码的处理逻辑以及根据用户偏好如校区优先级进行智能选择的策略。最终我将所有这些功能模块打包成一个完整的、可执行的Python项目也就是大家看到的这个.zip压缩包。接下来我就把这个项目的设计思路、实现细节、踩过的坑以及一些优化心得毫无保留地分享给大家。2. 整体架构与核心组件选型在动手写代码之前合理的架构设计和技术选型是项目成功的基础。我们需要一个能够稳定、高效、且足够“像人”的工具来与网页交互。2.1 为什么是Selenium而不是Requests提到网络自动化很多人第一反应可能是requests库。它轻量、快速适合直接发送HTTP请求获取数据。但对于抢票这个场景requests有几个致命弱点会话与状态管理复杂预约系统通常有复杂的登录状态Session、Cookie和页面跳转逻辑。用requests模拟需要手动管理这些状态一个环节出错就前功尽弃。动态内容处理困难现代网页大量使用JavaScript渲染关键按钮、表单可能由JS动态生成。requests获取的是初始HTML无法直接与这些元素交互。反爬规避难度高简单的请求头User-Agent伪装对requests来说不难但面对更复杂的交互验证如点击行为轨迹则力不从心。而Selenium则完美避开了这些问题。它通过驱动一个真实的浏览器如Chrome、Edge来操作所有的JS都会被执行页面状态和用户会话由浏览器天然维护。我们的脚本只需要像真人一样告诉浏览器“点击这里”、“在那里输入文字”剩下的渲染、跳转、Cookie管理都交给浏览器本身。这使得脚本的编写逻辑非常直观几乎就是还原人工操作步骤稳定性也大大提升。当然代价是速度会比纯requests慢且更耗资源但在“成功率优先”的抢票场景下这个代价是值得的。2.2 验证码识别ddddocr的降维打击图形验证码是自动化脚本最大的拦路虎之一。东南大学预约系统的验证码不算最复杂的那一类通常是数字和字母的组合但可能有简单的扭曲、干扰线。早期我尝试过pytesseractTesseract OCR的Python封装但识别率在面对轻微干扰时下降明显需要大量的图像预处理灰度化、二值化、去噪调参过程繁琐且效果不稳定。后来我发现了ddddocr这个宝藏库。它基于深度学习模型专为验证码识别而生。其最大优点就是“开箱即用”。对于常见的字符验证码你几乎不需要做任何复杂的图像预处理直接传入图片字节或PIL图像对象它就能返回识别结果准确率非常高。它的API极其简洁import ddddocr ocr ddddocr.DdddOcr() with open(captcha.png, rb) as f: img_bytes f.read() res ocr.classification(img_bytes) print(res) # 输出识别出的字符串这种简洁高效让它成为本项目的不二之选。它大大降低了验证码识别模块的开发成本和维护难度。2.3 双因素认证与校区优先级的策略设计除了核心的“自动化”和“识别”这个脚本还考虑了现实场景中的两个细化需求短信验证码处理有些系统在登录或关键操作时会要求短信验证。完全自动化处理短信接收在技术上是可行的如通过特定硬件或运营商接口但涉及隐私和安全且普适性差。因此我采用的是一种半自动化策略脚本运行到需要输入短信验证码的环节时会暂停并提示用户“请在30秒内查看手机短信并将验证码输入到控制台”。用户手动输入后脚本再继续执行。这样既解决了问题又避免了触碰敏感边界。校区优先级讲座资源可能在不同校区开放。脚本允许用户在配置文件中设定校区偏好顺序如“九龙湖校区 四牌楼校区 丁家桥校区”。在抢票时脚本会按照优先级顺序依次尝试预约直到成功或全部失败。这比固定选择一个校区更加灵活和智能。基于以上考量项目的核心架构就清晰了一个由Selenium驱动浏览器作为执行主体ddddocr作为“眼睛”识别验证码再辅以用户交互和配置化策略的逻辑控制层。所有代码、配置和说明文档被打包在一起方便分发和使用。3. 核心模块拆解与实现细节有了清晰的架构我们就可以深入每个模块看看具体是怎么实现的以及其中有哪些需要注意的“坑”。3.1 环境搭建与Selenium驱动配置万事开头难一个稳定的环境是脚本能跑起来的前提。1. 安装Python与必要库确保你安装了Python 3.7及以上版本。然后通过pip安装核心依赖pip install selenium ddddocrselenium是自动化核心ddddocr用于验证码识别。建议在虚拟环境中进行避免包冲突。2. 下载与配置浏览器驱动Selenium需要对应的浏览器驱动WebDriver才能控制浏览器。以最常用的Chrome为例首先查看你电脑上Chrome浏览器的版本在地址栏输入chrome://version/。然后前往ChromeDriver官网或国内镜像站下载与你的Chrome版本号完全一致的驱动。将下载的chromedriver.exeWindows文件放在一个固定路径或者直接放入Python的Scripts目录这样可以在命令行直接访问。在代码中我们需要指定这个驱动的路径来初始化浏览器from selenium import webdriver from selenium.webdriver.chrome.service import Service # 指定你的chromedriver路径 driver_path rC:\path\to\your\chromedriver.exe service Service(executable_pathdriver_path) # 初始化浏览器可以添加一些选项以优化体验 options webdriver.ChromeOptions() # 可选以无头模式运行不显示浏览器界面适合后台运行 # options.add_argument(--headless) # 可选禁用GPU加速避免一些潜在问题 options.add_argument(--disable-gpu) # 禁用“Chrome正受到自动测试软件控制”的提示栏 options.add_experimental_option(excludeSwitches, [enable-automation]) options.add_experimental_option(useAutomatedExtension, False) driver webdriver.Chrome(serviceservice, optionsoptions)注意驱动版本与浏览器版本必须匹配这是新手最常遇到的问题。不匹配会导致无法启动或运行时出错。3.2 页面元素定位与自动化操作模拟脚本要操作网页第一步是找到对应的按钮、输入框等元素。Selenium提供了多种定位方式。1. 常用定位方式find_element(By.ID, “id”)通过元素的id属性定位最精确快速。find_element(By.NAME, “name”)通过name属性。find_element(By.CLASS_NAME, “class”)通过CSS类名。find_element(By.XPATH, “xpath”)通过XPath路径功能最强大可以定位几乎任何元素但表达式可能较复杂。find_element(By.CSS_SELECTOR, “css”)通过CSS选择器也很常用。对于东南大学的预约页面我们需要定位的元素通常包括学号/密码输入框、验证码图片、验证码输入框、登录按钮、讲座列表、预约按钮等。使用浏览器的开发者工具F12可以轻松查看这些元素的属性。2. 模拟人工操作的关键技巧找到元素后如何操作才能更“像人”避免被反爬机制识别输入文本使用send_keys()方法。不要在瞬间输入完毕可以模拟人的打字间隔from selenium.webdriver.common.keys import Keys from time import sleep username_input driver.find_element(By.ID, “username”) for char in “my_username”: username_input.send_keys(char) sleep(random.uniform(0.1, 0.3)) # 随机间隔点击操作优先使用click()方法。但有些元素可能需要先等待其可点击或者需要滚动到视野内再点击from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC # 等待按钮出现并可点击最多等10秒 button WebDriverWait(driver, 10).until( EC.element_to_be_clickable((By.ID, “submit-btn”)) ) # 滚动到元素位置 driver.execute_script(“arguments[0].scrollIntoView(true);”, button) sleep(0.5) # 滚动后稍作停顿 button.click()等待策略网页加载需要时间必须使用显式等待WebDriverWait而非固定的sleep。显式等待会在条件满足如元素出现、可点击后立即继续效率更高。固定sleep只在模拟人类思考停顿等特定场景下使用。3.3 ddddocr集成与验证码识别流程验证码识别是本项目的关键一环集成ddddocr的流程如下1. 捕获验证码图片首先需要从网页上把验证码图片抓取下来。验证码图片通常是一个img标签其src属性可能是Base64数据也可能是一个动态生成的图片URL。def get_captcha_image(driver, captcha_element): # 方法1如果src是base64 src captcha_element.get_attribute(“src”) if src.startswith(‘data:image’): # 格式通常为 “data:image/png;base64,iVBOR...” import base64 base64_data src.split(‘,’)[1] img_bytes base64.b64decode(base64_data) return img_bytes else: # 方法2如果是图片URL可以截图元素 captcha_element.screenshot(‘captcha.png’) with open(‘captcha.png’, ‘rb’) as f: img_bytes f.read() return img_bytes2. 调用ddddocr进行识别获取到图片的二进制数据后直接交给ddddocr处理。import ddddocr def recognize_captcha(img_bytes): ocr ddddocr.DdddOcr() # classification方法直接识别 captcha_text ocr.classification(img_bytes) return captcha_text实测下来对于数字字母混合、有简单干扰线的验证码ddddocr的识别率能达到95%以上完全满足需求。3. 处理识别结果与重试机制识别结果可能包含空格或大小写需要根据网页输入框的要求进行清洗如转大写、去空格。同时必须建立重试机制如果识别错误导致登录失败脚本应能自动刷新验证码并重新识别而不是卡死。max_retry 3 for attempt in range(max_retry): # 1. 获取新验证码 # 2. 识别 captcha_text recognize_captcha(img_bytes) # 3. 输入并提交 # 4. 检查是否登录成功如页面跳转或出现错误提示 if login_success: break else: print(f”第{attempt1}次识别失败刷新重试...”) driver.find_element(By.ID, “refresh_captcha_btn”).click() # 点击刷新按钮 sleep(2)3.4 校区优先级与智能选择逻辑当同时有多个校区的讲座可预约时智能选择逻辑就派上用场了。我将其设计为一个可配置的优先级列表。1. 配置设计使用一个简单的配置文件如config.ini或settings.py来定义优先级。# 在settings.py中 CAMPUS_PRIORITY [“九龙湖校区”, “四牌楼校区”, “丁家桥校区”]2. 页面解析与匹配脚本需要解析讲座列表页面提取每个讲座条目的信息包括讲座标题、时间、校区、剩余名额等。这通常通过遍历特定的HTML元素如tr或li来实现。lecture_items driver.find_elements(By.CSS_SELECTOR, “.lecture-list-item”) available_lectures [] for item in lecture_items: title item.find_element(By.CLASS_NAME, “title”).text campus item.find_element(By.CLASS_NAME, “campus”).text quota item.find_element(By.CLASS_NAME, “quota”).text if “有名额” in quota: available_lectures.append({‘title’: title, ‘campus’: campus, ‘element’: item})3. 优先级调度遍历配置的校区优先级列表在available_lectures中寻找匹配该校区的讲座。找到第一个匹配的、有名额的讲座就执行预约点击。for target_campus in CAMPUS_PRIORITY: for lecture in available_lectures: if target_campus in lecture[‘campus’]: print(f”尝试预约 {lecture[‘title’]} - {lecture[‘campus’]}“) lecture[‘element’].find_element(By.CLASS_NAME, “book-btn”).click() # 处理后续可能的确认弹窗等 # ... # 如果预约成功跳出所有循环 break # 如果内层循环因成功而break这里也会通过else-continue机制跳出外层循环这种设计使得脚本行为高度可定制用户无需修改代码只需调整配置列表即可改变抢票策略。4. 完整抢票流程串联与异常处理将各个模块像拼图一样组合起来就形成了完整的自动化流程。同时一个健壮的系统必须能妥善处理各种异常。4.1 主流程步骤分解整个脚本的执行主线可以概括为以下步骤我将其封装在一个main()函数中初始化与登录准备def main(): # 1. 读取配置文件校区优先级、账号密码等 config load_config() # 2. 初始化浏览器驱动 driver init_driver() # 3. 访问预约系统登录页 driver.get(“https://预约系统网址”) wait WebDriverWait(driver, 15)登录与验证码破解# 4. 填写学号、密码 input_username(driver, config[‘username’]) input_password(driver, config[‘password’]) # 5. 定位验证码图片捕获并识别 captcha_element wait.until(EC.presence_of_element_located((By.ID, “captchaImg”))) img_bytes get_captcha_image(driver, captcha_element) captcha_code recognize_captcha(img_bytes) # 6. 输入验证码 input_captcha(driver, captcha_code) # 7. 点击登录并等待登录成功如跳转到主页 click_login(driver) wait.until(EC.url_contains(“main”)) # 等待URL包含main表示登录成功导航与讲座列表获取# 8. 跳转到讲座预约页面 driver.get(“https://预约系统网址/lecture”) # 9. 等待列表加载并解析出所有可预约讲座 lecture_data parse_lecture_list(driver)智能预约与确认# 10. 根据校区优先级选择目标讲座并点击预约 success book_lecture_by_priority(driver, lecture_data, config[‘CAMPUS_PRIORITY’]) if success: # 11. 处理可能的预约确认弹窗 handle_confirm_dialog(driver) # 12. 检查最终预约结果如出现“预约成功”提示 check_booking_result(driver) print(“ 预约成功”) else: print(“ 当前无符合条件的可预约讲座。”)收尾工作# 13. 可选截图保存结果 driver.save_screenshot(“booking_result.png”) # 14. 等待片刻后关闭浏览器 sleep(5) driver.quit()4.2 异常处理与鲁棒性增强网络不稳定、页面元素加载慢、验证码识别错误、讲座已被抢光……脚本运行中会遇到各种意外。我们必须为这些情况做好准备。1. 网络超时与元素加载失败广泛使用WebDriverWait配合各种预期条件expected_conditions并为每一步关键操作设置合理的超时时间。如果超时则抛出异常并被捕获。from selenium.common.exceptions import TimeoutException, NoSuchElementException try: login_button WebDriverWait(driver, 10).until( EC.element_to_be_clickable((By.ID, “loginBtn”)) ) login_button.click() except TimeoutException: print(“错误登录按钮在10秒内未出现或不可点击。”) # 可以尝试刷新页面或执行其他恢复操作 driver.refresh() # 或者记录日志并退出 log_error(“Login button timeout”) driver.quit() sys.exit(1)2. 验证码识别失败重试如前所述将验证码识别和登录步骤放入一个循环设定最大重试次数。每次失败后触发刷新验证码操作。3. 预约冲突或失败点击预约按钮后系统可能返回“已约满”、“冲突”等提示。脚本需要能检测到这些提示信息。def check_booking_result(driver): try: # 尝试查找成功提示元素 success_msg driver.find_element(By.ID, “successAlert”) return True except NoSuchElementException: pass try: # 尝试查找失败提示元素 fail_msg driver.find_element(By.ID, “failAlert”) print(f”预约失败{fail_msg.text}“) return False except NoSuchElementException: # 什么都没找到可能是页面状态未知 print(“警告无法明确判断预约结果。”) return False4. 日志记录为脚本添加日志功能至关重要。它可以帮助你事后复盘看脚本是在哪一步失败的。import logging logging.basicConfig(levellogging.INFO, format‘%(asctime)s - %(levelname)s - %(message)s’, handlers[logging.FileHandler(‘booking.log’), logging.StreamHandler()]) logging.info(“开始执行抢票脚本...”)将这些异常处理逻辑编织到主流程中脚本的健壮性会得到极大提升能够应对大多数非致命的意外情况而不是轻易崩溃。5. 实战部署、优化与伦理思考脚本写好了怎么让它真正跑起来并跑得更好同时我们也必须思考使用这类工具的道德边界。5.1 部署与定时执行抢票通常需要在某个精确的时间点如中午12:00开始。我们不可能每次都手动运行脚本。1. 计划任务Windows/LinuxWindows使用“任务计划程序”。创建一个基本任务触发器设置为特定时间操作为“启动程序”指向你的Python解释器python.exe和脚本路径。Linux/Mac使用cron定时任务。编辑crontab文件crontab -e添加一行例如55 11 * * * /usr/bin/python3 /path/to/your/booking_script.py这表示每天11:55分执行脚本提前5分钟启动等待准点抢票。2. 无头模式运行在服务器或后台运行时不需要看到浏览器界面。在初始化浏览器选项时添加--headless参数即可。options.add_argument(‘--headless’) # 无头模式 options.add_argument(‘--no-sandbox’) # Linux服务器常需要 options.add_argument(‘--disable-dev-shm-usage’) # 解决共享内存问题注意在无头模式下一些依赖于视觉或特定渲染的交互可能出问题最好先在图形界面下测试通过。5.2 性能优化与反反爬策略虽然Selenium模拟真人但过于规律和快速的操作仍可能被检测。1. 操作随机化随机延迟在操作之间加入随机等待时间模拟人类反应。import random, time def human_delay(min_s0.5, max_s2.0): time.sleep(random.uniform(min_s, max_s))随机移动轨迹对于关键的点击可以使用ActionChains模拟更自然的鼠标移动而不是直接从A点跳到B点。from selenium.webdriver.common.action_chains import ActionChains button driver.find_element(...) actions ActionChains(driver) actions.move_to_element(button).pause(random.uniform(0.1, 0.5)).click().perform()2. 浏览器指纹伪装进阶一些网站会检测WebDriver特征。Selenium可以通过CDPChrome DevTools Protocol来覆盖一些暴露的变量。options.add_argument(“--disable-blink-featuresAutomationControlled”) driver.execute_cdp_cmd(“Page.addScriptToEvaluateOnNewDocument”, { “source”: “”” Object.defineProperty(navigator, ‘webdriver’, { get: () undefined }); “”” })3. 使用更稳定的定位器优先使用ID、NAME等稳定属性定位。如果元素只有动态生成的类名或复杂结构使用相对稳定的XPath或CSS Selector并尽量避免使用绝对路径。5.3 伦理边界与负责任的使用开发和使用自动化抢票脚本必须建立在合法、合理、公平的框架内。1. 遵守网站规则首要原则是尊重目标网站的服务条款Terms of Service。明确禁止自动化的网站不应使用此类脚本否则可能导致账号被封禁甚至承担法律责任。2. 资源占用与公平性脚本不应进行高频、暴力的请求以免对服务器造成不必要的压力影响其他正常用户的访问。我们的目的是“自动化一次成功的预约”而不是“以量取胜”或进行攻击。3. 个人学习与教育目的本项目以及类似的分享其核心价值在于技术学习与实践——学习Web自动化、图像识别、异常处理等编程技能。它应该被视作一个学习案例而不是一个纯粹的“抢票武器”。在分享时应着重强调其技术实现原理和编程思路。4. 隐私与安全脚本中会涉及个人账号密码。务必妥善保管代码和配置文件切勿上传到公开仓库时包含真实的账号信息。建议使用配置文件或环境变量来管理敏感信息并在.gitignore中忽略这些配置文件。我个人在完成这个项目后最大的体会是技术是一把双刃剑。它能极大地提升效率解决重复劳动但它的使用必须伴随着对规则的敬畏和对他人权益的考量。在编写每一行代码尤其是设计重试逻辑和请求频率时我都在问自己这个操作是否过度是否会影响到系统和其他用户的正常使用作为开发者我们有责任让技术向善。这个脚本最终被我用于帮助实验室几位确实有听讲座需求的同学在大家协商好不重复抢占资源的前提下公平地使用。我想这才是技术工具应有的温度。本文还有配套的精品资源点击获取