Node.js凭借其异步非阻塞特性,成为构建高性能SERP API应用的理想选择。本文将详细讲解如何使用Node.js开发SERP API应用,从基础到生产环境部署的完整流程。
相关教程:什么是SERP API | Python教程 | API文档
为什么选择Node.js开发SERP应用?
Node.js的核心优势
- 异步非阻塞I/O:完美适配API调用场景
- 高并发处理:单线程事件循环,轻松处理大量请求
- JavaScript全栈:前后端使用同一语言
- 丰富的生态:npm提供海量工具库
- 快速开发:代码简洁,开发效率高
适用场景
- 🚀 高并发搜索服务
- 🔄 实时数据采集
- 🤖 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次)构建高性能搜索应用。
立即开始:
相关资源
开发教程:
- Python教程 – Python开发
- AI Agent开发 – AI应用
- SEO工具开发 – SEO应用
技术文档: