mirror of
https://github.com/jackwener/OpenCLI.git
synced 2026-09-14 18:25:42 +08:00
b1c0bcb464
* feat(autoresearch): add Layer 4 "Save as CLI" eval + fix operate verify - New eval-save.ts: tests full init → write → verify pipeline (14 tasks) - 8 PUBLIC strategy tasks (httpbin, jsonplaceholder, HN, wiki, lobsters, devto) - 6 COOKIE strategy tasks (zhihu hot/search/question, xhs feed/search/note) - New save-reliability preset for autoresearch engine iteration - Fix: operate verify no longer hardcodes --limit 3 for adapters without limit arg - Rename sediment → save throughout * experiment(operate): 两个新任务都基于已有通过任务使用的同一 API,期望 pass_count 从 14 → 16。 * experiment(operate): Added 2 new tasks ( and ) that use the exact same APIs already proven to pass in exis * experiment(operate): Both new tasks pass. The change adds 2 more tasks ( and ) using the same proven API, i * fix(autoresearch): rename SedimentTask → SaveTask, fix bracket indent, gitignore results.tsv * refactor(autoresearch): complex multi-step save tasks + adapterFile support - Replace simple COOKIE tasks with 6 complex multi-step chains: - zhihu: hot+top-answer (6-step), search+question-stats (7-step), question+answers+related (8-step) - xhs: search+scroll+dedup (6-step), note+comments (7-step), explore+scroll+sort (8-step) - Move complex adapter code to save-adapters/*.ts files (avoids JSON escape issues) - eval-save.ts: support adapterFile field to read adapter from file - Preset scope now includes skills/opencli-operate/SKILL.md for skill improvement - All 20/20 tasks passing * experiment(save): add hn-best and hn-jobs tasks using proven Firebase API pattern, pass_count 20→22 * fix(autoresearch): increase Claude Code timeout 180s → 300s to reduce ETIMEDOUT failures * experiment(save): add restcountries and nager-holidays tasks using stable public APIs, pass_count 22→24
63 lines
2.8 KiB
TypeScript
63 lines
2.8 KiB
TypeScript
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
|
|
cli({
|
|
site: 'test-xhs',
|
|
name: 'search-full',
|
|
description: '小红书搜索 + 滚动加载 + 去重',
|
|
domain: 'www.xiaohongshu.com',
|
|
strategy: Strategy.COOKIE,
|
|
browser: true,
|
|
args: [
|
|
{ name: 'query', type: 'string', default: '咖啡', positional: true, help: 'Search query' },
|
|
{ name: 'limit', type: 'int', default: 10, help: 'Number of results' },
|
|
],
|
|
columns: ['rank', 'title', 'author', 'likes', 'url'],
|
|
func: async (page, kwargs) => {
|
|
const query = encodeURIComponent(kwargs.query ?? '咖啡');
|
|
const limit = kwargs.limit ?? 10;
|
|
// Step 1: Navigate to search page
|
|
await page.goto('https://www.xiaohongshu.com/search_result?keyword=' + query + '&source=web_search_result_notes');
|
|
// Step 2: Wait for async render via MutationObserver
|
|
await page.evaluate(`new Promise(function(resolve) {
|
|
var check = function() { return document.querySelectorAll('section.note-item').length > 0; };
|
|
if (check()) return resolve(true);
|
|
var observer = new MutationObserver(function(m, obs) { if (check()) { obs.disconnect(); resolve(true); } });
|
|
observer.observe(document.body, { childList: true, subtree: true });
|
|
setTimeout(function() { observer.disconnect(); resolve(false); }, 8000);
|
|
})`);
|
|
// Step 3: Scroll 3x to load more content
|
|
for (let i = 0; i < 3; i++) {
|
|
await page.evaluate('window.scrollTo(0, document.body.scrollHeight)');
|
|
await page.wait(1);
|
|
}
|
|
// Step 4: Extract from DOM with deduplication
|
|
const result = await page.evaluate(`(function() {
|
|
var seen = {};
|
|
var items = [];
|
|
document.querySelectorAll('section.note-item').forEach(function(el) {
|
|
var linkEl = el.querySelector('a[href]');
|
|
var href = linkEl ? linkEl.getAttribute('href') || '' : '';
|
|
var m = href.match(/explore\\/([a-f0-9]+)/);
|
|
var noteId = m ? m[1] : href;
|
|
if (!noteId || seen[noteId]) return;
|
|
seen[noteId] = true;
|
|
var titleEl = el.querySelector('.title span') || el.querySelector('a.title');
|
|
var authorEl = el.querySelector('.author-wrapper .name') || el.querySelector('.author .name');
|
|
var likesEl = el.querySelector('.like-wrapper .count') || el.querySelector('.interact-container .count');
|
|
if (titleEl) {
|
|
items.push({
|
|
title: (titleEl.textContent || '').trim(),
|
|
author: (authorEl ? authorEl.textContent || '' : '').trim(),
|
|
likes: (likesEl ? likesEl.textContent || '0' : '0').trim(),
|
|
url: 'https://www.xiaohongshu.com' + href,
|
|
});
|
|
}
|
|
});
|
|
return items;
|
|
})()`);
|
|
return (result as any[]).slice(0, limit).map((item: any, i: number) => ({
|
|
rank: i + 1, title: item.title, author: item.author, likes: item.likes, url: item.url,
|
|
}));
|
|
},
|
|
});
|