feed.js
1 import { cli, Strategy } from '@jackwener/opencli/registry'; 2 import { getPostDataJs } from './utils.js'; 3 /** 4 * 即刻首页动态流适配器 5 * 6 * 策略:导航到 web.okjike.com/following(需登录), 7 * 通过 React fiber 树提取帖子数据。 8 */ 9 cli({ 10 site: 'jike', 11 name: 'feed', 12 description: '即刻首页动态流', 13 domain: 'web.okjike.com', 14 strategy: Strategy.COOKIE, 15 browser: true, 16 args: [ 17 { name: 'limit', type: 'int', default: 20 }, 18 ], 19 columns: ['author', 'content', 'likes', 'comments', 'time', 'url'], 20 func: async (page, kwargs) => { 21 const limit = kwargs.limit || 20; 22 // 1. 导航到即刻首页,等待 SPA 重定向到 /following 23 await page.goto('https://web.okjike.com'); 24 // 2. 通过 React fiber 提取帖子数据 25 const extract = async () => { 26 return (await page.evaluate(`(() => { 27 ${getPostDataJs} 28 29 const results = []; 30 const seen = new Set(); 31 const elements = document.querySelectorAll('[class*="_post_"]'); 32 33 for (const el of elements) { 34 const data = getPostData(el); 35 if (!data || !data.id || seen.has(data.id)) continue; 36 seen.add(data.id); 37 38 // 转发帖的正文可能为空,取 target(原帖)的内容作 fallback 39 const author = data.user?.screenName || data.target?.user?.screenName || ''; 40 const content = data.content || data.target?.content || ''; 41 42 // 跳过无内容且无作者的条目(如 PERSONAL_UPDATE) 43 if (!author && !content) continue; 44 45 results.push({ 46 author, 47 content: content.replace(/\\n/g, ' ').slice(0, 120), 48 likes: data.likeCount || 0, 49 comments: data.commentCount || 0, 50 time: data.actionTime || data.createdAt || '', 51 url: 'https://web.okjike.com/originalPost/' + data.id, 52 }); 53 } 54 55 return results; 56 })()`)); 57 }; 58 let posts = await extract(); 59 // 3. 如果数量不足,自动滚动加载更多 60 if (posts.length < limit) { 61 await page.autoScroll({ times: Math.ceil(limit / 10), delayMs: 2000 }); 62 posts = await extract(); 63 } 64 return posts.slice(0, limit); 65 }, 66 });