抖音自带的数据导出功能只能导出30天内的最后100条视频。当数据量大时,统计分析每月视频将非常麻烦。因此编写以下脚本用于直接导出视频数据到指定日期,非常实用方便。
脚本仅供学习参考,不得用于商业违法行为。如有侵权请联系删除。
加载方式将代码保存为js文件后再请上网搜索“油猴导入脚本”、“篡改猴导入脚本”。
// ==UserScript==
// @name 导出抖音视频数据
// @namespace doupoa.site
// @version 0.1
// @description 导出抖音视频数据
// @author doupoa
// @copyright 2023,doupoa.site
// @license MIT
// @match https://creator.douyin.com/creator-micro/content/manage
// @icon https://p1-ecda.byteimg.com/tos-cn-i-n15nrygpm8/563a38e870c441b8af75c0c8c38b1a39~tplv-n15nrygpm8-image.image
// @grant none
// ==/UserScript==
(function () {
'use strict';
var videoData = "视频标题,发布时间,播放量,点赞量,评论量,转发量,状态,视频ID,链接\r\n";
var deadline = '';
function formatDate(value) {
try {
value = value.replace("年", "-").replace("月", "-").replace("日", "")
return Date.parse(value)
} catch {
return value
}
}
// 视频标题,发布时间,播放量,点赞量,评论量,转发量,状态,视频ID,链接
function parseVideoData(data) {
data = JSON.parse(data);
return new Promise((resolve) => {
for (var i = 0; i < data.aweme_list.length; i++) {
let title = data.aweme_list[i].desc.replace(/[\r\n]/g, "") //视频标题 去除回车换行符
let createTime = new Date(data.aweme_list[i].create_time * 1000) //发布时间
let playCount = data.aweme_list[i].statistics.play_count // 播放量
let diggCount = data.aweme_list[i].statistics.digg_count //点赞量
let commentCount = data.aweme_list[i].statistics.comment_count //评论量
let forwardCount = data.aweme_list[i].statistics.forward_count //转发量
let status = data.aweme_list[i].status.private_status ? "私密" : "已发布" //状态
let videoID = data.aweme_list[i].aweme_id //视频ID
let shareUrl = data.aweme_list[i].share_url //视频分享链接
if (createTime < formatDate(deadline)) { // 视频创建时间小于截止日期,退出循环。
resolve(-1) // 返回自定义状态码,终止循环
} else {
videoData += title + "," + createTime.toLocaleString() + "," + playCount + "," + diggCount + "," + commentCount + "," + forwardCount + "," + status + "," + videoID + "," + shareUrl + "\r\n"
}
}
resolve(data.max_cursor)
})
}
function doGet(url) {
return new Promise((resolve, reject) => {
let req = new XMLHttpRequest();
req.open("Get", url);
req.send();
req.onload = function () {
if (req.status == 200) {
resolve(req.response);
}
else {
reject(Error("Network Error"));
}
};
req.onerror = function () {
reject(Error("Network Error"));
};
});
}
function export_csv(data, name) {
const blob = new Blob(["\ufeff" + data], {
type: 'text/csv;charset=utf-8;'
});
// 如果是 IE 浏览器
if (window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveOrOpenBlob(blob, name + ".csv");
} else {
const link = document.createElement('a');
const url = URL.createObjectURL(blob);
link.href = url;
link.setAttribute('download', name + ".csv");
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
}
async function main() {
deadline = document.getElementById('deadline').value;
if (deadline == '') {
alert("请先设置数据截止日期");
return;
} else {
var cursor = 0 // 游标
while (true) {
let code = await parseVideoData(await doGet("https://creator.douyin.com/web/api/media/aweme/post/?count=20&max_cursor=" + cursor))
if (code != -1) { // 自定义状态码为-1时即代表数据已达截止日期
cursor = code;
} else if (code == cursor) { //返回的游标跟上一返回游标一致时退出循环。
break
} else {
break
}
}
let date = new Date();
export_csv(videoData, date.toLocaleString());
}
}
function createElement() {
var div_root = document.createElement('div');
div_root.className = "container--2UxXM";
var div_node_1 = document.createElement('div');
div_node_1.className = "info-content";
var div_node_1_1 = document.createElement('div')
div_node_1_1.className = "profit-info--4SoCI";
var div_node_1_2 = document.createElement('div');
div_node_1_2.className = "profit-title--pXNAj";
div_node_1_2.textContent = "导出视频数据"
var div_node_1_2_1 = document.createElement('div');
div_node_1_2_1.className = "main-text--2_OUZ";
var div_node_1_2_1_1 = document.createElement('div');
div_node_1_2_1_1.textContent = "数据截止于"
var div_node_1_2_1_2 = document.createElement('input')
div_node_1_2_1_2.type = "date"
div_node_1_2_1_2.id = "deadline"
div_node_1_2_1_2.name = "deadline"
var div_node_2 = document.createElement('div');
div_node_2.className = "btn-group--1izHb";
var div_node_2_1 = document.createElement('button');
div_node_2_1.className = "semi-button semi-button-tertiary semi-button-light";
div_node_2_1.type = "button";
div_node_2_1.addEventListener("click", main);
var div_node_2_1_1 = document.createElement('span');
div_node_2_1_1.className = "semi-button-content"
div_node_2_1_1.textContent = "导出";
div_node_2_1.appendChild(div_node_2_1_1);
div_node_2.appendChild(div_node_2_1);
div_node_1_2_1.appendChild(div_node_1_2_1_1);
div_node_1_2_1.appendChild(div_node_1_2_1_2);
div_node_1_1.appendChild(div_node_1_2);
div_node_1_1.appendChild(div_node_1_2_1);
div_node_1.appendChild(div_node_1_1);
div_root.appendChild(div_node_1);
div_root.appendChild(div_node_2);
var doc = document.getElementsByClassName("profit-container--3YxDf")[0]
doc.appendChild(div_root);
/*
上述构造元素如下:
<div class="container--2UxXM">
<div class="info-content">
<div class="profit-info--4SoCI">
<div class="profit-title--pXNAj">导出视频数据</div>
<div class="main-text--2_OUZ">
<div>数据截止于</div>
<input type="date" id="start" name="deadline" value="">
</div>
</div>
</div>
<div class="btn-group--1izHb">
<button class="semi-button semi-button-tertiary semi-button-light" type="button">
<span class="semi-button-content">导出</span>
</button>
</div>
</div>
*/
}
window.setTimeout(() => {
createElement(); // 创建用户界面节点
}, 8000);
})();
发表回复