Python + Playwright 实现问卷星自动化填写
Python Playwright 实现问卷星自动化填写:从自动解析到按比例批量填写本文将分享一个基于 Python Playwright 的问卷星自动化工具的实现思路与完整代码,涵盖问卷结构解析、隐藏控件的点击处理、按权重比例填写、多选题概率控制、多页问卷翻页,以及调用 DeepSeek 自动生成建议占比等实用技巧。一、前言最近在做一个问卷相关的小工具,需要批量测试问卷的填写流程。手动一份一份填显然不现实,于是我用 Python Playwright 写了一套问卷星自动化脚本,顺手把过程中踩的坑和解决思路整理成这篇文章。项目链接这套工具的主要能力:自动识别问卷中的题型:单选、多选、填空、下拉、矩阵(量表)、评分星;按权重比例填写:单选题按占比分布(比如男 40% / 女 60%),多选题按每个选项的被选中概率独立决定;批量提交 N 份,结束后自动统计每题的实际分布;自动解析任意问卷链接并生成配置文件,可选调用 DeepSeek 生成符合人群分布的建议比例和填空题答案池;支持多页问卷自动翻页、提交前必填校验、失败才截图。使用须知:本工具仅适用于填写自己创建或已获授权的问卷,请勿用于伪造数据、刷票或干扰他人问卷。二、技术栈组件说明Python 3.9主开发语言Playwright浏览器自动化框架,支持 Chrome / EdgeDeepSeek APIOpenAI 兼容接口,用于生成建议比例(可选)标准库 urllib调用 DeepSeek API,避免额外依赖选择 Playwright 而不是 Selenium 的原因:安装简单、自带等待机制、locator API 更现代,而且可以直接复用系统已安装的 Chrome/Edge,国内用户不需要额外下载浏览器内核。三、核心实现思路3.1 问卷结构解析:不依赖固定的 class 名问卷星有多个模板,class 命名差异很大。比如手机版模板的题目容器是div.field(带data-rolefieldcontain),经典模板是fieldset,还有一种模板用.qnr-question。所以解析时按优先级依次尝试:letcontainersArray.from(document.querySelectorAll(div.field[data-rolefieldcontain]));if(!containers.length){containersArray.from(document.querySelectorAll(div.field));}if(!containers.length){containersArray.from(document.querySelectorAll(fieldset));}if(!containers.length){containersArray.from(document.querySelectorAll(.qnr-question, .div_question));}每个容器内部,再根据控件类型判定题型:constradiosc.querySelectorAll(input[typeradio]);constchecksc.querySelectorAll(input[typecheckbox]);constselectsc.querySelectorAll(select);consttextareasc.querySelectorAll(textarea);如果容器内存在多个不同name的 radio/checkbox,就判定为矩阵题(量表题)。3.2 最大的坑:输入框全是display:none问卷星手机版模板里,所有 radio/checkbox 的input都是display:none,页面上显示的是旁边的a classjqradio和div classlabel。如果像常规做法一样只筛选可见元素,会发现一道题都识别不到。解决办法是不做可见性过滤,只排除typehidden的输入框;点击时依次尝试多个可见的点击目标:defclick_option(c,input_type,idx):input_locc.locator(finput[type{input_type}]).nth(idx-1)candidates[input_loc.locator(xpathancestor::label[1]),# 经典模板:label 包裹input_loc.locator(xpathfollowing-sibling::a[1]),# 手机版:a.jqradioinput_loc.locator(xpathancestor::span[contains(class,wrapper)][1]),input_loc.locator(xpathancestor::div[contains(class,ui-radio) or contains(class,ui-checkbox)][1]//div[contains(class,label)][1]),input_loc.locator(xpathfollowing-sibling::div[contains(class,label)][1]),]forlocincandidates:ifloc.is_visible(timeout1000):loc.click()ifinput_loc.is_checked():returnTrue# 兜底:强制勾选input_loc.check(forceTrue)returninput_loc.is_checked()每次点击后都回读is_checked()确认生效,失败就换下一个候选目标,最后用check(forceTrue)兜底。3.3 按权重比例填写单选题用加权随机:每个选项给一个权重,按权重占比随机选一个。权重不要求和为 100,代码会自动归一化:defweighted_index(weights,options,rng):entries[]fori,oinenumerate(options):wfloat(weights.get(o[label],weights.get(str(i1),0)))ifw0:entries.append((i1,w))totalsum(wfor_,winentries)rrng.random()*totalforidx,winentries:r-wifr0:returnidxreturnentries[-1][0]多选题则把每个选项的权重当成被选中的概率(0-100),独立随机决定是否勾选,并保证至少选一个:defweighted_checkbox_picks(weights,options,rng):picks[]fori,oinenumerate(options):wfloat(weights.get(o[label],0))ifw1:w*100ifrng.random()*100w:picks.append(i1)ifnotpicks:# 至少选一个:选权重最高的picks.append(max(range(len(options)),keylambdai:weights.get(options[i][label],0))1)returnpicks3.4 填空题答案池 留空填空题支持两种玩法:传一个字符串数组作为答案池,每次提交随机取一条,避免所有答卷的答案一模一样;非必填填空题支持blank_rate留空概率,比如 20% 的概率留空。ifblank_rate0andnotq.get(required)andrng.random()*100blank_rate:v# 留空else:vrng.choice(answers)# 随机取一条必填题强制填答案,否则问卷星会拦截提交。3.5 多页问卷自动翻页问卷星的多页问卷在 DOM 里通常分多个fieldset,后面的页styledisplay:none。处理思路:先识别当前页可见的题目并填写;查找下一页/下一题按钮,点击后等待页面切换;循环直到没有下一页,再滚动到底部提交;用集合记录已填题号,避免重复填写。whileTrue:structcollect_structure(page)forqinstruct[questions]:ifq[n]infilled_ns:continuecontainerpage.locator(struct[containerSelector]).nth(q[rawIndex])ifnotcontainer.is_visible(timeout1000):continue# 当前页不可见,等翻页后再填# ... 填写逻辑 ...filled_ns.add(q[n])next_btnfind_next_button(page)# 查找下一页/下一题ifnext_btnisNone:breaknext_btn.click()3.6 提交前的必填校验为了避免没填就点提交、卡在页面校验的问题,提交前会检查所有必填题是否都填成功,有未填的就不提交并截图,方便排查:ifunfilled:print(f[警告] 有{len(unfilled)}道题未填成功,本次不提交)take_shot(page,cfg,unfilled)returnFalse3.7 调用 DeepSeek 自动生成建议比例解析工具parse_survey.py在识别完题目后,可以把题目清单发给 DeepSeek,让它按公开调研常识给出每道题的占比:单选题:各选项权重之和等于 100;多选题:每项给出被选中概率(0-100);填空题:生成 10-20 条简短候选答案。payload{model:deepseek-chat,messages:[{role:system,content:你是一名问卷数据分析专家,只输出 JSON。},{role:user,content:prompt},],response_format:{type:json_object},}API Key 放在.env文件中(已被 gitignore 忽略),代码里从环境变量或.env读取,不硬编码。3.8 结果分布统计批量刷完后,脚本会把每题各选项的出现次数和百分比打印出来,方便核对实际分布是否符合设定的权重: 答案分布统计(共 20 份) 3.你的性别: 女 75.0%(15), 男 25.0%(5)四、快速开始4.1 安装依赖pipinstall-rrequirements.txt-ihttps://pypi.tuna.tsinghua.edu.cn/simple如果本机没有 Chrome/Edge,再执行playwright install chromium(国内可用 npmmirror 加速)。4.2 解析问卷并生成配置python parse_survey.py--urlhttps://v.wjx.cn/vm/xxxxxxxx.aspx--outconfig.json--ai--ai会调用 DeepSeek 生成建议比例和填空题答案池;不加--ai则使用默认等权重。4.3 开始填写# 先有头模式试一次python fill_wjx.py--configconfig.json--headed# 确认无误后批量python fill_wjx.py--configconfig.json--headless--count100五、配置文件详解{url:https://v.wjx.cn/vm/xxxxxxxx.aspx,count:10,headless:false,delay_min_ms:800,delay_max_ms:2000,timeout_ms:30000,rules:[{match:性别,weights:{男:48,女:52}},{match:.*爱好.*,weights:{阅读:70,运动:50}},{match:建议,value:[答案1,答案2,答案3],blank_rate:20}]}字段说明match正则匹配题目文字weights单选:占比(自动归一化);多选:每项被选中概率value填空题固定答案、答案池数组或randomblank_rate非必填填空题留空概率(0-100)unmatched未匹配题目的处理:random随机填 /skip跳过count提交份数六、常见问题1. 遇到验证码/滑块怎么办?脚本不绕过验证码,改用--headed有头模式,手动拖一下滑块,脚本会继续等待提交成功。2. 提示未能识别到问卷题目?问卷可能已结束、需要登录,或页面模板特殊。用--debug输出识别结构排查。3. 提交后提示未成功?通常是必填题没填上,查看screenshots目录的失败截图,补对应规则。4. 批量刷被平台拦截?平台有 IP 频次、答题时长等风控。调大delay、减少count,并且只用于合规场景。七、结语这套工具的核心价值在于通用:不管问卷是什么模板、什么题型,都能自动识别并按配置的比例填写;配合 DeepSeek 自动生成占比,换新问卷只需一条命令。最后再次强调:自动化工具是双刃剑,请只在自己创建或已获授权的问卷上使用,不要用来伪造调查数据或干扰他人问卷。技术本身无罪,用途决定价值。项目完整代码见 github.com/Ggbikun/wjx,欢迎交流。