#!/usr/bin/env node /** * 清理状态文件中的乱码标题记录 + 删除 DB 中乱码线索,然后重新抓取对应日期 * 用于修复 fetch-v1.2.js chunk 边界截断导致的标题乱码问题 * * 用法: node scripts/refetch_corrupted.js # 清理并重新抓取 * node scripts/refetch_corrupted.js --dry-run # 仅预览不做操作 */ const { execSync } = require('child_process') const fs = require('fs') const path = require('path') const BASE_DIR = '/data/pingtai/招标公告' const SCRIPT = '/opt/pingtai/scripts/tender-fetcher/fetch-v1.2.js' const DRY_RUN = process.argv.includes('--dry-run') /** 收集所有乱码条目 */ function findCorrupted() { const results = [] const dirs = fs.readdirSync(BASE_DIR).filter(d => /^\d{4}-\d{2}-\d{2}$/.test(d)).sort() for (const dir of dirs) { const statePath = path.join(BASE_DIR, dir, 'downloaded.json') if (!fs.existsSync(statePath)) continue const state = JSON.parse(fs.readFileSync(statePath, 'utf8')) for (const [key, v] of Object.entries(state.downloaded || {})) { if (v.title && v.title.includes('\uFFFD')) { results.push({ date: dir, key, statePath, info: v }) } } } return results } /** 从状态文件中移除指定 key */ function removeFromState(statePath, key) { const state = JSON.parse(fs.readFileSync(statePath, 'utf8')) if (state.downloaded?.[key]) { delete state.downloaded[key] fs.writeFileSync(statePath, JSON.stringify(state, null, 2)) return true } return false } async function main() { const corrupted = findCorrupted() const affectedDates = [...new Set(corrupted.map(c => c.date))].sort() const affectedIds = corrupted.map(c => c.info.id).filter(Boolean) console.log(`找到 ${corrupted.length} 条乱码记录,涉及 ${affectedDates.length} 天`) console.log(`受影响日期: ${affectedDates.join(', ')}`) console.log(`受影响 ID: ${affectedIds.join(', ')}`) if (corrupted.length === 0) { console.log('无需清理') return } // 1. 清理状态文件 console.log('\n=== 清理状态文件 ===') let cleaned = 0 for (const c of corrupted) { if (DRY_RUN) { console.log(` [DRY] 将从 ${c.date}/downloaded.json 移除 key: ${c.key}`) cleaned++ continue } if (removeFromState(c.statePath, c.key)) { console.log(` ✅ ${c.date}: 已移除 ${c.key}`) cleaned++ } } console.log(`已清理 ${cleaned}/${corrupted.length} 条状态记录`) // 2. 重新抓取每个受影响日期 console.log('\n=== 重新抓取 ===') for (const date of affectedDates) { if (DRY_RUN) { console.log(` [DRY] 将执行: START_DATE=${date} node ${SCRIPT}`) continue } try { console.log(` ⏳ 抓取 ${date} 的数据...`) const output = execSync(`START_DATE=${date} node ${SCRIPT}`, { cwd: path.dirname(SCRIPT), timeout: 120000, encoding: 'utf8', }) // 提取最后几行输出 const lines = output.trim().split('\n').slice(-5).join('\n') console.log(` ✅ ${date}: 完成\n${lines}`) } catch (e) { console.error(` ❌ ${date}: ${e.message}`) } // 间隔等待,避免请求过频 await new Promise(r => setTimeout(r, 2000)) } console.log('\n=== 完成 ===') if (!DRY_RUN) { console.log('提示:执行后端采集器以更新 DB(手动触发"中电建"采集器运行)') console.log('或等待下次定时调度自动更新') } } main().catch(console.error)