sectors.js
1 // eastmoney sectors — industry / concept / region sector board ranking. 2 // 3 // opencli eastmoney sectors 4 // opencli eastmoney sectors --type concept --sort money-flow --limit 30 5 6 import { cli, Strategy } from '@jackwener/opencli/registry'; 7 import { CliError } from '@jackwener/opencli/errors'; 8 9 const SECTOR_TYPES = { 10 industry: 'm:90+t:2', 11 concept: 'm:90+t:3', 12 region: 'm:90+t:1', 13 }; 14 15 const SORTS = { 16 change: { fid: 'f3', order: 'desc' }, 17 drop: { fid: 'f3', order: 'asc' }, 18 'money-flow': { fid: 'f62', order: 'desc' }, 19 'out-flow': { fid: 'f62', order: 'asc' }, 20 turnover: { fid: 'f6', order: 'desc' }, 21 }; 22 23 cli({ 24 site: 'eastmoney', 25 name: 'sectors', 26 description: '板块排行(行业/概念/地域)按涨跌幅、主力资金或成交额排序', 27 domain: 'push2.eastmoney.com', 28 strategy: Strategy.PUBLIC, 29 browser: false, 30 args: [ 31 { name: 'type', type: 'string', default: 'industry', help: '板块类型:industry / concept / region' }, 32 { name: 'sort', type: 'string', default: 'change', help: '排序:change / drop / money-flow / out-flow / turnover' }, 33 { name: 'limit', type: 'int', default: 20, help: '返回数量 (max 100)' }, 34 ], 35 columns: ['rank', 'code', 'name', 'price', 'changePercent', 'mainNet', 'leadStock', 'leadChangePercent', 'upCount', 'downCount'], 36 func: async (_page, args) => { 37 const typeKey = String(args.type ?? 'industry').toLowerCase(); 38 const fs = SECTOR_TYPES[typeKey]; 39 if (!fs) throw new CliError('INVALID_ARGUMENT', `Unknown sector type "${typeKey}". Valid: ${Object.keys(SECTOR_TYPES).join(', ')}`); 40 const sortKey = String(args.sort ?? 'change').toLowerCase(); 41 const sort = SORTS[sortKey]; 42 if (!sort) throw new CliError('INVALID_ARGUMENT', `Unknown sort "${sortKey}". Valid: ${Object.keys(SORTS).join(', ')}`); 43 const limit = Math.max(1, Math.min(Number(args.limit) || 20, 100)); 44 45 const url = new URL('https://push2.eastmoney.com/api/qt/clist/get'); 46 url.searchParams.set('pn', '1'); 47 url.searchParams.set('pz', String(limit)); 48 url.searchParams.set('po', sort.order === 'desc' ? '1' : '0'); 49 url.searchParams.set('np', '1'); 50 url.searchParams.set('fltt', '2'); 51 url.searchParams.set('invt', '2'); 52 url.searchParams.set('fid', sort.fid); 53 url.searchParams.set('fs', fs); 54 url.searchParams.set('fields', 'f12,f14,f2,f3,f62,f104,f105,f128,f136,f140,f141'); 55 url.searchParams.set('ut', 'b2884a393a59ad64002292a3e90d46a5'); 56 57 const resp = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0' } }); 58 if (!resp.ok) throw new CliError('HTTP_ERROR', `sectors failed: HTTP ${resp.status}`); 59 const data = await resp.json(); 60 const diff = Array.isArray(data?.data?.diff) ? data.data.diff : []; 61 if (diff.length === 0) throw new CliError('NO_DATA', 'eastmoney returned no sector data'); 62 63 return diff.slice(0, limit).map((it, i) => ({ 64 rank: i + 1, 65 code: it.f12, 66 name: it.f14, 67 price: it.f2, 68 changePercent: it.f3, 69 mainNet: it.f62, 70 leadStock: it.f128, 71 leadChangePercent: it.f136, 72 upCount: it.f104, 73 downCount: it.f105, 74 })); 75 }, 76 });