油猴脚本 之 网教通直播评论记录抓取
动机
看一看一天下来,谁在刷屏,谁在搞事呢。
我的一个同学用 Python 写了一个(Github 传送门)。于是我想用我自己的方法也实现一个,也让自己明白,自己每天上课在说些什么……
思路
通过浏览器的开发人员工具,可以发现,这个聊天框其实是一个指向 recreation-chat.sdp.101.com 域名下一个页面的 iframe
。因此我们需要声明作用域为:
// ==UserScript==
// ...
// @match https://recreation-chat.sdp.101.com/*
// ...
// ==/UserScript==
我们知道,我们之所以要去抓取这个,就是因为网教通的评论区里面只保留最近 100 条消息,其他的会被折叠,需要点击按钮才可以展开。那简单,定位到那个按钮,一句话的事:
var timer = setInterval(function () {
if (document.querySelector('div._3Bl-4NTv1A._3bGFQ6biuP'))
document.querySelector('div._3Bl-4NTv1A._3bGFQ6biuP').click();
}, 100);
即每 100ms(0.1s)模拟点击一次按钮。
可是这也带来了一个问题:上面的代码,可以视作一个死循环,没有终点。所以我们需要能够停止这个 timer
。我的想法是通过按下 Esc
键来停止点击,即:
document.onkeydown = function (e) {
var ev = e || window.event;
if (ev.keyCode == 27) { //Esc 键的 keycode 是 27
clearInterval(timer);
}
}
然后呢?然后我们就需要拿到这里已经加载出来的聊天记录,即聊天记录区域元素(其 id
为 msg-flow-wrapper
)的 innerHTML
。即
var data = document.getElementById('msg-flow-wrapper').innerHTML;
于是我们可以获取到(以下是已经把元素的所有不必要属性 去掉 并进行 代码格式化 之后的结果,以下是只有 一条 消息的情况):
<div>
查看更多消息
</div>
<div>
<div>
<div>
<div>
<div>
<img src="..." />
</div>
<div>
<div>
<span><span title="林**">林**</span></span>
</div>
<div>
04-22 17:14:20
</div>
<div>
<div>
<span><span>噗哈哈哈哈哈</span></span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
%¥#=^&$…@,这谁看得下去。必须整理这些HTML代码。开始吧。
首先是对HTML标签的处理。全部先换成换行符。正则表达式上场:
data = data.replace(/<[^>]+>/g, "\n");
导出来,发现有一个地方不对劲:理论上来说,每一条消息的结构都应该是一样的,怎么上一条的消息内容与下一条消息之间的换行符数量时多时少?而且有的消息内容还会被折成两行?
问题就在于其中添加的表情包。因为表情其实是一张图片,即一个 <img>
标签,所以也会被替换成换行符。解决方法很简单,在上面的那句话之前添上
data = data.replace(/<img[^>]+>/g, "");
即可,把所有的 <img>
标签都先去掉。
然后数一下,消息和消息之间应有 19 个换行符,名字与时间之间有 4 个换行符,时间与消息内容之间有 5 个换行符。由于4 个换行符是 5 个换行符的字串,5 个换行符又是 19 个换行符的字串,所以要注意排序的问题。
data = data.replace(/\n{19}/g, '\n');
data = data.replace(/\n{5}/g, ',');
data = data.replace(/\n{4}/g, ',');
另外要把顶上的那个“查看更多消息”或“数据加载中...”的字样删去:
data = data.replace(/^\n[^\n]+\n{2}/, "");
删去头尾多加的逗号:
data = data.replace(/^,{2}/, "");
data = data.replace(/,{2}$/, "");
看上去我们好像成功地处理出了有格式的字符串。但是你有没有想过:如果这个元素的 HTML 代码里面本来就有换行符怎么办?结构肯定会大乱,因为这种做法是通过 HTML 标签替换成的换行符来确定格式的。所以我们在替换 HTML 标签的时候不应当使用任何可能出现在这串代码里面的字符。
那应该怎么办呢?
那就换成一个不存在的字符吧。网络上经常有人喜欢用“龘”这个字来用作这个“不存在的字符”,但其实这不妥。这个字在 JS 的 Unicode 表示法中编码为\u9F98
,那我们是不是可以动一下手脚……
只要 UTF-8 里面的空间没用完,字符 \uFFFF
就不存在,对吧?它的样子大家很熟悉:�。
于是刚刚的代码整合一下即
var data = document.getElementById('msg-flow-wrapper').innerHTML;
const linebreaker = '\n';
const attrbreaker = ',';
data = data.replace(/<img[^>]+>/g, "");
data = data.replace(/<[^>]+>/g, "\uFFFF");
data = data.replace(/\uFFFF{19}/g, linebreaker);
data = data.replace(/^\uFFFF[^\uFFFF]+\uFFFF{2}/, "");
data = data.replace(/\uFFFF{5}/g, attrbreaker);
data = data.replace(/\uFFFF{4}/g, attrbreaker);
data = data.replace(/\uFFFF/g, "");
data = data.replace(/^,{2}/, "");
data = data.replace(/,{2}$/, "");
然后我们现在把它导出为 .csv
文件。JS 的纯文本文件导出呢,这里给送给大家一个模板:
var element = document.createElement('a');
const blob = new Blob(['你的文件内容']);
element.download = '文件名.扩展名';
element.style = "display: none";
element.href = URL.createObjectURL(blob);
document.body.appendChild(element);
element.click();
setTimeout(function () {
document.body.removeChild(element);
window.URL.revokeObjectURL(blob);
}, 100);
于是就可以直接套用进来:
var element = document.createElement('a');
const blob = new Blob([data]);
element.download = '评论区记录.csv';
element.style = "display: none";
element.href = URL.createObjectURL(blob);
document.body.appendChild(element);
element.click();
setTimeout(function () {
document.body.removeChild(element);
window.URL.revokeObjectURL(blob);
}, 100);
最终程序实现
// ==UserScript==
// @name 评论记录批量加载
// @namespace https://www.cnblogs.com/henrylin/
// @version 0.1
// @description 按 Esc 键停止记录
// @author 林洪平
// @match https://recreation-chat.sdp.101.com/*
// @grant none
// ==/UserScript==
(function () {
'use strict';
var x = false;
var timer = setInterval(function () {
if (document.querySelector('div._3Bl-4NTv1A._3bGFQ6biuP'))
document.querySelector('div._3Bl-4NTv1A._3bGFQ6biuP').click();
}, 100);
document.onkeydown = function (e) {
var ev = e || window.event;
if (ev.keyCode == 27) {//esc
if (x) { alert('Already done!'); return; }
x = true;
alert('Export?');
clearInterval(timer);
var data = document.getElementById('msg-flow-wrapper').innerHTML;
const linebreaker = '\n';
const attrbreaker = ',';
data = data.replace(/<img[^>]+>/g, "");
data = data.replace(/<[^>]+>/g, "\uFFFF");
data = data.replace(/\uFFFF{19}/g, linebreaker);
data = data.replace(/^\uFFFF[^\uFFFF]+\uFFFF{2}/, "");
data = data.replace(/\uFFFF{5}/g, attrbreaker);
data = data.replace(/\uFFFF{4}/g, attrbreaker);
data = data.replace(/\uFFFF/g, "");
data = data.replace(/^,{2}/, "");
data = data.replace(/,{2}$/, "");
var element = document.createElement('a');
const blob = new Blob([data]);
element.download = '评论区记录.csv';
element.style = "display: none";
element.href = URL.createObjectURL(blob);
document.body.appendChild(element);
element.click();
setTimeout(function () {
document.body.removeChild(element);
window.URL.revokeObjectURL(blob);
}, 100);
}
}
})();
这样,只要开启脚本,刷新页面,就会开始批量加载聊天记录,按下 Esc
键停止加载并导出。
注意事项
如果刚刚有认真看,就知道这个脚本不是实时记录,而是抓取之前的记录。所以,脚本应保持常闭状态,否则只要打开直播界面,就会开始不间断抓取评论(除非你每次都按下 Esc
键)。
这样导出来的 .csv 文件编码为 UTF-8,可能用 Excel 打开会乱码。建议直接用记事本打开,复制文本到 Excel 并使用分列功能还原表格。
更新
脚本有更新了!请前往 油猴脚本 之 网教通直播评论记录抓取 v2.0 。
posted on 2020-04-24 13:09 Henrylin666 阅读(851) 评论(0) 编辑 收藏 举报