
简介这是一套基于Python的问卷星自动化问卷设计脚本源码面向需要批量开展在线问卷的科研人员、市场调研及教学评估者也适合希望用编程替代手动问卷操作的Python开发者。资源共35个文件压缩包16.18MB以26个Python脚本为核心覆盖问卷创建、题型配置、页面跳转、数据提交与收集等环节同时包含ini、yaml、json配置文件用于自定义问卷结构与逻辑并附带exe可执行文件让不熟悉编程的用户也能直接运行日志文件则便于定位程序状态。目前已有555人学习下载。通过研读源码可以理解完整的问卷星自动化设计流程包括题型配置文件夹中的多题型处理、代理IP集成、随机比例控制、浏览器驱动管理等关键技术点项目目录按公共模块、页面对象与主流程划分结构清晰便于按需修改和二次开发是一份兼具实用性与学习价值的自动化脚本参考。1. 基于Python的问卷星自动化问卷设计脚本到底在自动化什么问卷星这类在线问卷工具真正消耗时间的不是“填答案”而是“设计题面”。一份30题的人力调研问卷如果要求出两套不同维度版本一套按题序固定、一套随机打乱光在网页上重复添加题目、勾选必答、配置分值就能耗掉半个下午。更重要的是这种操作无法审计你无法用diff去看上一次问卷和这一次问卷到底哪里不一样。把“设计问卷”这个动作从手点鼠标变成数据转换就是这篇文章想解决的问题。核心思路是定义一套干净的Python数据模型来承载问卷结构再用浏览器自动化去驱动问卷星设计态的编辑器自动完成登录、建问卷、加题、填选项、设分值、发布这一整条链路。整体实现以Playwright为执行引擎不依赖某份“一次性源码包”把每一步拆成可以复制改写的代码块。需要先划清边界脚本只作用于“设计态”也就是创建和配置问卷本身不涉及帮任何第三方批量填写问卷或改写答案数据。适合的人群包括需要批量交付问卷的调研团队、做竞品问卷拆解的产品经理以及想把问卷搭建纳入测试数据准备流程的工程师。下面从技术选型开始讲。2. 技术选型与环境准备用Playwright驱动浏览器设计态2.1 接口直连与浏览器自动化两条路线怎么选问卷星的设计态是一个典型的SPA编辑器页面上的所有操作最终都会变成对后端接口的调用。理论上可以直接用Python的requests库模拟这些异步请求速度会快很多但这条路线的问题也很明显接口参数结构复杂且带有时间戳、加密签名等动态因子每次问卷星调整前端代码都可能让脚本失效高频请求还会触发风控校验。对于“设计问卷”这种本身频率不高的场景接口直连的收益远小于风险。浏览器自动化的好处是它模拟的是真实用户操作页面DOM改版时通常只需要调整选择器不需要从抓包开始重新逆向整个接口协议。配合Playwright的自动等待机制脚本对网络波动的容忍度比纯time.sleep方案高一个量级。两条路线的对比如下对比项直接HTTP接口浏览器自动化Playwright开发速度快但要先抓包分析中等借助codegen生成脚本稳定性接口签名变更即挂DOM变化时改选择器即可风控概率高容易触发行为校验低但频繁操作仍会被限可维护性差参数黑盒好代码即文档适用场景只读数据拉取问卷创建、编辑、发布我的建议是创建问卷、添加题目这类写操作走浏览器自动化读取问卷统计数据走requests接口两者互补不要混在一起写。2.2 环境初始化Python版本、Playwright与Chromium依赖请先确认Python版本至少在3.9以上建议使用3.10或3.11。老项目里常见的Python 3.7在Windows Server上跑Playwright时容易出现ssl证书解析问题不值得为省一个版本号去折腾。环境初始化完整命令如下python -m venv .venv source .venv/bin/activate # Windows PowerShell 下执行 .venv\Scripts\Activate.ps1 pip install --upgrade pip pip install playwright1.44.0 playwright install chromium这段命令的逻辑是先创建并激活虚拟环境避免把依赖装进系统级Python随后安装Playwright库并通过playwright install chromium下载对应版本的Chromium浏览器内核。这里只装Chromium即可Firefox和WebKit在问卷星这种国产网页上兼容性没有明显优势还会白白多占约200MB磁盘。下载内置浏览器时如果遇到网络超时可以用playwright install --with-deps chromium先安装系统依赖库。Windows环境下如果缺少Visual C运行库chromium启动会报缺失DLL通常在微软官网安装“Microsoft Visual C 2015-2022 Redistributable”即可解决。2.3 登录态持久化用storage_state避免每次扫码问卷星支持扫码登录但每次跑脚本都扫码显然不现实。Playwright的storage_state机制可以把登录后的cookies和localStorage保存到本地文件第二次运行时直接加载效果等同“记住登录状态”。下面这段代码解决登录态复用问题from playwright.sync_api import sync_playwright def save_login_state(state_file: str wjx_state.json) - None: 手动登录一次并保存登录态到本地文件 with sync_playwright() as p: browser p.chromium.launch(headlessFalse) ctx browser.new_context() page ctx.new_page() page.goto(https://www.wjx.cn/login.aspx) print(请在打开的浏览器窗口手动登录完成后回到终端按回车...) input() ctx.storage_state(pathstate_file) browser.close() def load_context(browser, state_file: str wjx_state.json): 加载已保存的登录态返回新context return browser.new_context(storage_statestate_file)这段脚本中save_login_state在第一阶段以有头模式启动浏览器让用户手动完成扫码或密码登录input()阻塞进程等用户确认后再把整个上下文的存储状态写入JSON文件。load_context则供后续脚本复用每次运行都直接加载这个文件不需要重新登录。需要注意登录态有效期取决于问卷星服务器端的会话策略通常会维持数天到数周。如果脚本执行时报“登录已过期”重新运行save_login_state即可不需要改任何业务代码。3. 从数据模型到设计态把问卷结构映射成Python对象3.1 用dataclass定义问卷模板让配置先于页面存在脚本的核心不是“点击”而是“结构”。先用Python的dataclass把问卷拆成Survey、Question、Option三层对象后续所有自动化操作都以这个对象为输入。这样做的好处是问卷结构可以脱离页面而存在模板可以写成JSON存放也能在git里做版本对比。基础模型定义如下from dataclasses import dataclass, field from typing import List, Dict dataclass class Option: 单个选项 text: str # 选项文本 score: float 0 # 选项分值用于量表计分 dataclass class Question: 单个题目 title: str # 题干 qtype: str radio # 题型radio/checkbox/fill/score/dropdown required: bool True # 是否必答 shuffle: bool False # 选项是否随机排序 options: List[Option] field(default_factorylist) dataclass class Survey: 一份问卷 title: str # 问卷名称 questions: List[Question] field(default_factorylist) desc: str # 问卷说明这段模型把“一份问卷”定义为一组有序的题目每个题目拥有独立的题型、必答属性和选项列表。qtype使用短字符串而不是中文是为了让脚本代码更简洁实际映射在下一节完成。score字段在评分量表中使用普通单选题中保持默认0即可。这一步是为自动化铺路。如果没有这层中间模型脚本里的每个自动化动作都会直接依赖页面DOM页面稍微调整脚本全部重写。有了dataclass模型后页面变化只影响“写入”这一层代码问卷内容本身不感知。3.2 主循环登录后自动创建问卷并写入题目数据模型准备完毕后进入真正的自动化环节。下面这段代码会加载登录态、创建一份空白问卷然后逐题写入题干和选项from playwright.sync_api import sync_playwright Q_TYPE_LABEL { radio: 单选题, checkbox: 多选题, fill: 填空题, score: 评分题, dropdown: 下拉题, } def create_survey(survey: Survey, state_file: str wjx_state.json): with sync_playwright() as p: browser p.chromium.launch(headlessTrue) ctx browser.new_context(storage_statestate_file) page ctx.new_page() page.set_default_timeout(15_000) # 进入新建问卷页问卷星此类页面URL一般形如 /design/create.aspx page.goto(https://www.wjx.cn/design/create.aspx) page.locator(input[placeholder*问卷名称]).fill(survey.title) for q in survey.questions: # 点击“添加题目”按钮再点击对应的题型入口 page.locator(text添加题目).last.click() page.wait_for_timeout(300) page.locator(ftext{Q_TYPE_LABEL[q.qtype]}).last.click() # 题干输入框在题型创建后出现取最后一个 title_input page.locator(.question-title-input).last title_input.click() title_input.fill(q.title) for opt in q.options: page.locator(text添加选项).last.click() page.wait_for_timeout(150) opt_input page.locator(.option-text-input).last opt_input.fill(opt.text) ctx.storage_state(pathstate_file) # 顺带刷新登录态 browser.close()这段脚本的核心是“定位最后一个匹配元素”。因为每个新题目会在页面中追加区块旧题目的元素仍保留在DOM中所以必须使用.last确保操作落在最新创建的那一道题上。text添加题目是文本定位方式比层级长的class选择器更抗页面重构。需要特别注意不同版本的问卷星设计器DOM类名如.question-title-input可能不同。首次运行前建议用Playwright自带的playwright codegen命令手工操作一次生成实际定位器再替换脚本中的类名。这里的脚本给的是通用结构实际生产使用需要按你账号所见的DOM微调。3.3 首次运行最容易翻车的三个点第一Playwright的fill()方法只会对input、textarea等标准表单元素生效。如果问卷星的题干输入框是contenteditable的divfill()会直接抛错此时需要改成click()后调用press_sequentially(q.title)模拟真实打字输入。第二页面动画导致点击失效。题目创建时编辑器会有一段展开动画如果连续快速执行点击第二个按钮可能点击空白区域。这就是上面代码在每个动作之间保留wait_for_timeout的原因虽然不优雅但对付前端动画最可靠。更规范的替代方案是等待某个“题目卡片”元素visible后再点下一步。第三网络慢导致元素找不到。set_default_timeout(15_000)把默认等待时间提到15秒避免在低速网络下频繁超时。不建议设成超过30秒因为真等到30秒还加载不出来大概率是页面逻辑已经变化问题不是等待能解决的。4. 问卷参数化题型、分值、必答、顺序与批量生成4.1 问卷星常见题型的脚本配置速查表问卷设计自动化不能只停留在“把文字填进输入框”题型参数才是真正解放生产力的地方。下面这张表整理了常见题型在脚本模型中的对应关系以及必配参数题型模型qtype值必配参数脚本实现要点单选题radiooptions至少2个选项互斥点击后自动取消其他选项多选题checkboxoptions至少2个问卷星默认限制至少选1项下拉选择题dropdownoptions至少2个点击下拉控件后逐项选中填空题fill无options只需填题干无需添加选项评分题scoreoptions代表分值通常显示为1-N分量表多选填空fill_multicolumns字段需扩展模型增加列定义在上述配置中score题型最特殊。问卷星的评分题不是“选项文本”而是“维度名称 分数量级”。对应的模型处理方式会稍微不同Option.text可以存维度名score字段存分值上限。添加时脚本需要定位到“评分维度”而不是“选项”。4.2 用JSON模板批量生成同一份问卷的多个版本实际项目中经常需要一份问卷产生多版本题干相同但题目顺序不同、分值不同、必答项不同。与其在Python代码里改参数不如把问卷模板抽成JSON文件同一套Python脚本接收不同JSON就能生成不同版本。模板示例{ title: 2024员工满意度调研-A卷, questions: [ { title: 你对当前办公环境的满意度是, qtype: score, required: true, shuffle: false, options: [ {text: 办公工位, score: 5}, {text: 茶水间, score: 5}, {text: 会议室, score: 5} ] }, { title: 你更希望改善以下哪些方面, qtype: checkbox, required: true, shuffle: true, options: [ {text: 网络质量}, {text: 空调温度}, {text: 照明亮度} ] } ] }批量生成的驱动代码只需读取JSON并转成dataclass对象import json def load_survey_from_json(path: str) - Survey: with open(path, r, encodingutf-8) as f: data json.load(f) questions [] for q in data[questions]: opts [Option(**o) for o in q.get(options, [])] questions.append(Question( titleq[title], qtypeq.get(qtype, radio), requiredq.get(required, True), shuffleq.get(shuffle, False), optionsopts, )) return Survey(titledata[title], questionsquestions) # 批量生成A/B/C三个版本 for version in [A, B, C]: survey load_survey_from_json(ftemplate_{version}.json) create_survey(survey)这段代码的关键点是Option(**o)把JSON里的键值对直接映射到dataclass字段模型中新增字段时JSON文件同步增加键即可调用方代码不用改。问卷星对同一账号短时间创建大量问卷有限流策略批量执行时建议在循环内加time.sleep(10)避免触发“操作过于频繁”的验证弹窗。4.3 分值计算与选项顺序的扩展实现评分题的分值数字化是自动化的高价值点。创建评分题时脚本需要先确定量表的级数然后逐个添加评分维度。举例要做一个“1-5分满意度量表”维度分别是“工位舒适度”和“团队氛围”def add_score_question(page, q: Question): page.locator(text添加题目).last.click() page.wait_for_timeout(200) page.locator(text评分题).last.click() # 维度名称输入 for opt in q.options: dim_input page.locator(.score-dimension-name).last dim_input.fill(opt.text) page.locator(text添加维度).last.click() page.wait_for_timeout(100) # 设置分数级数默认是5 page.locator(.score-scale-select).last.select_option(str(int(q.options[0].score)))这段代码先给每个维度填入名称并继续添加下一个维度最后通过下拉选项设置分值上限。参数说明q.options[0].score在这里被复用为“刻度上限”而不是某个选项的分值这样模型不需要额外增加字段。如果问卷星的评分题控件是点击星形而非下拉框则需要换成逐个点击第N颗星的方式。轮换A/B卷时随机顺序的处理同样在模型层完成只需在JSON里把shuffle设为true脚本创建选项时按random.sample打乱顺序写入页面端不再需要额外设置。这样做的好处是“随机逻辑可复现”每份问卷用固定seed记录顺序后续数据分析时能倒推出实际呈现顺序。5. 验证自动化成果的两个实用技巧导出回读与模板版本化5.1 用问卷星导出文件反向校验题目结构脚本执行完不一定等于问卷创建成功。我常用的验证手段是登录问卷星进入问卷的“设计问卷”页面导出设计结构再用代码回读题目数量与模型比对。问卷星支持将问卷导出为Excel或Word导出后解析可以完全脱离浏览器。以下代码统计Excel中非空的题干行import pandas as pd def validate_survey(excel_path: str, expect_survey: Survey) - bool: df pd.read_excel(excel_path, headerNone) # 问卷星导出格式中题干通常在“标题”列出现先做非空过滤 exported_titles df[df[0].notna()][0].tolist() exported_titles [str(t).strip() for t in exported_titles] expected_titles [q.title.strip() for q in expect_survey.questions] missing set(expected_titles) - set(exported_titles) if missing: print(缺失题目:, missing) return False print(f校验通过共找到 {len(expected_titles)} 题) return True这段逻辑不关心题目顺序只对比两个集合的差异适合快速筛出漏题。更严格的验证可以再对比选项文本集合。顺便提一句如果脚本创建的题目数量较多推荐分段验证每生成10题后暂停一次人工扫一眼问卷星的多端预览再继续下一批。5.2 模板版本化把问卷模板纳入git管理把JSON模板放到git仓库里管理每次修改问卷配置后提交一次变更长期维护时会非常省心。进行版本迭代时的一套可操作流程是git add template_A.json git commit -m A卷调整第3题分值为5分制 git tag survey_2024Q1之后如果想对比两份问卷的差异直接git diff survey_2024Q1..HEAD -- template_A.json就能看到题干和选项的精确变化这个能力是手工维护问卷完全没有的。特别是多部门复用同一套问卷框架时让非技术同事改JSON字段而非代码脚本的可维护性会高很多。把问卷结构当成代码库来管理远比把自动化脚本做成黑盒工具更长久。如果模板中某些字段长期保持默认值比如required大部分为true可以在JSON加载逻辑中做默认值填充不必每道题手写以减少模板体积。最终你会发现写脚本的过程本身不是核心资产沉淀下来的模板模型和验证流程才是。本文还有配套的精品资源点击获取