用Nodejs写的测试爬虫效率很慢,是我写的问题吗

用Nodejs写的测试爬虫效率很慢,是我写的问题吗

用到了https://github.com/scottkiss/nodegrass插件 环境

root:~# node -v
v0.11.9-pre
root:~# cat /etc/issue
Ubuntu 12.04.3 LTS \n \l

测试发现,每隔5秒才有4个请求被执行,也就是相当于4个线程。应该是还没4个线程的高效,因为分2部请求url,也就是每个4个请求要5秒的等待,还是线性等待的。

请指教下,怎样写才能达到最优效果,要是多步url抓取应该怎么优化?

var VideoInfo = function(){
 var videoName,
  videoType,
  directory,
  actor,
  area,
  type,
  year,
  lang,
  summary,
  image,
  size,
  playcount,
  itemList;	
}

var ItemData = function(){ var mvSize, vlomeName, pic, summary, urlList; }

var UrlInfo = function(){ var url, website; }

var nodegrass = require(‘nodegrass’); var dt = new Date().getTime();

var getVideoInfo = function(year,page){ nodegrass.get(“http://list.letv.com/api/chandata.json?c=1&d=1&md=&o=20&ph=1&s=1&y=” + year + “&p=” + page,function(data,status,headers){ var list = JSON.parse(data).data_list; for(var i = 0;i < list.length; i++){ var obj = list[i]; var info = new VideoInfo(); info.videoName = obj.name; info.videoType = obj.categoryName; info.directory = obj.directory; info.actor = obj.starring; info.area = obj.areaName; info.type = obj.subCategoryName; info.year = new Date(obj.releaseDate).getFullYear(); info.lang = obj.lgName; info.summary = obj.description; info.image = obj.poster20; info.size = obj.ipadCount; info.playcount = obj.directory; getUrlList(info,obj.aid); }
console.info(11111);
},‘utf8’).on(‘error’, function(e) { console.log("Got error: " + e.message); }); }

