Node.js 40 分钟阅读

Node.js SERP API开发完整指南:构建高性能搜索应用

讲解如何用Node.js开发SERP API应用。含Express集成、异步处理、错误重试、速率限制和生产部署方案。含代码示例,构建高性能搜索,处理100+请求/秒。

15,638 字

Node.js凭借其异步非阻塞特性,成为构建高性能SERP API应用的理想选择。本文将详细讲解如何使用Node.js开发SERP API应用,从基础到生产环境部署的完整流程。

相关教程什么是SERP API | Python教程 | API文档

为什么选择Node.js开发SERP应用?

Node.js的核心优势

  1. 异步非阻塞I/O:完美适配API调用场景
  2. 高并发处理:单线程事件循环,轻松处理大量请求
  3. JavaScript全栈:前后端使用同一语言
  4. 丰富的生态:npm提供海量工具库
  5. 快速开发:代码简洁,开发效率高

适用场景

  • 🚀 高并发搜索服务
  • 🔄 实时数据采集
  • 🤖 AI Agent后端服务
  • 📊 数据分析API
  • 🌐 Web应用后端

项目初始化

创建项目

mkdir serp-api-app
cd serp-api-app
npm init -y

安装依赖

npm install express axios dotenv cors helmet
npm install --save-dev nodemon

依赖说明

  • express:Web框架
  • axios:HTTP客户端
  • dotenv:环境变量管理
  • cors:跨域支持
  • helmet:安全中间件
  • nodemon:开发时自动重启

项目结构

serp-api-app/
├── src/
│   ├── config/
│   │   └── index.js
│   ├── services/
│   │   └── serpService.js
│   ├── controllers/
│   │   └── searchController.js
│   ├── routes/
│   │   └── searchRoutes.js
│   ├── middleware/
│   │   ├── errorHandler.js
│   │   └── rateLimiter.js
│   └── app.js
├── .env
├── .gitignore
├── package.json
└── server.js

核心模块开发

1. 配置管理

创建src/config/index.js

require('dotenv').config();

module.exports = {
  port: process.env.PORT || 3000,
  nodeEnv: process.env.NODE_ENV || 'development',
  
  // SearchCans API配置
  searchcans: {
    apiKey: process.env.SEARCHCANS_API_KEY,
    baseUrl: 'https://searchcans.youxikuang.cn/api/search',
    timeout: 30000,
    retries: 3
  },
  
  // 速率限制
  rateLimit: {
    windowMs: 15 * 60 * 1000, // 15分钟
    max: 100 // 最多100个请求
  },
  
  // 缓存配置
  cache: {
    ttl: 3600, // 1小时
    checkPeriod: 600 // 10分钟检查一次
  }
};

创建.env文件:

NODE_ENV=development
PORT=3000
SEARCHCANS_API_KEY=your_api_key_here

2. SERP服务层

创建src/services/serpService.js

const axios = require('axios');
const config = require('../config');

class SERPService {
  constructor() {
    this.client = axios.create({
      baseURL: config.searchcans.baseUrl,
      timeout: config.searchcans.timeout,
      headers: {
        'Authorization': `Bearer ${config.searchcans.apiKey}`,
        'Content-Type': 'application/json'
      }
    });
    
    // 请求拦截器
    this.client.interceptors.request.use(
      (config) => {
        console.log(`[SERP] 请求: ${config.data?.s}`);
        return config;
      },
      (error) => Promise.reject(error)
    );
    
    // 响应拦截器
    this.client.interceptors.response.use(
      (response) => {
        console.log(`[SERP] 响应成功`);
        return response;
      },
      (error) => {
        console.error(`[SERP] 响应失败:`, error.message);
        return Promise.reject(error);
      }
    );
  }
  
  /**
   * 执行搜索
   */
  async search(query, options = {}) {
    const payload = {
      s: query,
      t: options.engine || 'bing',
      p: options.page || 1,
      d: options.delay || 3000
    };
    
    try {
      const response = await this.client.post('', payload);
      return this.formatResponse(response.data);
    } catch (error) {
      throw this.handleError(error);
    }
  }
  
