问卷星自动填写脚本:Selenium浏览器自动化实现与防封策略
简介面向需要批量、高效填写问卷星等在线表单的用户这份资源提供了一套基于 Tampermonkey 的 JavaScript 自动填写脚本将重复、冗长的手工信息录入转变为自动化操作显著降低耗时与出错率。资源包共4个文件以核心用户脚本为主同时包含 HTML 演示页、编辑器配置文件.inscode和 Git 忽略规则压缩包仅9KB结构精简便于直接查看、调试与二次开发。脚本内置可自定义的个人信息数组覆盖姓名、电话、邮箱等常用字段并通过对页面输入框选择器的匹配实现自动填充实际测试结果验证了填充准确度也提示了自动化操作可能触发的平台安全限制建议在合规场景下使用。借助这一实例开发者能够系统理解 Tampermonkey 脚本的注入原理、DOM 元素定位与操作方式、表单自动化的完整设计思路还能利用随包配置文件快速搭建本地调试环境并参照测试说明自行验证和扩展字段。该源代码完整开放适合具备基础 JavaScript 知识、希望提升表单填写效率的开发者学习参考目前已有278人学习说明其具备一定的实践参考价值。1. 问卷星自动填写脚本重复点击交给代码而不是绕过规则问卷星自动填写脚本这个标题单看容易误会成“偷懒刷问卷”的旁门左道。可我接触到的真实需求大多是某实验室要收集 200 份带逻辑跳转的体验问卷人工点选要一整天某公司运营要在新版问卷上线前反复提交 20 多遍做逻辑自测每次选项组合还不能一样。两类需求的核心都是重复操作而不是绕过规则——让代码模拟打开问卷、逐题作答、点击提交的完整行为把录入和验证的人力省掉。这套方案基于浏览器自动化不碰后端接口也不做破解行为路径和真人操作一致。适合有一定 Python 基础、要批量录入问卷或反复验证问卷逻辑的开发者和测试人员。下面从选型讲起一路写到能直接改着用的代码顺带把容易踩的坑一并说清。2. 选型先立住为什么自动填写问卷要用浏览器自动化2.1 接口方案有三个硬伤动态签名、维护成本、容易被识别如果你做过网站签到脚本第一反应很可能是抓接口——找到提交问卷的 POST 请求直接复制参数用 Python 脚本发出去。这个思路放在问卷星上走不通。问卷星的提交接口带一组动态生成的签名参数字段值跟时间戳、随机数绑定页面每次刷新都会更新。抓包只能看到参数结果很难反推出生成规则。逆向这套签名的成本很高前端代码是压缩混淆过的关键逻辑拆在多个文件里就算定位到核心函数复现算法也要花上数天。更麻烦的是前端只要升级一次签名规则就可能变化脚本立刻作废。相比之下浏览器自动化不碰业务逻辑——它复现的是用户点击页面结构变了更新选择器就行不需要理解背后的加密算法。接口方案还有一个识别风险短时间高频 POST 请求服务端很容易从速率、请求头指纹上判断异常流量。浏览器自动化则可以把点击节奏做到接近真人触发风控的概率大大降低。这不是说浏览器自动化万能而是它把“怎么提交”这个黑匣子交给了浏览器本身开发者只关心页面元素和操作顺序。对比维度抓接口自动提交浏览器自动化提交参数依赖签名算法逆向成本高不关心参数真实点击触发前端升级影响签名规则变动脚本作废只需更新元素选择器请求特征高频 POST易被识别可控节奏接近真人操作2.2 Selenium 与 Puppeteer 怎么选我推荐 Selenium Python 的理由浏览器自动化框架里常见的是 Selenium 和 Puppeteer 两条路线。Selenium 支持的语言更全Python、Java、C# 都能写Puppeteer 是 Node.js 生态主要面向 Chrome 和 Chromium。问卷星自动填写这类场景脚本旁边往往还连着数据处理读题目列表、算选项、写日志、导出统计。Python 生态把这些环节串在一起很顺手所以我一般选 Selenium Python。Puppeteer 对截图和流控的支持其实更强但要求 Node.js 环境团队如果没有前端背景光环境搭建就会卡一道坎。还有一个容易忽略的差异Selenium 的 WebDriver 协议支持更多浏览器包括 FirefoxPuppeteer 基本只跟 Chromium 系走。考虑到问卷回收通常要看后台数据浏览器多样性不是刚需但作为容错维度Selenium 更稳。真正让 Selenium 胜出的点在于生态里的辅助库。webdriver-manager 自动管理 chromedriver 版本不需要手忙脚乱地下载和 Chrome 版本匹配的驱动配合 WebDriverWait 和 expected_conditions处理异步加载的题目也足够方便。这套组合在 Python 社区里的中文资料最多遇到问题搜一下就能找到对应的示例代码学习成本明显更低。2.3 动手写脚本前的准备装环境、用 DevTools 拆页面骨架# 创建虚拟环境并安装依赖 python -m venv wjx_env source wjx_env/bin/activate # Windows 下是 wjx_env\Scripts\activate pip install selenium webdriver-manager这段命令的逻辑很清楚venv 创建独立 Python 环境避免依赖污染全局环境webdriver-manager 负责自动下载和匹配 chromedriver省去手动确认 Chrome 版本的痛苦。参数注意两点一是 Windows 激活命令路径不同二是如果机器不能访问外网下载驱动webdriver-manager 会失败这时需要手动下载 chromedriver 放到 PATH再用Service(executable_path./chromedriver)指定路径。环境装好后打开 Chrome 按 F12 进入 DevTools点左上角的箭头图标再点页面上的题目区域Elements 面板会自动定位到对应的元素。问卷星的默认模板里常见结构是这样的题目容器带># 最小可用的问卷星自动填写脚本 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 from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager # 初始化 Chrome 驱动webdriver-manager 自动匹配本机 Chrome 版本 driver webdriver.Chrome(serviceService(ChromeDriverManager().install())) # 换成你要自动填写的问卷链接 driver.get(https://www.wjx.cn/vm/example.aspx) wait WebDriverWait(driver, 20) # 等待第一个单选题组加载出来 first_question wait.until( EC.presence_of_element_located((By.CSS_SELECTOR, .ui-controlgroup)) ) # 点击该题的第一个选项label 是选项的可点击区域 first_question.find_element(By.CSS_SELECTOR, label).click() # 点击提交按钮问卷星的按钮 id 通常是 submit_button submit_btn wait.until( EC.element_to_be_clickable((By.ID, submit_button)) ) submit_btn.click() # 提交后页面跳转到 finish 页面等待 URL 变化即视为成功 wait.until(EC.url_contains(finish)) print(提交成功最终地址, driver.current_url) driver.quit()代码里有几层保护WebDriverWait 是显式等待避免页面异步加载导致元素还没出现就报错提交按钮用的是element_to_be_clickable而不是presence_of_element_located因为按钮在 DOM 里存在不代表可点击需要等它真正就绪。超时时间 20 秒是个平衡值网络差可以调到 30 秒但也不要无限等卡住时得有报错出来否则排查很被动。注意.ui-controlgroup和#submit_button是问卷星默认模板的类名和 id不同皮肤或模板可能不同。如果你的问卷不是默认模板先在 DevTools 里确认实际选择器再替换。第一次跑通时不妨把driver.get换成预览模式的链接避免误提交正式数据。3.2 元素定位的取舍data-qid 优先class 只兜底问卷星有些模板开启了防作弊乱序选项顺序每刷新一次就变一次。脚本如果写死“点击第一个选项”提交的答案分布几乎没有随机性后台一眼就能看出是机器填写。正确做法是通过稳定的属性定位题目容器再在容器内部取选项。# 用># 方式二等待页面出现提交成功文案 success_text wait.until( EC.text_to_be_present_in_element( (By.CSS_SELECTOR, body), 问卷已提交 ) )方式三的实现成本较高Selenium 默认不暴露请求细节需要自己注入 JavaScript 监听 XHR 事件对新手不友好。我一般用第一种为主、第二种为辅先等 URL 跳转再确认页面文案两个条件都满足才记成功。还要注意文案里的标点可能是全角也可能是半角两种都判断一次避免误判。4. 从能跑到跑得像真人随机选项、随机延时、分支处理4.1 随机化是自动填写脚本不被规则拦截的基础课问卷星的防刷机制里有一个重要维度是填写耗时。一份 20 题的问卷人工填写至少需要 60 秒脚本如果 3 秒就提交后台可以直接判定异常。所以随机延时是必选项不是可选项。import random import time def random_delay(): 在 1.5 到 4 秒之间随机停顿模拟阅读题目和思考选项 time.sleep(random.uniform(1.5, 4.0)) def random_pick(question_container): 从题目容器里随机挑一个选项点击并返回选项文本 options question_container.find_elements(By.CSS_SELECTOR, label) target random.choice(options) target.click() return target.textrandom.uniform(1.5, 4.0)的下界不能小于 1 秒太快会被识别为机器上界控制在 5 秒以内太慢会让整份问卷的提交时长变得异常同样引人怀疑。每道题之间调用一次random_delay()形成不规律的点击节奏这是最基础也是最有效的反识别手段。随机选选项的逻辑里有一个容易被忽略的细节random.choice每次只选一个如果一道多选题需要选多个就要用random.sample保证不重复。下面第 4.2 节的通用函数会一起处理这个问题。4.2 分支处理单选、多选、填空、矩阵题一份真实问卷远不止单选题。做得可靠的填写脚本第一步应该是探测题目容器里有哪些控件再决定怎么操作。我把这部分封装成一个fill_question函数按照控件的类型分别处理。from selenium.webdriver.common.by import By def fill_question(driver, qid): 根据题目容器内的控件类型自动填写单选、多选、填空 container driver.find_element(By.CSS_SELECTOR, fdiv[data-qid{qid}]) radios container.find_elements(By.CSS_SELECTOR, input[typeradio]) checkboxes container.find_elements(By.CSS_SELECTOR, input[typecheckbox]) textareas container.find_elements(By.CSS_SELECTOR, textarea) if radios: # 单选题随机点一个 label options container.find_elements(By.CSS_SELECTOR, label) random.choice(options).click() random_delay() elif checkboxes: # 多选题随机选 2~3 个注意 sample 不会重复选同一个 options container.find_elements(By.CSS_SELECTOR, label) k random.randint(2, min(3, len(options))) for opt in random.sample(options, k): opt.click() time.sleep(random.uniform(0.3, 0.8)) random_delay() elif textareas: # 填空题填入固定内容或从预置文本里随机取一条 textareas[0].send_keys(这是一条通过自动化脚本填写的测试内容) random_delay()多选分支里random.sample(options, k)不会重复选取同一个选项k random.randint(2, min(3, len(options)))则保证选择数量不超过题目上限。如果选项总数只有 2 个min(3, 2)会把上限压到 2不会出现 n 选 3 的越界错误。矩阵题的处理方式和普通单选略有不同。矩阵题本质上是多行单选每一行是一道独立题目。可以遍历行容器再对每一行选一个选项# 矩阵题的每一行都是一组单选 matrix_rows driver.find_elements(By.CSS_SELECTOR, table.ui-table tr) for row in matrix_rows[1:]: # 跳过表头 cells row.find_elements(By.CSS_SELECTOR, label) if cells: random.choice(cells).click()还有一类特殊选项叫“其他”点击它之后通常会展开一个输入框让用户补一句说明。这类交互要用逻辑分支单独处理# 处理“其他”选项的补充输入 for opt in options: if 其他 in opt.text: opt.click() extra_input container.find_element(By.CSS_SELECTOR, input.other-input) extra_input.send_keys(补充说明内容) breakother-input这个 class 在不同模板里可能不一样看不到输入框时先到 DevTools 里确认真实类名。这个分支最容易翻车因为很多问卷模板会把“其他”默认隐藏点击后才动态渲染输入框这时必须等输入框出现再填。5. 问卷星自动填写脚本的高频坑位验证码、元素找不到、重复提交5.1 现象一跑到一半弹出验证码脚本直接卡死现象脚本执行到第三四题时页面里出现滑块验证码或图形验证码后续click操作全部失效脚本超时报错。 原因问卷星统计到同一网络短时间内的提交频率异常或者浏览器自动化特征被识别触发了验证码策略。 解决首先给每次提交之间设置足够长的随机间隔把random_delay的下界提高到 3 秒以上其次在检测到验证码元素出现时暂停脚本等待人工滑动完成再继续执行。如果验证码频繁出现说明这次的提交节奏太快需要把整体执行频率降下来比如一份 20 题的问卷至少跑满 60 秒。5.2 现象二NoSuchElementException 和 element not interactable 交替出现现象脚本在本地跑正常换到另一台机器或另一个网络环境后经常报元素找不到或者元素存在但点击无效。 原因问卷星的题目是异步渲染的网络状况差时容器先加载、选项后加载还有部分题目嵌在 iframe 里Selenium 默认只在主文档里查找元素。 解决显式等待要等到“可点击”状态而不是“存在”状态遇到 iframe 时要先切换上下文再定位。最简单的做法是截图留证报错时把当前页面截图存下来比反复猜测快得多。# 点击前先等待元素变成可点击状态 target wait.until( EC.element_to_be_clickable((By.CSS_SELECTOR, div[data-qid1] label)) ) target.click()element_to_be_clickable和presence_of_element_located的区别在于前者会额外检查元素是否可见、是否被其他元素遮挡。问卷星题目区域经常有浮动广告或引导层遮挡用前者能避开大部分“元素存在但点不了”的诡异问题。5.3 现象三提交后提示“您已填写过此问卷”现象第一次提交成功第二次用同一个浏览器再打开问卷直接提示已经填写。 原因问卷星在浏览器本地写入了 Cookie 或 localStorage 标识同一浏览器会话会被识别为重复提交。 解决每次执行都使用全新的用户数据目录避免复用默认 Chrome 缓存。用时间戳生成临时目录保证两次运行之间没有 Cookie 残留。import time options webdriver.ChromeOptions() # 每次用时间戳生成独立用户目录隔离 Cookie options.add_argument(f--user-data-dir/tmp/wjx_profile_{int(time.time())}) driver webdriver.Chrome( serviceService(ChromeDriverManager().install()), optionsoptions )脚本结束时要清理这些临时目录否则跑几十次后磁盘会被占满。清理可以在 Python 里用shutil.rmtree完成也可以交给系统的临时文件清理工具。这个参数很多人不知道等到提示重复填写才回头找原因白白耗掉不少时间。5.4 现象四点提交按钮没反应页面停在原地不跳转现象submit_button.click()执行了但页面没有任何变化也没报错URL 一直停留在问卷页。 原因部分模板在点击提交后会先弹出一个二次确认层必须再点一次“确认提交”按钮才真正提交。 解决点击提交后加一个分支判断看是否出现确认弹层有弹层则再点一次确认按钮之后再等待跳转。# 提交后可能存在“确认提交”弹层 confirm_btn driver.find_elements(By.CSS_SELECTOR, .btn-primary) if confirm_btn and confirm_btn[0].is_displayed(): confirm_btn[0].click() time.sleep(1)find_elements返回的是列表不会在找不到元素时抛异常所以用if confirm_btn判断列表是否为空是安全的。注意.btn-primary是常见模板的类名具体以 DevTools 看到的为准。这个二次确认弹层的坑特别隐蔽因为脚本不报错只有看后台回收数据时才发现提交的是空记录。6. 再进一步把脚本做成可复用的填卷模板6.1 用配置文件驱动不同问卷每次换问卷都改脚本里的选择器和逻辑是很低效的做法。我一般会把问卷 URL 和每道题的策略抽到一个 JSON 配置文件里脚本启动时读取新增问卷只改配置不改代码。{ url: https://www.wjx.cn/vm/your-survey-id.aspx, questions: { 1: {type: single, strategy: random}, 2: {type: multi, min: 2, max: 3}, 3: {type: text, value: 固定回填内容} } }加载配置后直接复用第 4.2 节的fill_question函数只是把策略参数传进去。这样一份问卷对应一个 JSON脚本主体完全不用动后期维护成本低很多。6.2 定时执行与结果留痕批量填写场景里时间分散比集中更安全。Linux 上可以用 cron 每隔一段时间执行一次Windows 就交给任务计划程序。每次运行要把提交时间、问卷地址、成功与否追加到 CSV 文件方便日后核对回收数据。# 每 45 分钟运行一次日志追加写入 */45 * * * * cd /path/to/project python run.py wjx.log 21留痕的好处是出问题时能快速定位是脚本故障还是问卷问题。之前某次回收数据缺了一半靠日志才发现是提交按钮选择器在模板升级后失效了半小时内就定位到原因。6.3 验证脚本可靠性的三种方式新拿到一份问卷不要直接用于正式收集先在问卷星的预览模式里自测。第一种方式是跑一遍脚本后手动检查每个题目是否都有且只有一个答案第二种是核对完成后页的提示文案是否和脚本里的判定条件一致第三种是把随机延时临时调到 0.1 秒连续跑 10 次检查是否有漏选、重复选的情况。三步走完脚本才算真正能在目标问卷上稳定运行。之前吃过一次亏把问卷里的填空题控件当成了单选处理脚本跑完后台回收数据才发现一半答案是空的。后来固定了“先探测控件类型再填写”的流程这个问题再没出现过。自动填写脚本看似简单实际坑全藏在问卷模板的细节里。希望这套思路和代码能帮到你少走一点我踩过的弯路。本文还有配套的精品资源点击获取