Python自动化实战从零构建问卷星抢座脚本最近身边不少朋友都在抱怨一些热门讲座的报名链接刚放出来就秒没手动刷新页面根本来不及。这种重复性的点击操作其实正是自动化脚本大显身手的地方。今天我想分享一个用Python和Selenium实现的自动化方案即使你之前没怎么接触过编程跟着步骤走也能搭建起来。这不是什么高深的技术更像是一个有趣的工具制作过程能帮你把时间花在更有价值的事情上。这个方案的核心思路很简单让程序模拟人类在浏览器里的操作——打开网页、找到输入框、填写信息、点击提交。整个过程清晰可控而且代码写一次就能反复使用。下面我会从环境搭建开始一步步拆解每个环节并提供完整的代码和避坑指南。1. 环境准备与基础工具安装在开始写代码之前我们需要先把“舞台”搭好。这就像你要做木工得先准备好锯子、锤子和木料。对于我们的自动化脚本来说主要的工具就是Python编程语言和一个叫Selenium的库。1.1 Python安装与验证如果你电脑上还没有Python需要先去官网下载安装。这里有个小建议安装时务必勾选“Add Python to PATH”这个选项这能避免后续在命令行里调用Python时出现“找不到命令”的麻烦。安装完成后可以打开命令行工具Windows上是CMD或PowerShellMac上是终端验证一下。输入下面的命令并回车python --version或者python3 --version如果能看到类似Python 3.8.5这样的版本信息说明安装成功了。我个人的习惯是使用Python 3.7以上的版本兼容性更好一些。注意有些系统可能同时安装了Python 2和Python 3这时候python命令可能指向旧版本。如果你看到的是Python 2.x的版本号可以尝试用python3命令。为了统一后文我们都用python3来指代。1.2 安装必要的Python库Python本身功能有限我们需要安装一些扩展库来增强它的能力。最主要的就是Selenium它是实现浏览器自动化的核心工具。在命令行里输入以下命令来安装pip install selenium如果你用的是Mac或Linux有时候需要加上sudo前缀sudo pip install selenium或者使用pip3 install selenium。安装过程可能会持续一两分钟取决于你的网络速度。除了Selenium我还建议安装一个辅助库用于更优雅地处理等待和超时pip install webdriver-manager这个webdriver-manager能自动管理不同浏览器的驱动程序版本省去手动下载和配置的麻烦。1.3 浏览器与驱动配置Selenium需要通过与浏览器驱动程序通信来控制浏览器。不同的浏览器需要不同的驱动浏览器驱动程序获取方式ChromeChromeDriver自动推荐或手动下载Firefoxgeckodriver自动推荐或手动下载EdgeMicrosoft Edge Driver自动推荐或手动下载推荐使用自动管理方式这也是为什么我们安装了webdriver-manager。在代码中初始化时它会自动检测你的浏览器版本并下载匹配的驱动完全不用手动干预。如果你坚持要手动配置需要查看浏览器版本在Chrome中点击右上角三个点 → 帮助 → 关于Google Chrome到对应的驱动下载网站下载相同版本的驱动将驱动文件放在系统PATH包含的目录中或者直接在代码里指定驱动路径手动方式的维护成本较高每次浏览器更新都可能需要重新下载驱动所以我强烈推荐自动管理方案。2. Selenium基础操作解析很多人一听到“自动化”、“爬虫”就觉得复杂其实Selenium的基本操作非常直观。它提供的API几乎就是把你手动操作浏览器的动作翻译成了代码指令。2.1 启动与配置浏览器实例让我们从最基础的开始——打开浏览器。使用webdriver-manager的方式非常简洁from selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager # 自动管理ChromeDriver service Service(ChromeDriverManager().install()) driver webdriver.Chrome(serviceservice) # 最大化窗口可选但建议 driver.maximize_window() # 设置隐式等待时间单位秒 driver.implicitly_wait(10)这段代码做了三件事自动下载并配置合适版本的ChromeDriver启动Chrome浏览器设置浏览器窗口最大化和等待策略implicitly_wait(10)这行很重要它告诉Selenium如果在页面上找不到某个元素不要立即报错先等待最多10秒钟。这能有效应对网络延迟或页面加载慢的情况。2.2 页面导航与元素定位打开浏览器后第一件事就是访问目标网页# 访问问卷星页面 driver.get(https://www.wjx.cn/vm/hash12345.aspx)这里的URL需要替换成你实际要访问的问卷链接。获取链接后就可以开始与页面元素交互了。Selenium提供了多种定位元素的方式每种适合不同的场景ID定位最精确的方式如果元素有id属性driver.find_element(id, username)Name定位常用于表单元素driver.find_element(name, email)XPath定位最灵活可以定位任何元素driver.find_element(xpath, //input[typetext])CSS选择器定位性能较好语法简洁driver.find_element(css selector, .form-control)链接文本定位专门用于链接driver.find_element(link text, 点击这里)在实际项目中我通常按这个优先级选择定位方式ID Name CSS选择器 XPath。ID和Name最稳定但并不是所有元素都有这些属性。CSS选择器性能好XPath功能最强但速度稍慢。2.3 表单操作与交互找到元素后就可以模拟用户操作了。对于问卷填写主要涉及以下几种操作文本输入是最基础的操作# 定位到姓名输入框并输入 name_input driver.find_element(id, q1) name_input.clear() # 先清空可能存在的默认值 name_input.send_keys(张三)单选按钮和复选框的处理略有不同# 单选按钮通常是一组中选一个 radio_button driver.find_element(xpath, //input[valueoption1]) radio_button.click() # 复选框可以多选 checkbox driver.find_element(id, agree) if not checkbox.is_selected(): # 如果还没选中 checkbox.click()下拉选择框需要特殊处理from selenium.webdriver.support.ui import Select # 定位下拉框 grade_select Select(driver.find_element(name, grade)) # 有三种选择方式 grade_select.select_by_value(3) # 通过value属性 grade_select.select_by_index(1) # 通过索引从0开始 grade_select.select_by_visible_text(大三) # 通过显示的文本提交按钮的点击submit_button driver.find_element(css selector, button[typesubmit]) submit_button.click()有时候页面提交后需要等待跳转可以添加一个显式等待from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC # 等待直到新页面加载完成通过页面标题判断 WebDriverWait(driver, 10).until( EC.title_contains(提交成功) )3. 完整抢座脚本的实现了解了基础操作后我们来组装一个完整的脚本。这个脚本会处理从打开浏览器到成功提交的完整流程并且考虑了一些实际使用中可能遇到的问题。3.1 脚本架构设计一个好的脚本应该有清晰的结构方便维护和修改。我通常会把功能模块化import time from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait, Select from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager class QuestionnaireAutoFiller: def __init__(self, url): 初始化浏览器驱动 self.service Service(ChromeDriverManager().install()) self.driver webdriver.Chrome(serviceself.service) self.driver.maximize_window() self.driver.implicitly_wait(10) self.url url self.wait WebDriverWait(self.driver, 15) def open_page(self): 打开问卷页面 print(正在打开问卷页面...) self.driver.get(self.url) # 等待页面主要元素加载 self.wait.until( EC.presence_of_element_located((By.TAG_NAME, form)) ) print(页面加载完成) def fill_basic_info(self, name, student_id, email): 填写基本信息部分 print(f正在填写信息{name}) # 填写姓名 name_field self.wait.until( EC.element_to_be_clickable((By.NAME, name)) ) name_field.clear() name_field.send_keys(name) # 填写学号 id_field self.driver.find_element(By.NAME, studentId) id_field.send_keys(student_id) # 填写邮箱 email_field self.driver.find_element(By.NAME, email) email_field.send_keys(email) def select_options(self): 选择问卷中的单选和多选题 # 示例选择第一个单选按钮 first_radio self.driver.find_element( By.CSS_SELECTOR, input[typeradio][value1] ) first_radio.click() # 示例选择前两个复选框 checkboxes self.driver.find_elements( By.CSS_SELECTOR, input[typecheckbox] ) for i in range(min(2, len(checkboxes))): if not checkboxes[i].is_selected(): checkboxes[i].click() def submit_form(self): 提交表单并确认 # 找到提交按钮 submit_btn self.wait.until( EC.element_to_be_clickable( (By.CSS_SELECTOR, button.submit-btn) ) ) submit_btn.click() # 处理可能的确认弹窗 try: alert WebDriverWait(self.driver, 3).until( EC.alert_is_present() ) alert.accept() # 点击确认 print(已处理确认弹窗) except: pass # 没有弹窗就继续 print(表单提交完成) def run(self, user_info): 主运行流程 try: self.open_page() self.fill_basic_info(**user_info) self.select_options() self.submit_form() # 等待提交成功页面 time.sleep(3) print(操作流程结束) except Exception as e: print(f执行过程中出错{e}) # 这里可以添加截图功能便于调试 self.driver.save_screenshot(error_screenshot.png) finally: # 稍作停留后关闭浏览器 time.sleep(5) self.driver.quit() # 使用示例 if __name__ __main__: # 配置用户信息 user_data { name: 李四, student_id: 20230001, email: lisiexample.com } # 问卷链接需要替换为实际链接 questionnaire_url https://www.wjx.cn/vm/example.aspx # 创建并运行自动填写器 auto_filler QuestionnaireAutoFiller(questionnaire_url) auto_filler.run(user_data)这个类结构的好处是逻辑清晰每个方法只负责一个具体功能。如果需要修改填写逻辑只需要改动对应的方法不会影响其他部分。3.2 关键问题与解决方案在实际使用中你可能会遇到一些棘手的问题。这里我总结了几种常见情况及应对策略1. 页面元素加载太慢或结构动态变化这是最常遇到的问题。除了使用implicitly_wait更推荐使用显式等待Explicit Waitfrom selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC # 等待特定元素出现 element WebDriverWait(driver, 20).until( EC.presence_of_element_located((By.ID, dynamic-element)) ) # 等待元素可点击 clickable_element WebDriverWait(driver, 20).until( EC.element_to_be_clickable((By.NAME, submit)) ) # 等待元素可见 visible_element WebDriverWait(driver, 20).until( EC.visibility_of_element_located((By.CLASS_NAME, result)) )2. 验证码或人机验证如果页面有验证码纯Selenium方案就不够了。这时候可以考虑如果是简单的图片验证码可以集成OCR库如pytesseract如果是复杂验证码可能需要人工干预或使用专业打码平台对于极验、滑动验证等有专门的破解方案但比较复杂提示对于学习目的建议先从没有验证码的页面开始练习。实际应用中要确保你的自动化操作符合网站的使用条款。3. 浏览器被检测为自动化工具有些网站会检测Selenium的自动化特征。可以通过添加一些选项来“隐藏”自动化特征from selenium.webdriver.chrome.options import Options chrome_options Options() chrome_options.add_argument(--disable-blink-featuresAutomationControlled) chrome_options.add_experimental_option(excludeSwitches, [enable-automation]) chrome_options.add_experimental_option(useAutomationExtension, False) # 添加这些选项到driver driver webdriver.Chrome( serviceservice, optionschrome_options ) # 执行CDP命令移除navigator.webdriver标志 driver.execute_cdp_cmd(Page.addScriptToEvaluateOnNewDocument, { source: Object.defineProperty(navigator, webdriver, { get: () undefined }) })4. 处理iframe内嵌框架如果表单在iframe里需要先切换到对应的frame# 通过id或name切换 driver.switch_to.frame(frame-id) # 通过索引切换从0开始 driver.switch_to.frame(0) # 通过定位到的元素切换 frame_element driver.find_element(By.TAG_NAME, iframe) driver.switch_to.frame(frame_element) # 操作iframe内的元素 # ... 填写表单操作 ... # 操作完成后切回主文档 driver.switch_to.default_content()5. 文件上传处理如果问卷需要上传文件Selenium也可以处理# 定位文件上传input元素 file_input driver.find_element(By.CSS_SELECTOR, input[typefile]) # 直接发送文件路径不要使用click() file_input.send_keys(/path/to/your/file.pdf)注意这种方式只适用于input typefile元素对于其他类型的文件上传控件可能需要不同的处理方式。4. 高级技巧与优化建议当你掌握了基础操作后可以进一步优化脚本让它更健壮、更高效。4.1 多账号批量处理如果需要用多个账号抢座可以这样组织代码import json from concurrent.futures import ThreadPoolExecutor def load_user_profiles(filename): 从JSON文件加载用户配置 with open(filename, r, encodingutf-8) as f: return json.load(f) def process_single_user(user_info, questionnaire_url): 处理单个用户的填写任务 filler QuestionnaireAutoFiller(questionnaire_url) try: filler.run(user_info) return f{user_info[name]}: 成功 except Exception as e: return f{user_info[name]}: 失败 - {str(e)} def batch_processing(user_profiles, questionnaire_url, max_workers3): 批量处理多个用户 results [] # 使用线程池控制并发数 with ThreadPoolExecutor(max_workersmax_workers) as executor: futures [] for user in user_profiles: future executor.submit( process_single_user, user, questionnaire_url ) futures.append(future) # 收集结果 for future in futures: results.append(future.result()) return results # 使用示例 if __name__ __main__: # 加载用户配置 users load_user_profiles(users.json) # 批量处理 questionnaire_url https://www.wjx.cn/vm/example.aspx results batch_processing(users, questionnaire_url, max_workers2) # 输出结果 for result in results: print(result)对应的users.json文件格式[ { name: 张三, student_id: 20230001, email: zhangsanexample.com }, { name: 李四, student_id: 20230002, email: lisiexample.com } ]4.2 定时执行与监控抢座往往需要在特定时间点执行这时候就需要定时功能import schedule import time from datetime import datetime def job(): 定时执行的任务 print(f{datetime.now()}: 开始执行抢座任务) # 这里放置你的抢座代码 user_data { name: 王五, student_id: 20230003, email: wangwuexample.com } questionnaire_url https://www.wjx.cn/vm/example.aspx auto_filler QuestionnaireAutoFiller(questionnaire_url) auto_filler.run(user_data) print(f{datetime.now()}: 任务执行完成) # 设置定时规则 # 每天10:30执行 schedule.every().day.at(10:30).do(job) # 每隔5分钟执行一次 schedule.every(5).minutes.do(job) # 每周一10:00执行 schedule.every().monday.at(10:00).do(job) print(定时任务已启动等待执行...) while True: schedule.run_pending() time.sleep(1) # 每秒检查一次这个定时脚本可以一直运行在后台到指定时间自动执行抢座任务。如果你不想一直开着命令行窗口可以考虑将脚本部署到云服务器上。4.3 错误处理与日志记录健壮的脚本需要有完善的错误处理和日志记录import logging from logging.handlers import RotatingFileHandler def setup_logging(): 配置日志系统 logger logging.getLogger(__name__) logger.setLevel(logging.INFO) # 控制台输出 console_handler logging.StreamHandler() console_handler.setLevel(logging.INFO) # 文件输出自动轮转每个文件最大1MB保留3个备份 file_handler RotatingFileHandler( questionnaire_auto.log, maxBytes1024*1024, # 1MB backupCount3, encodingutf-8 ) file_handler.setLevel(logging.DEBUG) # 设置日志格式 formatter logging.Formatter( %(asctime)s - %(name)s - %(levelname)s - %(message)s ) console_handler.setFormatter(formatter) file_handler.setFormatter(formatter) logger.addHandler(console_handler) logger.addHandler(file_handler) return logger # 在脚本中使用 logger setup_logging() try: # 业务代码 logger.info(开始执行问卷填写任务) # ... 填写操作 ... logger.info(任务执行成功) except Exception as e: logger.error(f任务执行失败: {e}, exc_infoTrue) # exc_infoTrue会记录完整的异常堆栈4.4 性能优化技巧当需要处理大量任务时性能优化就很重要了1. 减少不必要的等待# 不推荐固定等待 time.sleep(5) # 推荐智能等待 from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC # 只等待必要的元素出现 element WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.ID, target)) )2. 使用无头模式不显示浏览器界面from selenium.webdriver.chrome.options import Options chrome_options Options() chrome_options.add_argument(--headless) # 无头模式 chrome_options.add_argument(--no-sandbox) chrome_options.add_argument(--disable-dev-shm-usage) driver webdriver.Chrome(optionschrome_options)无头模式可以节省系统资源特别适合在服务器上运行。但调试时建议使用普通模式方便查看页面状态。3. 复用浏览器会话对于需要多次操作的情况可以考虑复用浏览器实例而不是每次操作都重新打开关闭class BrowserManager: def __init__(self): self.driver None def get_driver(self): if self.driver is None: service Service(ChromeDriverManager().install()) self.driver webdriver.Chrome(serviceservice) self.driver.implicitly_wait(10) return self.driver def close(self): if self.driver: self.driver.quit() self.driver None # 使用示例 browser_manager BrowserManager() # 第一次使用 driver1 browser_manager.get_driver() driver1.get(https://example.com) # 第二次使用复用同一个浏览器实例 driver2 browser_manager.get_driver() driver2.get(https://another-site.com) # 所有操作完成后 browser_manager.close()4. 并行处理优化当有多个独立任务时可以使用多线程或异步IO提高效率import asyncio from selenium_driverless import webdriver from selenium_driverless.types.by import By async def main(): options webdriver.ChromeOptions() async with webdriver.Chrome(optionsoptions) as driver: await driver.get(https://example.com) # 异步操作... asyncio.run(main())注意Selenium的传统API是同步的如果需要真正的异步支持可以考虑使用selenium-driverless或playwright等替代方案。4.5 实际部署考虑当你需要长期稳定运行脚本时还需要考虑一些部署相关的问题配置管理不要把敏感信息如账号密码硬编码在脚本里import os from dotenv import load_dotenv # 从.env文件加载配置 load_dotenv() USERNAME os.getenv(QUESTIONNAIRE_USERNAME) PASSWORD os.getenv(QUESTIONNAIRE_PASSWORD) URL os.getenv(QUESTIONNAIRE_URL).env文件内容QUESTIONNAIRE_USERNAMEyour_username QUESTIONNAIRE_PASSWORDyour_password QUESTIONNAIRE_URLhttps://www.wjx.cn/vm/example.aspx异常恢复机制脚本应该能够从某些异常中恢复def robust_operation(max_retries3): for attempt in range(max_retries): try: # 尝试执行操作 perform_operation() break # 成功则跳出循环 except TemporaryError as e: # 临时性错误 if attempt max_retries - 1: wait_time 2 ** attempt # 指数退避 print(f操作失败{wait_time}秒后重试...) time.sleep(wait_time) else: print(重试次数用尽操作失败) raise except PermanentError as e: # 永久性错误 print(遇到不可恢复错误) raise监控与报警长时间运行的脚本需要有监控import psutil import smtplib from email.mime.text import MIMEText def check_system_resources(): 检查系统资源使用情况 cpu_percent psutil.cpu_percent(interval1) memory psutil.virtual_memory() if cpu_percent 80: send_alert(CPU使用率过高, f当前CPU使用率: {cpu_percent}%) if memory.percent 80: send_alert(内存使用率过高, f当前内存使用率: {memory.percent}%) def send_alert(subject, message): 发送报警邮件 msg MIMEText(message) msg[Subject] f脚本监控报警: {subject} msg[From] monitorexample.com msg[To] adminexample.com # 这里需要配置SMTP服务器 # with smtplib.SMTP(smtp.example.com) as server: # server.send_message(msg) print(f报警: {subject} - {message})这些优化措施能让你的脚本从“能用”升级到“好用”特别是在需要长时间稳定运行的场景下。当然具体采用哪些优化还要根据你的实际需求和运行环境来决定。最后我想说的是自动化脚本虽然方便但使用时一定要遵守网站的使用条款不要对服务器造成过大压力。技术应该用来提高效率而不是制造麻烦。在实际项目中我通常会设置合理的延迟和重试机制确保脚本的行为更像一个真实的用户而不是一个攻击性的机器人。