×

想在Node.js里给接口发请求、抓网页数据,却不知道从哪下手?

作者:Terry2025.06.29来源:Web前端之家浏览:60评论:0
关键词:js;HTTP请求

Node.js 原生模块怎么发 HTTP 请求?

Node.js 本身自带 httphttps 模块,不用额外装依赖,适合做基础请求,先从最常见的 GET、POST 说起。

GET 请求:一行代码发起,重点处理响应

http.get() 能快速发 GET 请求,它自动帮你处理请求方法、路径这些细节,比如调个公开 API 拿数据:

const http = require('http');
http.get('http://api.example.com/data', (res) => {
  let data = '';
  // 分段接收响应体(大响应时会分片)
  res.on('data', (chunk) => { data += chunk; });
  // 响应结束后处理数据
  res.on('end', () => {
    try {
      const result = JSON.parse(data);
      console.log('拿到数据:', result);
    } catch (err) {
      console.error('解析失败:', err);
    }
  });
  // 监听网络错误(比如连不上服务器)
  res.on('error', (err) => {
    console.error('请求出错:', err);
  });
}).on('error', (err) => { // 请求本身的错误(如DNS解析失败)
  console.error('发起请求失败:', err);
});

这里要注意,响应是“流”形式,得用 data 事件把分片拼起来,end 事件里处理完整数据。

POST 请求:手动构造请求头和请求体

POST 需要自己设置请求方法、请求头(Content-Type),还要把请求体写进去,举个发 JSON 数据的例子:

const http = require('http');
const postData = JSON.stringify({ name: '小明', age: 18 });
const options = {
  hostname: 'api.example.com',
  path: '/user',
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Content-Length': postData.length // 告诉服务端body长度
  }
};
const req = http.request(options, (res) => {
  // 同样用data和end事件收响应
  let data = '';
  res.on('data', (chunk) => { data += chunk; });
  res.on('end', () => {
    console.log('POST 响应:', data);
  });
});
req.on('error', (err) => {
  console.error('POST 出错:', err);
});
// 把请求体写进请求
req.write(postData);
req.end(); // 一定要end,否则请求发不出去

原生 POST 步骤多些:配 options、建请求对象、写 body、监听响应和错误,要是发表单(application/x-www-form-urlencoded),原理类似,只是 postData 改成 name=小明&age=18 这种格式,再调整 Content-Type 就行。

原生模块的优缺点

优点是“零依赖”,项目不想装第三方库时很省心;缺点也明显——代码繁琐(比如处理 JSON 解析、错误分支),复杂场景(如拦截请求、自动重试)得自己写逻辑,所以简单请求用原生,复杂需求更适合第三方库。

常用第三方库有啥优势?怎么选?

Node.js 生态里第三方 HTTP 库一大堆,核心是帮你简化代码、处理重复逻辑,挑几个最火的讲讲:

axios:前后端通吃的“万金油”

前端开发者很熟,Node 里也能装(npm i axios),它基于 Promise,语法简洁,还支持请求拦截、响应拦截、自动转换 JSON,比如发 GET:

const axios = require('axios');
axios.get('http://api.example.com/data')
  .then(res => console.log(res.data))
  .catch(err => console.error('请求失败:', err));

POST 更简单,直接传数据:

axios.post('http://api.example.com/user', { name: '小明', age: 18 })
  .then(res => console.log(res.data))
  .catch(err => { /* 处理错误 */ });

优势是“前后端一致”,团队技术栈统一时特方便;拦截器能统一加 token、处理错误(401 自动跳登录),但体积稍大,纯 Node 项目追求极致轻量的话,可能选更小巧的库。

got:轻量又现代的“新生代选手”

安装 npm i got,API 设计很“Node 风格”,支持流、Promise、TypeScript,还内置重试、超时、cookie 管理,比如发请求并流式处理大文件:

const got = require('got');
// 把响应直接存到本地文件(流处理,不占内存)
got.stream('https://example.com/big-file.zip').pipe(fs.createWriteStream('file.zip'));

普通 JSON 请求更简洁:

(async () => {
  const res = await got.get('http://api.example.com/data', { responseType: 'json' });
  console.log(res.body); // 自动解析成对象
})();

适合 Node 后端项目,尤其是要处理大文件、需要高可配置性的场景,文档也写得特清楚。

superagent:链式调用写着爽

安装 npm i superagent,语法像写句子一样流畅:

