GitHub Actions Bot commited on
Commit
7668e81
·
1 Parent(s): 2cff0cd

🚀 Auto-deploy: 27b0c30bc71c932c4699cdd45f05255ae06d4b3e

Browse files
Files changed (3) hide show
  1. server.ts +8 -8
  2. src/config.ts +9 -24
  3. src/otto-crawler.ts +9 -1
server.ts CHANGED
@@ -38,10 +38,10 @@ app.get('/health', (req, res) => {
38
  // 2. 执行爬取任务接口
39
  app.post('/crawl', async (req, res) => {
40
  try {
41
- const { paltform, urls, preset = 'stable' } = req.body;
42
 
43
  // 参数验证
44
- if (!paltform || typeof paltform !== 'string') {
45
  return res.status(400).json({
46
  success: false,
47
  error: '请提供有效的 platform 参数 (例如: "Otto")'
@@ -56,22 +56,22 @@ app.post('/crawl', async (req, res) => {
56
  }
57
 
58
  // 检查是否支持该源
59
- if (!(paltform in crawlerMap)) {
60
  return res.status(400).json({
61
  success: false,
62
- error: `不支持的爬虫源: ${paltform}。支持的源: ${Object.keys(crawlerMap).join(', ')}`
63
  });
64
  }
65
 
66
- log.info(`[API] 收到爬取请求: paltform=${paltform}, ${urls.length} 个 URL, preset=${preset}`);
67
 
68
- // 根据 paltform 选择对应的爬虫执行
69
- const crawler = crawlerMap[paltform as CrawlerPlatform];
70
  const results: ProductData[] = await crawler(urls, { preset });
71
 
72
  res.json({
73
  success: true,
74
- paltform: paltform,
75
  count: results.length,
76
  data: results
77
  });
 
38
  // 2. 执行爬取任务接口
39
  app.post('/crawl', async (req, res) => {
40
  try {
41
+ const { platform, urls, preset = 'stable' } = req.body;
42
 
43
  // 参数验证
44
+ if (!platform || typeof platform !== 'string') {
45
  return res.status(400).json({
46
  success: false,
47
  error: '请提供有效的 platform 参数 (例如: "Otto")'
 
56
  }
57
 
58
  // 检查是否支持该源
59
+ if (!(platform in crawlerMap)) {
60
  return res.status(400).json({
61
  success: false,
62
+ error: `不支持的爬虫源: ${platform}。支持的源: ${Object.keys(crawlerMap).join(', ')}`
63
  });
64
  }
65
 
66
+ log.info(`[API] 收到爬取请求: platform=${platform}, ${urls.length} 个 URL, preset=${preset}`);
67
 
68
+ // 根据 platform 选择对应的爬虫执行
69
+ const crawler = crawlerMap[platform as CrawlerPlatform];
70
  const results: ProductData[] = await crawler(urls, { preset });
71
 
72
  res.json({
73
  success: true,
74
+ platform: platform,
75
  count: results.length,
76
  data: results
77
  });
src/config.ts CHANGED
@@ -14,11 +14,11 @@ export interface CrawlerConfig {
14
  args?: string[];
15
  };
16
  selectors: {
 
17
  price: string[];
 
18
  rating: string[];
19
  reviews: string[];
20
- title?: string[];
21
- imageUrl?: string[];
22
  };
23
  }
24
 
@@ -107,35 +107,20 @@ const PRESETS: Record<string, Partial<CrawlerConfig>> = {
107
 
108
  export const DEFAULT_SELECTORS = {
109
  // 标题选择器 - 用于智能等待判断页面是否加载完成
110
- title: [
111
- 'h1.pdp-product-title',
112
- 'h1[data-test="product-title"]',
113
- '.product-title h1',
114
- 'div.pdp_short-info__main-name'
115
  ],
116
  price: [
117
- 'span[data-test="price-current"]',
118
- 'span.js_pdp_price__current-price',
119
- '.price--current',
120
- '[data-test="product-price"]',
121
  'span.js_pdp_price__retail-price__value_original'
122
  ],
 
 
 
123
  rating: [
124
- 'div[data-test="rating-score"]',
125
- 'div.pdp_cr-rating-score span',
126
- '.rating-stars',
127
- '[data-test="average-rating"]'
128
  ],
129
  reviews: [
130
- 'span[data-test="review-count"]',
131
- 'span.js_pdp_cr-rating--review-count',
132
- '.review-count-text',
133
- '[data-test="total-reviews"]'
134
- ],
135
- imageUrl: [
136
- 'img[data-test="product-image"]',
137
- '.pdp-gallery__active img',
138
- 'meta[property="og:image"]'
139
  ]
140
  };
141
 
 
14
  args?: string[];
15
  };
16
  selectors: {
17
+ brand: string[];
18
  price: string[];
19
+ originalPrice: string[];
20
  rating: string[];
21
  reviews: string[];
 
 
22
  };
23
  }
24
 
 
107
 
108
  export const DEFAULT_SELECTORS = {
109
  // 标题选择器 - 用于智能等待判断页面是否加载完成
110
+ brand: [
111
+ '.js_pdp_short-info__brand-link'
 
 
 
112
  ],
113
  price: [
 
 
 
 
114
  'span.js_pdp_price__retail-price__value_original'
115
  ],
116
+ originalPrice: [
117
+ 'span.pdp_price__strike-through-price'
118
+ ],
119
  rating: [
120
+ 'div.pdp_cr-rating-score span'
 
 
 
121
  ],
122
  reviews: [
123
+ 'span.js_pdp_cr-rating--review-count'
 
 
 
 
 
 
 
 
124
  ]
125
  };
126
 
src/otto-crawler.ts CHANGED
@@ -8,6 +8,7 @@ let currentConfig: CrawlerConfig = getCrawlerConfig();
8
  export interface ProductData {
9
  url: string;
10
  crawledAt: string;
 
11
  price?: string | null;
12
  rating?: string | null;
13
  reviews?: string | null;
@@ -44,10 +45,17 @@ async function extractText(page: Page, ...selectors: string[]): Promise<string |
44
  async function extractOttoData(page: Page, product: ProductData = {} as ProductData): Promise<ProductData> {
45
  try {
46
  const selectors = currentConfig.selectors;
 
 
 
 
47
 
48
  // 提取价格
49
  const price = await extractText(page, ...selectors.price);
50
  if (price) product.price = price;
 
 
 
51
 
52
  // 提取评分
53
  const rating = await extractText(page, ...selectors.rating);
@@ -58,7 +66,7 @@ async function extractOttoData(page: Page, product: ProductData = {} as ProductD
58
  if (reviews) product.reviews = reviews;
59
 
60
  log.info(`✓ 成功提取商品数据:`, {
61
- url: product.url || page.url(),
62
  price: product.price || 'N/A',
63
  rating: product.rating || 'N/A',
64
  reviews: product.reviews || 'N/A'
 
8
  export interface ProductData {
9
  url: string;
10
  crawledAt: string;
11
+ brand?: string | null;
12
  price?: string | null;
13
  rating?: string | null;
14
  reviews?: string | null;
 
45
  async function extractOttoData(page: Page, product: ProductData = {} as ProductData): Promise<ProductData> {
46
  try {
47
  const selectors = currentConfig.selectors;
48
+
49
+ // 提取品牌
50
+ const brand = await extractText(page, ...selectors.brand);
51
+ if (brand) product.brand = brand;
52
 
53
  // 提取价格
54
  const price = await extractText(page, ...selectors.price);
55
  if (price) product.price = price;
56
+
57
+ const originalPrice = await extractText(page, ...selectors.originalPrice);
58
+ if (price) product.originalPrice = originalPrice;
59
 
60
  // 提取评分
61
  const rating = await extractText(page, ...selectors.rating);
 
66
  if (reviews) product.reviews = reviews;
67
 
68
  log.info(`✓ 成功提取商品数据:`, {
69
+ brand: product.brand || 'N/A',
70
  price: product.price || 'N/A',
71
  rating: product.rating || 'N/A',
72
  reviews: product.reviews || 'N/A'