doupoa
一个不甘落后的热血青年!
Ping通途说

视频数据导出-抖音创作者服务中心

抖音自带的数据导出功能只能导出30天内的最后100条视频。当数据量大时,统计分析每月视频将非常麻烦。因此编写以下脚本用于直接导出视频数据到指定日期,非常实用方便。

脚本仅供学习参考,不得用于商业违法行为。如有侵权请联系删除。

加载方式将代码保存为js文件后再请上网搜索“油猴导入脚本”、“篡改猴导入脚本”。

https://doupoa.site/wp-content/uploads/2023/11/1698985328-image-1024x355.png

以下油猴脚本不再更新,如有需要可以看看下一篇代码

// ==UserScript==
// @name         导出抖音视频数据
// @namespace    doupoa.site
// @version      1.2
// @description  导出抖音视频数据
// @author       doupoa
// @copyright    2024,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-QT0YYw";

    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-yh9YMK";

    var div_node_1_2 = document.createElement('div');
    div_node_1_2.className = "profit-title-JwUHmS";
    div_node_1_2.textContent = "导出视频数据"

    var div_node_1_2_1 = document.createElement('div');
    div_node_1_2_1.className = "main-text-qYVJkl";

    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-VUnTzL")[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);
})();

以下版本的代码直接在网页端使用F12打开开发者工具,随后粘贴代码回车运行即可。

(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 < deadline && !("is_pinned" in data.aweme_list[i])) {
                    // 视频创建时间小于截止日期,退出循环。
                    console.log(createTime + "<" + deadline)
                    resolve(-1)
                    // 返回自定义状态码,终止循环
                } else {
                    console.log(createTime.toLocaleString())
                    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(time) {
        console.log("开始运行..")
        deadline = new Date(time);
        if (deadline == '') {
            alert("请先设置数据截止日期");
            return;
        } else {
            console.log("截止日期为:" + deadline)
            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))
                console.log("当前游标:"+code)
                if (code != -1) {
                    // 自定义状态码为-1时即代表数据已达截止日期
                    cursor = code;
                } else if (code == cursor) {
                    //返回的游标跟上一返回游标一致时退出循环。
                    console.log("游标一致退出循环")
                    break
                } else{
                    console.log("游标为:"+code+",退出循环")
                    break
                }
            }
            let date = new Date();
            export_csv(videoData, date.toLocaleString());
        }
    }

    let time = "2025/01/01 00:00";
    // 数据截止日期,格式为"年/月/日 时:分" 例如:"2025/02/01 00:00" 必须按照格式修改,否则无法识别
    main(time);
}
)();
赞赏

doupoa

文章作者

诶嘿

发表回复

textsms
account_circle
email

  • 11

    为啥用不了

    4 月前 回复
    • doupoa博主

      @11: 脚本已更新,请覆盖旧脚本并重试

      3 月前

Ping通途说

视频数据导出-抖音创作者服务中心
抖音自带的数据导出功能只能导出30天内的最后100条视频。当数据量大时,统计分析每月视频将非常麻烦。因此编写以下脚本用于直接导出视频数据到指定日期,非常实用方便。 脚本仅供学习…
扫描二维码继续阅读
2023-11-03

Optimized by WPJAM Basic