search.js
1 import { cli, Strategy } from '@jackwener/opencli/registry'; 2 import { clampInt, requireNonEmptyQuery } from '../_shared/common.js'; 3 cli({ 4 site: 'jd', 5 name: 'search', 6 description: '京东商品搜索', 7 domain: 'search.jd.com', 8 strategy: Strategy.COOKIE, 9 args: [ 10 { name: 'query', positional: true, required: true, help: '搜索关键词' }, 11 { name: 'limit', type: 'int', default: 10, help: '返回结果数量 (max 30)' }, 12 ], 13 columns: ['rank', 'title', 'price', 'shop', 'sku', 'url'], 14 navigateBefore: false, 15 func: async (page, kwargs) => { 16 const limit = clampInt(kwargs.limit, 10, 1, 30); 17 const query = requireNonEmptyQuery(kwargs.query); 18 await page.goto(`https://search.jd.com/Search?keyword=${encodeURIComponent(query)}&enc=utf-8`); 19 await page.wait(5); 20 await page.autoScroll({ times: 2, delayMs: 1500 }); 21 const data = await page.evaluate(` 22 (async () => { 23 const normalize = v => (v || '').replace(/\\s+/g, ' ').trim(); 24 for (let i = 0; i < 20; i++) { 25 if (document.querySelectorAll('div[data-sku]').length > 0) break; 26 await new Promise(r => setTimeout(r, 500)); 27 } 28 const items = document.querySelectorAll('div[data-sku]'); 29 const results = []; 30 for (const el of items) { 31 const sku = el.getAttribute('data-sku') || ''; 32 if (!sku) continue; 33 const text = normalize(el.textContent); 34 if (text.length < 10) continue; 35 36 const priceMatch = text.match(/¥([\\d,.]+)/); 37 const price = priceMatch ? '¥' + priceMatch[1] : ''; 38 39 let title = ''; 40 if (priceMatch) { 41 const beforePrice = text.substring(0, text.indexOf('¥')); 42 title = beforePrice.replace(/^(海外无货|京东超市|自营|秒杀|新品|预售|PLUS)/, '').trim(); 43 } 44 if (!title || title.length < 4) continue; 45 46 let shop = ''; 47 const shopMatch = text.match(/(\\S{2,15}(?:旗舰店|专卖店|自营店|官方旗舰店|京东自营旗舰店|京东自营))/); 48 if (shopMatch) shop = shopMatch[1]; 49 50 results.push({ 51 rank: results.length + 1, 52 title: title.slice(0, 80), 53 price, 54 shop, 55 sku, 56 url: 'https://item.jd.com/' + sku + '.html', 57 }); 58 if (results.length >= ${limit}) break; 59 } 60 return results; 61 })() 62 `); 63 return Array.isArray(data) ? data : []; 64 }, 65 });