reviews.js
1 import { cli, Strategy } from '@jackwener/opencli/registry'; 2 import { clampInt, normalizeNumericId } from '../_shared/common.js'; 3 cli({ 4 site: 'taobao', 5 name: 'reviews', 6 description: '淘宝商品评价', 7 domain: 'item.taobao.com', 8 strategy: Strategy.COOKIE, 9 args: [ 10 { name: 'id', positional: true, required: true, help: '商品 ID' }, 11 { name: 'limit', type: 'int', default: 10, help: '返回评价数量 (max 20)' }, 12 ], 13 columns: ['rank', 'user', 'content', 'date', 'spec'], 14 navigateBefore: false, 15 func: async (page, kwargs) => { 16 const itemId = normalizeNumericId(kwargs.id, 'id', '827563850178'); 17 const limit = clampInt(kwargs.limit, 10, 1, 20); 18 await page.goto('https://www.taobao.com'); 19 await page.wait(2); 20 await page.evaluate(`location.href = ${JSON.stringify(`https://item.taobao.com/item.htm?id=${itemId}`)}`); 21 await page.wait(6); 22 const data = await page.evaluate(` 23 (async () => { 24 const normalize = v => (v || '').replace(/\\s+/g, ' ').trim(); 25 let sellerId = ''; 26 const pageText = document.documentElement.innerHTML || ''; 27 const sellerMatch = pageText.match(/sellerId['":\\s]+['"]?(\\d+)/) || pageText.match(/userId['":\\s]+['"]?(\\d+)/) || pageText.match(/shopId['":\\s]+['"]?(\\d+)/); 28 if (sellerMatch) sellerId = sellerMatch[1]; 29 30 if (!sellerId) { 31 const shopLink = document.querySelector('a[href*="shopId="], a[href*="seller_id="], a[href*="userId="]'); 32 const href = shopLink?.getAttribute('href') || ''; 33 const m = href.match(/(?:shopId|seller_id|userId)=(\\d+)/); 34 if (m) sellerId = m[1]; 35 } 36 37 const url = 'https://rate.tmall.com/list_detail_rate.htm?itemId=' + ${JSON.stringify(itemId)} 38 + (sellerId ? '&sellerId=' + sellerId : '') 39 + '&order=3¤tPage=1&append=0&content=1&tagId=&posi=&picture=&groupValue=&needFold=0&_ksTS=' + Date.now(); 40 41 try { 42 const results = await new Promise((resolve) => { 43 const cbName = '_ocli_rate_' + Date.now(); 44 let settled = false; 45 const cleanup = (value) => { 46 if (settled) return; 47 settled = true; 48 delete window[cbName]; 49 script.remove(); 50 resolve(value); 51 }; 52 window[cbName] = (payload) => { 53 const list = payload?.rateDetail?.rateList || []; 54 cleanup(list.slice(0, ${limit}).map((item, i) => ({ 55 rank: i + 1, 56 user: (item.displayUserNick || item.userNick || '').slice(0, 15), 57 content: normalize(item.rateContent || '').slice(0, 150), 58 date: (item.rateDate || '').slice(0, 10), 59 spec: normalize(item.auctionSku || '').slice(0, 40), 60 }))); 61 }; 62 const script = document.createElement('script'); 63 script.src = url + '&callback=' + cbName; 64 script.onerror = () => cleanup([]); 65 document.head.appendChild(script); 66 setTimeout(() => cleanup([]), 10000); 67 }); 68 if (results.length > 0) return results; 69 } catch {} 70 71 return []; 72 })() 73 `); 74 return Array.isArray(data) ? data : []; 75 }, 76 });