// hf-space/src/lib/crawler/engine.ts import puppeteer from 'puppeteer'; import { extractTapTap } from './sites/taptap'; import { extractSteam } from './sites/steam'; import { extractGeneric } from './sites/generic'; import type { ExtractedContent } from './extractor'; import * as crypto from 'crypto'; export interface CrawlResult { url: string; title: string; content: string; contentHash: string; success: boolean; error?: string; } function getSiteName(url: string): string { if (url.includes('taptap.cn') || url.includes('taptap.io')) return 'taptap'; if (url.includes('store.steampowered.com')) return 'steam'; return 'generic'; } function createHash(content: string): string { return crypto.createHash('sha256').update(content).digest('hex'); } export async function crawlPage(url: string): Promise { const browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage'], }); try { const page = await browser.newPage(); await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'); await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 }); // 等待内容渲染 await new Promise((r) => setTimeout(r, 2000)); const html = await page.content(); const siteName = getSiteName(url); let extracted: ExtractedContent; switch (siteName) { case 'taptap': extracted = extractTapTap(html, url); break; case 'steam': extracted = extractSteam(html, url); break; default: extracted = extractGeneric(html, url); } if (!extracted.content || extracted.content.length < 100) { return { url, title: extracted.title, content: '', contentHash: '', success: false, error: 'Content too short' }; } return { url, title: extracted.title, content: extracted.content, contentHash: createHash(extracted.content), success: true, }; } catch (err) { return { url, title: '', content: '', contentHash: '', success: false, error: String(err) }; } finally { await browser.close(); } }