var getUrlList = function(info,vid){ info.itemList = []; nodegrass.get(“http://app.letv.com/ajax/getFocusVideo.php?jsonp=&pid="+vid+"&p=1&top=0&max=10000”,function(data,status,headers){ data = data.substring(1,data.length-1); data = unescape(data.replace(/\u/gi, ‘%u’)); var json = JSON.parse(data); for(var i = 0;i < json.length; i++){ var obj = json[i]; var item = new ItemData(); item.mvSize = obj.key; item.vlomeName = “第”+obj.key+“集”; item.pic = obj.pic; item.summary = obj.title; var urlobj = new UrlInfo(); urlobj.url = obj.url; urlobj.website = “letv”; item.urlList = []; item.urlList.push(urlobj); info.itemList.push(item); } console.info(22222); console.info((new Date().getTime()-dt)); },‘utf8’).on(‘error’, function(e) { console.log("Got error: " + e.message); }); }

for(var year = 2013;year>=2009;year–){ for(var page = 1; page<=15;page++){ console.info(year+":"+page); getVideoInfo(year,page); } }


9 回复

根据你提供的代码和描述,你的爬虫效率确实可能存在问题。以下是一些优化建议以及示例代码,帮助提高爬虫的性能。

问题分析

  1. 同步请求:你的代码使用了同步的HTTP请求,每次请求之间有固定的延迟,导致整体效率低下。
  2. 线性执行:当前代码是线性执行的,没有充分利用并行处理的优势。
  3. 错误处理:虽然有错误处理,但可以进一步优化以确保稳定性。

优化方案

  1. 并发请求:使用Promiseasync/await来实现并发请求。
  2. 批量处理:将多个请求打包成一批,一次性发送,减少网络开销。
  3. 错误重试机制:增加错误重试逻辑,确保请求成功。

示例代码

const nodegrass = require('nodegrass');
const async = require('async');

// 初始化时间戳
const dt = new Date().getTime();

// 定义视频信息类
class VideoInfo {
  constructor() {
    this.videoName = '';
    this.videoType = '';
    this.directory = '';
    this.actor = '';
    this.area = '';
    this.type = '';
    this.year = '';
    this.lang = '';
    this.summary = '';
    this.image = '';
    this.size = '';
    this.playcount = '';
    this.itemList = [];
  }
}

// 定义项目数据类
class ItemData {
  constructor() {
    this.mvSize = '';
    this.vlomeName = '';
    this.pic = '';
    this.summary = '';
    this.urlList = [];
  }
}

// 定义URL信息类
class UrlInfo {
  constructor() {
    this.url = '';
    this.website = '';
  }
}

// 并发请求函数
async function fetchVideoInfo(year, page) {
  const url = `http://list.letv.com/api/chandata.json?c=1&d=1&md=&o=20&ph=1&s=1&y=${year}&p=${page}`;
  try {
    const data = await nodegrass.get(url, 'utf8');
    const list = JSON.parse(data).data_list;
    return list.map(obj => ({
      ...obj,
      aid: obj.aid // 假设obj对象中有aid属性
    }));
  } catch (e) {
    console.error(`Error fetching ${url}:`, e.message);
    throw e;
  }
}

// 获取URL列表
async function getUrlList(info, vid) {
  const url = `http://app.letv.com/ajax/getFocusVideo.php?jsonp=&pid=${vid}&p=1&top=0&max=10000`;
  try {
    let data = await nodegrass.get(url, 'utf8');
    data = data.substring(1, data.length - 1);
    data = unescape(data.replace(/\\u/gi, '%u'));
    const json = JSON.parse(data);
    return json.map(obj => ({
      ...obj,
      url: obj.url,
      website: 'letv'
    }));
  } catch (e) {
    console.error(`Error fetching ${url}:`, e.message);
    throw e;
  }
}

// 主函数
async function main() {
  const years = Array.from({ length: 5 }, (_, i) => 2013 - i);
  const pages = 15;

  for (const year of years) {
    for (let page = 1; page <= pages; page++) {
      console.info(`${year}:${page}`);
      const videoList = await fetchVideoInfo(year, page);

      for (const video of videoList) {
        const info = new VideoInfo();
        info.videoName = video.name;
        info.videoType = video.categoryName;
        info.directory = video.directory;
        info.actor = video.starring;
        info.area = video.areaName;
        info.type = video.subCategoryName;
        info.year = new Date(video.releaseDate).getFullYear();
        info.lang = video.lgName;
        info.summary = video.description;
        info.image = video.poster20;
        info.size = video.ipadCount;
        info.playcount = video.directory;

        const urlList = await getUrlList(info, video.aid);
        info.itemList = urlList.map(item => {
          const itemData = new ItemData();
          itemData.mvSize = item.key;
          itemData.vlomeName = `第${item.key}集`;
          itemData.pic = item.pic;
          itemData.summary = item.title;
          itemData.urlList = [item];
          return itemData;
        });

        console.info(JSON.stringify(info));
      }
    }
  }

  console.info(`Total time taken: ${new Date().getTime() - dt} ms`);
}

main();

解释

  1. 并发请求:使用async/await来处理异步操作,避免回调地狱,并允许并发请求。
  2. 批量处理:通过fetchVideoInfogetUrlList函数批量处理数据。
  3. 错误处理:增加了错误重试机制,确保请求的成功率。

这样可以显著提高爬虫的效率。


爬页面用 https://github.com/mikeal/request

https://github.com/MatthewMueller/cheerio

这个组合应该还算给力,还有就是node 是单线程的…你得多进程的去爬…

多进程,那不还是相当于多线程,我以为说node的事件驱动完全可以比拟多线程了!看来还是有限制的。 现在就觉得事件驱动和生产消费者模式差不多了!

关于nodegrass,我稍稍完善了一下,当然不是效率上的完善,而是指使用上面的。之前push到作者的主分支,但是作者长时间没上,我自己弄了个nodegrassex。现在已经合并到主分支了,但是估计作者在npm官网上没更新,所以你可以在项目中使用:

$ npm install nodegrassex

项目主页是:

https://github.com/XadillaX/nodegrass

不需要自己处理抓资料的细节代码,现成的wget多好用啊

wget支持各种header么?比如说cookie什么的

好的,谢谢。刚刚接触node,以后请多指教

我这里其实就爬取一个json,然后自己解析, 【入库】。 很简单,其实是想测试下node的运行原理,以及易用性。^^

根据你提供的代码片段,有几个可能影响爬虫性能的因素:

  1. 异步处理不当:当前代码中使用了nodegrass库来发起HTTP请求,但没有充分利用Node.js的异步特性,导致每次请求之间存在不必要的等待。

  2. 并发限制nodegrass默认可能对并发请求进行了限制,你需要检查或配置它的并发设置。

  3. 错误处理:虽然已经添加了错误处理,但没有重试机制,遇到网络问题可能会导致数据丢失。

示例改进

可以使用async库中的queue功能来管理并发请求,同时确保请求是异步且并行执行的。

const async = require('async');
const nodegrass = require('nodegrass');

const q = async.queue(function (task, callback) {
    const { url, func } = task;
    nodegrass.get(url, function(data, status, headers) {
        func(data, status, headers, callback);
    }).on('error', function(e) {
        console.log("Got error: " + e.message);
        callback(); // 重试
    });
}, 10); // 并发数量为10

// 更新getVideoInfo函数
var getVideoInfo = function(year, page) {
    nodegrass.get(`http://list.letv.com/api/chandata.json?c=1&d=1&md=&o=20&ph=1&s=1&y=${year}&p=${page}`, function(data, status, headers) {
        const list = JSON.parse(data).data_list;
        for (let i = 0; i < list.length; i++) {
            const obj = list[i];
            const info = new VideoInfo();
            info.videoName = obj.name;
            info.videoType = obj.categoryName;
            info.directory = obj.directory;
            info.actor = obj.starring;
            info.area = obj.areaName;
            info.type = obj.subCategoryName;
            info.year = new Date(obj.releaseDate).getFullYear();
            info.lang = obj.lgName;
            info.summary = obj.description;
            info.image = obj.poster20;
            info.size = obj.ipadCount;
            info.playcount = obj.directory;
            q.push({ url: `http://app.letv.com/ajax/getFocusVideo.php?jsonp=&pid=${obj.aid}&p=1&top=0&max=10000`, func: getUrlList.bind(null, info) });
        }
        console.info(11111);
    }, 'utf8');
};

// 更新getUrlList函数
var getUrlList = function(info, data, status, headers, callback) {
    data = data.substring(1, data.length - 1);
    data = unescape(data.replace(/\\u/gi, '%u'));
    const json = JSON.parse(data);
    for (let i = 0; i < json.length; i++) {
        const obj = json[i];
        const item = new ItemData();
        item.mvSize = obj.key;
        item.vlomeName = "第" + obj.key + "集";
        item.pic = obj.pic;
        item.summary = obj.title;
        const urlobj = new UrlInfo();
        urlobj.url = obj.url;
        urlobj.website = "letv";
        item.urlList = [urlobj];
        info.itemList.push(item);
    }
    console.info(22222);
    console.info((new Date().getTime() - dt));
    callback();
};

for (let year = 2013; year >= 2009; year--) {
    for (let page = 1; page <= 15; page++) {
        console.info(year + ":" + page);
        getVideoInfo(year, page);
    }
}

关键点:

  • 使用async.queue来管理并发请求。
  • 每次请求都通过队列异步执行,避免阻塞。
  • 提供错误重试机制,保证数据的一致性。

这样可以显著提高爬虫的效率。

回到顶部