// ==UserScript==
// @name 抖音视频采集(简洁版)
// @namespace http://tampermonkey.net/
// @version 2.0.0
// @description 抖音切换到我的喜欢和收藏列表,提取当前页面抖音视频链接,采集到第三方场景上,可以定制相关插件
// @author qqlcx5
// @match https://www.douyin.com/user/*
// @match https://www.douyin.com/search/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=douyin.com
// @grant GM_setClipboard
// @run-at document-end
// @license MIT
// @downloadURL https://update.greasyfork.icu/scripts/517736/%E6%8A%96%E9%9F%B3%E8%A7%86%E9%A2%91%E9%87%87%E9%9B%86%EF%BC%88%E7%AE%80%E6%B4%81%E7%89%88%EF%BC%89.user.js
// @updateURL https://update.greasyfork.icu/scripts/517736/%E6%8A%96%E9%9F%B3%E8%A7%86%E9%A2%91%E9%87%87%E9%9B%86%EF%BC%88%E7%AE%80%E6%B4%81%E7%89%88%EF%BC%89.meta.js
// ==/UserScript==
(function() {
'use strict';
// 添加 Font Awesome 图标库
function addFontAwesome() {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css';
document.head.appendChild(link);
}
/**
* 存储提取到的视频链接和点赞数
* Stores the extracted video links and like counts
*/
let videoLinks = [];
/**
* 提取用户主页中的所有视频链接和点赞数
* Extracts all video links and like counts from the user's profile page
*/
function extractVideoLinks() {
// 定义需要提取链接的节点选择器
const selectors = [
'div[data-e2e="user-post-list"]',
'div[data-e2e="user-like-list"]'
];
const videoListContainer = document.querySelector(selectors);
if (!videoListContainer) {
console.warn('未找到视频列表元素');
return;
}
const videoAnchorElements = videoListContainer.querySelectorAll('a[href^="/video/"]');
videoLinks = Array.from(videoAnchorElements).map(anchor => {
const videoElement = anchor.closest('li');
const likeCountElement = videoElement ? videoElement.querySelector('.b3Dh2ia8') : null;
const likeCount = likeCountElement ? parseLikeCount(likeCountElement.textContent) : 0;
// 获取视频标题(如果有)
const titleElement = videoElement ? videoElement.querySelector('div[data-e2e="video-title"]') : null;
const title = titleElement ? titleElement.textContent.trim() : '无标题';
// 获取视频封面(如果有)
const imgElement = videoElement ? videoElement.querySelector('img') : null;
const thumbnail = imgElement ? imgElement.src : '';
// 构建完整URL
const url = new URL(anchor.href, window.location.origin);
return {
href: url.toString(),
likeCount: likeCount,
title: title,
thumbnail: thumbnail,
id: url.pathname.split('/').pop() // 提取视频ID
};
});
console.info(`提取到 ${videoLinks.length} 个视频链接`);
}
/**
* 将点赞数文本转换为数字
* Converts like count text to a number
* @param {string} text - 点赞数文本 (Like count text)
* @returns {number} - 转换后的点赞数 (Converted like count)
*/
function parseLikeCount(text) {
if (!text) return 0;
if (text.includes('万')) {
return parseFloat(text) * 10000;
}
return parseInt(text, 10) || 0;
}
/**
* 按点赞数排序视频链接
* Sorts video links by like count
* @param {boolean} ascending - 是否升序排序 (Whether to sort in ascending order)
*/
function sortVideoLinksByLikes(ascending = false) {
videoLinks.sort((a, b) => {
return ascending ? a.likeCount - b.likeCount : b.likeCount - a.likeCount;
});
}
/**
* 复制所有视频链接到剪贴板
* Copies all video links to the clipboard
* @param {boolean} shouldSort - 是否按点赞数排序 (Whether to sort by like count)
*/
function copyAllVideoLinks(shouldSort = false) {
extractVideoLinks();
if (videoLinks.length === 0) {
showNotification('未找到视频链接', 'error');
return;
}
if (shouldSort) {
sortVideoLinksByLikes();
showNotification('已按点赞数降序排序', 'info');
}
const linksText = videoLinks.map(video => video.href).join('\n');
GM_setClipboard(linksText);
showNotification(`已复制 ${videoLinks.length} 个视频链接`, 'success');
}
/**
* 复制JSON到剪贴板
* Copy JSON to clipboard
*/
function copyAsJSON() {
extractVideoLinks();
if (videoLinks.length === 0) {
showNotification('未找到视频链接', 'error');
return;
}
const jsonData = JSON.stringify(videoLinks, null, 2);
GM_setClipboard(jsonData);
showNotification(`已复制 ${videoLinks.length} 个视频的JSON数据`, 'success');
}
/**
* 导出为JSON文件
* Export to JSON file
*/
function exportToJSON() {
extractVideoLinks();
if (videoLinks.length === 0) {
showNotification('未找到视频链接', 'error');
return;
}
const jsonData = JSON.stringify(videoLinks, null, 2);
const blob = new Blob([jsonData], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const date = new Date();
const filename = `douyin_videos_${date.getFullYear()}-${(date.getMonth()+1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}.json`;
GM_download({
url: url,
name: filename,
onload: function() {
showNotification(`已导出 ${videoLinks.length} 个视频到JSON文件`, 'success');
URL.revokeObjectURL(url);
},
onerror: function(error) {
showNotification('导出失败: ' + error.error, 'error');
URL.revokeObjectURL(url);
}
});
}
/**
* 导出为CSV文件
* Export to CSV file
*/
function exportToCSV() {
extractVideoLinks();
if (videoLinks.length === 0) {
showNotification('未找到视频链接', 'error');
return;
}
// 创建CSV标题行
let csvContent = "标题,链接,点赞数,视频ID\n";
// 添加数据行
videoLinks.forEach(video => {
// 处理标题中的逗号(用引号包裹)
const title = video.title.includes(',') ? `"${video.title}"` : video.title;
csvContent += `${title},${video.href},${video.likeCount},${video.id}\n`;
});
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
const url = URL.createObjectURL(blob);
const date = new Date();
const filename = `douyin_videos_${date.getFullYear()}-${(date.getMonth()+1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}.csv`;
GM_download({
url: url,
name: filename,
onload: function() {
showNotification(`已导出 ${videoLinks.length} 个视频到CSV文件`, 'success');
URL.revokeObjectURL(url);
},
onerror: function(error) {
showNotification('导出失败: ' + error.error, 'error');
URL.revokeObjectURL(url);
}
});
}
/**
* 创建并添加悬浮按钮组到页面
* Creates and adds a floating button group to the page
*/
function createFloatingButtonGroup() {
const buttonGroup = document.createElement('div');
buttonGroup.id = 'floating-button-group';
Object.assign(buttonGroup.style, {
position: 'fixed',
right: '24px',
bottom: '24px',
display: 'flex',
flexDirection: 'column',
gap: '12px',
zIndex: '10000',
});
// 主按钮 - 导出选项
const mainButton = createButton('', '#007AFF', () => {
toggleExportMenu();
});
// 导出选项菜单
const exportMenu = document.createElement('div');
exportMenu.id = 'export-menu';
exportMenu.style.display = 'none';
exportMenu.style.flexDirection = 'column';
exportMenu.style.gap = '10px';
Object.assign(exportMenu.style, {
display: 'none',
flexDirection: 'column',
gap: '10px',
backgroundColor: '#FFFFFF',
padding: '12px',
borderRadius: '12px',
boxShadow: '0 4px 20px rgba(0, 0, 0, 0.15)',
border: '1px solid #E0E0E0'
});
// 创建各种导出按钮
const normalButton = createMenuButton(' 复制链接', () => {
copyAllVideoLinks(false);
hideExportMenu();
});
const sortButton = createMenuButton(' 按点赞数复制', () => {
copyAllVideoLinks(true);
hideExportMenu();
});
const copyJsonButton = createMenuButton(' 复制JSON', () => {
copyAsJSON();
hideExportMenu();
});
const jsonButton = createMenuButton(' 导出JSON', () => {
exportToJSON();
hideExportMenu();
});
const csvButton = createMenuButton(' 导出CSV', () => {
exportToCSV();
hideExportMenu();
});
exportMenu.appendChild(normalButton);
exportMenu.appendChild(sortButton);
exportMenu.appendChild(copyJsonButton);
exportMenu.appendChild(jsonButton);
exportMenu.appendChild(csvButton);
buttonGroup.appendChild(mainButton);
buttonGroup.appendChild(exportMenu);
document.body.appendChild(buttonGroup);
// 点击页面其他区域关闭菜单
document.addEventListener('click', (e) => {
if (!buttonGroup.contains(e.target) && exportMenu.style.display === 'flex') {
hideExportMenu();
}
});
}
/**
* 切换导出菜单的显示/隐藏
* Toggle export menu visibility
*/
function toggleExportMenu() {
const exportMenu = document.getElementById('export-menu');
if (exportMenu.style.display === 'flex') {
hideExportMenu();
} else {
showExportMenu();
}
}
/**
* 显示导出菜单
* Show export menu
*/
function showExportMenu() {
const exportMenu = document.getElementById('export-menu');
exportMenu.style.display = 'flex';
}
/**
* 隐藏导出菜单
* Hide export menu
*/
function hideExportMenu() {
const exportMenu = document.getElementById('export-menu');
exportMenu.style.display = 'none';
}
/**
* 创建菜单按钮
* Creates a menu button
* @param {string} html - 按钮HTML内容
* @param {function} onClick - 点击事件
* @returns {HTMLElement} - 按钮元素
*/
function createMenuButton(html, onClick) {
const button = document.createElement('button');
button.innerHTML = html;
Object.assign(button.style, {
padding: '10px 16px',
backgroundColor: 'transparent',
color: '#333333',
border: 'none',
borderRadius: '8px',
cursor: 'pointer',
fontSize: '14px',
fontWeight: '500',
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
transition: 'all 0.2s ease',
textAlign: 'left',
width: '100%',
whiteSpace: 'nowrap'
});
button.addEventListener('mouseenter', () => {
button.style.backgroundColor = '#F0F0F0';
});
button.addEventListener('mouseleave', () => {
button.style.backgroundColor = 'transparent';
});
button.addEventListener('click', onClick);
return button;
}
/**
* 创建按钮
* Creates a button
* @param {string} html - 按钮HTML内容
* @param {string} color - 按钮背景色
* @param {function} onClick - 点击事件
* @returns {HTMLElement} - 按钮元素
*/
function createButton(html, color, onClick) {
const button = document.createElement('button');
button.innerHTML = html;
Object.assign(button.style, {
padding: '16px',
backgroundColor: color,
color: '#FFFFFF',
border: 'none',
borderRadius: '50%',
boxShadow: '0 2px 8px rgba(0, 122, 255, 0.15)',
cursor: 'pointer',
fontSize: '18px',
fontWeight: '500',
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
WebkitAppearance: 'none',
margin: '0',
userSelect: 'none',
WebkitTapHighlightColor: 'transparent',
width: '50px',
height: '50px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
});
button.addEventListener('mouseenter', () => {
button.style.transform = 'scale(1.05) translateY(-1px)';
button.style.boxShadow = '0 4px 12px rgba(0, 122, 255, 0.2)';
button.style.backgroundColor = '#0066D6';
});
button.addEventListener('mouseleave', () => {
button.style.transform = 'none';
button.style.boxShadow = '0 2px 8px rgba(0, 122, 255, 0.15)';
button.style.backgroundColor = color;
});
button.addEventListener('mousedown', () => {
button.style.transform = 'scale(0.95)';
});
button.addEventListener('mouseup', () => {
button.style.transform = 'scale(1.05) translateY(-1px)';
});
button.addEventListener('click', function(e) {
e.stopPropagation();
onClick();
});
return button;
}
function showNotification(message, type = 'info') {
const colors = {
success: '#34C759',
error: '#FF3B30',
info: '#007AFF'
};
const notification = document.createElement('div');
notification.textContent = message;
Object.assign(notification.style, {
position: 'fixed',
bottom: '90px',
right: '24px',
backgroundColor: '#FFFFFF',
color: colors[type],
padding: '12px 20px',
borderRadius: '12px',
boxShadow: '0 4px 16px rgba(0, 0, 0, 0.08)',
opacity: '0',
transform: 'translateY(10px)',
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
zIndex: '10000',
fontSize: '15px',
fontWeight: '500',
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
border: `1px solid ${colors[type]}20`
});
document.body.appendChild(notification);
requestAnimationFrame(() => {
notification.style.opacity = '1';
notification.style.transform = 'translateY(0)';
});
setTimeout(() => {
notification.style.opacity = '0';
notification.style.transform = 'translateY(10px)';
setTimeout(() => {
document.body.removeChild(notification);
}, 300);
}, 3000);
}
function initializeScript() {
addFontAwesome();
createFloatingButtonGroup();
console.info('抖音视频链接提取器已启用');
}
if (document.readyState === 'complete' || document.readyState === 'interactive') {
setTimeout(initializeScript, 1000);
} else {
document.addEventListener('DOMContentLoaded', function() {
setTimeout(initializeScript, 1000);
});
}
})();