$keyword: 搜索关键词(如"耳机"、"笔记本"、"手机")$pages: 要采集的页数,默认2页从京东搜索页采集指定关键词的商品数据,导出为 CSV,包含标题、价格、销量、店铺四个字段。
用浏览器打开京东搜索,第1页:
https://search.jd.com/Search?keyword=URL编码后的$keyword&enc=utf-8&page=1
Success criteria: 页面加载完成,搜索框中显示关键词,结果区域可见
京东使用懒加载,商品数据在滚动到可视区域后才加载。必须慢速滚动到底部。
使用 evaluate 执行以下 JavaScript:
await new Promise(resolve => {
let scrolled = 0;
const step = 400;
const iv = setInterval(() => {
window.scrollBy(0, step);
scrolled += step;
if (scrolled >= document.body.scrollHeight * 1.5) {
clearInterval(iv);
resolve('滚动完成');
}
}, 600);
});
Success criteria: 滚动完成后商品图片和价格完整显示
使用 evaluate 执行以下 JavaScript 提取函数:
function extractProducts() {
const allDivs = document.querySelectorAll('div');
const map = new Map(); // div -> data 去重用
let counter = 0;
for (const div of allDivs) {
const text = div.textContent.trim();
if (!text.includes('¥') || text.length < 80 || text.length > 2000) continue;
if (text.includes('顶部导航') || text.includes('购物车')) continue;
// 去重:跳过被已处理父容器包含的 div
let skip = false;
for (const existing of map.keys()) {
if (existing.contains(div)) { skip = true; break; }
}
if (skip) continue;
let title = text.split('¥')[0].trim().replace(/^广告/, '').split('\n')[0].trim();
const priceMatch = text.match(/¥(\d+\.?\d*)/);
const salesMatch = text.match(/已售(\d+[万+]?)/);
let shop = '';
for (const suffix of ['旗舰店', '专卖店', '专营店', '官方店', '总店']) {
const idx = text.indexOf(suffix);
if (idx > 0) {
const before = text.substring(Math.max(0, idx - 30), idx + suffix.length);
const m = before.match(/([^\s¥\n\d]+?)(?:旗舰店|专卖店|专营店|官方店|总店)/);
if (m) { shop = m[0]; break; }
}
}
if (title && title.length > 8 && !title.includes('热卖榜')) {
map.set(div, {
title: title.substring(0, 80),
price: priceMatch ? '¥' + priceMatch[1] : '',
sales: salesMatch ? '已售' + salesMatch[1] : '',
shop
});
}
}
return [...map.values()];
}
Success criteria: 提取到的商品数量 > 10,每条包含标题和价格
使用 evaluate 查找并点击第2页按钮:
// 方法一:找分页容器中的"2"按钮
const pageBtns = document.querySelectorAll('[class*="page"] a, [class*="Page"] a, [class*="pagin"] *');
for (const btn of pageBtns) {
if (btn.textContent.trim() === '2') {
btn.click();
break;
}
}
// 如果方法一不行,回退到遍历所有元素
const all = document.querySelectorAll('a, span, em');
for (const el of all) {
if (el.textContent.trim() === '2' && el.offsetParent !== null) {
el.click();
break;
}
}
点击后等待 2-3 秒加载新页面。
验证翻页成功的方法:提取前3个商品标题,跟上一页对比——完全不同则翻页成功。
Success criteria: 页面内容改变,商品列表刷新为新的商品
重复步骤 2 和 3,将第 $pages 页的数据全部提取。
// 用 evaluate 收集所有页的数据
// 在 Node.js 侧用 fs.writeFileSync 写入
const csvHeader = '标题,价格,销量,店铺';
const csvRows = allProducts.map(p => {
// CSV 转义:含逗号或引号的字段用双引号包裹
const escape = v => '"' + String(v || '').replace(/"/g, '""') + '"';
return [escape(p.title), escape(p.price), escape(p.sales), escape(p.shop)].join(',');
});
const csv = [csvHeader, ...csvRows].join('\n');
// 写入 UTF-8 编码
fs.writeFileSync(outputPath, csv, 'utf-8');
输出路径:/Users/[用户名]/.openclaw/workspace/jd_{$keyword}_{$pages}pages.csv
Success criteria: 验证 wc -l 行数 = 1(表头)+ 商品总数,用 head 查看前3行确保无乱码
共 1 个版本