淘宝百亿补贴赛道采集
做电商的朋友应该都有体感:现在想冲销售额,‘百亿补贴’几乎是必选项,尤其是对我们这种非旗舰店、多品牌的C店来说,不上百补真的很难熬。
但问题来了,你点开百补赛道一看,足足500页!靠人工一个个翻?根本不现实。虽然能搜品牌名,但如果我想发掘竞品、找新机会怎么办?
为了解决这个痛点,我写了个采集脚本,亲测有效,一次性就能把数据全部拉下来。
⚠️ 这里必须强调一下哈: 这个脚本纯粹是技术交流和自用分享,不提供任何售后支持。大家用的时候务必注意合规,千万别对平台造成负担或损害,相关风险一定要自负哦。
/**
* ╔══════════════════════════════════════════════════════════════╗
* ║ 百亿补贴全量赛道数据抓取工具 - 控制台版 ║
* ║ ║
* ║ 使用方法: ║
* ║ 1. 在千牛百亿补贴页面按 F12 打开开发者工具 ║
* ║ 2. 切换到 Console(控制台) 标签 ║
// ║ 3. 粘贴以下全部代码,按回车执行 ║
* ║ 4. 等待自动完成(约2-3分钟),CSV文件会自动下载 ║
* ╚══════════════════════════════════════════════════════════════╝
*/
(async function() {
'use strict';
const startTime = Date.now();
console.log('%c🚀 百亿补贴全量赛道抓取工具启动...', 'color:#1F4E79;font-size:14px;font-weight:bold');
// ========== 配置 ==========
const API_URL = 'https://sale.taobao.com/list/raceList/queryListData.do';
const SITE_ID = '127';
const BIZ_CODE = 'bybt_bidding';
const CONCURRENCY = 15; // 并发请求数
const DELAY_MS = 60; // 请求间隔(ms)
// ========== 获取Token ==========
const tokenMatch = document.cookie.match(/_tb_token_=([^;]+)/);
if (!tokenMatch) {
console.error('❌ 未找到 _tb_token_,请确认已登录千牛');
return;
}
const TOKEN = tokenMatch[1];
console.log(`%c✅ Token获取成功`, 'color:green');
// ========== 请求函数 ==========
async function fetchPage(pageNo) {
const params = new URLSearchParams({
'_tb_token_': TOKEN,
'page': String(pageNo),
'siteId': SITE_ID,
'bizCode': BIZ_CODE,
'isYpys': 'false',
'raceTypes': '0'
});
const resp = await fetch(`${API_URL}?${params.toString()}`, {
method: 'GET',
credentials: 'include',
headers: { 'X-Requested-With': 'XMLHttpRequest' }
});
return resp.json();
}
// ========== 第1页:获取总数 ==========
console.log('%c📡 正在连接API,获取数据总量...', 'color:#E67E22');
let firstPage;
try {
firstPage = await fetchPage(1);
} catch(e) {
console.error('❌ API连接失败:', e.message);
return;
}
const firstList = firstPage?.data?.list || [];
const totalCount = firstPage?.data?.totalItem || 5000;
const actualPageSize = firstList.length || 10;
const totalPages = Math.ceil(totalCount / actualPageSize);
if (firstList.length === 0) {
console.error('❌ API未返回数据,响应内容:', JSON.stringify(firstPage).substring(0, 300));
return;
}
console.log(`%c📊 数据概览:`, 'color:#2980B9;font-weight:bold');
console.log(` 总记录数: ${totalCount}`);
console.log(` 每页条数: ${actualPageSize}`);
console.log(` 总页数: ${totalPages}`);
console.log(` 字段示例: ${Object.keys(firstList[0]).join(', ')}`);
// ========== 批量抓取所有页面 ==========
const allData = [...firstList];
const errors = [];
console.log(`\n%c⏳ 开始全量抓取 (并发${CONCURRENCY})...`, 'color:#8E44AD;font-weight:bold');
for (let batchStart = 2; batchStart <= totalPages; batchStart += CONCURRENCY) {
const batchEnd = Math.min(batchStart + CONCURRENCY - 1, totalPages);
const promises = [];
for (let p = batchStart; p <= batchEnd; p++) {
promises.push(
fetchPage(p)
.then(json => json?.data?.list || [])
.catch(e => {
errors.push({ page: p, error: e.message });
return [];
})
);
}
const results = await Promise.all(promises);
for (const list of results) allData.push(...list);
// 进度显示
const pct = ((batchEnd / totalPages) * 100).toFixed(1);
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
const eta = ((Date.now() - startTime) / batchEnd * (totalPages - batchEnd) / 1000).toFixed(0);
if (batchEnd % 50 === 0 || batchEnd >= totalPages) {
console.log(` 📥 进度: ${batchEnd}/${totalPages}页 (${pct}%) | 已获${allData.length}条 | ⏱️${elapsed}s | 预计剩余${eta}s`);
}
await new Promise(r => setTimeout(r, DELAY_MS));
if (allData.length >= totalCount) break;
}
// ========== 完成 ==========
const totalTime = ((Date.now() - startTime) / 1000).toFixed(1);
console.log('\n' + '═'.repeat(50));
console.log('%c🎉 抓取完成!', 'color:#27AE60;font-size:16px;font-weight:bold');
console.log(` 📊 总记录数: ${allData.length}`);
console.log(` ⏱️ 耗时: ${totalTime}秒`);
console.log(` ❌ 失败: ${errors.length}个请求`);
if (errors.length > 0) {
console.log(` 🔍 失败详情:`, errors.slice(0, 5));
}
// ========== 导出CSV ==========
// 提取所有字段
const fieldSet = new Set();
allData.forEach(item => Object.keys(item).forEach(k => fieldSet.add(k)));
const fields = [...fieldSet].sort();
// 特殊字段处理
function formatValue(val, field) {
if (val === null || val === undefined) return '';
if (field === 'raceLabels' && Array.isArray(val)) {
return val.map(l => l.raceLabelName || '').join('; ');
}
if (typeof val === 'boolean') return val ? '是' : '否';
return String(val);
}
// 构建CSV(带BOM头支持中文)
const csvRows = [fields.join(',')];
allData.forEach(item => {
csvRows.push(fields.map(f => {
let v = formatValue(item[f], f);
if (v.includes(',') || v.includes('"') || v.includes('\n')) {
v = '"' + v.replace(/"/g, '""') + '"';
}
return v;
}).join(','));
});
const csvContent = '\uFEFF' + csvRows.join('\n');
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = `百亿补贴全量赛道_${totalCount}条_${new Date().toISOString().slice(0,10)}.csv`;
link.click();
console.log(`\n%c📥 CSV文件已下载!`, 'color:#27AE60;font-weight:bold');
console.log(` 文件名: 百亿补贴全量赛道_${totalCount}条_${new Date().toISOString().slice(0,10)}.csv`);
// 同时保存到全局变量供查看
window.__BYBT_ALL_TRACKS__ = allData;
console.log(`\n💡 完整数据已保存到全局变量: window.__BYBT_ALL_TRACKS__`);
console.log(` 可在控制台输入此变量名查看或进一步操作`);
// ========== 数据预览统计 ==========
console.log('\n' + '─'.repeat(50));
console.log('%c📈 数据预览:', 'color:#2980B9;font-weight:bold');
// 品牌分布
const brandCount = {};
allData.forEach(d => { const b = d.raceBrandName || '未知'; brandCount[b] = (brandCount[b]||0)+1; });
const topBrands = Object.entries(brandCount).sort((a,b)=>b[1]-a[1]).slice(0,10);
console.log(` Top10品牌:`);
topBrands.forEach(([b,c]) => console.log(` ${b}: ${c}条`));
// 类目分布
const cateCount = {};
allData.forEach(d => { const c = d.raceCateName || '未知'; cateCount[c] = (cateCount[c]||0)+1; });
const topCates = Object.entries(cateCount).sort((a,b)=>b[1]-a[1]).slice(0,10);
console.log(` Top10类目:`);
topCates.forEach(([c,n]) => console.log(` ${c}: ${n}条`));
// 营销玩法分布
const sceneCount = {};
allData.forEach(d => { const s = d.sceneName || '未知'; sceneCount[s] = (sceneCount[s]||0)+1; });
console.log(` 营销玩法分布:`);
Object.entries(sceneCount).sort((a,b)=>b[1]-a[1]).forEach(([s,n]) => console.log(` ${s}: ${n}条`));
console.log('\n' + '═'.repeat(50));
})();