每次打开一个问卷页面,看着十几道重复的选择题来回点鼠标,我就觉得这事应该有个更聪明的办法。2026年了,油猴(Tampermonkey)早就不只是“改改网页背景”的小玩具,它完全能帮我们把高频、重复的问卷填写流程自动化,省下大把时间。这篇教程会从零开始,手把手带你装好工具、看懂脚本原理、写出第一个问卷自动填表脚本,再把你实际运行中会遇到的各种坑提前讲透。无论你是没写过代码的小白,还是想快速上手用户脚本的开发老手,这都值得花十分钟读完。
我们说的“油猴”,英文名是Tampermonkey,中文社区习惯叫它油猴,最近网上还有个外号叫“油泼猴”,其实是同一个东西。它的本质是一个浏览器扩展,核心能力是往网页里注入你自己写的JavaScript代码。你写一个规则,告诉它“访问某个页面时,自动执行某段操作”,它就会在每次打开页面时帮你干活。接下来我按自己实际用的路径,把从安装到调试、从踩坑到稳定运行的完整过程拆开讲。
1. 先搞清楚两件事:油猴到底是什么,为什么问卷自动填表找它最合适
1.1 用一句大白话解释油猴的工作机制
你可以把油猴理解成浏览器的“自动执行师傅”。平时我们在浏览器里打开一个网页,页面上的表单、按钮、输入框都是静态的HTML加动态的JavaScript。油猴干的事,就是在这个页面完全加载好之后,再往里面塞一段你指定的脚本,让脚本代替你操作页面里的元素。
比如一个问卷页面上有一个文本框,原本需要你自己点击、输入、再点击“下一页”。油猴脚本做的事就是:等这个文本控件出现在页面上,直接给它赋值,然后自动触发确认按钮。整个过程对服务器来说,跟真人操作没有本质区别——它发送的就是正常的HTTP请求和表单数据。说白了,油猴就是一个让浏览器按剧本自动演出的工具,而“剧本”就是用户脚本本身。
1.2 为什么收集问卷时最喜欢用Tampermonkey
市面上能做浏览器脚本注入的扩展不止一个,但Tampermonkey是目前兼容性、功能和社区活跃度最均衡的。它支持Chrome、Edge、Firefox、Safari等主流浏览器,在2026年依然保持同步更新,并且已经适配了Chrome最新的Manifest V3扩展规范。这意味着在新版浏览器上,Tampermonkey依然可以稳定运行,不会像某些老扩展一样莫名其妙被禁用。
另一个理由是它的权限模型清晰。一个用户脚本能作用在哪些网址上,由脚本头部的@match字段声明,不会像无头浏览器那样把整个浏览器都控制住。对多数人来说,我们需要的就是这种“只管单个网页、不管其他系统”的隔离性。Tampermonkey还支持GM_系列API,比如GM_setValue可以本地存数据、GM_notification可以发系统通知,这些能力在问卷自动填表里都很有用。
1.3 油猴脚本能做到什么、不能做什么
先说能做到的:自动填充文本框、自动选择单选框和复选框、下拉框按条件选中、自动翻页、自动提交,甚至可以根据答案之间的逻辑跳转来动态决定下一题选什么。这些都是纯前端操作,不需要后端权限,实施起来非常直接。
不能做的也很多:它绕不过验证码,不影响服务端的反作弊逻辑,也替代不了需要登录态会话的复杂加密流程。更关键的是,油猴脚本只适合处理“你本人有权限访问、且平台允许自动填写的问卷”。如果你拿它去批量刷问卷、刷奖励、抢限量资格,那就属于滥用用户脚本,轻则脚本被平台识别并封禁账号,重则可能带来合规风险。这篇教程只讲如何提升个人填写表单的效率,不去碰那些灰色用途。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 2026年的环境准备:不同浏览器的安装与配置实测
2.1 主流浏览器安装Tampermonkey的方式
安装这一步并不难,但不同浏览器在2026年的入口有差异。我这里分几个场景说:
- Chrome和Edge:直接打开各自的应用商店,搜索“Tampermonkey”,找到带有蓝色油壶图标的官方扩展,点击添加。需要注意的是,Chrome应用商店的版本和Edge加载项商店的版本都是官方维护的,但Edge商店里偶尔会出现假货,认准开发者为“Jan Biniok”或官网域名结尾的发布者。
- Firefox:打开Firefox附加组件中心,搜索Tampermonkey,同样认准官方出品。安装完成后,工具栏会出现油壶图标。
- Safari:Safari的扩展安装路径比较特殊,需要先在系统设置中允许扩展,再到App Store或开发者官网下载安装。配置上略复杂,不过日常使用Chrome或Edge会更顺手。
装好之后,首次点击油壶图标,会进入欢迎页或脚本管理面板,这一步直接下一步即可。
2.2 安装后必须完成的几项基础设置
很多人装完油猴就开始找现成脚本,结果发现脚本没生效,九成原因是基础设置有问题。我一般会先到Tampermonkey的设置页(Dashboard左下角“设置”按钮)检查几项:
- 通用模式:保持“初学者”模式即可,不需要调整。
- 自动更新:建议开启“每12小时检查一次”,这样脚本作者修复bug后你能及时收到更新。
- 存储:如果用到
GM_setValue,这里可以查看本地存储配额,一般不用动。 - 安全设置:如果脚本内容来源不明,不建议关闭“增强安全验证”。尤其是从陌生网站复制的脚本,强烈建议先在编辑器里通读一遍,确认没有请求外部未知地址的逻辑。
另外还有一点容易踩:扩展商店里的Tampermonkey默认是“在特定网站运行”,有些页面是后台网页或iframe,可能需要你手动点击油壶图标,再选择“在此网站允许”。否则打开目标站点时,脚本并不会自动激活。
2.3 如何验证脚本环境已经就绪
确认油猴安装成功最直观的方式,是访问一个测试页面,点击油壶图标,看面板是否显示“已授权访问此网站”。如果你想得到一个更专业的验证,可以写一个极简脚本,在页面上弹出一个问候框:
javascript复制// ==UserScript==
// @name 环境验证脚本
// @namespace http://tampermonkey.net/
// @version 1.0.0
// @match https://example.com/*
// @grant none
// ==/UserScript==
(function() {
'use strict';
console.log('油猴脚本生效');
alert('油猴脚本生效:环境正常');
})();
把这段代码粘贴到新建脚本里,保存,然后打开https://example.com,如果弹出提示框,说明环境已经完全就绪。如果没弹,优先检查@match是否写对了域名,再到浏览器开发者工具里的Console看有没有脚本报错。
3. 保姆级实操:手写第一个问卷自动填表脚本
3.1 先花三分钟看懂问卷页面结构
写脚本前最重要的一步不是写代码,而是分析页面。以最典型的问卷页面为例,打开目标问卷页面后,按F12进入开发者工具,在Elements面板里找到表单区域。你需要确认几个关键信息:
- 表单元素是原生HTML标签(
input、select、textarea),还是某个前端框架渲染的自定义组件。 - 输入框、单选框、复选框的
name、id或class属性是否有规律。 - 页面是一次性加载所有题目,还是点击“下一页”后才动态加载下一组。
原生HTML是最容易处理的。比如一个文本框,代码通常是<input type="text" name="realname">,脚本里直接用document.querySelector('input[name="realname"]')就能定位。单选框是<input type="radio" name="gender" value="male">,脚本里给它设置checked = true并触发change事件即可。复选框同理。
如果页面是React或Vue组件,光改DOM属性往往不够,还必须触发框架能感知到的原生事件,这一块我在第5章会专门讲。
3.2 脚本骨架与生命周期
油猴脚本的开头是一段元数据注释块,这段注释不是普通注释,Tampermonkey会读取里面的字段来决定脚本的运行时机和权限。以一个完整的问卷自动填表脚本为例:
javascript复制// ==UserScript==
// @name 问卷自动填表助手
// @namespace https://example.local/auto-form-filler
// @version 1.0.0
// @description 根据配置自动填写问卷表单
// @author your-name
// @match https://survey.example.com/survey/*
// @match https://*.survey-site.cn/questionnaire/*
// @run-at document-idle
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_notification
// ==/UserScript==
@match决定了脚本在哪些网址生效,可以写多个。@run-at有多个可选值,问卷脚本一般用document-idle,也就是页面加载完成后执行,避免抢跑。@grant声明需要用到的Tampermonkey扩展API,如果只用原生DOM操作,写@grant none即可,这样受页面安全策略限制更少。
写代码时,我建议外层包一个IIFE(立即执行函数),这是用户脚本社区的标准写法,既能隔离变量,又不污染全局命名空间。
3.3 单选框、复选框、下拉框、填空的自动填充写法
下面是一个简化但结构完整的自动填充函数,覆盖了最常见的四种控件:
javascript复制(function() {
'use strict';
// 配置区:不同问卷的答案都放这里,方便改
const answers = {
name: '张三',
email: 'zhangsan@example.com',
gender: 'male',
hobbies: ['reading', 'coding'],
city: 'beijing',
comment: '整体体验良好。'
};
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function fillTextField(selector, value) {
const el = document.querySelector(selector);
if (!el) return;
el.value = value;
el.dispatchEvent(new Event('input', { bubbles: true }));
el.dispatchEvent(new Event('change', { bubbles: true }));
}
function checkRadio(name, value) {
const el = document.querySelector(`input[name="${name}"][value="${value}"]`);
if (el) {
el.checked = true;
el.dispatchEvent(new Event('change', { bubbles: true }));
}
}
function checkBoxes(name, values) {
values.forEach(value => {
const el = document.querySelector(`input[name="${name}"][value="${value}"]`);
if (el) {
el.checked = true;
el.dispatchEvent(new Event('change', { bubbles: true }));
}
});
}
function selectOption(selectName, value) {
const sel = document.querySelector(`select[name="${selectName}"]`);
if (!sel) return;
sel.value = value;
sel.dispatchEvent(new Event('change', { bubbles: true }));
}
async function fillSurvey() {
// 等待页面表单完全加载
const form = document.querySelector('form');
if (!form) return;
fillTextField('input[name="name"]', answers.name);
fillTextField('input[name="email"]', answers.email);
checkRadio('gender', answers.gender);
checkBoxes('hobbies', answers.hobbies);
selectOption('city', answers.city);
fillTextField('textarea[name="comment"]', answers.comment);
console.log('自动填写完成');
}
window.addEventListener('load', fillSurvey);
})();
这套代码的思路非常直接:通过选择器找到元素,设置值,然后触发事件。为什么要触发input和change事件?因为现代前端框架在提交表单前会监听这两个事件来做状态同步。如果你只改了el.value而不触发事件,React或Vue组件里绑定的数据还是旧值,最后提交的数据就为空。
3.4 自动提交与防检测的边界处理
填写完表单之后,是否要自动提交,取决于你的实际场景。如果是一个一次性问卷,你当然希望脚本直接把“提交”按钮也点了。但如果是为了应对频繁出现的重复问卷,我建议不要自动提交,而是填充完成后由你自己点一下提交按钮。原因有二:一是自动提交出错的概率更高,比如漏选了某个必填项,提交时会有弹窗提示,但脚本并不会帮你点弹窗;二是从使用边界来看,手动点击最终提交按钮,意味着你对这次提交是知情且负责的,不容易被平台判定为纯机器行为。
如果你确实需要自动提交,可以等所有值填完后,先调用表单的checkValidity()方法检查是否通过前端校验:
javascript复制const form = document.querySelector('form');
if (form.checkValidity()) {
const submitBtn = document.querySelector('button[type="submit"]');
submitBtn.click();
} else {
console.log('表单校验未通过,请人工检查', document.querySelectorAll(':invalid'));
}
这里切忌无脑form.submit(),submit()方法会跳过按钮绑定的一些校验逻辑,导致数据丢失或者触发服务端校验失败。用按钮的click()方法更接近真人操作。
4. 从“能跑”到“好用”:脚本里的工程化细节
4.1 用示例配置管理常用答案
第一次写的脚本通常是把答案硬编码在代码里。一旦问卷数量变多,每份问卷的字段名不同、答案不一样,改动就非常痛苦。我的做法是把答案抽成一个独立的配置对象,放在脚本最顶部,像配置文件一样维护。
javascript复制const profile = {
base: {
name: '张三',
email: 'zhangsan@example.com',
phone: '13800138000'
},
work: {
company: '示例科技有限公司',
position: '产品经理',
years: '3-5年'
},
surveyAnswers: {
// 键对应问卷字段的name,值对应用户想填的内容
'input[name="real_name"]': '张三',
'input[name="email"]': 'zhangsan@example.com',
'select[name="province"]': '北京市'
}
};
这样改答案时完全不用动逻辑代码。更进一步,可以用GM_getValue和GM_setValue把配置放到Tampermonkey的本地存储中,好处是脚本更新后配置不丢失。虽然第一次使用时要多做一步“设置答案”,但长期用下来省心很多。
4.2 延迟、随机化与人机交互节奏
问卷填写被识别为机器操作的常见特征包括:填表时间极短、所有答案几乎在同一毫秒内提交、操作间隔是严格的固定间隔。所以在开发自动填表脚本时,加入适当的延迟与随机化很有必要。
我的经验值是:每填写一个字段后等待300到800毫秒,每翻一页等待800到2000毫秒,整个问卷完成时间控制在正常人工填写的1.5到3倍之间。前端代码用setTimeout或者Promise加sleep函数都能实现。随机化不能搞过于均匀的数组循环,建议直接用Math.random()生成每次都不一样的延迟。
但这里必须提醒一句:延迟和随机化只是“模拟得更像人”,并不是“彻底隐藏机器痕迹”。如果你要面对的是强风控平台,它还会分析鼠标轨迹、点击热区、输入速度变化等更复杂的特征。用户脚本在这方面能力有限,所以不要抱着“靠随机延迟就能绕过检测”的心态,那既不可靠,也容易越界。
4.3 断点续填与异常处理
问卷一旦涉及十几道题,填到一半脚本报错,整份表单清空重来,这种体验很糟糕。我在脚本里加了两层保护:
第一层是操作异常捕获。每个填充函数都包一层try...catch,某个字段找不到时,记录日志而不是中断整个流程。这样页面上如果某个题目被逻辑跳隐藏了,脚本也能继续处理后面的题。
javascript复制function safeSetField(fillFn) {
try {
fillFn();
} catch (err) {
console.error('字段填充失败:', err);
}
}
第二层是填写进度保存。用一个对象记录当前已经完成到第几页,然后通过GM_setValue('survey_progress', pageIndex)保存。下次打开问卷时,读取进度,从对应页开始继续填充。对于那种每天都会重置的重复问卷,这个功能尤其实用。
5. 真实跑批中遇到的坑:一次问卷自动填表的完整排查记录
5.1 坑一:动态加载元素导致脚本失效
我最早写问卷脚本时遇到最典型的场景是:使用window.onload触发填充,但打开页面后发现什么都没发生,Console也没有报错。打开Elements面板一看,表单区域是空的,所有题目都是页面加载后通过AJAX接口渲染进来的。这就意味着onload事件触发时,表单还不存在。
解决思路是用MutationObserver监听DOM变化,当目标元素出现后再执行填充。Tampermonkey脚本里也可以用一个轮询函数:
javascript复制function waitForElement(selector, timeout = 10000) {
return new Promise((resolve, reject) => {
const start = Date.now();
const timer = setInterval(() => {
const el = document.querySelector(selector);
if (el) {
clearInterval(timer);
resolve(el);
} else if (Date.now() - start > timeout) {
clearInterval(timer);
reject(new Error(`等待元素超时: ${selector}`));
}
}, 200);
});
}
然后在填表入口处改成:
javascript复制waitForElement('form')
.then(fillSurvey)
.catch(err => console.error(err));
这比onload可靠得多,对单页应用同样适用。
5.2 坑二:iframe嵌套页面拿不到DOM
有些问卷平台会把表单嵌套在iframe里,从主页面直接document.querySelector永远只能拿到外层框架,脚本看起来没有任何效果。排查方法很简单:在Elements面板里搜索是否有iframe标签。如果题目真的在iframe里,脚本必须进入iframe的contentDocument操作。
javascript复制function getIframeDocument(iframeSelector) {
const iframe = document.querySelector(iframeSelector);
return iframe.contentDocument || iframe.contentWindow.document;
}
const iframeDoc = getIframeDocument('iframe[name="survey-frame"]');
const nameInput = iframeDoc.querySelector('input[name="name"]');
nameInput.value = '张三';
这里还要注意同源策略问题。如果iframe的内容来自另一个域名(比如问卷系统的域名和主站域名不一致),contentDocument会被浏览器拦截,返回null。这时可以改成把Tampermonkey脚本的@match直接指向iframe的源地址,让脚本直接跑在iframe对应的文档里。操作起来会复杂一些,可打开油壶图标,选择“在此网站允许”后重新加载页面验证。
5.3 坑三:事件触发器未生效
前面提到现代前端框架需要触发事件同步状态。但如果你触发了change事件后,某个下拉框的联动仍然不执行,别急,这很可能是框架监听的是input事件而不是change事件,或者需要两个事件都触发。最稳妥的做法是同时触发input和change,并且确保事件带有bubbles: true和composed: true。
javascript复制function setNativeValue(element, value) {
const prototype = Object.getPrototypeOf(element);
const descriptor = Object.getOwnPropertyDescriptor(prototype, 'value');
descriptor.set.call(element, value);
}
// 以React为例
function setReactInputValue(input, value) {
setNativeValue(input, value);
input.dispatchEvent(new Event('input', { bubbles: true }));
input.dispatchEvent(new Event('change', { bubbles: true }));
}
React内部维护了一套虚拟DOM的值跟踪,直接设置el.value后,React认为值没有变,不会更新内部状态。上面这段代码通过setNativeValue调用原生value的setter,再触发事件,是社区里验证过的解决方式。
5.4 坑四:脚本被页面安全策略拦截
如果脚本报错信息里出现Refused to execute inline script或者Content Security Policy相关字样,说明目标网站设置了CSP(内容安全策略),限制页面加载额外的内联脚本或者外部资源。Tampermonkey在部分受限页面会把脚本注入到页面上下文,但执行某些API时会被拦截。
我的应对思路是:脚本里尽量用safe的原生API操作,不调用eval、Function构造器,不注入外部JS文件。如果你需要跨域请求,优先用GM_xmlhttpRequest而不是fetch,因为GM_xmlhttpRequest由扩展发起,不受页面CSP限制。另外,把@grant字段明确列出需要的API,避免使用@grant unsafeWindow这样的宽松权限,可以减少被拦截的概率。
6. 给长期使用的几条务实建议
6.1 现成脚本怎么找、怎么安全判断
如果你不想从零写脚本,可以到用户脚本分享社区搜索关键词“问卷”或“auto fill form”,很多现成脚本能帮你快速上手。但我强烈建议,不管是从哪下载的脚本,保存前都要看一遍源码,至少确认以下几点:
- 脚本里面有没有包含外部网址的请求,尤其是那些跟问卷功能无关的URL。
- 有没有把填写内容上传到第三方服务器的可疑代码。
- 有没有
eval、Function这类动态执行代码的危险操作。 - 脚本作者更新频率如何,评论区有没有人反馈问题。
用户脚本的权限很大,它能读取你当前页面上的所有内容。一个不起眼的恶意脚本,完全可以在你不知情的情况下把表单数据外传。这不是危言耸听,而是我们在2026年这个脚本生态里必须养成的习惯。
6.2 问卷平台改版后脚本失效怎么办
页面改版是用户脚本保持长期可用的最大敌人。改版可能只是改了某个字段的name,也可能是把整个表单从jQuery重构成了Vue,这两者都会让脚本瞬间失效。我的处理习惯是:
- 给脚本加上
@version号,每次修复后递增,配合Tampermonkey自动更新机制。 - 把选择器集中封装在
selectors对象里,页面改版时优先改这里。 - 记录页面结构变化,这类信息用注释写在脚本头部,方便下次排查。
还有一个经验是:不要过度依赖选择器把脚本写得太死。比如有的问卷选项文本变化频繁,直接按显示文本去匹配“同意”或“不同意”,会比匹配死板的value值更抗变化。
6.3 关于自动填表的边界与口碑
站在我们普通用户的角度,用油猴脚本自动填表,最大的价值是把自己从重复劳动里解放出来。比如每周都要填的项目周报问卷、多次出现的满意度调查,这类场景用脚本提升效率完全合理。但拿脚本去刷量、刷奖品、制造垃圾数据,不仅容易引发平台反作弊机制,也会污染数据质量,最终让大家都失去可信赖的问卷环境。
我的原则始终是:脚本只处理“我是一个打字慢、手速不快、但确实认真在填的人”的场景,它帮我减少重复点击,但不替代我的真实判断。对于需要主观回答、需要真实意见的题目,我会把运行开关点掉,自己手动打几个字。这样既保留了效率工具的意义,也守住了使用工具的边界。
6.4 从这里还可以继续扩展的方向
问卷自动填表只是油猴入门的第一个练习场景。熟悉了用户脚本的编写逻辑之后,你能做的东西其实很多:
- 自动整理页面数据:定时抓取目标页面上的信息,再用
GM_setValue保存,形成自己的数据日志。 - 表单配置模板化管理:把答案按用户身份分了多套配置,在不同场景下自动切换。
- 与本地服务通信:通过
GM_xmlhttpRequest访问自己电脑上运行的小服务,实现更多自动化流程。 - 更友好的交互:在页面上注入一个控制面板,自己选择填哪套配置、是否自动提交。
这些进阶玩法本质上都是同一套能力:理解页面、控制页面、让页面按照你的意图执行。有了问卷脚本打底,后面的路会非常顺。
我在实际写脚本的过程中,最大的体会是:不要急着写代码,先把页面结构吃透,把目标控件的加载时机、事件绑定方式、嵌套关系这些细节查清楚,脚本的稳定性会高出一个数量级。很多看似“脚本失效”的问题,其实都是页面还没加载完成或者事件没触发对。最后再提醒一句,自动填表脚本请在你有权限填写、并且平台允许的范围内使用,让它当效率工具,而不是作弊工具。下次你遇到一份几十道题目的重复问卷,回头看今天读的这篇教程,就知道该怎么省下这十分钟了。
