douyin-user-data-download

下载抖音用户主页数据!

当前为 2023-08-01 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name douyin-user-data-download
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.2.7
  5. // @description 下载抖音用户主页数据!
  6. // @author xxmdmst
  7. // @match https://www.douyin.com/user/*
  8. // @icon https://xxmdmst.oss-cn-beijing.aliyuncs.com/imgs/favicon.ico
  9. // @grant none
  10. // @license MIT
  11. // ==/UserScript==
  12.  
  13. (function () {
  14. 'use strict';
  15. let table;
  16.  
  17. function initGbkTable() {
  18. // https://en.wikipedia.org/wiki/GBK_(character_encoding)#Encoding
  19. const ranges = [
  20. [0xA1, 0xA9, 0xA1, 0xFE],
  21. [0xB0, 0xF7, 0xA1, 0xFE],
  22. [0x81, 0xA0, 0x40, 0xFE],
  23. [0xAA, 0xFE, 0x40, 0xA0],
  24. [0xA8, 0xA9, 0x40, 0xA0],
  25. [0xAA, 0xAF, 0xA1, 0xFE],
  26. [0xF8, 0xFE, 0xA1, 0xFE],
  27. [0xA1, 0xA7, 0x40, 0xA0],
  28. ];
  29. const codes = new Uint16Array(23940);
  30. let i = 0;
  31.  
  32. for (const [b1Begin, b1End, b2Begin, b2End] of ranges) {
  33. for (let b2 = b2Begin; b2 <= b2End; b2++) {
  34. if (b2 !== 0x7F) {
  35. for (let b1 = b1Begin; b1 <= b1End; b1++) {
  36. codes[i++] = b2 << 8 | b1
  37. }
  38. }
  39. }
  40. }
  41. table = new Uint16Array(65536);
  42. table.fill(0xFFFF);
  43. const str = new TextDecoder('gbk').decode(codes);
  44. for (let i = 0; i < str.length; i++) {
  45. table[str.charCodeAt(i)] = codes[i]
  46. }
  47. }
  48.  
  49. function str2gbk(str, opt = {}) {
  50. if (!table) {
  51. initGbkTable()
  52. }
  53. const NodeJsBufAlloc = typeof Buffer === 'function' && Buffer.allocUnsafe;
  54. const defaultOnAlloc = NodeJsBufAlloc
  55. ? (len) => NodeJsBufAlloc(len)
  56. : (len) => new Uint8Array(len);
  57. const defaultOnError = () => 63;
  58. const onAlloc = opt.onAlloc || defaultOnAlloc;
  59. const onError = opt.onError || defaultOnError;
  60.  
  61. const buf = onAlloc(str.length * 2);
  62. let n = 0;
  63.  
  64. for (let i = 0; i < str.length; i++) {
  65. const code = str.charCodeAt(i);
  66. if (code < 0x80) {
  67. buf[n++] = code;
  68. continue
  69. }
  70. const gbk = table[code];
  71.  
  72. if (gbk !== 0xFFFF) {
  73. buf[n++] = gbk;
  74. buf[n++] = gbk >> 8
  75. } else if (code === 8364) {
  76. buf[n++] = 0x80
  77. } else {
  78. const ret = onError(i, str);
  79. if (ret === -1) {
  80. break
  81. }
  82. if (ret > 0xFF) {
  83. buf[n++] = ret;
  84. buf[n++] = ret >> 8
  85. } else {
  86. buf[n++] = ret
  87. }
  88. }
  89. }
  90. return buf.subarray(0, n)
  91. }
  92.  
  93. let aweme_list = [];
  94. let userKey = [
  95. "昵称", "关注", "粉丝",
  96. "获赞", "抖音号", "IP属地",
  97. "年龄", "签名", "作品数", "主页"
  98. ];
  99. let userData = [];
  100. let timer;
  101.  
  102. function extractDataFromScript() {
  103. const scriptTag = document.getElementById('RENDER_DATA');
  104. if (!scriptTag) return;
  105. let data = JSON.parse(decodeURIComponent(scriptTag.innerHTML));
  106.  
  107. for (const prop in data) {
  108. if (prop !== "_location" && prop !== "app") {
  109. let userInfo = data[prop].user.user;
  110. userData.push(
  111. userInfo.nickname, userInfo.followingCount, userInfo.mplatformFollowersCount,
  112. userInfo.totalFavorited, '\t' + (userInfo.uniqueId === "" ? userInfo.uniqueId : userInfo.shortId), userInfo.ipLocation,
  113. userInfo.age, '"' + (userInfo.desc === undefined ? '' : userInfo.desc) + '"', userInfo.awemeCount, "https://www.douyin.com/user/" + userInfo.secUid
  114. );
  115. }
  116. }
  117. timer = setTimeout(() => createDownloadButton(), 1000);
  118. }
  119.  
  120. function copyToClipboard(text) {
  121. try {
  122. const textarea = document.createElement("textarea");
  123. textarea.setAttribute('readonly', 'readonly');
  124. textarea.value = text;
  125. document.body.appendChild(textarea);
  126. textarea.select();
  127. let flag = document.execCommand("copy");
  128. document.body.removeChild(textarea);
  129. return flag;
  130. } catch (e) {
  131. console.log(e);
  132. return false;
  133. }
  134. }
  135.  
  136. function openLink(url) {
  137. const link = document.createElement('a');
  138. link.href = url;
  139. link.target = "_blank";
  140. document.body.appendChild(link);
  141. link.click();
  142. document.body.removeChild(link);
  143. }
  144.  
  145. function createVideoButton(text, top, func) {
  146. const button = document.createElement("button");
  147. button.textContent = text;
  148. button.style.position = "absolute";
  149. button.style.right = "0px";
  150. button.style.top = top;
  151. button.style.opacity = "0.5";
  152. button.addEventListener("click", func);
  153. return button;
  154. }
  155.  
  156. function createDownloadButton() {
  157. let targetNodes = document.querySelectorAll("ul.EZC0YBrG > li.Eie04v01 > div > a");
  158. for (let i = 0; i < targetNodes.length; i++) {
  159. let targetNode = targetNodes[i];
  160. if (targetNode.dataset.added)
  161. continue;
  162. const button2 = createVideoButton("复制链接", "0px", (event) => {
  163. event.preventDefault();
  164. event.stopPropagation();
  165. if (copyToClipboard(aweme_list[i].url))
  166. button2.textContent = "复制成功";
  167. else
  168. button2.textContent = "复制失败";
  169. setTimeout(() => {
  170. button2.textContent = '复制链接';
  171. }, 2000);
  172. });
  173. targetNode.appendChild(button2);
  174. const button3 = createVideoButton("打开链接", "21px", (event) => {
  175. event.preventDefault();
  176. event.stopPropagation();
  177. openLink(aweme_list[i].url);
  178. });
  179. targetNode.appendChild(button3);
  180. const button = createVideoButton("下载", "42px", (event) => {
  181. event.preventDefault();
  182. event.stopPropagation();
  183. let xhr = new XMLHttpRequest();
  184. xhr.open('GET', aweme_list[i].url.replace("http://", "https://"), true);
  185. xhr.responseType = 'blob';
  186. xhr.onload = (e) => {
  187. let a = document.createElement('a');
  188. a.href = window.URL.createObjectURL(xhr.response);
  189. a.download = (aweme_list[i].desc ? aweme_list[i].desc.replace(/[\/:*?"<>|]/g, "") : aweme_list[i].awemeId) + ".mp4";
  190. a.click()
  191. };
  192. xhr.onprogress = (event) => {
  193. if (event.lengthComputable) {
  194. button.textContent = "下载" + (event.loaded * 100 / event.total).toFixed(1) + '%';
  195. }
  196. };
  197. xhr.send();
  198. });
  199. targetNode.appendChild(button);
  200. targetNode.dataset.added = true;
  201. }
  202. }
  203.  
  204. function createButton(title, top) {
  205. top = top === undefined ? "60px" : top;
  206. const button = document.createElement('button');
  207. button.textContent = title;
  208. button.style.position = 'fixed';
  209. button.style.right = '5px';
  210. button.style.top = top;
  211. button.style.zIndex = '90000';
  212. document.body.appendChild(button);
  213. return button
  214. }
  215.  
  216. function txt2file(txt, filename) {
  217. const blob = new Blob([txt], {type: 'text/plain'});
  218. const url = URL.createObjectURL(blob);
  219. const link = document.createElement('a');
  220. link.href = url;
  221. link.download = filename.replace(/[\/:*?"<>|]/g, "");
  222. document.body.appendChild(link);
  223. link.click();
  224. document.body.removeChild(link);
  225. URL.revokeObjectURL(url);
  226. }
  227.  
  228. function downloadData(encoding) {
  229. let text = userKey.join(",") + "\n" + userData.join(",") + "\n\n";
  230. text += "作品描述,点赞数,评论数,收藏数,分享数,发布时间,下载链接\n";
  231. aweme_list.forEach(item => {
  232. text += ['"' + item.desc + '"', item.diggCount, item.commentCount,
  233. item.collectCount, item.shareCount, item.date, item.url].join(",") + "\n"
  234. });
  235. if (encoding === "gbk")
  236. text = str2gbk(text);
  237. txt2file(text, userData[0] + ".csv");
  238. }
  239.  
  240. function interceptResponse() {
  241. const originalSend = XMLHttpRequest.prototype.send;
  242. XMLHttpRequest.prototype.send = function () {
  243. const self = this;
  244. this.onreadystatechange = function () {
  245. if (self.readyState === 4) {
  246. if (self._url.indexOf("/aweme/v1/web/aweme/post") > -1) {
  247. var json = JSON.parse(self.response);
  248. let post_data = json.aweme_list.map(item => Object.assign(
  249. {"awemeId": item.aweme_id, "desc": item.desc},
  250. {
  251. "diggCount": item.statistics.digg_count,
  252. "commentCount": item.statistics.comment_count,
  253. "collectCount": item.statistics.collect_count,
  254. "shareCount": item.statistics.share_count
  255. },
  256. {
  257. "date": new Date(item.create_time * 1000).toLocaleString(),
  258. "url": item.video.play_addr.url_list[0]
  259. }));
  260. aweme_list = aweme_list.concat(post_data);
  261. if (timer !== undefined)
  262. clearTimeout(timer);
  263. timer = setTimeout(() => createDownloadButton(), 1000);
  264. }
  265. }
  266. };
  267. originalSend.apply(this, arguments);
  268. };
  269. }
  270.  
  271. function scrollPageToBottom() {
  272. const SCROLL_DELAY = 1000; // Adjust the delay between each scroll action (in milliseconds)
  273. let scrollInterval;
  274.  
  275. function getScrollPosition() {
  276. return scrollY || pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
  277. }
  278.  
  279. function scrollToBottom() {
  280. scrollTo(0, document.body.scrollHeight);
  281. }
  282.  
  283. function hasReachedBottom() {
  284. return getScrollPosition() >= (document.body.scrollHeight - innerHeight);
  285. }
  286.  
  287. function scrollLoop() {
  288. if (!hasReachedBottom()) {
  289. scrollToBottom();
  290. } else {
  291. console.log("Reached the bottom of the page!");
  292. clearInterval(scrollInterval);
  293. }
  294. }
  295.  
  296. function startScrolling() {
  297. scrollInterval = setInterval(scrollLoop, SCROLL_DELAY);
  298. }
  299.  
  300. let button = createButton('开启自动下拉到底', '60px');
  301. button.addEventListener('click', startScrolling);
  302. }
  303.  
  304. // To start scrolling, call the function:
  305. scrollPageToBottom();
  306. interceptResponse();
  307. window.onload = () => {
  308. extractDataFromScript();
  309. createButton("下载已加载数据", "81px").addEventListener('click', (e) => downloadData("gbk"));
  310. };
  311. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址