资讯详情

问卷星自动填写Python脚本:requests与selenium选型及避坑指南

📅 2026/10/9 23:42:03 | 华诺云谱 👁 阅读
问卷星自动填写Python脚本:requests与selenium选型及避坑指南
简介一份面向问卷星等在线表单高频填写场景的Tampermonkey自动填写脚本现以ZIP代码包形式共享。资源面向具备一定JavaScript基础、了解浏览器开发者工具的开发者通过自定义信息数组与页面元素匹配逻辑将原本重复耗时的人工录入过程自动化。压缩包共4个文件整体仅9KB核心为可直接运行的.user.js脚本另含HTML辅助页面便于本地模拟表单验证匹配效果以及项目配置文件与Git忽略清单结构轻量、便于按需修改个人信息项与选择器。目前已有278人学习下载。源码完整呈现了从信息定义、元素定位到自动填充的整套逻辑并附有测试结果说明拿到后既可在Tampermonkey中安装试用也可作为学习浏览器用户脚本开发、页面DOM操作和前端自动化的实用范例开发者还可在此基础上继续扩展地址、教育经历等更多填表场景。1. 问卷星自动填写脚本它解决什么问题适合谁用“问卷星自动填写脚本”在多数讨论里总被当成刷量工具但我更愿意把它看成表单场景的自动化回归测试准备好答案脚本去和问卷星的后台接口完成一次标准握手模拟一次真实提交。这个过程一旦跑通日常里真正琐碎的部分——比如每天批量录入同一份结构的内部调研数据、反复提交测试问卷验证题目逻辑、给某个项目做答题链路的压力验证——就能从“一次次点击”变成“一次执行”。这一篇只解决三个问题选哪条技术路线、参数从哪里来、踩坑之后怎么排查。原料只有问卷地址和一份你拟好的答案产出是一个能随时运行的 Python 脚本。适合有 Python 基础、正在被重复填表折磨的开发者阅读。2. requests 还是 selenium两条技术路线的选型与抓包起点问卷星自动填写脚本的技术路线不外乎两条一条是让 requests 直接向后端提交接口发 POST另一条是让 selenium 驱动浏览器像人一样点选项、点提交。两条都能完成自动填写但适用面、开发成本和被拦截的概率差别很大。先想清楚选哪条再动手写代码能省掉大量返工。2.1 requests 与 selenium 的取舍为什么先抓包再选requests 路线的本质是“知道接口规矩直接和服务器对话”。它不加载页面、不执行 JavaScript只是把浏览器在点击“提交”那一刻发出的网络请求原样重放一遍。好处是轻量一次提交耗时通常在几百毫秒到一两秒写起来也短坏处是一旦页面里有动态 token、验证码、滑块这类依赖浏览器环境的机制单纯重放请求就会失败。selenium 路线则是驱动一个真实浏览器加载页面、执行脚本、模拟点击行为和真人几乎一致。它能处理 requests 搞不定的复杂交互但因为要拉起浏览器进程速度和资源占用都高一个量级并发开大了机器先扛不住。到底选哪条我的判断标准很简单先打开开发者工具看一次真实提交如果提交动作是一个干净的 POST 请求就优先走 requests如果页面上有明显的动态校验或滑块交互再上 selenium。对比项requests 直接 POSTselenium 模拟点击提交速度百毫秒到秒级秒级到十秒级开发成本低几十行脚本可跑通高要处理等待和元素定位浏览器环境依赖不依赖依赖能过简单验证码不能部分可以并发成本低高每个线程一个浏览器适用场景无复杂校验的批量提交有动态交互的表单一般来说我会先用 requests 分析抓包结果发现卡在动态参数上再评估是否换 selenium。多花十分钟看一次请求好过把两条路线各写一半最后都跑不通。2.2 提交链路打开、作答、提交背后的请求序列要理解参数从哪来得先看清问卷星提交的完整链路。整条流程可以拆成四步第一步浏览器 GET 问卷首页拿到 HTML、JavaScript 和一份题目配置第二步页面脚本根据你的点击行为把选项状态编码成一串特定格式的答案数据第三步浏览器 POST 到后台的写库接口带上 Cookie、时间戳和必要参数第四步接口返回成功回执页面跳转到“提交成功”。真正决定脚本成败的是第三步里的 POST body而 body 里最关键的是第二步生成的答案编码。换句话说“提交”按钮只是触发了一个已经构造好的请求你点的每一个选项最终都被翻译成了一段字符串。脚本要做的就是跳过页面点击直接用代码构造出这段字符串。理解这条链路还有一个实际好处你知道脚本至少要有“打开问卷页”和“提交答案”两个动作。只 POST 不先 GET某些问卷会因为缺少来源 Cookie 或页面预热参数而返回校验失败。这也是很多新手脚本第一步就翻车的原因。2.3 抓出第一份“标准答案”用开发者工具复制一次真实提交在写任何代码之前先手工提交一次把真实请求完整抓下来。这一步拿到的 cURL 样本就是后续所有参数对照的“标准答案”。操作步骤如下打开你自己创建的测试问卷按 F12 进入开发者工具。切到 Network 面板勾选 Preserve log避免页面跳转后请求记录被清空。在页面上正常做一遍单选、多选、填空然后点提交。在过滤器里选择 Fetch/XHR找到名字类似 write 或 submit 的请求。右键该请求选择 Copy → Copy as cURL。把这份 cURL 保存到本地它就是整个脚本的基准样本。提示抓包最好用自己创建的测试问卷来跑。如果你想分析真实问卷的请求结构也可以观察但不要把别人的真实答案数据带入自己的脚本里。拿到 cURL 后先别急着转 Python。先看它里面的 POST body里面有每一道题的答案编码格式也有时间戳这类动态变化的值。把 body 里每个字段的格式和含义弄明白再进入下一步的字段拆解。这一步花的时间越长后面写脚本时踩的坑越少。3. 读懂提交参数从 cURL 样本到字段表与动态提取cURL 样本里最唬人的是那一大串 Header 和 body看着像黑匣子其实拆开就三类固定的身份信息、可变的动态参数、真正的答案数据。这一章把这三类逐层拆开你就能看懂提交接口在说什么。3.1 把 cURL 样本改写成 Python 请求头拿到 cURL 后最常见的做法是把它转成 requests 代码。在线转换工具能用但最好自己转一遍因为你需要理解每个参数是什么。先看 Header 部分通常需要提取和保留这几类信息headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64), Accept: application/json, text/javascript, */*; q0.01, X-Requested-With: XMLHttpRequest, Content-Type: application/x-www-form-urlencoded; charsetUTF-8, Referer: 你的问卷页地址, Cookie: 从 cURL 里复制的完整 Cookie 值, }这里面有几个关键点。User-Agent保持和浏览器一致避免被基础风控识别Referer指向问卷页地址缺失时部分接口会直接拒绝Cookie是维持会话身份的核心通常要完整保留。Content-Type则决定了 POST body 的编码方式问卷星这类表单接口一般用application/x-www-form-urlencoded也就是键值对拼接。Header 里最容易忽略的是X-Requested-With这个请求头标明了这是一次 Ajax 请求。后端一旦校验它的值脚本里不带上就会返回异常。这类细节不抓包根本看不出来所以每次写脚本前都要重新看一眼真实请求不要凭记忆写。3.2 提交接口的字段表哪些参数决定成败把 Header 剥离之后剩下的核心是 POST body。字段名在不同问卷配置下会有差异但以常见抓包结果来看基本可以归纳成下面这张表参数名常见命名以实际抓包为准类型来源与作用必填性curID字符串问卷 ID从问卷链接里解析必填submitdataJSON 字符串各题答案的编码数据必填starttime数字打开问卷页时生成的时间戳多数必填t数字提交请求时生成的时间戳多数必填动态 token字符串页面脚本生成的随机校验值部分问卷必填submitdata是整个请求的核心它把“用户点击选项”翻译成后端约定的编码格式。这个编码格式由后端定义前端只是按约定拼接字符串。所以你只要照着 cURL 样本里的格式构造就能绕过页面点击直接提交。时间戳参数需要注意starttime表示打开问卷的时间t表示提交动作发生的时间。有些问卷会校验两者间隔间隔太短或者完全没有间隔就会被判定为机器行为。这也是为什么脚本里不能只传提交时间而要保留一个“打开页面”的时机。3.3 动态参数从页面源码里二次提取别硬编码cURL 样本里的时间戳和 token 是一次性的直接用样本里的值去提交第二份问卷几乎必然失败。动态参数的正确做法是先用 requests 打开问卷页再从页面源码里提取这些值。import re import time import requests session requests.Session() page_resp session.get(questionnaire_url, headersheaders) html_text page_resp.text # 常见做法从页面内联 JS 或隐藏 input 里找动态值 token_match re.search(rtoken:([^]), html_text) dynamic_token token_match.group(1) if token_match else params { curID: questionnaire_id, starttime: str(int(time.time() * 1000)), t: str(int(time.time() * 1000)), submitdata: submit_data, token: dynamic_token, }这段代码的逻辑是先 GET 问卷页拿到页面 HTML用正则从内联脚本里提取 token再用当前时间生成时间戳。拿到这些动态参数后再和答案数据一起组装成最终提交参数。正则只是提取手段之一实际页面的动态值可能藏在script标签、隐藏 input 或某个变量的赋值语句里。写提取逻辑时可以灵活替换匹配规则。核心原则就一条凡是每次请求都在变化的值都要从页面里现取不要复用上一次的值。4. 写一个能稳定提交的 Python 脚本请求、延时、并发理论讲完这一章落到代码。这里给出一份可直接复用的脚本骨架包含会话保持、答案编码、动态参数提取、提交回执校验四个部分。你可以把它当成模板再按自己问卷的字段结构做调整。4.1 脚本骨架会话保持、答案编码与提交回执import json import time import random import requests class QuestionnaireSubmitter: def __init__(self, qid, headers, submit_url): self.qid qid self.headers headers self.submit_url submit_url self.session requests.Session() self.session.headers.update(headers) def build_submit_data(self, answers: dict) - str: # answers 结构为 {题目编码: 答案值} # 单选题答案直接放选项编号多选题用逗号连接 return json.dumps(answers, ensure_asciiFalse) def fetch_dynamic_params(self, page_url: str) - dict: # 打开问卷页提取 starttime/token 等动态值 resp self.session.get(page_url, timeout10) # 这里按实际页面结构调整提取逻辑 return {starttime: str(int(time.time() * 1000))} def submit(self, page_url: str, answers: dict) - bool: dynamic_params self.fetch_dynamic_params(page_url) payload { curID: self.qid, submitdata: self.build_submit_data(answers), **dynamic_params, } resp self.session.post(self.submit_url, datapayload, timeout10) # 成功判断以实际接口返回为准 return 成功 in resp.text or resp.json().get(code) 1 if __name__ __main__: submitter QuestionnaireSubmitter( qid12345678, headers{User-Agent: Mozilla/5.0}, submit_urlhttps://your-domain/joinnew/writejx.aspx, ) ok submitter.submit( page_urlhttps://your-domain/vj/12345678.aspx, answers{1: 2, 2: 1,3, 3: 这是一段文本}, ) print(提交成功 if ok else 提交失败请查看返回内容)这份代码里有几个设计需要说明。使用requests.Session()而不是直接requests.post是为了让每次请求都保持同一套 Cookie这对依赖会话状态的问卷接口很重要。build_submit_data把答案字典序列化成 JSON 字符串函数内部对多选和文本题的处理比较直观单选放一个值多选用逗号连接文本直接放字符串。提交后的成功判断逻辑我建议根据你抓包时看到的返回结构来写。有些接口返回 JSON里面有 code 字段有些返回纯文本包含“成功”字样。判断逻辑不要写太死最好把resp.text先打出来看一眼。4.2 让延时像人均匀随机与错峰提交脚本本身能提交之后下一件事是控制节奏。直接连发会被风控盯上所以要在每次提交之间插入一个看起来“像人”的等待时间。随机延时的常见实现如下def human_delay(base_sec8, jitter_sec12): # base_sec 控制最小等待jitter_sec 控制额外随机范围 delay base_sec random.uniform(0, jitter_sec) time.sleep(delay)这个函数的逻辑是等待时间由固定基线和随机浮动两部分组成。base_sec是下界jitter_sec是额外随机波动的上界。实际等待时间会在 8 到 20 秒之间浮动。这样比固定间隔更难被简单频率统计识别出来。参数怎么调取决于问卷本身要花多久填。一份 5 分钟的问卷人工填写时间不可能只有 5 秒。所以在设延时前先自己填一次问卷记下耗时把base_sec设置在人工耗时的 1/3 到 1/2 左右再给一点随机浮动看起来就不突兀。注意随机延时解决的是“过于规律”的问题不是让你无限压低等待时间。把base_sec调成 0 只会让脚本死的更快。4.3 并发与重试线程池的边界和失败恢复当你有几十份数据要提交时串行太慢很多人会自然想到开并发。用concurrent.futures.ThreadPoolExecutor可以很方便地控制并发数量from concurrent.futures import ThreadPoolExecutor def run_one(user_data): submitter QuestionnaireSubmitter(qid, headers, submit_url) for attempt in range(3): try: if submitter.submit(page_url, user_data): return True except requests.exceptions.RequestException: time.sleep(5) return False with ThreadPoolExecutor(max_workers3) as executor: results list(executor.map(run_one, user_data_list))这块代码里max_workers3把并发控制在 3 个线程。每次提交失败会重试最多 3 次每次重试间隔 5 秒。这个重试逻辑很重要网络抖动导致的单次失败很常见直接放弃会让整批数据缺漏。并发数不是越高越好。单个入口的并发一旦上去风控识别的概率会明显提升。以我的经验3 到 5 个线程是比较稳的区间超过这个数收益就开始下降风险反而上升。如果你的数据量很大更推荐的做法是拉长总时长、分批执行而不是同一时间疯狂并发。5. 问卷星自动填写脚本的避坑记录:五个翻车现场脚本写出来能跑只是第一步真正让人头疼的是那种“看起来正常但结果不对”的情况。这一章把最常见的五类故障逐一拆解每条按现象、原因、解决三个部分讲清楚。5.1 返回 success 但后台空最常见也最迷惑现象接口返回了成功标识脚本打印“提交成功”但打开问卷后台记录列表里找不到这条数据。原因最常见的是curID和页面实际问卷 ID 不一致或者提交地址来自缓存的旧接口。还有一种情况是接口只校验了格式没校验归属请求被接受但被判定为无效数据丢弃。解决把脚本里的提交 URL 和 cURL 样本里的地址逐字符比对确认curID是从问卷链接中解析出来的。测试时提交完不要关页面立刻去后台刷新查看。如果后台延迟等几秒再刷一次区分“没有写入”和“还没刷新”。5.2 多选题只剩第一项编码格式没对齐现象单选题、文本题都正常唯独多选题后台只保存了第一个选项。原因多选题的答案编码格式和单选不一样。抓包时看到的是字符串但实际格式可能是逗号连接的选项编号也可能是 JSON 数组。脚本如果按单选的格式传后端只解析到第一个值。解决回看 cURL 样本里submitdata字段中多选题对应的片段。常见格式是把选项编号用逗号连接例如1,3,5或者用数组形式[1,3,5]。在build_submit_data里对不同题型分别处理编码逻辑不要用一个通用函数处理所有题。5.3 “问卷已停止”不等于风控先查有效期现象脚本正常提交但返回“问卷已停止”或“链接失效”同一个链接在浏览器里手动打开却能正常作答。原因脚本提交时带的starttime时间戳有问题。有些问卷设置了有效期后端会校验提交时间是否落在有效区间内。如果starttime被硬编码成样本里的旧值或者时区处理错误就会带着过期时间戳提交被判定为无效问卷。解决先确认问卷本身没有停止收集再检查starttime和t是否为当前时间。脚本在每次提交前重新生成时间戳不要复用上一次的值。这一步排查不复杂但很容易被误判为风控拦截。5.4 一提速就 403 或滑块频率与行为特征现象低频提交一切正常把base_sec调小、并发开大之后开始出现 403 或滑块验证码。原因提交频率的统计特征太明显。固定 3 秒一次或者并发 8 个线程同时提交会让服务器的风控模块快速识别出机器行为。滑块出现意味着已经触发了行为校验不是单纯改请求头能绕过的。解决把延时恢复到合理范围降低并发数并且让每次提交前都走一遍“打开页面再提交”的完整链路。直接从脚本 POST 到提交接口缺失了浏览器的铺垫动作也会让风控更容易命中。先把自己的节奏调健康而不是去研究怎么绕过校验。5.5 参数硬编码的翻车token 过期与问卷换版现象脚本昨天还能跑今天开始全部提交失败检查接口返回报的是参数校验失败。原因动态参数处理方式太粗暴。比如把 token 写死在脚本里或者用固定正则提取但问卷发布者调整了页面结构提取规则失效。这类问题处理起来最容易让人暴躁因为脚本代码一行没改坏的是环境。解决给动态参数提取单独封装一个函数每次提交前从页面现取不要缓存。在脚本里加日志失败时把接口返回的 body 写入本地文件方便对比是 token 过期还是结构变了。正则提取规则要尽量宽松定位到稳定的标识符附近提取而不是匹配完整字符串。6. 进阶把单份脚本变成批处理 pipeline 并验证结果脚本能稳定提交单份后下一个问题是让它处理多份问卷、多份数据并且能自动运行。这一章给出三个提升方向让脚本从“一次性工具”变成“可维护的小系统”。6.1 让脚本接受命令行参数:问卷链接与答案文件解耦把问卷 ID 和答案写死在代码里换一份问卷就要改代码。常见做法是让脚本接受命令行参数把数据和代码分开import sys import json if __name__ __main__: qid sys.argv[1] answers_file sys.argv[2] with open(answers_file, r, encodingutf-8) as f: answers json.load(f) # 后续把 qid 和 answers 传入提交器调用方式是python questionnaire.py 12345678 answers.json。这样换问卷只需准备新的答案文件脚本本身不用动。这个思路和给另一个 py 脚本传递参数的逻辑类似都是通过参数解耦脚本之间不互相依赖便于维护和复用。6.2 定时全自动执行:像跑设备老化测试一样跑问卷填问卷脚本如果只是手动跑省下的时间有限。更进一步的做法是把它挂到计划任务里让它每天定时执行。Windows 上用任务计划程序Linux 上用 cron本质上都在做同一件事定时拉起脚本、执行、写日志。这里有一份 Windows 任务计划的最小配置思路创建一个批处理文件里面依次执行 Python 脚本并附带参数然后在任务计划程序里设置每天触发一次程序指向 Python参数指向脚本路径。运行结果可以通过重定向写入日志文件方便回查。这其实就是把问卷脚本当成一个微型的设备老化测试全自动执行脚本在跑重复、留痕、可追踪。6.3 一份最少验证清单提交后五秒内该查什么自动化脚本跑完不等于完成验证结果才是闭环的最后一步。每次跑完我至少会确认四件事接口返回是否包含成功标识这个标识要和抓包时看到的成功返回一致。去问卷后台查看记录条数确认数量和数据源对应。抽查一条记录比对多选题是否完整、文本题是否有乱码。看日志里的每次提交间隔避免出现过于规律的固定间隔。这套验证习惯帮我校对过很多次脚本问题。曾经有一次我把并发开到 8问卷后台直接限制了整个入口的提交连手动填写都受影响。那次之后我彻底明白这类自动化脚本真正的价值不是快而是准确、可控、可解释。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