// ==UserScript== // @name 抖音用户主页数据下载 // @namespace http://tampermonkey.net/ // @version 0.2.3 // @description 下载抖音用户主页数据! // @author xxmdmst // @match https://www.douyin.com/user/* // @icon https://lf1-cdn-tos.bytegoofy.com/goofy/ies/douyin_web/public/favicon.ico // @grant none // @license MIT // @downloadURL none // ==/UserScript== (function () { 'use strict'; window.aweme_list = []; let userKey = [ "昵称", "关注", "粉丝", "获赞", "抖音号", "IP属地", "年龄", "签名", "作品数", "主页" ]; window.userData = []; let timer; function extractDataFromScript() { const scriptTag = document.getElementById('RENDER_DATA'); if (!scriptTag) return; let data = JSON.parse(decodeURIComponent(scriptTag.innerHTML)); for (const prop in data) { if (data.hasOwnProperty(prop) && prop !== "_location" && prop !== "app") { const user = data[prop]; let userInfo = user.user.user; userData.push( userInfo.nickname, userInfo.followingCount, userInfo.mplatformFollowersCount, userInfo.totalFavorited, (userInfo.uniqueId === "" ? userInfo.uniqueId : userInfo.shortId), userInfo.ipLocation, userInfo.age, '"' + (userInfo.desc === undefined ? '' : userInfo.desc) + '"', userInfo.awemeCount, "https://www.douyin.com/user/" + userInfo.secUid ); let post_data = user.post.data.map(item => Object.assign( {"awemeId": item.awemeId, "desc": item.desc}, item.stats, { "date": new Date(item.createTime * 1000).toLocaleString(), "url": "https:" + item.video.playAddr[0].src })); aweme_list = aweme_list.concat(post_data); } } timer = setTimeout(() => createDownloadButton(), 1000); } function createDownloadButton() { let targetNodes = document.querySelectorAll("ul.EZC0YBrG > li.Eie04v01 > div > a"); for (let i = 0; i < targetNodes.length; i++) { let targetNode = targetNodes[i]; if (targetNode.dataset.added) continue; const button = document.createElement("button"); button.textContent = "下载"; button.style.position = "absolute"; button.style.right = "5px"; button.style.top = "5px"; button.style.opacity = "0.5"; button.addEventListener("click", function (event) { event.preventDefault(); event.stopPropagation(); let x = new XMLHttpRequest(); x.open('GET', aweme_list[i].url.replace("http://", "https://"), true); x.responseType = 'blob'; x.onload = (e) => { let a = document.createElement('a'); a.href = window.URL.createObjectURL(x.response); a.download = aweme_list[i].awemeId + ".mp4"; a.click() }; x.send() }); targetNode.appendChild(button); targetNode.dataset.added = true; } } function createButton(title, top) { top = top === undefined ? "60px" : top; const button = document.createElement('button'); button.textContent = title; button.style.position = 'fixed'; button.style.right = '5px'; button.style.top = top; button.style.zIndex = '90000'; document.body.appendChild(button); return button } function txt2file(txt, filename) { const blob = new Blob([txt], {type: 'text/plain'}); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = filename.replace(/[\/:*?"<>|]/g, ""); document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); } function downloadData() { let text = userKey.join(",") + "\n" + userData.join(",") + "\n\n"; text += "作品描述,点赞数,评论数,收藏数,分享数,发布时间,下载链接\n"; aweme_list.forEach(item => { text += ['"' + item.desc + '"', item.diggCount, item.commentCount, item.collectCount, item.shareCount, item.date, item.url].join(",") + "\n" }); txt2file(text, userData[0] + ".csv"); } function interceptResponse() { const originalSend = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.send = function () { const self = this; this.onreadystatechange = function () { if (self.readyState === 4) { if (self._url.indexOf("/aweme/v1/web/aweme/post") > -1) { var json = JSON.parse(self.response); let post_data = json.aweme_list.map(item => Object.assign( {"awemeId": item.aweme_id, "desc": item.desc}, { "diggCount": item.statistics.digg_count, "commentCount": item.statistics.comment_count, "collectCount": item.statistics.collect_count, "shareCount": item.statistics.share_count }, { "date": new Date(item.create_time * 1000).toLocaleString(), "url": item.video.play_addr.url_list[0] })); aweme_list = aweme_list.concat(post_data); if (timer !== undefined) clearTimeout(timer); timer = setTimeout(() => createDownloadButton(), 1000); } } }; originalSend.apply(this, arguments); }; } function scrollPageToBottom() { const SCROLL_DELAY = 1000; // Adjust the delay between each scroll action (in milliseconds) let scrollInterval; function getScrollPosition() { return scrollY || pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0; } function scrollToBottom() { scrollTo(0, document.body.scrollHeight); } function hasReachedBottom() { return getScrollPosition() >= (document.body.scrollHeight - innerHeight); } function scrollLoop() { if (!hasReachedBottom()) { scrollToBottom(); } else { console.log("Reached the bottom of the page!"); clearInterval(scrollInterval); } } function startScrolling() { scrollInterval = setInterval(scrollLoop, SCROLL_DELAY); } let button = createButton('开启自动下拉到底', '60px'); button.addEventListener('click', startScrolling); } // To start scrolling, call the function: scrollPageToBottom(); interceptResponse(); window.onload = () => { extractDataFromScript(); let button = createButton("下载已加载数据", "81px"); button.addEventListener('click', downloadData); }; })();