// ==UserScript==
// @name 阿里数据地域分布采集--树洞先生
// @namespace http://tampermonkey.net/
// @version 1.0
// @description 采集阿里巴巴数据概览的地域分布数据,支持自定义参数配置
// @author 树洞先生
// @license MIT
// @match https://data.alibaba.com/*
// @match https://mydata.alibaba.com/*
// @grant none
// @require https://cdn.sheetjs.com/xlsx-0.20.0/package/dist/xlsx.full.min.js
// ==/UserScript==
(function() {
'use strict';
// 全局变量
let collectedData = {};
let userConfig = {
statisticsType: 'month',
terminalType: 'total',
selected: '1'
};
// 创建启动按钮
function createStartButton() {
// 移除已存在的启动按钮
const existingButton = document.getElementById('aliDataCollectorStartBtn');
if (existingButton) {
existingButton.remove();
}
// 寻找"数据总览"文本的父容器
const titleSpan = document.querySelector('.dataportal-base-area-title');
if (!titleSpan || !titleSpan.textContent.includes('数据总览')) {
console.log('未找到数据总览元素,使用默认位置');
createStartButtonDefault();
return;
}
// 找到包含"数据总览"的容器div
const titleContainer = titleSpan.parentElement;
if (!titleContainer) {
console.log('未找到标题容器,使用默认位置');
createStartButtonDefault();
return;
}
const startButton = document.createElement('div');
startButton.id = 'aliDataCollectorStartBtn';
startButton.style.cssText = `
position: relative !important;
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
width: 80px !important;
height: 28px !important;
background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%) !important;
border: none !important;
border-radius: 14px !important;
box-shadow: 0 2px 6px rgba(76, 175, 80, 0.3) !important;
cursor: pointer !important;
font-size: 12px !important;
color: white !important;
font-weight: bold !important;
transition: all 0.3s ease !important;
user-select: none !important;
margin-left: 8px !important;
vertical-align: middle !important;
z-index: 999 !important;
`;
startButton.innerHTML = `
<span style="font-size: 11px; line-height: 1;">📊 数据采集</span>
`;
startButton.title = '点击打开阿里数据地域分布采集面板';
// 悬停效果
startButton.addEventListener('mouseenter', function() {
this.style.transform = 'scale(1.05)';
this.style.boxShadow = '0 3px 10px rgba(76, 175, 80, 0.5)';
});
startButton.addEventListener('mouseleave', function() {
this.style.transform = 'scale(1)';
this.style.boxShadow = '0 2px 6px rgba(76, 175, 80, 0.3)';
});
// 点击事件
startButton.addEventListener('click', function() {
createControlPanel();
this.style.display = 'none'; // 隐藏启动按钮
});
// 确保容器支持flex布局
if (titleContainer.style.display !== 'flex' && titleContainer.style.display !== 'inline-flex') {
titleContainer.style.display = 'inline-flex';
titleContainer.style.alignItems = 'center';
}
// 将按钮添加到"数据总览"旁边
titleContainer.appendChild(startButton);
console.log('阿里数据采集启动按钮已创建在"数据总览"旁边');
}
// 创建默认位置的启动按钮(备用方案)
function createStartButtonDefault() {
const startButton = document.createElement('div');
startButton.id = 'aliDataCollectorStartBtn';
startButton.style.cssText = `
position: fixed !important;
top: 20px !important;
right: 20px !important;
width: 60px !important;
height: 60px !important;
background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%) !important;
border: none !important;
border-radius: 50% !important;
box-shadow: 0 4px 15px rgba(76, 175, 80, 0.4) !important;
z-index: 999999 !important;
cursor: pointer !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
font-size: 24px !important;
color: white !important;
font-weight: bold !important;
transition: all 0.3s ease !important;
user-select: none !important;
`;
startButton.innerHTML = `
<span style="font-size: 16px; line-height: 1;">数据</span>
`;
startButton.title = '点击打开阿里数据地域分布采集面板';
// 悬停效果
startButton.addEventListener('mouseenter', function() {
this.style.transform = 'scale(1.1)';
this.style.boxShadow = '0 6px 20px rgba(76, 175, 80, 0.6)';
});
startButton.addEventListener('mouseleave', function() {
this.style.transform = 'scale(1)';
this.style.boxShadow = '0 4px 15px rgba(76, 175, 80, 0.4)';
});
// 点击事件
startButton.addEventListener('click', function() {
createControlPanel();
this.style.display = 'none'; // 隐藏启动按钮
});
document.body.appendChild(startButton);
console.log('阿里数据采集启动按钮已创建(默认位置)');
}
// 创建控制面板
function createControlPanel() {
// 移除已存在的面板
const existingPanel = document.getElementById('aliDataCollectorPanel');
if (existingPanel) {
existingPanel.remove();
}
const panel = document.createElement('div');
panel.id = 'aliDataCollectorPanel';
panel.style.cssText = `
position: fixed !important;
top: 20px !important;
right: 20px !important;
width: 350px !important;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
border: none !important;
border-radius: 15px !important;
padding: 20px !important;
box-shadow: 0 10px 30px rgba(0,0,0,0.3) !important;
z-index: 999999 !important;
font-family: 'Microsoft YaHei', sans-serif !important;
color: white !important;
backdrop-filter: blur(10px) !important;
`;
panel.innerHTML = `
<div style="margin-bottom: 15px; text-align: center;">
<h3 style="margin: 0; color: white; font-size: 18px; font-weight: bold;">阿里数据地域分布采集</h3>
</div>
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px; font-weight: bold;">统计类型:</label>
<select id="statisticsType" style="width: 100%; padding: 8px; border: none; border-radius: 5px; font-size: 14px;">
<option value="month">月度统计</option>
<option value="week">周度统计</option>
<option value="day">日度统计</option>
</select>
</div>
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px; font-weight: bold;">终端类型:</label>
<select id="terminalType" style="width: 100%; padding: 8px; border: none; border-radius: 5px; font-size: 14px;">
<option value="total">全部终端</option>
<option value="PC">PC端</option>
<option value="web">Web端</option>
<option value="APP">移动APP</option>
</select>
</div>
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px; font-weight: bold;">时间选择:</label>
<input type="number" id="selected" value="1" min="1" max="12"
style="width: 100%; padding: 8px; border: none; border-radius: 5px; font-size: 14px; box-sizing: border-box;"
placeholder="输入时间范围数字">
<small style="color: #e0e0e0; font-size: 12px;">月度:1-12个月,周度:1-52周,日度:1-365天</small>
</div>
<div style="margin-bottom: 15px;">
<button id="startCollecting" style="width: 100%; padding: 12px; background: #4CAF50; color: white; border: none; border-radius: 8px; font-size: 16px; font-weight: bold; cursor: pointer; transition: all 0.3s;">
开始采集数据
</button>
</div>
<div style="margin-bottom: 15px;">
<button id="exportExcel" style="width: 100%; padding: 12px; background: #2196F3; color: white; border: none; border-radius: 8px; font-size: 16px; font-weight: bold; cursor: pointer; transition: all 0.3s;" disabled>
导出Excel文件
</button>
</div>
<div id="status" style="margin-top: 10px; padding: 10px; background: rgba(255,255,255,0.2); border-radius: 5px; font-size: 14px; min-height: 20px;">
等待开始采集...
</div>
<div style="margin-top: 10px; text-align: center;">
<button id="closePanel" style="background: #f44336; color: white; border: none; border-radius: 5px; padding: 5px 10px; cursor: pointer; font-size: 12px;">
关闭面板
</button>
</div>
`;
document.body.appendChild(panel);
// 绑定事件
document.getElementById('startCollecting').addEventListener('click', startDataCollection);
document.getElementById('exportExcel').addEventListener('click', exportToExcel);
document.getElementById('closePanel').addEventListener('click', () => {
panel.style.display = 'none';
// 重新显示启动按钮
const startButton = document.getElementById('aliDataCollectorStartBtn');
if (startButton) {
startButton.style.display = 'inline-flex';
} else {
// 延迟重新创建按钮,等待DOM更新
setTimeout(() => {
createStartButton();
}, 100);
}
});
// 按钮悬停效果
const buttons = panel.querySelectorAll('button');
buttons.forEach(button => {
button.addEventListener('mouseenter', function() {
this.style.transform = 'translateY(-2px)';
this.style.boxShadow = '0 5px 15px rgba(0,0,0,0.3)';
});
button.addEventListener('mouseleave', function() {
this.style.transform = 'translateY(0)';
this.style.boxShadow = 'none';
});
});
updateStatus('控制面板已加载,请配置参数后开始采集');
// 隐藏启动按钮
const startButton = document.getElementById('aliDataCollectorStartBtn');
if (startButton) {
startButton.style.display = 'none';
}
}
// 更新状态显示
function updateStatus(message) {
const statusDiv = document.getElementById('status');
if (statusDiv) {
statusDiv.textContent = message;
console.log(`[阿里数据采集] ${message}`);
}
}
// 获取用户配置
function getUserConfig() {
try {
userConfig.statisticsType = document.getElementById('statisticsType').value;
userConfig.terminalType = document.getElementById('terminalType').value;
userConfig.selected = document.getElementById('selected').value;
// 验证输入
if (!userConfig.selected || isNaN(userConfig.selected) || parseInt(userConfig.selected) <= 0) {
userConfig.selected = '1';
document.getElementById('selected').value = '1';
}
updateStatus(`配置: ${userConfig.statisticsType}, ${userConfig.terminalType}, 最近${userConfig.selected}`);
console.log('用户配置:', userConfig);
} catch (error) {
console.error('获取用户配置失败:', error);
updateStatus('配置获取失败,使用默认配置');
}
}
// 从cookie中提取ctoken
function extractCtokenFromCookie() {
const cookies = document.cookie;
const xmanUsMatch = cookies.match(/xman_us_t=([^;]+)/);
if (xmanUsMatch) {
const xmanUsValue = xmanUsMatch[1];
const ctokenMatch = xmanUsValue.match(/ctoken=([^&]+)/);
if (ctokenMatch) {
return ctokenMatch[1];
}
}
return 'lolr_y68znle'; // 默认值
}
// 发送API请求
async function sendAPIRequest() {
const ctoken = extractCtokenFromCookie();
const params = new URLSearchParams({
action: 'OneAction',
iName: 'vip/home/custom/getShopRegionAnalysis',
statisticsType: userConfig.statisticsType,
selected: userConfig.selected,
terminalType: userConfig.terminalType,
isMyselfUpgraded: 'true',
cateId: '711002',
statisticType: 'os',
region: 'os',
seperateByCate: 'false',
isVip: 'true',
ctoken: ctoken
});
const url = `https://mydata.alibaba.com/self/.json?${params.toString()}`;
try {
updateStatus('正在发送API请求...');
console.log('API请求URL:', url);
console.log('API请求参数:', Object.fromEntries(params));
const response = await fetch(url, {
method: 'GET',
headers: {
'accept': '*/*',
'accept-language': 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7',
'content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'same-site'
},
credentials: 'include'
});
console.log('API响应状态:', response.status);
if (!response.ok) {
throw new Error(`HTTP错误: ${response.status} - ${response.statusText}`);
}
const data = await response.json();
console.log('API响应数据:', data);
updateStatus('API请求成功,正在处理数据...');
return data;
} catch (error) {
console.error('API请求错误详情:', error);
updateStatus(`API请求失败: ${error.message}`);
return null;
}
}
// 计算统计周期
function calculateStatisticsPeriod(statisticsType, selected) {
const now = new Date();
const selectedNum = parseInt(selected);
if (statisticsType === 'month') {
// 月度统计:计算前 N 个月
const targetMonth = new Date(now.getFullYear(), now.getMonth() - selectedNum, 1);
return `${targetMonth.getFullYear()}年${targetMonth.getMonth() + 1}月`;
} else if (statisticsType === 'week') {
// 周度统计:计算前 N 周的开始和结束日期
const daysToSubtract = selectedNum * 7;
const targetDate = new Date(now.getTime() - daysToSubtract * 24 * 60 * 60 * 1000);
// 计算该周的开始日期(周一)
const startOfWeek = new Date(targetDate);
const dayOfWeek = startOfWeek.getDay();
const daysToMonday = dayOfWeek === 0 ? 6 : dayOfWeek - 1; // 周日为0,调整为周一开始
startOfWeek.setDate(targetDate.getDate() - daysToMonday);
// 计算该周的结束日期(周日)
const endOfWeek = new Date(startOfWeek);
endOfWeek.setDate(startOfWeek.getDate() + 6);
return `${startOfWeek.getMonth() + 1}月${startOfWeek.getDate()}日-${endOfWeek.getMonth() + 1}月${endOfWeek.getDate()}日`;
} else {
// day 保持原有逻辑,使用原始数据中的statDate
return '';
}
}
// 解析国家详情数据
function parseCountryDetail(countryDetailStr, targetCn, originalItem) {
const countries = [];
try {
const countryEntries = countryDetailStr.trim().replace(/;$/, '').split(';');
// 计算统计周期
const calculatedPeriod = calculateStatisticsPeriod(userConfig.statisticsType, userConfig.selected);
const finalPeriod = calculatedPeriod || originalItem.statDate || '';
countryEntries.forEach(entry => {
if (entry.includes('#')) {
const parts = entry.split('#');
let countryName, numericValue, percentageValue;
if (parts.length >= 3) {
countryName = parts[0].trim();
numericValue = parts[1].trim();
percentageValue = parts[2].trim();
} else if (parts.length === 2) {
countryName = parts[0].trim();
const dataPart = parts[1].trim();
// 简化的数据解析
const zeroDotPos = dataPart.indexOf('0.');
if (zeroDotPos > 0) {
numericValue = dataPart.substring(0, zeroDotPos);
percentageValue = dataPart.substring(zeroDotPos);
} else {
numericValue = dataPart;
percentageValue = '0';
}
} else {
return; // 跳过无效数据
}
// 确保数据不为空
if (!numericValue) numericValue = '0';
if (!percentageValue) percentageValue = '0';
countries.push({
地域: targetCn,
国家: countryName,
数值: numericValue,
占比: percentageValue,
终端类型: userConfig.terminalType,
统计周期: finalPeriod
});
}
});
} catch (error) {
console.error('解析国家详情时出错:', error, '原始数据:', countryDetailStr);
}
return countries;
}
// 处理响应数据
function processResponseData(data) {
try {
console.log('开始处理响应数据:', data);
if (!data) {
updateStatus('响应数据为空');
return null;
}
if (!data.data) {
updateStatus('响应中没有找到 data 字段');
console.log('响应结构:', Object.keys(data));
return null;
}
if (!data.data.returnValue) {
updateStatus('响应中没有找到 returnValue 字段');
console.log('data结构:', Object.keys(data.data));
return null;
}
const returnValue = data.data.returnValue;
console.log('returnValue 数据:', returnValue);
const groupedData = {};
const countryDetails = {};
if (Array.isArray(returnValue)) {
console.log(`开始处理 ${returnValue.length} 条数据`);
returnValue.forEach((item, index) => {
if (item && typeof item === 'object' && item.targetCn) {
const targetCn = item.targetCn;
console.log(`处理项目 ${index + 1}: ${targetCn}`);
if (!groupedData[targetCn]) {
groupedData[targetCn] = [];
}
groupedData[targetCn].push(item);
// 处理countryDetail数据
if (item.countryDetail) {
console.log(`${targetCn} 的 countryDetail:`, item.countryDetail);
const countries = parseCountryDetail(item.countryDetail, targetCn, item);
if (!countryDetails[targetCn]) {
countryDetails[targetCn] = [];
}
countryDetails[targetCn].push(...countries);
console.log(`${targetCn} 解析出 ${countries.length} 个国家`);
} else {
console.log(`${targetCn} 没有 countryDetail 数据`);
}
} else {
console.warn(`项目 ${index + 1} 格式不正确:`, item);
}
});
} else {
updateStatus('returnValue 不是数组格式');
console.log('returnValue 类型:', typeof returnValue);
return null;
}
updateStatus(`找到 ${Object.keys(groupedData).length} 个地域分组`);
console.log('分组数据:', groupedData);
console.log('国家详情:', countryDetails);
return { groupedData, countryDetails };
} catch (error) {
updateStatus(`处理数据时出错: ${error.message}`);
console.error('数据处理错误:', error);
return null;
}
}
// 开始数据采集
async function startDataCollection() {
getUserConfig();
const startButton = document.getElementById('startCollecting');
const exportButton = document.getElementById('exportExcel');
startButton.disabled = true;
startButton.textContent = '采集中...';
try {
const responseData = await sendAPIRequest();
if (responseData) {
const result = processResponseData(responseData);
if (result) {
collectedData = result;
updateStatus(`数据采集完成!共 ${Object.keys(result.countryDetails).length} 个地域`);
exportButton.disabled = false;
} else {
updateStatus('数据处理失败');
}
}
} catch (error) {
updateStatus(`采集失败: ${error.message}`);
} finally {
startButton.disabled = false;
startButton.textContent = '开始采集数据';
}
}
// 导出Excel文件
function exportToExcel() {
if (!collectedData || !collectedData.countryDetails) {
updateStatus('没有数据可以导出');
return;
}
try {
updateStatus('正在生成Excel文件...');
// 检查XLSX库是否加载
if (typeof XLSX === 'undefined') {
updateStatus('XLSX库未加载,请刷新页面重试');
return;
}
console.log('XLSX库版本:', XLSX.version || '未知');
// 字段名称映射
const fieldNameMapping = {
'最近排除联盟的搜索item和p4p商品相关点击次数': '最近商品点击次数',
'最近排除联盟的搜索item和p4p商品曝光量': '最近商品曝光次数'
};
// 工作表顺序
const sheetOrder = [
'最近商品曝光次数', // 曝光量
'最近商品点击次数', // 点击量
'最近店铺访客数', // 访客数
'最近店铺访问次数', // 访问次数
'最近询盘数', // 询盘数
'最近询盘客户数', // 询盘客户数
'最近TM咨询客户数', // TM咨询客户数
'半托管品首页曝光' // 半托管首页曝光
];
function mapFieldName(originalName) {
return originalName ? (fieldNameMapping[originalName] || originalName) : "未知分类";
}
const workbook = XLSX.utils.book_new();
// 1. 创建汇总统计表
const summaryData = [];
Object.entries(collectedData.countryDetails).forEach(([region, countries]) => {
const mappedRegionName = mapFieldName(region);
summaryData.push({
'地域': mappedRegionName,
'国家数量': countries.length,
'终端类型': userConfig.terminalType,
'统计周期': countries.length > 0 ? countries[0].统计周期 : ''
});
});
if (summaryData.length > 0) {
const summaryWorksheet = XLSX.utils.json_to_sheet(summaryData);
XLSX.utils.book_append_sheet(workbook, summaryWorksheet, '汇总统计');
}
// 2. 按指定顺序创建地域工作表
const sortedRegions = [];
// 按指定顺序添加地域
sheetOrder.forEach(orderRegion => {
Object.keys(collectedData.countryDetails).forEach(region => {
const mappedRegionName = mapFieldName(region);
if (mappedRegionName === orderRegion && !sortedRegions.includes(region)) {
sortedRegions.push(region);
}
});
});
// 添加剩余的地域
Object.keys(collectedData.countryDetails).forEach(region => {
if (!sortedRegions.includes(region)) {
sortedRegions.push(region);
}
});
// 为每个地域创建工作表
sortedRegions.forEach(region => {
const countries = collectedData.countryDetails[region];
if (countries && countries.length > 0) {
const mappedRegionName = mapFieldName(region);
// 创建简化的国家数据记录
const simplifiedCountries = countries.map(country => ({
'地域': mappedRegionName,
'国家': country.国家,
'数值': country.数值,
'占比': country.占比,
'终端类型': country.终端类型,
'统计周期': country.统计周期
}));
// 按国家排序
simplifiedCountries.sort((a, b) => a.国家.localeCompare(b.国家));
// 安全的工作表名称
const safeSheetName = mappedRegionName.length <= 30 ?
mappedRegionName : mappedRegionName.substring(0, 27) + "...";
const worksheet = XLSX.utils.json_to_sheet(simplifiedCountries);
XLSX.utils.book_append_sheet(workbook, worksheet, safeSheetName);
}
});
// 生成文件名
const timestamp = new Date().toISOString().slice(0, 19).replace(/[:\-T]/g, '').replace(/\..+/, '');
const filename = `阿里数据地域分布_${timestamp}.xlsx`;
// 处理空工作簿预防
if (workbook.SheetNames.length === 0) {
// 创建使用说明页面防止空工作簿错误
const instructionData = [{
'说明': '暂无数据,请检查API响应或配置参数',
'配置': `统计类型: ${userConfig.statisticsType}, 终端类型: ${userConfig.terminalType}, 时间选择: ${userConfig.selected}`,
'时间': new Date().toLocaleString('zh-CN')
}];
const instructionWorksheet = XLSX.utils.json_to_sheet(instructionData);
XLSX.utils.book_append_sheet(workbook, instructionWorksheet, '使用说明');
}
// 下载文件
try {
XLSX.writeFile(workbook, filename);
updateStatus(`Excel文件已生成: ${filename}`);
} catch (writeError) {
console.error('Excel写入错误:', writeError);
// 备用方案:使用下载方式
try {
const wbout = XLSX.write(workbook, { bookType: 'xlsx', type: 'array' });
const blob = new Blob([wbout], { type: 'application/octet-stream' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
updateStatus(`Excel文件已生成: ${filename}`);
} catch (backupError) {
updateStatus(`Excel导出失败: ${backupError.message}`);
console.error('备用下载方案也失败:', backupError);
}
}
} catch (error) {
updateStatus(`Excel导出失败: ${error.message}`);
console.error('Excel导出错误:', error);
}
}
// 监听API响应(可选功能,用于自动捕获)
function interceptFetchRequests() {
const originalFetch = window.fetch;
window.fetch = async function(...args) {
const response = await originalFetch.apply(this, args);
// 检查是否是目标API
if (args[0] && args[0].includes('mydata.alibaba.com/self') && args[0].includes('.json')) {
console.log('检测到地域分布API请求:', args[0]);
// 克隆响应以避免消费原始响应
const clonedResponse = response.clone();
try {
const data = await clonedResponse.json();
if (data && data.data && data.data.returnValue) {
console.log('自动捕获到地域分布数据');
const result = processResponseData(data);
if (result) {
collectedData = result;
updateStatus('自动捕获数据成功!');
const exportButton = document.getElementById('exportExcel');
if (exportButton) {
exportButton.disabled = false;
}
}
}
} catch (error) {
console.error('处理捕获的数据时出错:', error);
}
}
return response;
};
}
// 初始化脚本
function init() {
// 等待页面加载完成
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
setTimeout(createStartButton, 1000);
});
} else {
setTimeout(createStartButton, 1000);
}
// 启用API拦截
interceptFetchRequests();
// 监听页面变化,确保启动按钮始终可用
const observer = new MutationObserver(() => {
const hasButton = document.getElementById('aliDataCollectorStartBtn');
const hasPanel = document.getElementById('aliDataCollectorPanel');
const hasTargetElement = document.querySelector('.dataportal-base-area-title');
// 如果没有按钮和面板,且存在目标元素,则创建按钮
if (!hasButton && !hasPanel && hasTargetElement) {
setTimeout(createStartButton, 500);
}
// 如果目标元素不存在但没有按钮和面板,则使用默认位置
else if (!hasButton && !hasPanel && !hasTargetElement) {
setTimeout(createStartButtonDefault, 500);
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
console.log('阿里数据地域分布采集脚本已启动');
}
// 启动脚本
init();
})();