您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
This script retrieves all Tweets displayed on a Twitter profile page, with support for automatic scrolling and exporting to a JSON file.
// ==UserScript== // @name Twitter个人首页信息获取 // @namespace http://tampermonkey.net/ // @version 2025-08-18 // @description This script retrieves all Tweets displayed on a Twitter profile page, with support for automatic scrolling and exporting to a JSON file. // @author Ivan HU // @match https://x.com/* // @grant none // @license MIT // ==/UserScript== // 自动滚动页面并提取section元素 (function() { 'use strict'; let collectedSections = []; let currentScrollY = 0; let isScrolling = false; // 创建状态显示面板 const statusPanel = document.createElement('div'); statusPanel.style.cssText = ` position: fixed; top: 10px; right: 10px; background: rgba(0, 0, 0, 0.8); color: white; padding: 15px; border-radius: 8px; font-family: Arial, sans-serif; font-size: 14px; z-index: 10000; max-width: 300px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); transition: all 0.3s ease; `; const statusText = document.createElement('div'); statusText.innerHTML = '<strong>滚动提取器</strong><br>准备开始...'; // 输入框:滚动间隔 const intervalLabel = document.createElement('div'); intervalLabel.textContent = '滚动间隔(秒):'; intervalLabel.style.cssText = 'margin-top: 5px; font-size: 12px;'; const intervalInput = document.createElement('input'); intervalInput.type = 'number'; intervalInput.value = '3'; intervalInput.min = '1'; intervalInput.max = '10'; intervalInput.style.cssText = ` width: 100%; padding: 4px; border: 1px solid #ccc; border-radius: 4px; margin-top: 2px; font-size: 12px; `; const startButton = document.createElement('button'); startButton.textContent = '开始滚动'; startButton.style.cssText = ` background: #1da1f2; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; margin-top: 5px; width: 100%; `; const continueButton = document.createElement('button'); continueButton.textContent = '继续滚动'; continueButton.style.cssText = ` background: #794bc4; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; margin-top: 5px; width: 100%; `; const stopButton = document.createElement('button'); stopButton.textContent = '停止'; stopButton.style.cssText = ` background: #e0245e; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; margin-top: 5px; width: 100%; `; const downloadButton = document.createElement('button'); downloadButton.textContent = '导出HTML'; downloadButton.style.cssText = ` background: #17bf63; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; margin-top: 5px; width: 100%; `; const exportJsonButton = document.createElement('button'); exportJsonButton.textContent = '导出JSON'; exportJsonButton.style.cssText = ` background: #f39c12; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; margin-top: 5px; width: 100%; `; const toggleButton = document.createElement('button'); toggleButton.textContent = '−'; toggleButton.style.cssText = ` background: none; color: white; border: none; font-size: 16px; cursor: pointer; position: absolute; top: 5px; right: 10px; width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; `; statusPanel.appendChild(toggleButton); statusPanel.appendChild(statusText); statusPanel.appendChild(intervalLabel); statusPanel.appendChild(intervalInput); statusPanel.appendChild(startButton); statusPanel.appendChild(continueButton); statusPanel.appendChild(stopButton); statusPanel.appendChild(downloadButton); statusPanel.appendChild(exportJsonButton); document.body.appendChild(statusPanel); // 添加折叠/展开功能 let isCollapsed = true; const contentElements = [intervalLabel, intervalInput, startButton, continueButton, stopButton, downloadButton, exportJsonButton]; function toggleCollapse() { isCollapsed = !isCollapsed; if (isCollapsed) { statusPanel.style.cssText += ` width: auto; padding: 8px 25px 8px 15px; max-width: 200px; `; statusText.style.cssText = 'font-size: 12px; margin: 0;'; toggleButton.textContent = '+'; toggleButton.style.top = '3px'; contentElements.forEach(el => { if (el !== statusText) { el.style.display = 'none'; } }); } else { statusPanel.style.cssText += ` width: 100%; padding: 15px; max-width: 300px; `; statusText.style.cssText = ''; toggleButton.textContent = '−'; toggleButton.style.top = '5px'; contentElements.forEach(el => { if (el !== statusText) { el.style.display = ''; } }); } } // 默认折叠 toggleCollapse(); function updateStatus(text) { statusText.innerHTML = text.replace(/\n/g, '<br>'); } function extractSections() { const targetDivs = document.querySelectorAll('div[data-testid="cellInnerDiv"]'); if (targetDivs.length === 0) { updateStatus('错误:未找到任何 data-testid="cellInnerDiv" 的div元素,停止滚动'); stopScrolling(); return; } let foundNew = false; targetDivs.forEach(div => { if (!collectedSections.includes(div)) { collectedSections.push(div); foundNew = true; } }); if (!foundNew && collectedSections.length > 0) { updateStatus(`没有新元素,已收集 ${collectedSections.length} 个div\n滚动停止`); stopScrolling(); return; } updateStatus(`已收集 ${collectedSections.length} 个div\n当前位置: ${window.scrollY}px`); } function scrollAndExtract() { if (!isScrolling) return; currentScrollY += 1500; if (currentScrollY >= document.body.scrollHeight) { updateStatus(`滚动完成!\n共收集 ${collectedSections.length} 个div`); isScrolling = false; return; } window.scrollTo(0, currentScrollY); const intervalSeconds = parseInt(intervalInput.value) || 3; // 等待页面稳定后提取元素 setTimeout(() => { extractSections(); if (isScrolling) { setTimeout(scrollAndExtract, intervalSeconds * 1000); } }, 2000); } function startScrolling() { if (isScrolling) return; isScrolling = true; currentScrollY = window.scrollY; collectedSections = []; // 重置收集列表 updateStatus('开始滚动提取...'); scrollAndExtract(); } function continueScrolling() { if (isScrolling) return; isScrolling = true; currentScrollY = window.scrollY; // 从当前位置继续 updateStatus(`继续滚动提取...\n已收集 ${collectedSections.length} 个div`); scrollAndExtract(); } function stopScrolling() { isScrolling = false; updateStatus('已停止滚动'); } function exportCollectedHTML() { if (collectedSections.length === 0) { alert('没有收集到任何section'); return; } // 创建最简div容器 const container = document.createElement('div'); collectedSections.forEach(div => { const clone = div.cloneNode(true); container.appendChild(clone); }); // 创建仅包含div的HTML const htmlContent = `<div>${container.innerHTML}</div>`; // 下载文件 const blob = new Blob([htmlContent], { type: 'text/html' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `extracted_divs_${collectedSections.length}_${Date.now()}.html`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); updateStatus(`已导出 ${collectedSections.length} 个div到文件`); } function exportCollectedJSON() { if (collectedSections.length === 0) { alert('没有收集到任何section'); return; } const tweets = []; collectedSections.forEach(div => { const tweetData = {'浏览量': null, '链接': null, '文本': null, '时间': null}; // Extract views and link from a tag with href ending in 'analytics' const analyticsLink = div.querySelector('a[href$="analytics"]'); if (analyticsLink) { tweetData['浏览量'] = analyticsLink.textContent.trim(); const href = analyticsLink.getAttribute('href'); if (href) { tweetData['链接'] = "https://x.com/" + href.replace("/analytics", ""); } } // Extract text from div with data-testid='tweetText' const textDiv = div.querySelector('div[data-testid="tweetText"]'); if (textDiv) { tweetData['文本'] = textDiv.textContent.trim(); } // Extract time from time tag const timeTag = div.querySelector('time'); if (timeTag) { tweetData['时间'] = timeTag.getAttribute('datetime') || timeTag.textContent.trim(); } // Only add if we have at least some data if (tweetData['浏览量'] || tweetData['链接'] || tweetData['文本'] || tweetData['时间']) { tweets.push(tweetData); } }); // Deduplicate by link const seenLinks = new Set(); const uniqueTweets = []; tweets.forEach(tweet => { const link = tweet['链接']; if (link && !seenLinks.has(link)) { seenLinks.add(link); uniqueTweets.push(tweet); } else if (!link) { uniqueTweets.push(tweet); } }); // Create JSON content const jsonContent = JSON.stringify(uniqueTweets, null, 2); // Download file const blob = new Blob([jsonContent], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `extracted_tweets_${uniqueTweets.length}_${Date.now()}.json`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); updateStatus(`已导出 ${uniqueTweets.length} 条数据到JSON文件`); } // 绑定事件 startButton.addEventListener('click', startScrolling); continueButton.addEventListener('click', continueScrolling); stopButton.addEventListener('click', stopScrolling); downloadButton.addEventListener('click', exportCollectedHTML); exportJsonButton.addEventListener('click', exportCollectedJSON); toggleButton.addEventListener('click', toggleCollapse); // 初始提取当前可见的section extractSections(); updateStatus(`初始化完成\n当前可见: ${collectedSections.length} 个div\n点击"开始滚动"开始自动提取`); // 确保面板在最上层 setTimeout(() => { statusPanel.style.zIndex = "999999"; }, 1000); })();