  /**
   * 批量搜索
   */
  async batchSearch(queries, options = {}) {
    const promises = queries.map(query => 
      this.search(query, options)
        .catch(error => ({ error: error.message, query }))
    );
    
    return Promise.all(promises);
  }
  
  /**
   * 格式化响应
   */
  formatResponse(data) {
    return {
      success: true,
      results: data.organic || [],
      totalResults: data.organic?.length || 0,
      searchInfo: {
        query: data.searchParameters?.q,
        engine: data.searchParameters?.engine
      }
    };
  }
  
  /**
   * 错误处理
   */
  handleError(error) {
    if (error.response) {
      // API返回错误
      return new Error(`SERP API错误: ${error.response.status} - ${error.response.data?.message || '未知错误'}`);
    } else if (error.request) {
      // 请求发送但无响应
      return new Error('SERP API无响应,请检查网络连接');
    } else {
      // 其他错误
      return new Error(`请求配置错误: ${error.message}`);
    }
  }
}

module.exports = new SERPService();

3. 控制器层

创建src/controllers/searchController.js

const serpService = require('../services/serpService');

class SearchController {
  /**
   * 单次搜索
   */
  async search(req, res, next) {
    try {
      const { query, engine, page } = req.query;
      
      // 参数验证
      if (!query) {
        return res.status(400).json({
          success: false,
          error: '缺少query参数'
        });
      }
      
      // 执行搜索
      const results = await serpService.search(query, { engine, page });
      
      res.json(results);
    } catch (error) {
      next(error);
    }
  }
  
  /**
   * 批量搜索
   */
  async batchSearch(req, res, next) {
    try {
      const { queries, engine } = req.body;
      
      // 参数验证
      if (!queries || !Array.isArray(queries) || queries.length === 0) {
        return res.status(400).json({
          success: false,
          error: 'queries必须是非空数组'
        });
      }
      
      if (queries.length > 10) {
        return res.status(400).json({
          success: false,
          error: '单次最多批量搜索10个关键词'
        });
      }
      
      // 执行批量搜索
      const results = await serpService.batchSearch(queries, { engine });
      
      res.json({
        success: true,
        results
      });
    } catch (error) {
      next(error);
    }
  }
  
  /**
   * 获取前N个结果
   */
  async topResults(req, res, next) {
    try {
      const { query, count = 10, engine } = req.query;
      
      if (!query) {
        return res.status(400).json({
          success: false,
          error: '缺少query参数'
        });
      }
      
      const results = await serpService.search(query, { engine });
      
      res.json({
        success: true,
        results: results.results.slice(0, parseInt(count))
      });
    } catch (error) {
      next(error);
    }
  }
}

module.exports = new SearchController();

4. 路由配置

创建src/routes/searchRoutes.js

const express = require('express');
const router = express.Router();
const searchController = require('../controllers/searchController');

// 单次搜索
router.get('/search', searchController.search.bind(searchController));

// 批量搜索
router.post('/batch-search', searchController.batchSearch.bind(searchController));

// 获取前N个结果
router.get('/top-results', searchController.topResults.bind(searchController));

module.exports = router;

5. 中间件

创建src/middleware/errorHandler.js

module.exports = (err, req, res, next) => {
  console.error('错误:', err);
  
  const statusCode = err.statusCode || 500;
  const message = err.message || '服务器内部错误';
  
  res.status(statusCode).json({
    success: false,
    error: message,
    ...(process.env.NODE_ENV === 'development' && { stack: err.stack })
  });
};

创建src/middleware/rateLimiter.js

const rateLimit = require('express-rate-limit');
const config = require('../config');

module.exports = rateLimit({
  windowMs: config.rateLimit.windowMs,
  max: config.rateLimit.max,
  message: {
    success: false,
    error: '请求过于频繁,请稍后再试'
  },
  standardHeaders: true,
  legacyHeaders: false
});

6. 应用主文件

创建src/app.js

const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const searchRoutes = require('./routes/searchRoutes');
const errorHandler = require('./middleware/errorHandler');
const rateLimiter = require('./middleware/rateLimiter');

const app = express();

// 安全中间件
app.use(helmet());

// CORS
app.use(cors());

// 解析JSON
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// 速率限制
app.use('/api', rateLimiter);

