您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
更新加入了公海客户和我的客户导出并对客户行为字段进行爬取,"产品浏览数", "有效询盘数", "有效RFQ数", "登录天数", "垃圾询盘数", "被加为黑名单数", "订单总数", "订单总金额", "交易供应商数", "最近搜索词", "最常采购行业", "最近询盘产品链接"
// ==UserScript== // @name 阿里巴巴国际站客户信息导出--树洞先生 // @namespace http://tampermonkey.net/ // @version 1.5 // @license MIT // @description 更新加入了公海客户和我的客户导出并对客户行为字段进行爬取,"产品浏览数", "有效询盘数", "有效RFQ数", "登录天数", "垃圾询盘数", "被加为黑名单数", "订单总数", "订单总金额", "交易供应商数", "最近搜索词", "最常采购行业", "最近询盘产品链接" // @author YourName // @match https://alicrm.alibaba.com/* // @grant none // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/xlsx.full.min.js // ==/UserScript== (function() { 'use strict'; // 字段映射 const fields = [ "客户姓名", "旺旺ID", "业务员", "来源", "性别", "客户国家", "客户等级", "职位", "头像", "客户名片链接", "邮箱", "手机号码", "座机号码", "社交账号", "建档时间", "注册时间", "年采购额", "公司名称", "公司部门", "公司网址", "公司地址", // 行为字段 "产品浏览数", "有效询盘数", "有效RFQ数", "登录天数", "垃圾询盘数", "被加为黑名单数", "订单总数", "订单总金额", "交易供应商数", "最近搜索词", "最常采购行业", "最近询盘产品链接" ]; // 英文与中文字段映射 const behaviorFieldMap = { productViewCount: "产品浏览数", validInquiryCount: "有效询盘数", validRfqCount: "有效RFQ数", loginDays: "登录天数", spamInquiryMarkedBySupplierCount: "垃圾询盘数", addedToBlacklistCount: "被加为黑名单数", totalOrderCount: "订单总数", totalOrderVolume: "订单总金额", tradeSupplierCount: "交易供应商数", searchWords: "最近搜索词", preferredIndustries: "最常采购行业", latestInquiryProducts: "最近询盘产品链接" }; // 判断是否为公海客户页面 function isPublicCustomerPage() { // 直接根据URL hash判断 return location.hash === '#public-customer'; } // 采集公海客户ID(分页采集,支持筛选参数) async function getPublicCustomerIds(_tb_token_, progressCb) { let customerIds = []; let pageNum = 1; const filterParams = getCurrentFilterParams(); // 获取筛选参数 const pageSize = 10; let total = 0; while (true) { const body = { jsonArray: filterParams.jsonArray || '[]', orderDescs: [{col: 'opp_gmt_modified', asc: false}], pageNum: pageNum, pageSize: pageSize }; const resp = await fetch('https://alicrm.alibaba.com/eggCrmQn/crm/customerQueryServiceI/queryPublicCustomerList.json?_tb_token_=' + encodeURIComponent(_tb_token_), { method: 'POST', headers: { 'content-type': 'application/json;charset=UTF-8', 'accept': '*/*' }, credentials: 'include', body: JSON.stringify(body) }); const data = await resp.json(); if (pageNum === 1) { total = data?.data?.total || 0; } const customers = data?.data?.data || []; if (!customers.length) break; for (const c of customers) { if (c.customerId) customerIds.push(c.customerId); } if (progressCb) progressCb(pageNum, customerIds.length, total); if (customerIds.length >= total) break; pageNum++; await new Promise(r => setTimeout(r, 300)); } return customerIds; } // 获取cookie function getCookie(name) { const value = document.cookie.match('(^|;)\\s*' + name + '\\s*=\\s*([^;]+)'); return value ? value.pop() : ''; } // --- 自动捕获最新筛选参数 --- let lastJsonArray = null; window._lastJsonArray = lastJsonArray; if (typeof unsafeWindow !== 'undefined') { unsafeWindow._lastJsonArray = lastJsonArray; } (function() { const oldFetch = window.fetch; window.fetch = function(input, init) { if (typeof input === 'string' && input.includes('queryCustomerList.json') && init && init.body) { try { const body = JSON.parse(init.body); if (body.jsonArray) { lastJsonArray = body.jsonArray; window._lastJsonArray = lastJsonArray; if (typeof unsafeWindow !== 'undefined') { unsafeWindow._lastJsonArray = lastJsonArray; } } } catch (e) {} } return oldFetch.apply(this, arguments); }; })(); // 获取当前页面筛选参数(如有特殊参数可补充) function getCurrentFilterParams() { if (lastJsonArray) { return { jsonArray: lastJsonArray }; } // fallback: 可选,返回空或默认 return {}; } // 采集所有筛选结果的客户ID(分页采集) async function getFilteredCustomerIds(_tb_token_, progressCb) { let customerIds = []; let pageNum = 1; const filterParams = getCurrentFilterParams(); const pageSize = 10; // 和页面一致 let total = 0; while (true) { const body = { jsonArray: filterParams.jsonArray || '[]', orderDescs: [{col: 'opp_gmt_modified', asc: false}], pageNum: pageNum, pageSize: pageSize }; const resp = await fetch('https://alicrm.alibaba.com/eggCrmQn/crm/customerQueryServiceI/queryCustomerList.json?_tb_token_=' + encodeURIComponent(_tb_token_), { method: 'POST', headers: { 'content-type': 'application/json;charset=UTF-8', 'accept': '*/*' }, credentials: 'include', body: JSON.stringify(body) }); const data = await resp.json(); if (pageNum === 1) { total = data?.data?.total || 0; } const customers = data?.data?.data || []; if (!customers.length) break; for (const c of customers) { if (c.customerId) customerIds.push(c.customerId); } if (progressCb) progressCb(pageNum, customerIds.length, total); if (customerIds.length >= total) break; pageNum++; await new Promise(r => setTimeout(r, 300)); } return customerIds; } // 获取客户行为 async function getCustomerBehavior(customerId, _tb_token_) { const url = `https://alicrm.alibaba.com/eggCrmQn/crm/customerQueryServiceI/queryCustomerBehavior.json?customerId=${customerId}&_tb_token_=${encodeURIComponent(_tb_token_)}`; const resp = await fetch(url, { method: 'GET', credentials: 'include' }); const data = await resp.json(); const behavior = data?.data?.data || {}; // 处理映射和数组字段 const result = {}; for (const key in behaviorFieldMap) { let val = behavior[key]; if (Array.isArray(val)) { if (key === "latestInquiryProducts") { // 拼接图片和链接 val = val.map(item => item.productUrl).join(", "); } else { val = val.join(", "); } } // 特殊处理-1为客户隐藏 if (["totalOrderCount", "totalOrderVolume", "tradeSupplierCount"].includes(key) && val === -1) { val = "客户隐藏"; } result[behaviorFieldMap[key]] = val !== undefined ? val : ''; } return result; } // 获取客户详情 async function getCustomerDetail(customerId, _tb_token_) { const url = `https://alicrm.alibaba.com/eggCrmQn/crm/customerQueryServiceI/queryCustomerAndContacts.json?customerId=${customerId}&_tb_token_=${encodeURIComponent(_tb_token_)}`; const resp = await fetch(url, { method: 'GET', credentials: 'include' }); const data = await resp.json(); const customer = data?.data?.customerDetailCO || {}; const contacts = data?.data?.contactQueryCOList || []; const contact = contacts[0] || {}; // growthLevel优先取联系人,没有再取客户 const growthLevel = contact.growthLevel || (customer.growthLevelInfo?.growthLevel || ""); // address优先取客户,没有再取联系人 let address = customer.address || contact.address || ""; if (typeof address === 'object' && address !== null) { address = ["country", "province", "city", "district", "street"].map(k => address[k] || "None").join(","); } // 合并姓名 const name = [contact.firstName || "", contact.lastName || ""].join(" ").trim(); // registerDate 字段处理 let registerDate = ""; if (customer.registerDate && /^\d+$/.test(customer.registerDate)) { registerDate = new Date(Number(customer.registerDate) * 1000).toISOString().slice(0,10); } function list2str(val) { if (Array.isArray(val)) { if (val.length && typeof val[0] === 'object') { return val.map(item => ["countryCode", "areaCode", "number"].map(k => item[k] || "").join("-")).join(","); } else { return val.join(","); } } return val || ""; } // 字段映射 const result = { "客户姓名": name, "旺旺ID": contact.loginId || "", "业务员": customer.oppModifier || "", "来源": customer.source || "", "性别": contact.gender || "", "客户国家": customer.country || "", "客户等级": growthLevel, "职位": contact.position || "", "头像": contact.avatar || "", "客户名片链接": contact.profileLink || "", "邮箱": list2str(contact.email), "手机号码": list2str(contact.mobiles), "座机号码": list2str(contact.phoneNumbers), "社交账号": list2str(contact.ims), "建档时间": customer.gmtCreate || "", "注册时间": registerDate, "年采购额": customer.annualProcurement || "", "公司名称": customer.companyName || "", "公司部门": contact.department || "", "公司网址": customer.website || "", "公司地址": address }; // 合并行为数据 const behavior = await getCustomerBehavior(customerId, _tb_token_); return { ...result, ...behavior }; } // 导出为Excel function exportExcel(data, filename) { const ws = XLSX.utils.json_to_sheet(data, {header: fields}); const wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, "客户信息"); const wbout = XLSX.write(wb, {bookType:'xlsx', type:'array'}); const blob = new Blob([wbout], {type: "application/octet-stream"}); const url = URL.createObjectURL(blob); // 用 a 标签下载 const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); setTimeout(() => { document.body.removeChild(a); URL.revokeObjectURL(url); }, 100); } // 并发控制函数 async function concurrentMap(inputs, limit, asyncFn, progressCb) { const results = []; let idx = 0; let running = 0; return new Promise((resolve, reject) => { function next() { if (idx >= inputs.length && running === 0) { resolve(results); return; } while (running < limit && idx < inputs.length) { const curIdx = idx; running++; asyncFn(inputs[curIdx], curIdx) .then(res => { results[curIdx] = res; if (progressCb) progressCb(curIdx + 1, inputs.length); }) .catch(e => { results[curIdx] = null; }) .finally(() => { running--; next(); }); idx++; } } next(); }); } // 等待页面 loading 状态消失 async function waitForLoadingToFinish() { let count = 0; while (document.querySelector('.loading-indicator, .next-loading, .ant-spin-spinning')) { if (count++ > 50) { // 最多等10秒 console.log('loading 等待超时,强制继续'); break; } await new Promise(r => setTimeout(r, 200)); } } // 主入口 async function run() { if (!confirm("是否开始导出客户信息?")) return; const _tb_token_ = getCookie('_tb_token_'); if (!_tb_token_) { alert("未检测到 _tb_token_,请先登录并刷新页面!"); return; } // 等待页面 loading 状态消失,确保筛选请求已完成 await waitForLoadingToFinish(); // 获取进度元素 const progress = document.getElementById('exportCustomerProgress'); if (progress) { progress.style.display = 'block'; progress.textContent = `正在采集客户ID,请耐心等待...`; } // 采集客户ID(根据页面类型自动切换) let customerIds; if (isPublicCustomerPage()) { customerIds = await getPublicCustomerIds(_tb_token_, (page, total, all) => { if (progress) progress.textContent = `正在采集公海客户ID,请耐心等待...(第${page}页,已采集${total}个客户${all ? `/共${all}个` : ''})`; }); } else { customerIds = await getFilteredCustomerIds(_tb_token_, (page, total, all) => { if (progress) progress.textContent = `正在采集客户ID,请耐心等待...(第${page}页,已采集${total}个客户${all ? `/共${all}个` : ''})`; }); } if (!customerIds.length) { alert("未采集到客户ID,请确认筛选条件下有客户。"); if (progress) progress.style.display = 'none'; return; } alert(`共采集到 ${customerIds.length} 个客户ID,开始采集详情...`); if (progress) { progress.textContent = `正在导出 0/${customerIds.length}`; } // 并发采集详情 const concurrentLimit = 5; const results = await concurrentMap( customerIds, concurrentLimit, (id, i) => getCustomerDetail(id, _tb_token_), (done, total) => { if (progress) progress.textContent = `正在导出 ${done}/${total}`; console.log(`已采集详情 ${done}/${total}`); } ); const today = new Date().toISOString().slice(0,10).replace(/-/g, ""); const exportType = isPublicCustomerPage() ? "公海客户" : "客户列表"; const filename = `${exportType}信息导出${today}.xlsx`; exportExcel(results.filter(Boolean), filename); if (progress) { progress.textContent = `${exportType}导出完成!`; setTimeout(() => { progress.style.display = 'none'; }, 3000); } alert(`${exportType}导出完成!`); } // 页面添加按钮 function addButton() { if (document.getElementById('exportCustomerBtn')) return; const btn = document.createElement('button'); btn.id = 'exportCustomerBtn'; // 根据页面类型设置按钮名称 btn.textContent = isPublicCustomerPage() ? '公海客户导出--树洞先生' : '我的客户导出--树洞先生'; btn.style.position = 'fixed'; btn.style.top = '100px'; btn.style.right = '40px'; btn.style.zIndex = 9999; btn.style.background = '#4CAF50'; btn.style.color = '#fff'; btn.style.padding = '10px 20px'; btn.style.border = 'none'; btn.style.borderRadius = '5px'; btn.style.cursor = 'pointer'; btn.onclick = run; document.body.appendChild(btn); // 添加进度显示元素 const progress = document.createElement('span'); progress.id = 'exportCustomerProgress'; progress.style.position = 'fixed'; progress.style.top = '140px'; progress.style.right = '40px'; progress.style.background = 'rgba(0,0,0,0.7)'; progress.style.color = '#fff'; progress.style.padding = '6px 16px'; progress.style.borderRadius = '5px'; progress.style.fontSize = '16px'; progress.style.zIndex = 9999; progress.style.display = 'none'; document.body.appendChild(progress); } // 动态更新按钮名称(监听hash变化) function updateButtonName() { const btn = document.getElementById('exportCustomerBtn'); if (btn) { btn.textContent = isPublicCustomerPage() ? '公海客户导出--树洞先生' : '我的客户导出--树洞先生'; } } window.addEventListener('hashchange', updateButtonName); // 等待页面加载 window.addEventListener('load', () => { addButton(); updateButtonName(); }); })();