// ==UserScript==
// @name 阿里巴巴询盘数据采集-所有询盘(不仅仅是近一年的询盘)
// @namespace http://tampermonkey.net/
// @version 2.0
// @description 采集阿里巴巴开店至今所有询盘明细数据并导出为Excel
// @author 树洞先生
// @license MIT
// @match https://message.alibaba.com/message/*
// @grant none
// @require https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js
// @downloadURL https://update.greasyfork.icu/scripts/540816/%E9%98%BF%E9%87%8C%E5%B7%B4%E5%B7%B4%E8%AF%A2%E7%9B%98%E6%95%B0%E6%8D%AE%E9%87%87%E9%9B%86-%E6%89%80%E6%9C%89%E8%AF%A2%E7%9B%98%28%E4%B8%8D%E4%BB%85%E4%BB%85%E6%98%AF%E8%BF%91%E4%B8%80%E5%B9%B4%E7%9A%84%E8%AF%A2%E7%9B%98%29.user.js
// @updateURL https://update.greasyfork.icu/scripts/540816/%E9%98%BF%E9%87%8C%E5%B7%B4%E5%B7%B4%E8%AF%A2%E7%9B%98%E6%95%B0%E6%8D%AE%E9%87%87%E9%9B%86-%E6%89%80%E6%9C%89%E8%AF%A2%E7%9B%98%28%E4%B8%8D%E4%BB%85%E4%BB%85%E6%98%AF%E8%BF%91%E4%B8%80%E5%B9%B4%E7%9A%84%E8%AF%A2%E7%9B%98%29.meta.js
// ==/UserScript==
(function() {
'use strict';
// 字段映射
const FIELD_WHITELIST = [
"name", "highQualityLevelTag", "levelTag", "appFrom", "feedbackType",
"source", "subject", "tradeId", "createTime", "lastestReplyTime",
"readTime", "productId", "productName", "imageUrl", "url",
"ownerName", "noteCode", "registerDate", "country", "companyName",
"companyWebSite", "email", "mobileNumber", "phoneNumber",
"preferredIndustries", "productViewCount", "validInquiryCount",
"repliedInquiryCount", "validRfqCount", "loginDays",
"spamInquiryMarkedBySupplierCount", "addedToBlacklistCount",
"totalOrderCount", "totalOrderVolume", "tradeSupplierCount"
];
const FIELD_CHINESE_MAP = {
"name": "客户名", "highQualityLevelTag": "买家等级标签",
"levelTag": "买家类型标签", "appFrom": "询盘来源终端",
"feedbackType": "询盘类型", "source": "询盘来源",
"subject": "询盘标题", "tradeId": "询盘ID",
"createTime": "创建时间", "lastestReplyTime": "最新回复时间",
"readTime": "读取时间", "productId": "询盘产品ID",
"productName": "询盘产品标题", "imageUrl": "询盘产品图片",
"url": "询盘产品链接", "ownerName": "业务员",
"noteCode": "备注代码", "registerDate": "注册日期",
"country": "国家/地区", "companyName": "公司名称",
"companyWebSite": "公司网站", "email": "邮箱",
"mobileNumber": "手机号码", "phoneNumber": "电话号码",
"preferredIndustries": "最常采购行业", "productViewCount": "产品浏览数",
"validInquiryCount": "有效询价数", "repliedInquiryCount": "回复询价数量",
"validRfqCount": "有效RFQ数", "loginDays": "登录天数",
"spamInquiryMarkedBySupplierCount": "垃圾询盘数",
"addedToBlacklistCount": "被加为黑名单数", "totalOrderCount": "订单总数",
"totalOrderVolume": "订单总金额", "tradeSupplierCount": "交易供应商数"
};
let isCollecting = false;
// 工具函数
function extractToken(name) {
const match = document.cookie.match(new RegExp(name + '=([^;]+)'));
return match ? match[1] : '';
}
function getNested(obj, ...keys) {
for (let key of keys) {
if (!obj) return '';
obj = obj[key];
}
return obj || '';
}
function formatTimestamp(ts) {
try {
ts = parseInt(ts);
if (ts > 1e12) ts = Math.floor(ts / 1000);
const date = new Date(ts * 1000);
return date.toLocaleString('zh-CN', {
year: 'numeric', month: '2-digit', day: '2-digit',
hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false
}).replace(/\//g, '-');
} catch {
return ts;
}
}
function formatDate(ts) {
try {
ts = parseInt(ts);
const date = new Date(ts * 1000);
return date.toLocaleDateString('zh-CN').replace(/\//g, '-');
} catch {
return ts;
}
}
// 显示消息弹窗
function showMessage(message, type = 'info') {
const msgDiv = document.createElement('div');
msgDiv.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 24px 32px;
border-radius: 8px;
box-shadow: 0 4px 16px rgba(0,0,0,0.2);
z-index: 10002;
max-width: 400px;
font-size: 14px;
`;
const color = type === 'error' ? '#f44336' : type === 'success' ? '#4caf50' : '#2196f3';
msgDiv.innerHTML = `
${type === 'error' ? '❌ 错误' : type === 'success' ? '✅ 成功' : 'ℹ️ 提示'}
${message}
`;
document.body.appendChild(msgDiv);
document.getElementById('msg-close-btn').onclick = () => msgDiv.remove();
setTimeout(() => msgDiv.remove(), 5000);
}
// API请求函数
async function fetchPage(page, tokens) {
console.log(`正在请求第 ${page} 页数据...`);
const params = new URLSearchParams({
ctoken: tokens.ctoken,
dmtrack_pageid: '6797ac1f0b1a90b01751353557'
});
const paramsJson = {
system: "feedback",
listType: "all",
pageSize: 100,
pagination: { nextPage: page, pageSize: 100 },
filter: { isShowAtm: false, queryType: "history" },
order: { order: "desc", orderBy: "latest_contact_time" },
search: {}
};
const body = new URLSearchParams({
_csrf_token_: tokens.csrf,
postId: '1751353543558',
params: JSON.stringify(paramsJson)
});
try {
const response = await fetch(
`https://message.alibaba.com/message/ajax/feedback/subjectList.htm?${params}`,
{
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/json, text/javascript, */*; q=0.01',
'X-Requested-With': 'XMLHttpRequest'
},
body: body.toString(),
credentials: 'include'
}
);
const data = await response.json();
return data;
} catch (e) {
console.error('请求失败:', e);
throw e;
}
}
async function fetchAccountIdEncrypt(secTradeId, tokens) {
if (!secTradeId) return '';
const params = new URLSearchParams({
ctoken: tokens.ctoken,
_tb_token_: tokens.tb
});
const body = new URLSearchParams({
_csrf_token_: tokens.csrf,
params: JSON.stringify({ secTradeId })
});
try {
const response = await fetch(
`https://message.alibaba.com/message/ajax/feedback/querySummary.htm?${params}`,
{
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
},
body: body.toString(),
credentials: 'include'
}
);
const result = await response.json();
return result?.data?.contact?.accountIdEncrypt || '';
} catch (e) {
return '';
}
}
async function fetchKHTAccessToken(tradeId) {
if (!tradeId) return '';
try {
const response = await fetch(
`https://message.alibaba.com/message/maDetail.htm?imInquiryId=${tradeId}&hash=`,
{ credentials: 'include' }
);
const html = await response.text();
const match = html.match(/window\.KHTAccessToken\s*=\s*['"]([^'"]+)['"]/);
return match ? match[1] : '';
} catch (e) {
return '';
}
}
async function fetchCustomerInfo(accountIdEncrypt, secTradeId, khtToken, tokens) {
const params = new URLSearchParams({
buyerAccountId: accountIdEncrypt,
secTradeId: secTradeId,
buyerLoginId: '',
secReqToken: khtToken,
clientType: '',
ctoken: tokens.ctoken,
_tb_token_: tokens.tb,
callback: ''
});
try {
const response = await fetch(
`https://alicrm.alibaba.com/jsonp/customerPluginQueryServiceI/queryCustomerInfo.json?${params}`,
{ credentials: 'include' }
);
const text = await response.text();
const jsonStr = text.replace(/^\w+\((.*)\)$/, '$1');
const data = JSON.parse(jsonStr);
return data?.data || {};
} catch (e) {
return {};
}
}
async function fetchQuerySummaryFields(secTradeId, tokens) {
if (!secTradeId) return {};
const params = new URLSearchParams({
ctoken: tokens.ctoken,
_tb_token_: tokens.tb
});
const body = new URLSearchParams({
_csrf_token_: tokens.csrf,
params: JSON.stringify({ secTradeId })
});
try {
const response = await fetch(
`https://message.alibaba.com/message/ajax/feedback/querySummary.htm?${params}`,
{
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
},
body: body.toString(),
credentials: 'include'
}
);
const result = await response.json();
const contact = result?.data?.contact || {};
return {
name: contact.name || '',
productId: contact.productId || '',
productName: contact.productName || '',
imageUrl: contact.imageUrl || '',
url: contact.url || ''
};
} catch (e) {
return {};
}
}
// 显示设置弹窗
async function showSettingsDialog() {
if (document.getElementById('inquiry-export-dialog')) return;
if (isCollecting) {
showMessage('正在采集中,请稍候...', 'info');
return;
}
// 获取tokens
const tokens = {
ctoken: extractToken('ctoken'),
xsrf: extractToken('XSRF-TOKEN'),
tb: extractToken('_tb_token_'),
csrf: extractToken('XSRF-TOKEN')
};
if (!tokens.ctoken || !tokens.csrf) {
showMessage('无法获取必要的认证信息,请确保:\n1. 已登录阿里巴巴后台\n2. 在询盘消息页面运行此脚本\n3. 刷新页面后重试', 'error');
return;
}
// 获取总页数
let totalCount = 0;
let totalPages = 1;
let statusText = '正在获取总页数...';
try {
const result = await fetchPage(1, tokens);
totalCount = result?.data?.pagination?.totalCount || 0;
const pageSize = result?.data?.pagination?.pageSize || 100;
totalPages = Math.ceil(totalCount / pageSize);
if (totalPages < 1) totalPages = 1;
statusText = `共 ${totalCount} 条,约 ${totalPages} 页`;
} catch (e) {
statusText = '无法获取总页数';
}
// 创建遮罩层
const overlay = document.createElement('div');
overlay.id = 'inquiry-export-overlay';
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.5);
z-index: 10000;
`;
overlay.onclick = () => {
overlay.remove();
dialog.remove();
};
// 创建对话框
const dialog = document.createElement('div');
dialog.id = 'inquiry-export-dialog';
dialog.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: #fff;
border-radius: 8px;
box-shadow: 0 4px 16px rgba(0,0,0,0.18);
z-index: 10001;
padding: 32px 24px 24px 24px;
min-width: 400px;
font-family: Arial, sans-serif;
`;
dialog.onclick = (e) => e.stopPropagation();
dialog.innerHTML = `
📊 导出询盘明细
最大可采集页码数为 ${totalPages}
${statusText}
`;
document.body.appendChild(overlay);
document.body.appendChild(dialog);
// 按钮悬停效果
const startBtn = document.getElementById('start-export-btn');
const closeBtn = document.getElementById('close-export-btn');
startBtn.onmouseover = () => startBtn.style.background = '#0056b3';
startBtn.onmouseout = () => startBtn.style.background = '#007bff';
closeBtn.onmouseover = () => closeBtn.style.background = '#5a6268';
closeBtn.onmouseout = () => closeBtn.style.background = '#6c757d';
// 按钮事件
closeBtn.onclick = () => {
overlay.remove();
dialog.remove();
};
startBtn.onclick = () => {
const startPage = parseInt(document.getElementById('export-page-start').value, 10) || 1;
let pageCount = parseInt(document.getElementById('export-page-count').value, 10) || 1;
if (pageCount > totalPages) pageCount = totalPages;
startCollection(startPage, pageCount, totalPages, dialog, tokens);
};
}
// 更新状态
function updateStatus(message, dialog) {
const statusElement = dialog.querySelector('#inquiry-export-status');
if (statusElement) {
statusElement.textContent = message;
}
console.log(message);
}
// 开始采集
async function startCollection(startPage, pageCount, totalPages, dialog, tokens) {
if (isCollecting) return;
isCollecting = true;
const startBtn = document.getElementById('start-export-btn');
if (startBtn) {
startBtn.disabled = true;
startBtn.style.opacity = '0.6';
startBtn.style.cursor = 'not-allowed';
}
try {
const endPage = Math.min(startPage + pageCount - 1, totalPages);
updateStatus('开始采集数据...', dialog);
// 采集数据
const items = [];
const seenTradeIds = new Set();
for (let page = startPage; page <= endPage; page++) {
updateStatus(`正在采集第 ${page}/${endPage} 页...`, dialog);
const result = await fetchPage(page, tokens);
const dataList = result?.data?.list || [];
for (let entry of dataList) {
const itemList = Array.isArray(entry) ? entry : [entry];
for (let item of itemList) {
const tradeId = item.tradeId;
if (tradeId && !seenTradeIds.has(tradeId)) {
seenTradeIds.add(tradeId);
items.push(item);
}
}
}
}
updateStatus(`共采集 ${items.length} 条记录,正在处理...`, dialog);
// 处理数据
const allRows = [];
const seenKeys = new Set();
for (let i = 0; i < items.length; i++) {
const item = items[i];
updateStatus(`处理进度: ${i + 1}/${items.length}`, dialog);
const row = {};
const secTradeId = item.secTradeId || '';
// 采集产品信息
const productInfo = item.productInfo;
if (Array.isArray(productInfo) && productInfo.length > 0) {
const p = productInfo[0];
row.productId = p.productId || p.id || '';
row.productName = p.productName || '';
row.imageUrl = p.imageUrl || '';
row.url = p.url || '';
}
// 采集主字段
for (let field of FIELD_WHITELIST) {
if (!row[field]) {
row[field] = item[field] || '';
}
}
// 获取summary字段
const summaryFields = await fetchQuerySummaryFields(secTradeId, tokens);
for (let k of ['name']) {
if (!row[k]) {
row[k] = summaryFields[k] || '';
}
}
// 获取客户详细信息
const accountIdEncrypt = await fetchAccountIdEncrypt(secTradeId, tokens);
const khtToken = await fetchKHTAccessToken(row.tradeId);
const customerInfo = await fetchCustomerInfo(accountIdEncrypt, secTradeId, khtToken, tokens);
const dataInfo = customerInfo.data || {};
const alicrmInfo = dataInfo.alicrmCustomerInfo || {};
const buyerInfo = dataInfo.buyerInfo || {};
const buyerContact = buyerInfo.buyerContactInfo || {};
// 字段映射
const fieldMap = {
ownerName: () => getNested(alicrmInfo, 'ownerName'),
email: () => getNested(buyerContact, 'email') || getNested(alicrmInfo, 'email'),
mobileNumber: () => getNested(buyerContact, 'mobileNumber') || getNested(alicrmInfo, 'mobileNumber'),
phoneNumber: () => getNested(buyerContact, 'phoneNumber') || getNested(alicrmInfo, 'phoneNumber'),
companyName: () => getNested(buyerInfo, 'companyName') || getNested(alicrmInfo, 'companyName'),
companyWebSite: () => getNested(buyerInfo, 'companyWebSite') || getNested(alicrmInfo, 'companyWebSite'),
noteCode: () => getNested(alicrmInfo, 'noteCode'),
country: () => getNested(buyerInfo, 'country'),
levelTag: () => getNested(buyerInfo, 'levelTag'),
registerDate: () => getNested(buyerInfo, 'registerDate'),
productViewCount: () => getNested(buyerInfo, 'productViewCount'),
validInquiryCount: () => getNested(buyerInfo, 'validInquiryCount'),
repliedInquiryCount: () => getNested(buyerInfo, 'repliedInquiryCount'),
validRfqCount: () => getNested(buyerInfo, 'validRfqCount'),
loginDays: () => getNested(buyerInfo, 'loginDays'),
spamInquiryMarkedBySupplierCount: () => getNested(buyerInfo, 'spamInquiryMarkedBySupplierCount'),
addedToBlacklistCount: () => getNested(buyerInfo, 'addedToBlacklistCount'),
totalOrderCount: () => getNested(buyerInfo, 'totalOrderCount'),
totalOrderVolume: () => getNested(buyerInfo, 'totalOrderVolume'),
tradeSupplierCount: () => getNested(buyerInfo, 'tradeSupplierCount'),
highQualityLevelTag: () => getNested(buyerInfo, 'highQualityLevelTag'),
preferredIndustries: () => getNested(buyerInfo, 'preferredIndustries')
};
for (let field of FIELD_WHITELIST) {
if (fieldMap[field] && !row[field]) {
row[field] = fieldMap[field]() || row[field] || '';
}
}
// 格式化时间字段
for (let tsField of ['createTime', 'lastestReplyTime', 'readTime']) {
if (row[tsField]) {
row[tsField] = formatTimestamp(row[tsField]);
}
}
if (row.registerDate) {
row.registerDate = formatDate(row.registerDate);
}
// 替换-1为'客户隐藏'
for (let k in row) {
if (row[k] === -1 || row[k] === '-1') {
row[k] = '客户隐藏';
}
}
// 去重
const key = `${row.name}|${row.registerDate}|${row.companyName}`;
if (!seenKeys.has(key)) {
seenKeys.add(key);
allRows.push(row);
}
}
updateStatus(`正在导出Excel...`, dialog);
// 导出Excel
const ws_data = [FIELD_WHITELIST.map(k => FIELD_CHINESE_MAP[k])];
for (let row of allRows) {
const rowData = FIELD_WHITELIST.map(k => {
let v = row[k] || '';
if (Array.isArray(v)) v = v.join(', ');
return v;
});
ws_data.push(rowData);
}
const wb = XLSX.utils.book_new();
const ws = XLSX.utils.aoa_to_sheet(ws_data);
XLSX.utils.book_append_sheet(wb, ws, '询盘明细');
// 使用更兼容的方式导出Excel文件
const wbout = XLSX.write(wb, { bookType: 'xlsx', bookSST: false, type: 'array' });
const blob = new Blob([wbout], { type: 'application/octet-stream' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
const date = new Date().toISOString().slice(0, 10).replace(/-/g, '');
a.download = `询盘明细_${date}.xlsx`;
a.click();
window.URL.revokeObjectURL(url);
updateStatus(`✅ 导出完成!共 ${allRows.length} 条数据`, dialog);
showMessage(`导出完成!共 ${allRows.length} 条数据`, 'success');
} catch (error) {
console.error('采集过程出错:', error);
updateStatus(`❌ 采集失败: ${error.message}`, dialog);
showMessage(`采集失败: ${error.message}`, 'error');
} finally {
isCollecting = false;
if (startBtn) {
startBtn.disabled = false;
startBtn.style.opacity = '1';
startBtn.style.cursor = 'pointer';
}
}
}
// 添加启动按钮
function addButton() {
// 等待页面元素加载完成
const interval = setInterval(() => {
const replyInfoTitle = document.querySelector('.reply-info-title');
if (replyInfoTitle) {
clearInterval(interval);
// 创建导出按钮
const btn = document.createElement('button');
btn.id = 'inquiry-export-btn';
btn.textContent = '📊 采集询盘数据';
btn.style.cssText = `
margin-left: 10px;
padding: 4px 12px;
background: #ff6a00;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: all 0.3s;
vertical-align: middle;
`;
btn.onmouseover = () => btn.style.background = '#e65a00';
btn.onmouseout = () => btn.style.background = '#ff6a00';
btn.onclick = showSettingsDialog;
// 将按钮添加到"接待数据"标题后面
replyInfoTitle.appendChild(btn);
}
}, 1000); // 每秒检查一次元素是否加载完成
}
// 页面加载完成后添加按钮
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', addButton);
} else {
addButton();
}
})();