// 健康检查
app.get('/health', (req, res) => {
  res.json({ status: 'ok', timestamp: new Date().toISOString() });
});

// API路由
app.use('/api', searchRoutes);

// 404处理
app.use((req, res) => {
  res.status(404).json({
    success: false,
    error: '接口不存在'
  });
});

// 错误处理
app.use(errorHandler);

module.exports = app;

创建server.js

const app = require('./src/app');
const config = require('./src/config');

const server = app.listen(config.port, () => {
  console.log(`🚀 服务器运行在 http://localhost:${config.port}`);
  console.log(`📝 环境: ${config.nodeEnv}`);
});

// 优雅关闭
process.on('SIGTERM', () => {
  console.log('收到SIGTERM信号,正在关闭服务器...');
  server.close(() => {
    console.log('服务器已关闭');
    process.exit(0);
  });
});

7. 配置package.json

{
  "name": "serp-api-app",
  "version": "1.0.0",
  "scripts": {
    "start": "node server.js",
    "dev": "nodemon server.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "dependencies": {
    "axios": "^1.6.0",
    "cors": "^2.8.5",
    "dotenv": "^16.3.1",
    "express": "^4.18.2",
    "express-rate-limit": "^7.1.5",
    "helmet": "^7.1.0"
  },
  "devDependencies": {
    "nodemon": "^3.0.2"
  }
}

运行和测试

启动服务

# 开发模式
npm run dev

# 生产模式
npm start

API测试

1. 单次搜索

curl "http://localhost:3000/api/search?query=Node.js教程&engine=bing"

2. 批量搜索

curl -X POST http://localhost:3000/api/batch-search \
  -H "Content-Type: application/json" \
  -d '{
    "queries": ["JavaScript", "TypeScript", "React"],
    "engine": "bing"
  }'

3. 获取前5个结果

curl "http://localhost:3000/api/top-results?query=Python&count=5"

高级功能

1. 缓存实现

安装Redis客户端:

npm install redis

创建src/services/cacheService.js

const redis = require('redis');
const config = require('../config');

class CacheService {
  constructor() {
    this.client = redis.createClient({
      url: process.env.REDIS_URL || 'redis://localhost:6379'
    });
    
    this.client.on('error', (err) => console.error('Redis错误:', err));
    this.client.connect();
  }
  
  async get(key) {
    try {
      const data = await this.client.get(key);
      return data ? JSON.parse(data) : null;
    } catch (error) {
      console.error('缓存读取失败:', error);
      return null;
    }
  }
  
  async set(key, value, ttl = config.cache.ttl) {
    try {
      await this.client.setEx(key, ttl, JSON.stringify(value));
      return true;
    } catch (error) {
      console.error('缓存写入失败:', error);
      return false;
    }
  }
  
  async del(key) {
    try {
      await this.client.del(key);
      return true;
    } catch (error) {
      console.error('缓存删除失败:', error);
      return false;
    }
  }
  
  generateKey(query, options = {}) {
    return `serp:${query}:${options.engine || 'bing'}:${options.page || 1}`;
  }
}

module.exports = new CacheService();

在SERP服务中集成缓存:

const cacheService = require('./cacheService');

class SERPService {
  async search(query, options = {}) {
    // 检查缓存
    const cacheKey = cacheService.generateKey(query, options);
    const cached = await cacheService.get(cacheKey);
    
    if (cached) {
      console.log('[SERP] 使用缓存结果');
      return cached;
    }
    
    // 执行搜索
    const results = await this.performSearch(query, options);
    
    // 保存到缓存
    await cacheService.set(cacheKey, results);
    
    return results;
  }
}

2. 日志系统

安装Winston:

npm install winston

创建src/utils/logger.js

const winston = require('winston');

const logger = winston.createLogger({
  level: process.env.LOG_LEVEL || 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.errors({ stack: true }),
    winston.format.json()
  ),
  transports: [
    new winston.transports.File({ filename: 'logs/error.log', level: 'error' }),
    new winston.transports.File({ filename: 'logs/combined.log' })
  ]
});

if (process.env.NODE_ENV !== 'production') {
  logger.add(new winston.transports.Console({
    format: winston.format.combine(
      winston.format.colorize(),
      winston.format.simple()
    )
  }));
}

