File size: 4,628 Bytes
ec4bf22
 
ea67eaf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cd3562d
ea67eaf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
import { runOttoCrawler } from './otto-crawler.js';
import { ProductData } from './productData.js';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

/**
 * 从文件读取 URL 列表11
 */
function readUrlsFromFile(filePath: string): string[] {
  try {
    const content = fs.readFileSync(filePath, 'utf-8');
    return content
      .split('\n')
      .map(line => line.trim())
      .filter(line => line && !line.startsWith('#'));
  } catch (error: any) {
    console.error(`读取文件失败: ${filePath}`, error.message);
    return [];
  }
}

/**
 * 保存结果到 JSON 文件
 */
function saveResultsToJson(results: ProductData[], outputPath: string = './results/otto-products.json'): void {
  try {
    const dir = path.dirname(outputPath);
    if (!fs.existsSync(dir)) {
      fs.mkdirSync(dir, { recursive: true });
    }
    
    fs.writeFileSync(outputPath, JSON.stringify(results, null, 2), 'utf-8');
    console.log(`💾 结果已保存到: ${outputPath}`);
  } catch (error: any) {
    console.error('保存结果失败:', error.message);
  }
}

/**
 * 保存结果到 CSV 文件
 */
function saveResultsToCsv(results: ProductData[], outputPath: string = './results/otto-products.csv'): void {
  try {
    const dir = path.dirname(outputPath);
    if (!fs.existsSync(dir)) {
      fs.mkdirSync(dir, { recursive: true });
    }
    
    if (results.length === 0) {
      console.log('没有数据可保存');
      return;
    }
    
    // 获取所有字段名
    const fields = Object.keys(results[0]);
    
    // 创建 CSV 内容1
    const csvContent = [
      fields.join(','),
      ...results.map(product => 
        fields.map(field => {
          const value = String(product[field] || '');
          return `"${value.replace(/"/g, '""')}"`;
        }).join(',')
      )
    ].join('\n');
    
    fs.writeFileSync(outputPath, csvContent, 'utf-8');
    console.log(`💾 CSV 结果已保存到: ${outputPath}`);
  } catch (error: any) {
    console.error('保存 CSV 失败:', error.message);
  }
}

/**
 * 批量爬取主函数
 */
async function batchCrawl(options: {
  urls?: string[];
  file?: string;
  preset?: string;
  outputFormat?: 'json' | 'csv' | 'both';
  outputFile?: string;
} = {}) {
  let urls: string[] = [];
  
  // 从文件读取或从参数获取 URLs
  if (options.file) {
    console.log(`📄 从文件读取 URLs: ${options.file}`);
    urls = readUrlsFromFile(options.file);
  } else if (options.urls && options.urls.length > 0) {
    urls = options.urls;
  } else {
    console.error('❌ 请提供 URL 列表或文件路径');
    process.exit(1);
  }
  
  if (urls.length === 0) {
    console.error('❌ 没有有效的 URL');
    process.exit(1);
  }
  
  console.log(`📋 待爬取 URL 数量: ${urls.length}`);
  console.log(`🔧 配置预设: ${options.preset || 'stable'}`);
  
  try {
    // 执行爬取
    const results = await runOttoCrawler(urls, { 
      preset: options.preset || 'stable' 
    });
    
    console.log(`\n✅ 爬取完成!`);
    console.log(`📊 成功抓取: ${results.length} 个商品\n`);
    
    // 保存结果
    const outputFormat = options.outputFormat || 'json';
    const outputFile = options.outputFile || './results/otto-products';
    
    if (outputFormat === 'json' || outputFormat === 'both') {
      saveResultsToJson(results, `${outputFile}.json`);
    }
    
    if (outputFormat === 'csv' || outputFormat === 'both') {
      saveResultsToCsv(results, `${outputFile}.csv`);
    }
    
    return results;
  } catch (error: any) {
    console.error('\n❌ 爬取失败:', error.message);
    process.exit(1);
  }
}

// 如果直接运行此文件
if (import.meta.url === `file://${process.argv[1]}`) {
  const args = process.argv.slice(2);
  const options: any = {};
  
  // 解析命令行参数
  for (let i = 0; i < args.length; i++) {
    switch (args[i]) {
      case '--file':
      case '-f':
        options.file = args[++i];
        break;
      case '--preset':
      case '-p':
        options.preset = args[++i];
        break;
      case '--format':
        options.outputFormat = args[++i];
        break;
      case '--output':
      case '-o':
        options.outputFile = args[++i];
        break;
      default:
        if (!args[i].startsWith('-')) {
          if (!options.urls) options.urls = [];
          options.urls.push(args[i]);
        }
    }
  }
  
  batchCrawl(options);
}

export { batchCrawl, readUrlsFromFile, saveResultsToJson, saveResultsToCsv };