const superagent = require('superagent');
superagent
  .get('http://api.example.com/data')
  .query({ page: 1, size: 10 }) // 拼查询参数
  .set('Authorization', 'Bearer xxx') // 设请求头
  .end((err, res) => { // 回调风格,也支持Promise
    if (err) return console.error(err);
    console.log(res.body);
  });

链式调用对新手友好,一眼能看懂每步做啥,处理文件上传更方便:

superagent
  .post('http://api.example.com/upload')
  .attach('file', './avatar.jpg') // 自动转multipart/form-data
  .end((err, res) => { /* ... */ });

适合快速写 Demo 或小项目,代码可读性拉满。

node-fetch:前端开发者无缝迁移

如果你习惯浏览器里的 fetch,装 npm i node-fetch 就能在 Node 里用同款 API:

const fetch = require('node-fetch');
fetch('http://api.example.com/data')
  .then(res => res.json())
  .then(data => console.log(data))
  .catch(err => console.error(err));

优点是学习成本低,前端同学能快速上手;但功能相对基础,复杂需求(如重试)得自己加逻辑或装插件。

选库思路:

简单请求用 axiossuperagent 写着快;处理大文件、需要高级配置选 got;想和前端代码复用选 axiosnode-fetch,要是项目极轻量,原生模块也够。

不同场景下怎么选工具?

光知道库还不够,得看场景挑方案,举几个典型场景:

批量请求+并发控制

比如要调 100 个接口,全并发容易把服务器打崩,得限制同时请求数,用 axios + Promise.all + 分组:

const axios = require('axios');
const urls = ['url1', 'url2', ..., 'url100']; // 100个地址
const batchSize = 10; // 每次发10个
const results = [];
async function batchRequest() {
  for (let i = 0; i < urls.length; i += batchSize) {
    const batch = urls.slice(i, i + batchSize);
    const requests = batch.map(url => axios.get(url));
    const resList = await Promise.all(requests);
    results.push(...resList.map(r => r.data));
  }
  console.log('所有结果:', results);
}
batchRequest();

要是用 got,可以用 got.pipeline 做流处理,更高效处理大量请求。

文件上传(multipart/form-data)

原生模块得自己拼请求头和边界,特麻烦,用 superagent.attach() 一步到位:

const superagent = require('superagent');
superagent
  .post('https://api.example.com/upload')
  .attach('avatar', './avatar.jpg') // 文件名和路径
  .attach('doc', './report.pdf') // 多文件上传
  .end((err, res) => {
    if (err) return console.error(err);
    console.log('上传结果:', res.body);
  });

axios 也能做,不过得装 form-data 库配合:

const axios = require('axios');
const FormData = require('form-data');
const form = new FormData();
form.append('avatar', fs.createReadStream('./avatar.jpg'));
axios.post('https://api.example.com/upload', form, {
  headers: form.getHeaders() // 自动设Content-Type和边界
}).then(res => console.log(res.data));

处理大响应(避免内存爆炸)

比如下载大文件,用原生 http 的流管道:

const http = require('http');
const fs = require('fs');
const file = fs.createWriteStream('large-file.zip');
http.get('http://example.com/large-file.zip', (res) => {
  res.pipe(file); // 响应流直接写到文件,不用存内存
  file.on('finish', () => {
    file.close();
    console.log('下载完成');
  });
}).on('error', (err) => {
  fs.unlink('large-file.zip'); // 下载失败删临时文件
  console.error('下载出错:', err);
});

got 更简单,开流选项就行:

const got = require('got');
got.stream('http://example.com/large-file.zip').pipe(fs.createWriteStream('file.zip'));

自动重试+超时控制

网络不稳定时,请求失败要自动重试。got 内置重试逻辑:

const got = require('got');
got.get('http://api.example.com/data', {
  retry: { limit: 3 }, // 失败重试3次
  timeout: { request: 5000 } // 5秒没响应算超时
}).then(res => console.log(res.body))
  .catch(err => console.error('最终失败:', err));

axios 得装插件(如 axios-retry):

const axios = require('axios');
const axiosRetry = require('axios-retry');
axiosRetry(axios, { retries: 3 }); // 全局生效
axios.get('http://api.example.com/data', { timeout: 5000 })
  .then(res => console.log(res.data))
  .catch(err => console.error('重试后失败:', err));

异步处理和错误排查有啥技巧?

Node.js 里发请求几乎都是异步的,处理不好容易掉坑,这部分讲怎么理顺异步逻辑,快速定位错误。

用 Promise/async/await 管理异步