module.exports = logger;

3. 请求重试

创建src/utils/retry.js

async function retry(fn, options = {}) {
  const {
    retries = 3,
    delay = 1000,
    backoff = 2
  } = options;
  
  for (let i = 0; i < retries; i++) {
    try {
      return await fn();
    } catch (error) {
      if (i === retries - 1) throw error;
      
      const waitTime = delay * Math.pow(backoff, i);
      console.log(`重试 ${i + 1}/${retries},等待 ${waitTime}ms`);
      await new Promise(resolve => setTimeout(resolve, waitTime));
    }
  }
}

module.exports = retry;

生产环境部署

1. 使用PM2

安装PM2:

npm install -g pm2

创建ecosystem.config.js

module.exports = {
  apps: [{
    name: 'serp-api',
    script: './server.js',
    instances: 'max',
    exec_mode: 'cluster',
    env: {
      NODE_ENV: 'production',
      PORT: 3000
    },
    error_file: './logs/pm2-error.log',
    out_file: './logs/pm2-out.log',
    log_date_format: 'YYYY-MM-DD HH:mm:ss',
    merge_logs: true
  }]
};

启动应用:

pm2 start ecosystem.config.js
pm2 save
pm2 startup

2. Docker部署

创建Dockerfile

FROM node:18-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

COPY . .

EXPOSE 3000

CMD ["node", "server.js"]

创建docker-compose.yml

version: '3.8'

services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - SEARCHCANS_API_KEY=${SEARCHCANS_API_KEY}
      - REDIS_URL=redis://redis:6379
    depends_on:
      - redis
    restart: unless-stopped
  
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis-data:/data
    restart: unless-stopped

volumes:
  redis-data:

运行:

docker-compose up -d

3. Nginx反向代理

Nginx配置:

upstream serp_api {
    server localhost:3000;
    server localhost:3001;
    server localhost:3002;
}

server {
    listen 80;
    server_name api.yourdomain.com;
    
    location / {
        proxy_pass http://serp_api;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

性能优化

1. 连接池

const axios = require('axios');
const http = require('http');
const https = require('https');

const httpAgent = new http.Agent({
  keepAlive: true,
  maxSockets: 50
});

const httpsAgent = new https.Agent({
  keepAlive: true,
  maxSockets: 50
});

const client = axios.create({
  httpAgent,
  httpsAgent
});

2. 请求并发控制

const pLimit = require('p-limit');

const limit = pLimit(10); // 最多10个并发

async function batchSearch(queries) {
  const promises = queries.map(query =>
    limit(() => serpService.search(query))
  );
  
  return Promise.all(promises);
}

3. 响应压缩

npm install compression

const compression = require('compression');
app.use(compression());

监控和告警

使用Prometheus

npm install prom-client

const promClient = require('prom-client');

// 创建指标
const httpRequestDuration = new promClient.Histogram({
  name: 'http_request_duration_seconds',
  help: 'HTTP请求耗时',
  labelNames: ['method', 'route', 'status']
});

// 中间件
app.use((req, res, next) => {
  const start = Date.now();
  
  res.on('finish', () => {
    const duration = (Date.now() - start) / 1000;
    httpRequestDuration.labels(req.method, req.route?.path || req.path, res.statusCode).observe(duration);
  });
  
  next();
});

// 暴露指标
app.get('/metrics', async (req, res) => {
  res.set('Content-Type', promClient.register.contentType);
  res.end(await promClient.register.metrics());
});

总结

通过本教程,你已经学会了:

✅ Node.js SERP API项目搭建
✅ Express框架集成
✅ 异步处理和错误处理
✅ 缓存和性能优化
✅ 生产环境部署
✅ 监控和日志

使用SearchCans SERP API,你可以以极低成本(¥4.03/1000次)构建高性能搜索应用。

立即开始:

  1. 注册账户获取100积分
  2. 查看API文档
  3. 克隆本教程代码开始开发

相关资源

开发教程

技术文档

标签:

Node.js SERP API JavaScript 后端开发

准备好用 SearchCans 构建你的 AI 应用了吗?

立即体验我们的 SERP API 和 Reader API。每千次调用仅需 ¥0.56 起,无需信用卡即可免费试用。