// ==UserScript==
// @name 阿里巴巴商品工作台数据导出工具--树洞先生
// @namespace http://tampermonkey.net/
// @version 1.0
// @license MIT
// @author Assistant
// @description 采集阿里巴巴国际站商品工作台产品的信息
// @match https://hz-productposting.alibaba.com/product_operate/product_growth.htm*
// @grant none
// ==/UserScript==
(function() {
'use strict';
// 全局变量
let allProducts = []; // 存储所有收集到的商品数据
let isCollecting = false; // 标记是否正在收集数据
let currentPage = 1; // 当前页码
let totalPages = 1; // 总页数
// 创建主界面
function createMainInterface() {
// 创建主按钮,点击后弹出主对话框
const toolButton = document.createElement('button');
toolButton.textContent = '📊 商品数据导出--树洞';
toolButton.style.cssText = `
position: fixed;
top: 120px;
right: 20px;
z-index: 9999;
padding: 12px 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
font-weight: bold;
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
transition: all 0.3s ease;
`;
// 鼠标悬停效果
toolButton.onmouseover = () => {
toolButton.style.transform = 'translateY(-2px)';
toolButton.style.boxShadow = '0 6px 20px rgba(0,0,0,0.3)';
};
toolButton.onmouseout = () => {
toolButton.style.transform = 'translateY(0)';
toolButton.style.boxShadow = '0 4px 15px rgba(0,0,0,0.2)';
};
toolButton.onclick = showMainDialog; // 点击按钮显示主对话框
document.body.appendChild(toolButton);
}
// 显示主对话框
function showMainDialog() {
// 创建对话框容器
const dialog = document.createElement('div');
dialog.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 10000;
background: white;
border-radius: 12px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
padding: 30px;
width: 500px;
max-height: 80vh;
overflow-y: auto;
`;
// 设置对话框内容,包括状态、按钮、调试信息等
dialog.innerHTML = `
<div style="text-align: center; margin-bottom: 25px;">
<h2 style="margin: 0; color: #333; font-size: 24px;">📊 商品工作台数据导出工具--树洞</h2>
<p style="margin: 10px 0 0 0; color: #666;">根据自己商品工作台页面筛选结果进行采集,可以快速采集重铺品</p>
</div>
<div style="margin-bottom: 20px;">
<div style="display: flex; justify-content: space-between; margin-bottom: 10px;">
<span style="color: #333; font-weight: bold;">收集状态:</span>
<span id="collection-status" style="color: #666;">待开始</span>
</div>
<div style="display: flex; justify-content: space-between; margin-bottom: 10px;">
<span style="color: #333; font-weight: bold;">当前页面:</span>
<span id="current-page-info" style="color: #666;">-</span>
</div>
<div style="display: flex; justify-content: space-between; margin-bottom: 10px;">
<span style="color: #333; font-weight: bold;">已收集商品:</span>
<span id="collected-count" style="color: #666;">0</span>
</div>
<div style="background: #f5f5f5; border-radius: 8px; padding: 4px; margin-bottom: 15px;">
<div id="progress-bar" style="background: linear-gradient(90deg, #4CAF50, #45a049); height: 20px; border-radius: 6px; width: 0%; transition: width 0.3s ease;"></div>
</div>
</div>
<div style="display: flex; gap: 10px; margin-bottom: 20px;">
<button id="start-collection" style="flex: 1; padding: 12px; background: #4CAF50; color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: bold;">🔄 开始收集数据</button>
<button id="stop-collection" style="flex: 1; padding: 12px; background: #f44336; color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: bold;" disabled>⏹️ 停止收集</button>
</div>
<div style="display: flex; gap: 10px; margin-bottom: 20px;">
<button id="export-all" style="flex: 1; padding: 12px; background: #2196F3; color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: bold;" disabled>📥 导出所有数据</button>
<button id="clear-data" style="flex: 1; padding: 12px; background: #FF9800; color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: bold;">🗑️ 清空数据</button>
</div>
<div style="display: flex; gap: 10px;">
<button id="test-extraction" style="flex: 1; padding: 12px; background: #9C27B0; color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: bold;">🧪 测试提取</button>
<button id="close-dialog" style="flex: 1; padding: 12px; background: #666; color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: bold;">❌ 关闭</button>
</div>
<div id="debug-info" style="margin-top: 20px; padding: 15px; background: #f9f9f9; border-radius: 6px; font-family: monospace; font-size: 12px; max-height: 200px; overflow-y: auto; display: none;">
<strong>调试信息:</strong><br>
<div id="debug-content"></div>
</div>
`;
// 创建遮罩层,点击遮罩层可关闭对话框
const overlay = document.createElement('div');
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
z-index: 9999;
`;
overlay.appendChild(dialog);
document.body.appendChild(overlay);
// 绑定对话框按钮事件
bindDialogEvents(overlay);
}
// 绑定对话框事件,包括按钮点击等
function bindDialogEvents(overlay) {
const startBtn = overlay.querySelector('#start-collection'); // 开始收集按钮
const stopBtn = overlay.querySelector('#stop-collection'); // 停止收集按钮
const exportBtn = overlay.querySelector('#export-all'); // 导出数据按钮
const clearBtn = overlay.querySelector('#clear-data'); // 清空数据按钮
const testBtn = overlay.querySelector('#test-extraction'); // 测试提取按钮
const closeBtn = overlay.querySelector('#close-dialog'); // 关闭对话框按钮
startBtn.onclick = startCollection; // 绑定开始收集事件
stopBtn.onclick = stopCollection; // 绑定停止收集事件
exportBtn.onclick = exportAllData; // 绑定导出数据事件
clearBtn.onclick = clearAllData; // 绑定清空数据事件
testBtn.onclick = testExtraction; // 绑定测试提取事件
closeBtn.onclick = () => overlay.remove(); // 关闭对话框
// 点击遮罩层关闭对话框
overlay.onclick = (e) => {
if (e.target === overlay) overlay.remove();
};
}
// 数字格式处理函数 - 修复数字分割问题
function parseNumber(text) {
if (!text) return '';
// 移除所有逗号和空格,保留数字、小数点和百分号
const cleaned = text.replace(/[,-\s]/g, '');
// 如果包含百分号,保留百分号
if (cleaned.includes('%')) {
return cleaned;
}
// 如果是纯数字,返回清理后的数字
const number = parseFloat(cleaned);
if (!isNaN(number)) {
return number.toString();
}
// 如果无法解析,返回原始文本
return text;
}
// 基于实际DOM结构提取商品数据 - 修复数字格式问题
function extractProductsFromCurrentPage() {
const products = [];
try {
// 基于实际DOM结构,使用正确的选择器,遍历每一行商品
const productRows = document.querySelectorAll('div[role="row"].next-row');
console.log(`找到 ${productRows.length} 个商品行`);
productRows.forEach((row, index) => {
try {
const product = {};
// 商品图片
const imgElement = row.querySelector('._informationImg_gr0vo_49 img');
product.imageUrl = imgElement ? imgElement.src : '';
// 商品标题
const titleElement = row.querySelector('._informationText_gr0vo_67');
product.title = titleElement ? titleElement.textContent.trim() : '';
// 商品ID
const idElement = row.querySelector('._informationId_gr0vo_79');
product.id = idElement ? idElement.textContent.replace('ID:', '').trim() : '';
// 商品标签
const tagElements = row.querySelectorAll('._everyTag_gr0vo_92');
product.tags = Array.from(tagElements).map(tag => tag.textContent.trim()).join(', ');
// 访问数据 - 修复数字格式问题
const visitElements = row.querySelectorAll('._visitPut_gr0vo_126');
visitElements.forEach(element => {
const fullText = element.textContent.trim();
const spanElement = element.querySelector('span');
const rawValue = spanElement ? spanElement.textContent.trim() : '';
const value = parseNumber(rawValue); // 使用数字格式处理函数
// 根据不同的文本内容,提取不同的访问数据
if (fullText.includes('近30天搜索曝光数')) {
product.searchExposure = value;
} else if (fullText.includes('近30天访问人数')) {
product.visitors = value;
} else if (fullText.includes('近90天[TM+询盘]人数')) {
product.inquiries = value;
} else if (fullText.includes('近90天[TM+询盘]转化')) {
product.inquiryConversion = value;
} else if (fullText.includes('近90天支付买家数')) {
product.payingBuyers = value;
} else if (fullText.includes('近90天访客到支付转化率')) {
product.paymentConversion = value;
}
});
// 优化建议
const suggestionElements = row.querySelectorAll('._oneOptimization_gr0vo_170');
if (suggestionElements.length > 0) {
// 采集第一个优化建议块的所有文字内容(包含子元素)
const parentDiv = suggestionElements[0].closest('div');
if (parentDiv) {
let suggestionText = parentDiv.innerText.trim();
// 过滤无关提示词
suggestionText = suggestionText.replace(/去完成|配置服务/g, '');
product.suggestions = suggestionText.trim();
} else {
let suggestionText = Array.from(suggestionElements).map(el => el.textContent.trim()).join('; ');
suggestionText = suggestionText.replace(/去完成|配置服务/g, '');
product.suggestions = suggestionText.trim();
}
} else {
product.suggestions = '';
}
// 只添加有ID的商品
if (product.id) {
product.index = allProducts.length + products.length + 1;
product.pageNumber = currentPage;
products.push(product);
}
} catch (error) {
// 单个商品提取出错时,打印错误但不中断整体流程
console.error(`处理第 ${index + 1} 个商品时出错:`, error);
}
});
} catch (error) {
// 整体提取出错时,打印错误
console.error('提取商品数据时出错:', error);
}
return products;
}
// 测试数据提取,显示调试信息
function testExtraction() {
const debugInfo = document.querySelector('#debug-info');
const debugContent = document.querySelector('#debug-content');
debugInfo.style.display = 'block';
debugContent.innerHTML = '正在测试数据提取...<br>';
try {
const products = extractProductsFromCurrentPage();
debugContent.innerHTML += `✅ 成功提取到 ${products.length} 个商品<br>`;
if (products.length > 0) {
debugContent.innerHTML += '<br><strong>第一个商品示例:</strong><br>';
const firstProduct = products[0];
Object.keys(firstProduct).forEach(key => {
debugContent.innerHTML += `${key}: ${firstProduct[key]}<br>`;
});
// 测试数字格式处理
debugContent.innerHTML += '<br><strong>数字格式测试:</strong><br>';
debugContent.innerHTML += `原始: "3,897" -> 处理后: "${parseNumber('3,897')}"<br>`;
debugContent.innerHTML += `原始: "2.5%" -> 处理后: "${parseNumber('2.5%')}"<br>`;
debugContent.innerHTML += `原始: "1,234,567" -> 处理后: "${parseNumber('1,234,567')}"<br>`;
} else {
debugContent.innerHTML += '<br>❌ 未找到商品数据<br>';
}
} catch (error) {
debugContent.innerHTML += `❌ 测试失败: ${error.message}<br>`;
console.error('测试提取时出错:', error);
}
}
// 开始收集数据 - 修复最后一页卡住问题
async function startCollection() {
if (isCollecting) return; // 如果已经在收集则不重复执行
isCollecting = true;
updateUI();
try {
// 获取总页数
await getTotalPages();
// 从第一页开始收集
currentPage = 1;
// 循环遍历每一页,收集数据
while (currentPage <= totalPages && isCollecting) {
updateStatus(`正在收集第 ${currentPage} 页数据...`);
// 提取当前页面数据
const pageProducts = extractProductsFromCurrentPage();
allProducts.push(...pageProducts);
updateUI();
// 检查是否为最后一页 - 修复卡住问题
if (currentPage >= totalPages) {
updateStatus(`收集完成!共收集 ${allProducts.length} 个商品`);
break;
}
// 如果不是最后一页,翻到下一页
if (isCollecting) {
const success = await goToNextPage();
if (!success) {
updateStatus('翻页失败,收集停止');
break;
}
currentPage++;
// 等待页面加载
await sleep(3000); // 增加等待时间确保页面完全加载
}
}
if (isCollecting) {
updateStatus(`收集完成!共收集 ${allProducts.length} 个商品`);
}
} catch (error) {
updateStatus(`收集过程中出错: ${error.message}`);
console.error('收集数据时出错:', error);
} finally {
isCollecting = false;
updateUI();
}
}
// 停止收集
function stopCollection() {
isCollecting = false; // 设置标志位,主循环会自动停止
updateStatus('收集已停止');
updateUI();
}
// 获取总页数 - 改进页数检测
async function getTotalPages() {
try {
// 查找分页信息,使用更精确的方法
const paginationElements = document.querySelectorAll('.next-pagination-item');
let maxPage = 1;
paginationElements.forEach(element => {
const ariaLabel = element.getAttribute('aria-label');
if (ariaLabel && ariaLabel.includes('共') && ariaLabel.includes('页')) {
// 从aria-label中提取总页数,如"第1页,共76页"
const match = ariaLabel.match(/共(\d+)页/);
if (match) {
const pageNum = parseInt(match[1]);
if (pageNum > maxPage) {
maxPage = pageNum;
}
}
}
// 备用方法:从按钮文本中获取页码
const text = element.textContent.trim();
const pageNum = parseInt(text);
if (!isNaN(pageNum) && pageNum > maxPage) {
maxPage = pageNum;
}
});
totalPages = maxPage;
console.log(`检测到总页数: ${totalPages}`);
} catch (error) {
console.error('获取总页数时出错:', error);
totalPages = 1;
}
}
// 翻到下一页 - 改进翻页逻辑
async function goToNextPage() {
try {
// 查找下一页按钮
const nextButton = document.querySelector('.next-pagination-item.next-next');
if (nextButton && !nextButton.disabled && !nextButton.classList.contains('next-disabled')) {
console.log(`点击下一页按钮,从第 ${currentPage} 页到第 ${currentPage + 1} 页`);
nextButton.click();
// 等待页面开始加载
await sleep(1000);
// 检查页面是否真的翻页了
let attempts = 0;
const maxAttempts = 10;
while (attempts < maxAttempts) {
await sleep(500);
// 检查当前页码是否已更新
const currentPageElement = document.querySelector('.next-pagination-item.next-current');
if (currentPageElement) {
const displayedPage = parseInt(currentPageElement.textContent.trim());
if (displayedPage === currentPage + 1) {
console.log(`页面已成功翻到第 ${displayedPage} 页`);
return true;
}
}
attempts++;
}
console.log('翻页超时,可能页面加载缓慢');
return true; // 即使超时也继续,避免卡住
} else {
console.log('下一页按钮不可用或已到最后一页');
return false;
}
} catch (error) {
console.error('翻页时出错:', error);
return false;
}
}
// 导出所有数据
function exportAllData() {
if (allProducts.length === 0) {
alert('没有数据可导出');
return;
}
try {
// 生成CSV内容并下载
const csvContent = generateCSV(allProducts);
downloadCSV(csvContent, `阿里巴巴商品数据_${new Date().toISOString().split('T')[0]}.csv`);
updateStatus(`已导出 ${allProducts.length} 个商品数据`);
} catch (error) {
alert(`导出失败: ${error.message}`);
console.error('导出数据时出错:', error);
}
}
// 生成CSV内容
function generateCSV(products) {
// 定义表头
const headers = [
'序号', '商品ID', '商品标题', '商品图片链接','商品标签',
'近30天搜索曝光数', '近30天访问人数', '近90天询盘人数',
'近90天询盘转化率', '近90天支付买家数', '近90天支付转化率', '优化建议'
];
// 添加BOM头,防止中文乱码
let csvContent = '\uFEFF' + headers.join(',') + '\n';
// 遍历每个商品,生成每一行
products.forEach(product => {
const row = [
product.index || '',
product.id || '',
`"${(product.title || '').replace(/"/g, '""')}"`,
`"${(product.tags || '').replace(/"/g, '""')}"`,
product.imageUrl || '',
product.searchExposure || '',
product.visitors || '',
product.inquiries || '',
product.inquiryConversion || '',
product.payingBuyers || '',
product.paymentConversion || '',
`"${(product.suggestions || '').replace(/"/g, '""')}"`
];
csvContent += row.join(',') + '\n';
});
return csvContent;
}
// 下载CSV文件
function downloadCSV(content, filename) {
// 创建Blob对象并生成下载链接
const blob = new Blob([content], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
if (link.download !== undefined) {
const url = URL.createObjectURL(blob);
link.setAttribute('href', url);
link.setAttribute('download', filename);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
}
// 清空所有数据
function clearAllData() {
if (confirm('确定要清空所有收集的数据吗?')) {
allProducts = [];
currentPage = 1;
updateStatus('数据已清空');
updateUI();
}
}
// 更新状态显示
function updateStatus(message) {
const statusElement = document.querySelector('#collection-status');
if (statusElement) {
statusElement.textContent = message;
}
console.log(message);
}
// 更新UI,包括状态、进度条、按钮状态等
function updateUI() {
const statusElement = document.querySelector('#collection-status');
const pageInfoElement = document.querySelector('#current-page-info');
const countElement = document.querySelector('#collected-count');
const progressBar = document.querySelector('#progress-bar');
const startBtn = document.querySelector('#start-collection');
const stopBtn = document.querySelector('#stop-collection');
const exportBtn = document.querySelector('#export-all');
if (pageInfoElement) {
pageInfoElement.textContent = `${currentPage} / ${totalPages}`;
}
if (countElement) {
countElement.textContent = allProducts.length;
}
if (progressBar && totalPages > 0) {
const progress = (currentPage / totalPages) * 100;
progressBar.style.width = `${Math.min(progress, 100)}%`;
}
if (startBtn) {
startBtn.disabled = isCollecting;
}
if (stopBtn) {
stopBtn.disabled = !isCollecting;
}
if (exportBtn) {
exportBtn.disabled = allProducts.length === 0;
}
}
// 工具函数:延时,返回Promise
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// 初始化,等待页面加载完成后创建主界面
function init() {
// 等待页面加载完成
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', createMainInterface);
} else {
createMainInterface();
}
}
// 启动脚本
init();
})();