
发布于 2026-08-30 19:51更新于 2026-09-11 13:01332浏览看到官方「内容捕手第五期」活动,本来想着找个案例参加一下。
结果想了半天,居然有点想不起来自己还有什么解决不了的日期难题。(凡尔赛一下 😂)
以前做过不少自动化发布视频、图文内容的平台,基本都有定时发布功能。
那些平台的日期选择器其实都没什么特别大的难点,正常定位元素、点击、选择日期和时间,基本就解决了。
于是想着:
既然活动主题是日期选择器,那就找个真正有点难度的。
然后一番寻找之后,碰到了腾讯系相关网页,视频号、微信小店等场景。




这次的日期选择器就比较典型了:
元素藏在 Shadow DOM 里面
外面看到的是一个 input
但是 input 是 readonly
不能直接输入
点击后还涉及复杂的日期、时间组件
页面使用 Vue
而且最终真正控制日期的,并不是这个 input 本身
一开始我的思路当然也很传统:
找到 input
↓
修改 input.value
↓
触发 input/change
↓
希望页面识别
结果:
没用。
然后开始一步一步排查。
页面结构大致是:
<wujie-app>
#shadow-root
<input type="text" readonly>
</wujie-app>
普通的:
document.querySelector(...)
根本找不到。
于是先进入 wujie-app 的 shadowRoot:
const input=[...document.querySelectorAll('wujie-app')].flatMap(x=>x.shadowRoot?[...x.shadowRoot.querySelectorAll('input.weui-desktop-form__input[placeholder="请选择发表时间"]')]:[])[0];
终于拿到了 input。
但新的问题来了。
这个 input:
<input type="text" readonly placeholder="请选择发表时间">
显然不是让你直接输入的。
于是尝试修改:
input.value='2026-09-10 12:15';
以及使用原生 setter:
const setter=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'value').set;
setter.call(input,'2026-09-10 12:15');
再触发:
input
change
blur
依然没有真正解决。
这时候就开始怀疑:
这个 input 可能只是显示层,真正的数据根本不在这里。
继续查看这个 input 的父级:
INPUT
↓
SPAN
↓
DIV
↓
SPAN
↓
DT
↓
DL.weui-desktop-picker__date
这个:
.weui-desktop-picker__date
看起来就是日期选择器组件本体。
然后尝试:
input.closest('.weui-desktop-picker__date').__vue__
没想到:
直接拿到了 Vue 实例。
这一下思路就完全变了。
之前一直在研究:
怎么操作这个 input?
现在变成:
这个 input 背后的 Vue 组件到底保存了什么?
拿到实例:
const picker=input.closest('.weui-desktop-picker__date').__vue__;
然后看它有哪些方法:
Object.keys(picker.$options.methods||{})
发现里面有:
handleDaySelect
handleTimeSelect
handleTimeChange
setTime
handleMonthSelect
handleYearSelect
……
再看几个关键状态:
picker.selectedDate
picker.currentDate
picker.inputValue
picker.pick
picker.time
picker.format
其中最关键的就是:
picker.pick
它实际上就是这个日期选择器内部使用的时间戳。
于是尝试:
picker.pick=new Date(2026,7,30,20,0,0).getTime();
成功了。
这时候突然发现:
根本不需要点日期选择器。
也不需要:
模拟鼠标
↓
点击日期
↓
点击时间
↓
点击确认
而是:
找到 Vue 实例
↓
找到真正的数据
↓
直接修改
↓
让 Vue 自己更新页面
一开始以为:
一个时间戳
直接设置完整的:
2026-09-10 12:15
就完事了。
结果发现:
日期成功,时间不对。
继续扒组件内部代码,发现:
function(t,e){
t.setHours(e.getHours()),
t.setMinutes(e.getMinutes()),
t.setSeconds(e.getSeconds())
}
原来这个组件的日期和时间是分开处理的。
所以最终才确定下来:
先设置日期
↓
等待 Vue 更新
↓
再调用 setTime()
↓
最后更新完整 pick
最终在浏览器控制台验证成功:
const input=[...document.querySelectorAll('wujie-app')].flatMap(x=>x.shadowRoot?[...x.shadowRoot.querySelectorAll('input.weui-desktop-form__input[placeholder="请选择发表时间"]')]:[])[0];
const picker=input.closest('.weui-desktop-picker__date').__vue__;
const dateTime='2026-09-10 12:15';
const [date,time]=dateTime.split(' ');
const [year,month,day]=date.split('-').map(Number);
const [hour,minute]=time.split(':').map(Number);
const dateObj=new Date(year,month-1,day);
const timeObj=new Date(1970,0,1,hour,minute,0);
picker.pick=dateObj.getTime();
setTimeout(()=>{
const d=new Date(picker.pick);
picker.setTime(d,timeObj);
picker.pick=d.getTime();
console.log('设置成功:',picker.inputValue);
},0);
把代码放进影刀 JS 模块以后,发现:
F12 能运行,不代表影刀环境就完全一样。
于是开始增加日志,把:
picker.pick
picker.selectedDate
picker.currentDate
picker.inputValue
input.value
全部打印/返回出来。
结果又发现一个非常有意思的问题:
Vue 内部状态已经变成了目标日期:
picker.selectedDate
picker.inputValue
都已经正确。
但是:
input.value
还可能是旧值。
也就是说:
Vue 状态已经更新
↓
DOM 还没完成更新
这也解释了为什么前面会出现一些看起来“明明设置成功了,页面却不对”的情况。
影刀传入的参数有一次实际上是:
"2026-09-16"
而不是:
2026-09-16
多了一层字面量引号。
结果:
String(dateTime).trim().split('-')
解析出来当然不对。
最后增加:
String(dateTime).trim().replace(/^["']|["']$/g,'')
兼容这种情况。
最终用于影刀的版本:
function(element,dateTime){
const input=[...document.querySelectorAll('wujie-app')].flatMap(x=>x.shadowRoot?[...x.shadowRoot.querySelectorAll('input.weui-desktop-form__input[placeholder="请选择发表时间"]')]:[])[0];
if(!input)throw new Error('未找到发表时间输入框');
const picker=input.closest('.weui-desktop-picker__date')?.__vue__;
if(!picker)throw new Error('未找到日期选择器Vue实例');
const value=String(dateTime).trim().replace(/^["']|["']$/g,'');
const [date,time]=value.split(/\s+/);
if(!date||!time)throw new Error('dateTime格式必须为 YYYY-MM-DD HH:mm');
const [year,month,day]=date.split('-').map(Number);
const [hour,minute]=time.split(':').map(Number);
if([year,month,day,hour,minute].some(isNaN))throw new Error('dateTime格式错误,必须为 YYYY-MM-DD HH:mm');
const dateObj=new Date(year,month-1,day);
const timeObj=new Date(1970,0,1,hour,minute,0);
picker.pick=dateObj.getTime();
setTimeout(()=>{
const d=new Date(picker.pick);
picker.setTime(d,timeObj);
picker.pick=d.getTime();
},0);
return null;
}
输入:
2026-09-10 12:15
最终可以直接修改日期和时间。
其实这次解决的根本不是一个“腾讯/微信日期选择器”。
日期选择器只是一个例子。
真正值得记录的是一个网页自动化的思路:
找到元素
↓
点击
↓
输入
↓
选择
↓
确认
这种方式本质上是在:
模拟用户操作 UI。
而另一种思路是:
找到元素
↓
找到背后的组件
↓
找到组件的数据状态
↓
直接修改数据
↓
让前端框架自己完成渲染
也就是:
UI 只是数据的表现形式。
如果一个网页使用了 Vue、React、Angular 等前端框架,而我们又能够找到它背后的组件实例、状态对象、数据模型,甚至网站本身就暴露了相关数据对象,那么很多时候就没必要再模拟人工操作。
例如:
输入框
下拉框
日期选择器
时间选择器
单选框
复选框
表单
都有可能存在类似的思路。
当然,这并不意味着存在一个真正万能的“直接改值函数”。
不同网站、不同框架、不同组件,数据存放的位置完全可能不同。
真正通用的,其实不是某一段代码。
而是一套排查思路:
UI 操作困难
↓
先找元素
↓
判断是不是 Shadow DOM
↓
判断是不是框架组件
↓
寻找组件实例/状态
↓
寻找真正的数据源
↓
直接修改数据
↓
观察框架是否自动同步 UI
所以以后再遇到一个:
“这个控件怎么都点不动、输不进去、元素定位特别难”的网页
除了继续研究 XPath、CSS、鼠标模拟、键盘模拟之外,还可以换一个方向:
别急着操作它,先看看它背后的数据是什么。
这可能才是网页自动化里更值得掌握的一种思路。