昨天朋友突然发消息说以前用的B站的评论抽奖控制台脚本没用了,让我看看有没有什么办法。
b站是这样的,在达到一定的要求之前,是没有官方的抽奖工具的,也就是你想搞点评论关注抽奖这种东西是没有官方支持的,所以也就有了一些针对小Up的抽奖工具使用,就比如今天要谈的控制台脚本
先上演示吧:
很简单,只需要你打开对应的页面,然后和动图里面一样,按键盘的F12(笔记本按fn+F12),或者右键,点击检查,然后点击console(中文是控制台),然后把代码复制进去,敲下回车,等页面数据获取完成以后,敲入roll(要抽取的数量)
就可以啦
要复制的代码如下:
console.log('版本ver1.0 视频页和动态页皆可用,评论数大于10时适用,未过滤重复发言的账号,如果遇见重复的,建议按照重复次数重新roll几次')
console.log('版本ver1.1 改进了算法,现在可以过滤重复发言的账号了,只会计算一次')
console.log("程序开始运行");
console.log("定义集合存储数据");
let dataArray=[]
let rollTimes = 0;
console.log("数据载入中...");
// 循环变量
var my_loop;
// 下滑延时 500毫秒 网速/加载速度较慢的朋友们最好放慢速度 提高准确性
var r_time = 500;
// 评论数
var comment_num = 1;
let refer = '';
if(window.origin.match(/:\/\/[a-z]*\./)[0].match(/[a-z]/)[0]==='t'){//动态页
refer = 'comment'
}else{//视频页面
refer = 'total-reply'
}
if (document.getElementsByClassName(refer)[0].innerText.indexOf("万") != -1) {
comment_num = 10000 * (parseInt(document.getElementsByClassName(refer)[0].innerText) + 1);
}else {
comment_num = parseInt(document.getElementsByClassName(refer)[0].innerText);
}
// 下滑
function r() {
window.scroll(0, 1920 * comment_num);
console.log(`已加载${rollTimes+=1}页数据`)
// 没有评论后自动停止下滑 并开始收集数据
if (document.getElementsByClassName("loading-state")[0].innerText == "没有更多评论") {
// 停止下滑循环
stop_r();
// 收集数据
draw();
}
}
// 停止下滑循环
function stop_r() {
clearInterval(my_loop);
}
// 收集数据
function draw() {
var len = document.getElementsByClassName("con").length;
for (var i = 0; i < len; i++) {
let name = document.getElementsByClassName("list-item reply-wrap")[i].getElementsByClassName("con")[0].getElementsByClassName("user")[0].getElementsByTagName("a")[0].innerText;
let id = document.getElementsByClassName("list-item reply-wrap")[i].getElementsByClassName("con")[0].getElementsByClassName("user")[0].getElementsByTagName("a")[0].getAttributeNode("data-usercard-mid").value;
let level = document.getElementsByClassName("list-item reply-wrap")[i].getElementsByClassName("con")[0].getElementsByClassName("user")[0].querySelector('img.level').src.match(/[level]_[0-9]{1}/)[0].match(/\d/)[0]
let content = document.getElementsByClassName("list-item reply-wrap")[i].getElementsByClassName("con")[0].getElementsByClassName("text")[0].innerHTML
if(level>=3){
dataArray.push({name:name,id:id,content:content})
}
}
duplicateRemove(dataArray)
console.log("全部数据加载完毕");
console.log("符合条件共" + dataArray.length + "名用户",dataArray);
console.log('运行roll(要抽取的数量) 例如roll(3) 即会在'+dataArray.length+'名用户中抽取对应数量的幸运用户' );
}
//去除重复发言的账号
function duplicateRemove(array){
for(let z=array.length-1;z>=0;z--){
let item = array[z];
for(let x=z-1;x>=0;x--){
let subitem = array[x];
if(item.id===subitem.id){
array.splice(x,1);
}
}
}
return array
}
// 获取幸运儿
function roll(num) {
if(num===0){
alert('你是故意找茬是不是?抽0人?')
return
}
if(dataArray.length<=0){
alert('符合条件的人为零,抽奖失败');
return
}
if(num>dataArray.length){//抽奖数大于总人数
alert(`抽奖数大于总人数,修正为 ${dataArray.length} 人获奖`)
num = dataArray.length
}
var Lucky={}
var randomArray = this.randomFunc([],num)
randomArray.sort((a,b)=>{
return a-b
})
for (var i = 0; i < randomArray.length; i++) {
let lucky_num = randomArray[i]
Lucky[i+1]={
'用户ID':dataArray[lucky_num].id,
'用户名': dataArray[lucky_num].name,
'评论内容': dataArray[lucky_num].content
}
}
console.table(Lucky,['用户ID','用户名','评论内容'])
}
//递归算法 排除取到相同的随机数。
function randomFunc(array,total){
if(total<=0) {
return array
};
//随机数算法中,如果使用的是Math.round这种左右都能取到的随机数算法,在抽奖数越接近评论数的时候,就会遇到边际问题,长度为9的数组,会被取到0-9,实际上应该是0-8,所以采用Math.floor方法。
let num = Math.floor(Math.random() * (dataArray.length),10);
if(array.length>0){
if(this.cycle(array,num)){
array.push(num);
total-=1;
}
return this.randomFunc(array,total);
}else{
array.push(num);
total-=1
return this.randomFunc(array,total);
}
}
function cycle(array, num) {
for (let x = 0; x < array.length; x++) {
if (array[x] === num) {
return false
}
}
return true
}
// 开始自动下滑 r_time毫秒一次
my_loop = setInterval(r, r_time);
// 全部数据加载完毕后,使用 roll(中奖数) 抽取中奖者
对于数据我已经做了筛选,首先是只有等级大于等于3级的用户会被纳入到抽取范围内,其次是发了多次评论的同一个账号,只会被计算一次,不用担心疯狂发评论的人被抽到的概率会大。所有人都是一视同仁,几率相同。
对于普通用户看到这里其实就行了。
下面开始讲解代码思路
首先这个脚本的来源是 b站用户 Love丶伊卡洛斯
没有TA的基础代码,也就没有现在的改进版抽奖代码
实现的思路就是在代码中控制浏览器的Y轴滚动,不停的往下滚动直到所有的评论都被加载出来,然后收集评论人的已有信息,再进行各种过滤筛选,比如筛掉用户等级不足的,用户ID多次出现的,只算作一次这种。
var comment_num = 1;
let refer = '';//这个代表评论的来源,b站中动态页和视频页的页面结构不同,所以要分开处理
if(window.origin.match(/:\/\/[a-z]*\./)[0].match(/[a-z]/)[0]==='t'){//动态页
refer = 'comment'
}else{//视频页面
refer = 'total-reply'
}
if (document.getElementsByClassName(refer)[0].innerText.indexOf("万") != -1) {
comment_num = 10000 * (parseInt(document.getElementsByClassName(refer)[0].innerText) + 1);
}else {
comment_num = parseInt(document.getElementsByClassName(refer)[0].innerText);
}
稍微有点前端基础的人理解上面的代码是没有问题的,就是找到总共有多少个评论
拿到评论数以后,就大致的计算一下要往下滚动多少距离,等出现‘没有更多评论’的时候,就停止滚动。
// 下滑
function r() {
window.scroll(0, 1920 * comment_num);
console.log(`已加载${rollTimes+=1}页数据`)
// 没有评论后自动停止下滑 并开始收集数据
if (document.getElementsByClassName("loading-state")[0].innerText == "没有更多评论") {
// 停止下滑循环
stop_r();
// 收集数据
draw();
}
}
来看看核心方法收集数据draw()
// 收集数据
function draw() {
var len = document.getElementsByClassName("con").length;
for (var i = 0; i < len; i++) {
let name = document.getElementsByClassName("list-item reply-wrap")[i].getElementsByClassName("con")[0].getElementsByClassName("user")[0].getElementsByTagName("a")[0].innerText;
let id = document.getElementsByClassName("list-item reply-wrap")[i].getElementsByClassName("con")[0].getElementsByClassName("user")[0].getElementsByTagName("a")[0].getAttributeNode("data-usercard-mid").value;
let level = document.getElementsByClassName("list-item reply-wrap")[i].getElementsByClassName("con")[0].getElementsByClassName("user")[0].querySelector('img.level').src.match(/[level]_[0-9]{1}/)[0].match(/\d/)[0]
let content = document.getElementsByClassName("list-item reply-wrap")[i].getElementsByClassName("con")[0].getElementsByClassName("text")[0].innerHTML
if(level>=3){
dataArray.push({name:name,id:id,content:content})
}
}
duplicateRemove(dataArray)
console.log("全部数据加载完毕");
console.log("符合条件共" + dataArray.length + "名用户",dataArray);
console.log('运行roll(要抽取的数量) 例如roll(3) 即会在'+dataArray.length+'名用户中抽取对应数量的幸运用户' );
}
首先len的意义就是得到页面上有多少个主评论,也就是评论中的回复是不算在内的
上图可以看到,一个con
就是一个评论
然后再把所有的con进行循环,拿到对应的id数据,名称数据,评论内容数据
然后在这里进行判断,当用户的等级小于3级的时候,就不要这条数据。
然后注意,这时候的dataArray
是所有除了3级以下的用户的数据都会被放进去,所以这时候要对数据进行筛选,如果同一个用户评论了多条,那在数据里他就会出现多次,这对其他评论一次的用户是不够平的,所以要对数据进行筛选,排除掉多余的相同数据。也就是duplicateRemove()
方法
//去除重复发言的账号
function duplicateRemove(array){
for(let z=array.length-1;z>=0;z--){
let item = array[z];
for(let x=z-1;x>=0;x--){
let subitem = array[x];
if(item.id===subitem.id){
array.splice(x,1);
}
}
}
return array
}
一个简单的双重循环,自己和自己对比,如果id相同,就第二个开始相同的数据全部清除掉
把数据清理过以后,现在内存里就是可以抽奖的符合要求的用户数据了,下面就是抽奖的方法
// 获取幸运儿
function roll(num) {
if(num===0){
alert('你是故意找茬是不是?抽0人?')
return
}
if(dataArray.length<=0){
alert('符合条件的人为零,抽奖失败');
return
}
if(num>dataArray.length){//抽奖数大于总人数
alert(`抽奖数大于总人数,修正为 ${dataArray.length} 人获奖`)
num = dataArray.length
}
var Lucky={}
var randomArray = this.randomFunc([],num)
randomArray.sort((a,b)=>{
return a-b
})
for (var i = 0; i < randomArray.length; i++) {
let lucky_num = randomArray[i]
Lucky[i+1]={
'用户ID':dataArray[lucky_num].id,
'用户名': dataArray[lucky_num].name,
'评论内容': dataArray[lucky_num].content
}
}
console.table(Lucky,['用户ID','用户名','评论内容'])
}
var lucky={}
前面的判断可以不用看,锦上添花的作用
下面的var randomArray = this.randomFunc([],num)
这一段代码可得好好说说
这段代码的作用就是根据用户输入的数字num
,产生随机num
个不重复的整数,并且返回这些整数的数组,给randomArray
接收,即最终返回的结果是类似于num = 4 ;randomArray=[5,6,8,9]
这样的数据。
下面好好讲讲随机数组是怎么产生的
//递归算法 排除取到相同的随机数。
function randomFunc(array,total){
if(total<=0) {
return array
};
//随机数算法中,如果使用的是Math.round这种左右都能取到的随机数算法,在抽奖数越接近评论数的时候,就会遇到边际问题,长度为9的数组,会被取到0-9,实际上应该是0-8,所以采用Math.floor方法。
let num = Math.floor(Math.random() * (dataArray.length),10);
if(array.length>0){
if(this.cycle(array,num)){
array.push(num);
total-=1;
}
return this.randomFunc(array,total);
}else{
array.push(num);
total-=1
return this.randomFunc(array,total);
}
}
function cycle(array, num) {
for (let x = 0; x < array.length; x++) {
if (array[x] === num) {
return false
}
}
return true
}
首先randomFunc
方法接收两个参数,一个是数组array
,一个是total
即用户输入的抽奖个数,也就是循环次数,如果产生了total
个随机数,就会结束递归。let num = Math.floor(Math.random() * (dataArray.length),10);
这段代码的意思就是如果现在有18个用户,那么num
的范围就是[0,18)
,也就是会取到0,但是取不到18,只能取到17,如果不这样处理,在抽奖数趋近于用户总数的时候就会报错,因为在js里,数据不是从1开始,是从0开始,也就是一个长度为5的数组,序号是[0,4]
,也就是0,1,2,3,4 而不是1数到5,所以需要这样的处理
拿到取值范围内的num
后,如果array
的长度为0,就直接把num
放到数组里然后把循环次数-1,继续randomFunc
方法,如果array
里面已经有数据了,就需要对array
进行循环,和产生的随机数num
对比(也就是cycle
方法),如果array
里已经有了该随机数,就打回去重新生成随机数,直到array
里没有和num
相同的数据,再把num
塞到array
中,然后把循环次数-1,重复randomFunc
方法,直到total
也就是循环次数为0的时候,返回array
要指出的是,递归中,如果没有return 循环方法
而是直接执行循环方法的话,最后结束里的return 后的数据是不会返回到方法外的,会直接返回undefined
.
以上就是这次实践过程中得到的经验了,虽然已经干了蛮久的前端,但是还没有想过在浏览器中直接根据页面获取数据进行编程,这种编程体验蛮有趣,也蛮有意思的.