心理学线上实验进阶 (2024-2025)
自上次填坑后,又陆陆续续做了两三个线下兼线上的实验,有了更多的实践体验,因而又到了可以稍作总结分享的时候。和上一篇博文相比,现在的技术路线应该会更成熟一些。
即便对于愿意花费时间研究更自主可控的线上实验范式(这里特指设计、部署、数据收集过程)的人而言,投入的性价比也是需要衡量的问题。由此出发,我们主要讨论最能改善实验实施体验的部分,提供一些本可以做而没有做、而且不难做的功能,或者尽可能澄清上一篇博文没有讲清楚的部分。
实验部署
我们简单回顾线上实验的工作方式。在以 PsychoJS 为代表的线上实验框架中,每个实验的核心交互都是通过静态网页完成的。除非有用户插入的自定义代码,实验从开始到结束经过的过程如下:
- 打开网址,浏览器根据网页 HTML 的引用下载对应的在线资源;
- 实验按预先设计的流程进行,此时浏览器不再需要网络连接;
- 实验结束,收集数据文件,上传到对应的线上平台。
动手操作过 PsychoPy Builder,或者使用过线上实验平台的用户应该都清楚,Builder 可以输出一系列 .html
和 .js
文件,包含了实验所需的文件;同时,正常来说,PsychoPy Builder 会下载实验设置版本的依赖[1],保存到 /lib/
目录下;它们与用户指定的附加资源文件一起构成了运行线上实验所需的全部资源。线上实验平台所做的,不过是将用户打包上传的资源文件解压,提供域名访问,同时重载 /lib/
的 PsychoJS 库,使得实验程序能按照相同的接口向平台服务器上传实验产生的数据。
想要自己动手完成步骤 (1) 的朋友往往容易陷入思维定势,按照传统的思路自己搭建服务器,高估了操作的难度。实际上,现代云计算平台已经提供了大量的轻量级方案。
最简单直接的是(海外云计算的)S3 兼容对象存储。上一次博文中没有对此详细介绍,这次酌情多加着墨。以 AWS S3 为代表的对象存储相当于扁平化的网盘,不存在目录结构,而是将存储的文件用键值对和元数据的形式存储,键即文件名,可以包含 /
以便按传统目录格式划分层次,值即文件内容;多家云计算平台都提供了类似的服务,用户既可以通过网页控制面板完成上传下载操作,也可以通过各种编程语言的 API 完成自动化操作。重要的是,海外平台(如 AWS S3、Cloudflare R2)会为存储桶(相当于网盘的一个一级存储单元)提供独立的域名,也预期用户使用对象存储部署静态网页。这意味着,就像将实验资源上传到线上平台一样,将资源上传到对象存储的存储桶,就能用给定的域名在公网中访问,平台招募的被试也可以通过这种方式访问实验程序。其中涉及的大部分操作都简单直观,不必我多费口舌,按个人喜好或报价选择一个平台即可(从易用性出发,推荐 Cloudflare R2)。这种方式部署的实验在 PsychoJS 的设定中属于 “debug” 模式,在实验按正常流程结束后,会将数据文件存储到本地。以本地为主线上为辅,或者不介意让被试手动上传数据文件的用户,到此就可以宣告结束了;希望自己完成数据上传的,参考下文数据整理、上传部分。
其次是使用 Cloudflare Pages 部署,对于 Geek 而言的推荐方案。首先,你同样可以用最简单的方式使用它:将素材打包上传,由 Cloudflare 提供的 pages.dev 域名访问。通常来说,Cloudflare Pages 在内地访问性虽然并非完美,但是也足够好用;在内地之外则是唯一真神[2]。其次,你可以发挥它的更多功效,使用 Git 仓库进行版本管理,此时多个分支都可以同时部署,主域名对应于你指定的 Production 分支,其他分支则可以通过子域名访问,甚至还可以通过 hash 访问每一个节点对应的版本,凡是会使用 Git 的读者都应当不难想象这种特性带来的便利。最后,对实验部署来说不常用的功能是,你的代码产物(程序)可以不在仓库中出现,而是通过仓库中的代码构建,就像各种工具包一样。事实上,我的博客现在正是用这种方式部署的。感恩。
本地调试
上一篇博文我提到了自编简易 HTTP 服务器,但这明显效率太低。实际上,我们有相当多的现成工具:安装 node.js (通过官网或 nvm) 和 serve
即可用 npx serve
启用一个监听本地访问的 HTTP 服务器,它基于扩展名提供 MIME 类型,保证实验页面正常运行。这个工具也可以用于局域网环境的文件共享,详见文档。
数据整理
默认情况下,PsychoJS 会通过 psychoJS 实例的 ExperimentHandler 维护实验数据,每个试次对应一个 Object,最后通过 XLSX 库整理为输出格式。现在,既然我们决定要接管这一部分,摆在面前的就有两个选项:
- 沿用
psychoJS
的数据管理,通过psychoJS.experiment.addData(key, value)
记录自定义数据。此时,翻阅源码可知 value 类型为 Array 时会转换为字符串,为其他类型时则不加处理。这意味着,想通过它存储对象(类比 Python 的字典)时需要特别留心,因为它只会记录一个引用,对象后续的修改都会反映到最终的数据中。一种处理方式是通过structuredClone
保证独立性。在实验结束时,通过psychoJS.experimentHandler._trialsData.slice()
可以得到一个Array[Object]
记录的全部数据,你可以像 PsychoJS 一样整理成 csv,但也不妨直接JSON.stringify
序列化为 JSON,这可以避免大量空列的产生。 - 自己维护数据记录。这当然是最便于定制化需求,但也需要额外工作的方式。
数据上传,简易版
现在介绍基于 S3 兼容对象存储的(简易)上传方式。上文不加解释地引出了这个术语,现在是时候稍作介绍了。
数据上传,复杂版
(待填坑)
问卷嵌入
(待填坑)