Nodejs 为什么用http.request获取的网页源码,写入本地的文件的时候不是整个网页

Nodejs 为什么用http.request获取的网页源码,写入本地的文件的时候不是整个网页

var req = http.request({ host: ‘www.qq.com’, method: ‘get’ }, function(res) { res.setEncoding(‘utf8’); res.on(‘data’, function(data) { fs.writeFile(‘test.html’, data); }); }); req.end();

如题,上面的test.html文件打开后,不是www.qq.com那个网页的全部源码,只有部分,请问如何解决?


8 回复

Node.js 为什么用 http.request 获取的网页源码,写入本地的文件的时候不是整个网页

在使用 Node.js 的 http.request 方法获取网页源码时,经常会遇到一个问题:将获取到的数据写入本地文件时,文件中只包含了部分网页内容。这是因为数据可能被分多次传递,每次触发 data 事件时都会调用 fs.writeFile 方法来写入数据,导致之前的写入内容被覆盖或丢失。

解决方法

为了解决这个问题,我们需要累积所有的数据片段,并在所有数据都接收到之后再写入文件。可以通过以下步骤实现:

  1. 累积数据:在每次 data 事件触发时,将接收到的数据片段追加到一个变量中。
  2. 写入文件:在 end 事件触发时,将累积的所有数据一次性写入文件。

下面是修改后的示例代码:

const http = require('http');
const fs = require('fs');

const req = http.request({
    host: 'www.qq.com',
    method: 'GET'
}, function (res) {
    let data = ''; // 用于累积数据

    res.setEncoding('utf8');
    
    res.on('data', function (chunk) {
        data += chunk; // 追加数据片段
    });

    res.on('end', function () {
        fs.writeFile('test.html', data, function (err) {
            if (err) throw err;
            console.log('Data written to file successfully.');
        });
    });
});

req.end();

解释

  • 累积数据:我们创建了一个空字符串 data 来存储从服务器接收到的所有数据片段。每次 data 事件触发时,我们将接收到的数据片段 chunk 追加到 data 变量中。
  • 写入文件:当所有数据都接收完毕(即 end 事件触发)时,我们使用 fs.writeFile 将累积的所有数据一次性写入文件 test.html

这种方法确保了所有数据片段都被正确地累积起来,并且在所有数据接收完毕后再进行写入,从而避免了数据丢失或覆盖的问题。


页面太长,所以会分成几个部分,分别触发data事件。你这样写的结果就是只保存了最后的一个部分。应当先在内存中缓冲,最后一次性写入文件

不知道 fs.appendFile 成不成, 更好还是按楼上说的用 bufferhttp://nodejs.org/docs/latest/api/fs.html#fs_fs_appendfile_filename_data_encoding_utf8_callback

var http = require('http'),
	fs = require('fs');

http.get('http://www.qq.com', function( res ){
	res.on('error', function(e){
		console.log(e);
	}).pipe(fs.createWriteStream('qq.html'));
});

接收完数据后会触发end事件: 正确写法如下: var req = http.request({ host: ‘www.qq.com’, method: ‘get’ }, function(res) { res.setEncoding(‘utf8’); var html = []; res.on(‘data’, function(data) { html.push(data); }); res.on(‘end’,function(){ fs.writeFile(‘test.html’, html.join("")); }); }); req.end();

要说答案3楼的是最佳的,我在这里再说具体点 问题的关键是楼主用了fs.writeFile方法,请看它的源码: 来自lib/fs.js

fs.writeFile = function(path, data, encoding_, callback) {
  var encoding = (typeof(encoding_) == 'string' ? encoding_ : 'utf8');
  var callback_ = arguments[arguments.length - 1];
  callback = (typeof(callback_) == 'function' ? callback_ : null);
  fs.open(path, 'w', 438 /*=0666*/, function(openErr, fd) {
    if (openErr) {
      if (callback) callback(openErr);
    } else {
      var buffer = Buffer.isBuffer(data) ? data : new Buffer('' + data,
          encoding);
      writeAll(fd, buffer, 0, buffer.length, 0, callback);
    }
  });
};

可以看到它使用fs.open方法,使用使用了w模式,而这个模式表示,如果文件不存在则创建或者清空它。于是在Buffer写完64K之后就清空了一遍test.html。要知道QQ的首页少说得有300来K。于是就成这结果了。

res.on(‘end’)

在这个问题中,http.request 获取的数据是分段传输的,而你在每次接收到数据时就立即写入文件。这会导致多次写入文件,每次只覆盖之前的部分内容,最终结果就是文件中只包含最后一次接收到的数据片段。

为了解决这个问题,你应该将所有接收到的数据片段先累积起来,然后再一次性写入文件。示例如下:

const http = require('http');
const fs = require('fs');

let dataBuffer = '';

const req = http.request({
    host: 'www.qq.com',
    method: 'GET'
}, function (res) {
    res.setEncoding('utf8');
    res.on('data', function (chunk) {
        dataBuffer += chunk; // 累积数据片段
    });

    res.on('end', function () {
        fs.writeFile('test.html', dataBuffer, function (err) {
            if (err) throw err;
            console.log('File is created successfully.');
        });
    });
});

req.end();

这段代码中,我们将每次接收到的数据片段累积到 dataBuffer 变量中。当所有数据接收完毕(即触发 end 事件)时,我们再将完整的内容写入文件。这样可以确保写入文件的是完整的网页源码。

回到顶部