原生模块默认是回调风格,改成 Promise 更顺手,比如把 http.get 包成 Promise:

function getPromise(url) {
  return new Promise((resolve, reject) => {
    http.get(url, (res) => {
      let data = '';
      res.on('data', (chunk) => { data += chunk; });
      res.on('end', () => resolve(data));
      res.on('error', (err) => reject(err));
    }).on('error', (err) => reject(err));
  });
}
// 用async/await调用
(async () => {
  try {
    const data = await getPromise('http://api.example.com/data');
    console.log(data);
  } catch (err) {
    console.error('请求失败:', err);
  }
})();

第三方库基本都支持 Promise,直接用 async/await 写同步风格代码,逻辑更清晰。

错误分类处理

请求可能遇到三类错误:

  • 网络层错误:DNS 解析失败、连不上服务器(原生里是 req.on('error'),第三方库用 catch 捕获)。

  • 响应层错误:服务器返回 4xx(如参数错)、5xx(服务端崩了),原生要自己查 res.statusCode,第三方库(如 axios)会把状态码非 2xx 的情况抛到 catch

  • 业务层错误:比如接口返回 { code: 1001, msg: 'token过期' },得自己解析响应体判断。

举个 axios 处理所有错误的例子:

axios.get('http://api.example.com/data')
  .then(res => {
    if (res.data.code !== 0) { // 业务错误
      return Promise.reject(new Error(res.data.msg));
    }
    return res.data;
  })
  .catch(err => {
    if (err.response) { // 响应了但状态码不对(4xx/5xx)
      console.error('服务端错误:', err.response.status, err.response.data);
    } else if (err.request) { // 发了请求但没响应(超时、断网)
      console.error('网络错误:', err.request);
    } else { // 其他错误(如参数错)
      console.error('请求前错误:', err.message);
    }
  });

超时设置的坑

原生模块默认没超时,得自己加 setTimeout + abort

const req = http.get(url, (res) => { /* 处理响应 */ });
const timeout = setTimeout(() => {
  req.abort(); // 超时后终止请求
  console.error('请求超时');
}, 5000);
req.on('end', () => clearTimeout(timeout)); // 成功后清定时器

第三方库一般有 timeout 选项,直接配数字就行,axios{ timeout: 5000 }

实战案例:从接口请求到数据处理

光说不练假把式,用几个真实场景练手。

案例1:调用天气API(原生 vs axios)

假设用“和风天气”API,先拿原生 http 实现:

const http = require('http');
const apiKey = '你的密钥';
const city = '北京';
const url = `https://devapi.qweather.com/v7/weather/now?location=${city}&key=${apiKey}`;
http.get(url, (res) => {
  let data = '';
  res.on('data', (chunk) => { data += chunk; });
  res.on('end', () => {
    const weather = JSON.parse(data);
    console.log(`当前${city}天气:${weather.now.text},温度${weather.now.temp}°C`);
  });
}).on('error', (err) => {
  console.error('请求失败:', err);
});

换成 axios 后,代码更简洁:

const axios = require('axios');
const apiKey = '你的密钥';
const city = '北京';
async function getWeather() {
  try {
    const res = await axios.get('https://devapi.qweather.com/v7/weather/now', {
      params: { location: city, key: apiKey }
    });
    const { text, temp } = res.data.now;
    console.log(`当前${city}天气:${text},温度${temp}°C`);
  } catch (err) {
    console.error('查天气失败:', err);
  }
}
getWeather();

案例2:分页接口循环请求

比如某接口返回列表,支持 page 参数,要把所有页数据捞回来,用 got 做并发控制:

const got = require('got');
const baseUrl = 'https://api.example.com/list';
const pageSize = 20;
let currentPage = 1;
const allData = [];
async function fetchPage() {
  try {
    const res = await got.get(baseUrl, {
      searchParams: { page: currentPage, size: pageSize },
      responseType: 'json'
    });
    allData.push(...res.body.items);
    if (res.body.total > currentPage * pageSize) { // 还有下一页
      currentPage++;
      await fetchPage(); // 递归调自己
    } else {
      console.log('所有数据:', allData);
    }
  } catch (err) {
    console.error('分页请求失败:', err);
  }
}
fetchPage()

您的支持是我们创作的动力!
温馨提示:本文作者系Terry ,经Web前端之家编辑修改或补充,转载请注明出处和本文链接:
https://www.jiangweishan.com/article/nodejsusdu235235.html

网友评论文明上网理性发言 已有0人参与

发表评论: