ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Python+Selenium实现问卷星自动填写:题型映射与批量提交实战

2026/9/13 18:04:44 拓冰建站 浏览量
Python+Selenium实现问卷星自动填写:题型映射与批量提交实战 简介基于Python实现的问卷星调查问卷自动填写工具定位清晰适配希望学习自动化测试、爬虫或GUI操作的中小白开发者也可直接用于毕业设计、课程设计、工程实训或初期项目立项。资源包共有7个文件核心为2个Python脚本主程序与初始化模块搭配1份Markdown使用说明其余为XML及IML格式的工程配置信息整体压缩包仅9KB轻量易读。目前已有541人学习下载源码量小而结构清晰适合初学者快速剖析自动填写逻辑掌握通过脚本模拟表单提交、控制浏览器完成问卷交互的基本思路。借助说明文档与项目配置读者能够复现自动答题流程并在此基础上扩展问卷星批量处理、选项随机化、验证码识别等进阶功能为后续自动化办公或爬虫学习打下扎实基础。1. 问卷星自动填写工具为什么值得自己用 Python 写一遍问卷星这类在线表单表面上点点选选就能完成但遇到产品调研、毕业设计回收、内部培训签到这类需要重复填写的场景人工操作既慢又容易出错。自己动手写一个基于 Python 实现的问卷星调查问卷自动填写工具核心价值不只是代替手指而是把“题目字段识别、题型数据映射、提交校验、批量执行”这几个环节变成可复用代码。比较有挑战的地方在于问卷页面由 JavaScript 动态渲染直接抓 HTML 经常拿不到完整题目提交接口又带着一次性参数和时间校验如果批量跑还得处理频控和验证码。适合读这篇内容的读者是已经掌握 Python 基础、想系统搞懂网页自动化的开发者和测试工程师。下面从页面结构开始讲再用 Selenium 跑通一条完整链路最后补一条 Requests 接口模拟的进阶路线。2. 先读懂问卷星的题目结构从表单参数到题型映射2.1 问卷页面的提交链路脚本渲染、动态参数与后端接口问卷星不是传统服务端渲染的多页表单而是由 JavaScript 驱动的单页应用。打开问卷链接后浏览器先加载一段页面框架再从接口获取题目 JSON最终在浏览器内把单选、多选、填空渲染成可交互表单。用requests.get(link)直接拿响应拿到的往往是骨架 HTML真正的题目内容隐藏在src指向的 js 文件里。这个特性决定了自动填写工具的第一原则不要尝试用正则把整张问卷从 HTML 里抠出来而是应该借助浏览器环境或网络抓包先观察真实提交时浏览器发出去的数据。打开 Chrome 开发者工具勾选 Network 面板里的 Preserve log然后手动填一次问卷并提交。可以看到浏览器向接口发出一条 POST 请求请求体里带着q1234562这类参数同时还有curID、submittype、tnum、jqnonce等冗长字段。curID是问卷唯一标识tnum与答题耗时相关jqnonce是一次性随机数。Selenium 自动化方案不需要关心这些动态参数因为浏览器会自动生成它们只有做接口模拟时才需要逐项提取。2.2 题型与提交参数值的对应关系后端接收的不是点击事件而是一组结构化的 key/value。单选题的name是题目 IDvalue是选项索引多选题用竖线把多个选项值拼接成一个字符串单选题和量表题在页面上的表现几乎一样区别只在于value的取值范围。下面是我自己整理过的一份对应关系表照着它可以在大多数问卷里完成数据映射题型页面特征提交参数示例说明单选题input typeradio nameq123456q1234562选项值从 1 开始编号多选题input typecheckbox nameq123456q12345613下拉题select nameq123456q1234564选项顺序和 HTML 保持一致填空题input typetext nameq123456q123456你好中文在请求体里会做 URL 编码量表题input typeradio nameq123456q1234565常见分值范围是 1-5 或 1-7矩阵量表input nameq123456_0q123456_03行序号会拼在题目 ID 后表里的q123456是题目 ID数字部分在每份问卷里都不同。定位时不要假设固定编号要在页面源码中搜索nameq来确认终端 key。2.3 用浏览器控制台快速列出当前问卷的全部字段名只要能把当前页面上的字段名列全后面写 Selenium 定位就省掉一半时间。打开问卷页按 F12 进入控制台执行下面这段 JavaScriptconst inputs document.querySelectorAll([name^q], [name^submit]); const names [...new Set([...inputs].map(i i.name))]; console.log(JSON.stringify(names, null, 2));这段代码用属性选择器匹配name以q开头的所有输入框再配合Set去重得到的数组就是当前问卷所有题目字段的清单。对于多选题和矩阵量表字段名可能形如q123[]或q123_0同样会出现在结果里。拿到这份清单后再回到页面上对照每个题目对应的选项就能拼出一份完整的答案映射表。这个动作换成 Python 也可以做但控制台里执行最快也是我在每次写新问卷配置前都会做的一步。2.4 三个小检查判断定位信息是否可用字段名列出来后不要急着写完整代码先做三个小检查。第一检查题目的可见状态。问卷星的某些题目配置了“跳题逻辑”当前用户没有选择前置条件时后续题目不会渲染到 DOM 里。如果脚本定位不到元素不一定是 CSS 选择器写错要先确认题目是否处于可见状态。第二检查多个同名元素。矩阵量表里每一行都会生成多个相同name的 radio这时候要用find_elements按索引取不能再用find_element。第三检查外层包裹。问卷星的选项经常包在label和div里直接.click()可能提示“element not interactable”。遇到这种报错优先改用 JavaScript 点击后面章节会给出代码。3. 用 Python Selenium 实现问卷星自动填写的核心代码3.1 环境准备Python 安装、vscode 配置与驱动管理很多人在入门网页自动化时不是被业务逻辑难住而是被环境配置卡住。请先保证本机 Python 在 3.8 以上建议直接用 3.10 或 3.11。vscode Python 环境配置上重点确认解释器路径已经指向当前项目使用的 Python而不是系统里另一个版本。下方命令安装 Selenium 库pip install seleniumSelenium 版本建议装 4.6 以上因为从 4.6 开始内置 Selenium Manager首次启动浏览器时自动下载匹配的 chromedriver不需要再手动找驱动包。下面这段代码用来自检环境from selenium import webdriver from selenium.webdriver.chrome.options import Options options Options() driver webdriver.Chrome(optionsoptions) driver.get(https://www.wjx.cn) print(driver.title) driver.quit()如果能看到我的问卷或问卷网首页标题说明 Python 与浏览器驱动已经打通。常见报错是SessionNotCreatedException这多半是浏览器和 chromedriver 版本不匹配在 Selenium 4.6 以下版本尤其容易出现。建议直接升级 Selenium而不是手动下载旧版驱动。3.2 核心填写函数单选题、多选题、填空题的统一入口回答字段定位的问题我习惯把每一类题型封装成一个独立函数再用一个配置 dict 驱动整个流程。下面是一个最小可运行版本import random import time from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC answer_map { q001: 2, # 单选题选第 2 项 q002: [1, 3], # 多选题选第 1 项和第 3 项 q003: Python, # 填空题 q004: 5, # 量表题选第 5 档 } def click_option(driver, question_id, option_value): # 用 CSS 选择器精确定位特定题目的特定选项 radio driver.find_element( By.CSS_SELECTOR, finput[name{question_id}][value{option_value}] ) driver.execute_script(arguments[0].click();, radio) def click_multi(driver, question_id, option_values): for value in option_values: box driver.find_element( By.CSS_SELECTOR, finput[name{question_id}][value{value}] ) if not box.is_selected(): driver.execute_script(arguments[0].click();, box) def fill_text(driver, question_id, text): # 同时兼容 input 和 textarea 两种填空题 elem driver.find_element( By.CSS_SELECTOR, finput[name{question_id}], textarea[name{question_id}] ) elem.clear() elem.send_keys(text) def fill_survey(url, answers): driver webdriver.Chrome() driver.get(url) try: WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.CSS_SELECTOR, input[name^q])) ) for qid, ans in answers.items(): if isinstance(ans, list): click_multi(driver, qid, ans) elif isinstance(ans, int): click_option(driver, qid, ans) else: fill_text(driver, qid, ans) time.sleep(random.uniform(2, 4)) finally: driver.quit()代码逻辑分成两层外层fill_survey负责打开页面、等待题目渲染、按值类型分派处理内层三个函数处理具体交互。click_option使用input[nameq001][value2]的 CSS 选择器同时限定题目 ID 和选项值避免选错题目。click_multi先用is_selected()判断选中状态防止重复点击同一个选项导致取消。fill_text的定位串里用了逗号意思是“匹配input或textarea”这样填空题无论控件类型都能覆盖。所有点击都通过execute_script完成这是问卷星页面上最省事的做法。因为很多选项外层有label和自定义样式普通.click()会被遮挡层拦截JavaScript 点击直接触发元素事件更接近真实操作的成功率。3.3 把答案抽成 JSON 配置让代码与问卷数据解耦写死在函数里的answer_map只适合单份问卷。实际做项目时我一般会把 URL 和答案拆到 JSON 文件里实现一份代码跑多份问卷。{ url: https://www.wjx.cn/vm/xxxxx.aspx, answers: { q001: 2, q002: [1, 3], q003: Python }, wait_seconds: 3 }Python 侧读取配置import json def load_config(path): with open(path, r, encodingutf-8) as f: return json.load(f) if __name__ __main__: cfg load_config(config.json) fill_survey(cfg[url], cfg[answers])把url和answers拆开有个好处当题目顺序调整、选项编号变化时只改 JSON 文件即可不需要重新调整定位函数。配置里的wait_seconds可以在之后扩展到流程中用来控制页面加载后的等待时间。这里还顺便解决了另一个问题如果从外部数据源拿到答案只需把 CSV 或 Excel 内容转成同样的 dict 结构就能直接接入已有流程。4. 问卷星的校验与反自动化参数、节奏和浏览器特征4.1 三个高频拦截点计时校验、频次控制、智能验证自动填写工具跑不通时多数情况不是定位写错而是被问卷星的风控逻辑拦住。从实际反馈看第一是计时校验问卷会记录打开页面到提交答案的间隔几秒内提交会被判定为异常第二是频次控制同一 IP 或浏览器环境短时间连续提交多份会提示“访问频繁”第三是智能验证部分问卷提交前会弹出滑块或点选验证码。这三类问题在小批量回收场景下可以靠调整节奏解决验证码则需要人工介入或接入专门的识别服务。下面是我常用的处理方式触发原因页面表现建议处理方式提交过快提示“回答时间过短”填完后等待 15 到 25 秒频次限制提示“IP 访问过于频繁”两次填写之间至少间隔 60 秒元素不可点击定位时报错滚动到元素可见区域后再点击浏览器被识别页面显示不正常使用--disable-blink-featuresAutomationControlled这里的核心思路不是对抗而是让程序的行为曲线更接近真人。真实用户不会在 5 秒内完成一份 20 道题的问卷也不会每天都用同一个浏览器环境提交几十次问卷。4.2 给 Selenium 注入隐藏参数降低浏览器特征暴露Selenium 打开的 Chrome 会暴露navigator.webdriver属性部分前端脚本会读取这个字段判断是否为自动化工具。有一个低成本的处理方式from selenium import webdriver from selenium.webdriver.chrome.options import Options def create_driver(): options Options() options.add_argument(--disable-blink-featuresAutomationControlled) options.add_experimental_option(excludeSwitches, [enable-automation]) options.add_experimental_option(useAutomationExtension, False) # 调试时不要开 headless便于观察页面真实反馈 # options.add_argument(--headlessnew) driver webdriver.Chrome(optionsoptions) driver.execute_cdp_cmd(Page.addScriptToEvaluateOnNewDocument, { source: Object.defineProperty(navigator, webdriver, {get: () undefined}) }) return driver--disable-blink-featuresAutomationControlled关闭 Chrome 的自动化控制标记excludeSwitches去掉浏览器右上角“Chrome 正在受到自动测试软件控制”的提示条execute_cdp_cmd在页面加载之前执行一段脚本把navigator.webdriver的 getter 覆盖为返回undefined。这套组合能应对一部分前端检测但不代表能通过企业级风控。遇到强校验问卷更可行的做法是保留人工验证码环节让程序只完成前面的题目填充。4.3 高吞吐路线用 Requests 模拟问卷星提交接口Selenium 方案的好处是接近真实浏览器缺点是慢。如果有多轮测试问卷要提交且不需要完整走页面渲染可以退到底层接口用requests.Session()直接构造提交。先通过页面 HTML 获取题目字段和隐藏参数再向提交接口发送 POST。下面是一个不依赖具体接口地址的骨架import requests from bs4 import BeautifulSoup def extract_hidden_fields(html): soup BeautifulSoup(html, html.parser) fields {} for inp in soup.select(input[typehidden]): name inp.get(name) if name: fields[name] inp.get(value, ) return fields def submit_survey(session, post_url, data): resp session.post(post_url, datadata) result resp.json() if result.get(errcode) 0: print(提交成功) else: print(提交失败:, result)extract_hidden_fields用于收集页面上的隐藏字段这些字段通常包含问卷标识和一次性 token。submit_survey只是一个统一提交入口真实场景里还需要把题目答案的q字段拼进data。接口地址和参数结构会随问卷版本变化因此这段代码只能作为思路参考不能直接照搬。Requests 方案需要维护的参数更多但配合随机 User-Agent 和合理的请求间隔在某些授权测试场景下能获得比 Selenium 更高的吞吐量。4.4 随机延时的参数设计均匀分布与正态分布的选择延时不是越长越好而是越接近真实越好。真实用户填问卷的时长一般围绕某个平均值波动少数人极快或极慢。如果只用random.uniform(2, 4)出来的分布是平坦的反复跑几份后特征就会很明显。更好的做法是使用正态分布import random import time # mu 是平均延时sigma 是波动范围 delay random.gauss(18, 4) delay max(8, min(delay, 40)) time.sleep(delay)gauss生成以 18 秒为中心、标准差 4 秒的正态分布随机数。max和min把结果限制在 8 到 40 秒之间防止偶发的小概率值让程序等待过久。需要说明的是这个参数只在页面填充完成后调用题目与题目之间的短停顿可以继续用均匀分布。两者搭配的效果会更接近真实操作整体速度快慢有重心局部操作又有随机性。5. 小规模批量跑问卷时的可靠性技巧5.1 用 CSV 管理答案一个文件跑一批数据当问卷数量从 1 份变成 50 份时每份问卷都放一个 JSON 配置并不划算。常见做法是维护一个 CSV每一行代表一份问卷答案列名直接使用题目 ID。url,q001,q002,q003 https://www.wjx.cn/vm/xxxx.aspx,2,[1,3],Python https://www.wjx.cn/vm/yyyy.aspx,4,2,自动化测试读取这段 CSV 并逐行调用fill_surveyimport csv with open(answers.csv, r, encodingutf-8-sig) as f: reader csv.DictReader(f) for row in reader: url row[url] answers { q001: int(row[q001]), q002: [int(x) for x in row[q002].strip([]).split(,)], q003: row[q003], } fill_survey(url, answers)这里用utf-8-sig编码读取能自动处理 Excel 导出的 BOM 头。q002这种多选题字段要在 CSV 里存成字符串读取后再拆成整数列表。单份问卷执行失败时脚本会中断因此批量场景需要把fill_survey包在 try-except 里并在 except 中记录当前行号和数据。5.2 失败重试与结果校验提交后如何判断真的成功Selenium 方案最容易被忽略的一步是提交后的结果确认。只看按钮点点点还不够要检查页面是否跳转到“提交成功”页。一个轻量校验方式是记录提交前的窗口句柄提交后等待 URL 变化或者查找页面中“感谢”“提交成功”关键词。from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC def wait_submit_success(driver, timeout10): try: WebDriverWait(driver, timeout).until( EC.url_contains(finish) ) except Exception: driver.save_screenshot(ffail_{int(time.time())}.png) return False return TrueEC.url_contains(finish)是相对通用的判断方式问卷星提交成功后的跳转地址里通常带有finish或相同语义的路径片段。如果等待超时就截图保存现场便于后续排查是验证码拦截还是选项未被正确选中。截图文件名加上时间戳避免多次运行互相覆盖。重试策略可以做成简单的三次循环每次重试前把浏览器环境重启一遍而不是在同一页面里反复点击提交这样能排除一部分状态残留问题。本文还有配套的精品资源点击获取