分享一段Nodejs网页爬虫代码

分享一段Nodejs网页爬虫代码

看到这么多人对爬虫感兴趣,这里我把我之前写的爬虫代码贴出来,用于之前一个采集网站 发布一个用nodejs建的小站 ,网站使用geddy框架,不过因为域名备案的问题,目前暂时停掉了。 爬虫使用async做任务调度,iconv-lite转换gb2312编码,request请求页面内容,cheerio解析内容数据,gm生产缩略图。代码删掉了部分处理代码,保留了完整的流程,所以不能正常运行。代码没有半句注释,我认为好的代码就是最好的注释,不过我不是说我代码写得好,本人js菜鸟,只是没有写注释的习惯而已。 在此顺便提个问题,怎么在内存中生成缩略图?看了gm的文档好多遍都没有找到解决办法,找了github上其他的图像处理库,在windows下都没法用,只得先保存为临时文件再删除。。

var async = require('async');
var iconv=require('iconv-lite');
var request=require('request');
var cheerio=require('cheerio');
var querystring=require('querystring');
var Buffer=require('buffer').Buffer;
var gm=require('gm');
var fs=require('fs');

var log=function(str){
  var time=geddy.date.strftime(new Date(), '%Y.%m.%d %H:%M:%S')
  console.log(time+': '+str);
}

var postQueue=async.queue(function(task,callback){
  log('get post ---> board: ' + task.board + ' file: ' +task.file);
  getPost(task.board,task.file,task.replyCount);
  setTimeout(callback,5000);
});

var userQueue=async.queue(function(task,callback){
  log('get user ---> userid: ' + task.userid);
  getUser(task.userid);
  setTimeout(callback,5000);
},1);

var imageQueue=async.queue(function(task,callback){
  log('get image ---> url: ' + task.url );
  getImage(task.id,task.url);
  setTimeout(callback,2000);
},5);

var cookie='';
function login(){
  var qs=querystring.stringify({
    id:'',
    pw:'',
    xml:1
  });
  request.get('bbslogin?'+qs,{encoding: null},function(err,res,data){
    if(!err&&res.statusCode==200){
      var xml=iconv.decode(data,'gb2312');
      var $=cheerio.load(xml);
      var utmpnum= $('utmpnum').text();
      var utmpuserid=$('utmpuserid').text();
      var utmpkey=$('utmpkey').text();
      cookie='utmpnum='+utmpnum+'; utmpuserid='+utmpuserid+'; utmpkey='+utmpkey;
      log("login success");
    }
  })
}

function getPosts(){
  request.get('posttop10.xml',{ encoding: null },function(err,res,data){
    if(!err&&res.statusCode==200){
      var xml=iconv.decode(data,'gb2312');
      var $=cheerio.load(xml);
      var updatetime=$('updatetime').text();
      $('post').each(function(i,item){ if(i>2)return;
        var board=$(this).children('board').text();
        var file=$(this).children('file').text();
        var replyCount=parseInt( $(this).children('reply_count').text());
        geddy.model.Post.first({board:board, file:file},function (err, post) {
          if(null == post || (post.replyCount!=null&&post.replyCount!=replyCount)){
            log('add post task ---> board: '+board + ', file: ' +file);
            postQueue.push({
              board:board,
              file:file,
              replyCount:replyCount
            })
          }else{
            post.crawlTime=new Date();
            post.save();
          }
        });
      })
    }
  });
}

function replaceEmotion(content) {
  return content;
}
function purifyContent(content) {
  return content;
}
function getPostTime(content) {
  var postTime = new Date();
  return postTime;
}
function replace(content) {
  return content;
}
function getPost(board,file,replyCount){
  var qs=querystring.stringify({
    board:board,
    file:file,
    xml:1
  });
  var r = request.defaults({ encoding: null, headers: { cookie: cookie} });
  r.get('bbsnewtcon?'+qs,function(err,res,data){
    if(err)
    {
      log(err);
      return;
    }
    if(res.statusCode==403){
      //login();
      return;
    }
    if(res.statusCode==200){
      var xml=iconv.decode(data,'gb2312');
      if(xml.indexOf('<error>')>0){
        login();
        return;
      }
      var $=cheerio.load(xml);
      var postid,posttitle;
      var floor=0;
      async.eachSeries($('article'),function(item,callback1){
        //log($(item).text());
        var title=$(item).children('title').html();
        var content=$(item).children('content').text();
        var userid=$(item).children('owner').text();
        var filename=$(item).children('filename').text();
        var crawlTime=new Date();
        var replyTo=null;
        if(floor>0){
          var reg=/【 在 (\w+) [\w\W]*的大作中提到: 】/;
          var r=content.match(reg);
          if(r!=null){
            replyTo=r[1];
            content=content.replace(reg,'');
          }
        }
        content = replace(content);
        var postTime = getPostTime(content);
        var $$=cheerio.load(content);
        async.each($$('a'),function(item,callback){
          var tmp= $$(item).html().toLowerCase();
          if (tmp.indexOf(".gif") != -1
            || tmp.indexOf(".jpg") != -1
            || tmp.indexOf(".jpeg") != -1
            || tmp.indexOf(".bmp") != -1
            || tmp.indexOf(".png") != -1) {
            var url=$$(item).html();
            var crawlTime=new Date();
            geddy.model.Image.first({url:url} , function(err,image){
              if(null==image){
                image = geddy.model.Image.create({
                  url:url
                })
              }
              image.save(function(err){
                log('add image task ---> id: '+image.id + ', url: ' +url);
                imageQueue.push({
                  id:image.id,
                  url:url
                });
                var src='/images/'+ image.id;
                $$(item).html('<div/><img class="img" src="'+ src +'" />');
                $$(item).attr('href',src+'.jpg').attr('alt',src+'.jpg');
                callback();
              });
            });
          }
          else{
            callback();
          }
        },function(err){
          content=$$.html();
          content = purifyContent(content);
          if(floor==0){
            //replyCount=$('article').length-1;
            geddy.model.Post.first({board:board,file:file} , function(err,post){
              if(null==post){
                post = geddy.model.Post.create({
                  board : board ,
                  file : file ,
                  title : title
                });
                geddy.model.User.first({userid:userid},function (err, user) {
                  if(err) return;
                  if(null!=user && user.crawlTime.getDate()== new Date().getDate()) return;
                  userQueue.push({
                    userid:userid
                  });
                });
              }
              post.userid = userid;
              post.postTime = postTime;
              post.crawlTime=crawlTime;
              post.title = title;
              post.content = content;
              post.replyCount = replyCount;
              post.save(function(err){
                postid=post.id;
                posttitle=post.title;
                floor++;
                callback1();
              })
            });
          }
          else{
            geddy.model.Comment.first({board:board,file:filename} , function(err,comment){
              if(null==comment){
                comment = geddy.model.Comment.create({
                  board : board ,
                  file : filename
                });
                geddy.model.User.first({userid:userid},function (err, user) {
                  if(err) return;
                  if(null!=user && user.crawlTime.getDate()== new Date().getDate()) return;
                  userQueue.push({
                    userid:userid
                  });
                });
              }
              comment.postid=postid;
              comment.posttitle=posttitle;
              comment.replyTo=replyTo;
              comment.userid = userid;
              comment.postTime = postTime;
              comment.crawlTime=crawlTime;
              comment.title = title;
              comment.content = content;
              comment.floor = floor;
              comment.save(function(err){
                floor++;
                callback1();
              });
            });
          }
        })
      },function(err){
        if(!err)
          log('got post ---> board: ' + board + ' file: ' +file);
        else
          log('got post error ---> board: ' + board + ' file: ' +file);
      })
    }
  })
}

function getUser(userid){
  var qs=querystring.stringify({
    userid:userid,
    xml:1
  });
  request.get('bbsqry?'+qs,{ encoding: null },function(err,res,data){
    if(err || res.statusCode!=200){
      //userQueue.push({
      //  userid:userid
      //});
      return;
    }
    if(!err && res.statusCode==200){
      var xml=iconv.decode(data,'gb2312');
      if(xml.indexOf('<error>')>0)  return;
      var $=cheerio.load(xml);
      $=cheerio.load($('userinfo').html());
      var userid=$('userid').text();
      var nick=$('nick').text();
      nick=replaceEmotion(nick);
      var horoscope=$('horoscope').text();
      var lastloginstr=$('lastlogin').text().substring(0,19).replace('年', '-').replace('月', '-').replace('日', ' ');
      var lastlogin=geddy.date.parse(lastloginstr);
      var strposts=$('strposts').text();
      var strnetage=$('strnetage').text();
      var strexp=$('strexp').text();
      var strmoney=$('strmoney').text();
      var strmedals=$('strmedals').text();
      var duty=$('duty').text();
      var individual=$('individual').text();
      var plans=$('plans').text();
      plans=replace(plans);
      var numlogins=parseInt( $('numlogins').text());
      var gender=parseInt( $('gender').text());
      var newmail=parseInt( $('newmail').text());
      var numposts=parseInt( $('numposts').text());
      var netage=parseInt( $('netage').text());
      var life=parseInt( $('life').text());
      var exp=parseInt( $('exp').text());
      var money=parseInt( $('money').text());
      var medals=parseInt( $('medals').text());
      var crawlTime=new Date();
      geddy.model.User.first({userid:userid},function (err, user) {
        if(null==user){
          user = geddy.model.User.create({
            userid : userid
          })
        }
        user.nick = nick ;
        user.horoscope= horoscope;
        user.lastlogin = lastlogin;
        user.strposts =strposts;
        user.strnetage = strnetage;
        user.strexp = strexp;
        user.strmoney = strmoney;
        user.strmedals =strmedals;
        user.duty = duty;
        user.individual = individual;
        user.plans = plans;
        user.numlogins = numlogins;
        user.gender = gender;
        user.newmail = newmail;
        user.numposts = numposts;
        user.netage = netage;
        user.life = life;
        user.exp = exp;
        user.money = money;
        user.medals = medals;
        user.crawlTime = crawlTime;
        user.save(function(err){
          log('got user ---> userid: '+userid);
          var url='faceimg/'+userid.substring(0,1).toUpperCase()+'/'+userid+'.jpg';
          imageQueue.push({
            url:url
          })
        });
      });
    }
  });
}

function getImage(id,url){var r = request.defaults({ encoding: null, headers: { cookie: cookie} });
  r.get(url,function(err,res,data){
    if(err)
    {
      log(err);    /*
     imageQueue.push({
     id:id,
     url:url
     })         */
      return;
    }
    if(res.statusCode==200){
      if(id){
        geddy.model.Image.first({id:id} , function(err,image){
          image.data=data;
          image.crawlTime=new Date();
          image.save();
          log('saved image ---> id: '+id +' url: '+url);
          var tmpfile=id;
          gm(data).size({bufferStream:true},function(err,size){
            if(!err&&size.width>600){
              this.resize(600,size.height*600/size.width)
                .write(tmpfile,function(err){
                  if(!err){
                    fs.exists(tmpfile,function(exists){
                      if(!exists) return;
                      try{
                        var buf=fs.readFileSync(tmpfile);
                        image.thumbnail=buf;
                        image.save();
                        log('thumbnailed image ---> id: '+id +' url: '+url);
                        fs.unlink(tmpfile);
                      }
                      catch(ex){
                      }
                    })
                  }
                })
            }
          })
        });
      }
      else{
        var tmpfile=url.substring(url.lastIndexOf('/')+1);
        var userid=tmpfile.substring(0,tmpfile.lastIndexOf('.')-1);
        async.waterfall([
          function(callback){
            gm(data).thumb(100,100,tmpfile,100,function(err,stdout,stderr){
              if(!err&&fs.existsSync(tmpfile)){
                try{
                  var buf=fs.readFileSync(tmpfile);
                  fs.unlink(tmpfile);
                  callback(null,data,buf);
                }
                catch(ex) {
                  callback(null,data,null)
                }
              }else{
                callback(null,data,null)
              }
            })
          },function(data,thumbnail,callback){
            geddy.model.Image.first({url:url},function(err,image){
              if(null == image){
                image=geddy.model.Image.create({
                  data:data,
                  thumbnail:thumbnail,
                  url:url,
                  crawlTime:new Date()
                })
              }else{
                image.data=data;
                image.thumbnail=thumbnail;
                image.url=url;
                image.crawlTime=new Date();
              }
              image.save();
            })
          }
        ],function(err,result){
          if(!err)
            log('saved gravatar ---> userid: '+userid );
        })
      }
    }
  });
}

exports.getCookie=function(){return cookie;};
exports.postQueue=postQueue;
exports.userQueue=userQueue;
exports.imageQueue=imageQueue;
exports.run=function(){
  login();
  async.whilst(
    function () { return true; },
    function (callback) {
      if(postQueue.tasks.length==0){
        log('request top10 posts');
        getPosts();
      }
      setTimeout(callback, 1000*60*5);
    },
    function (err) {
      log(err);
    }
  );
};

5 回复

当然,下面是一段简化后的Node.js网页爬虫代码,它展示了如何使用async进行任务调度、iconv-lite处理编码、request请求页面内容以及cheerio解析内容数据。为了便于理解,我将添加一些注释。

const async = require('async');
const iconv = require('iconv-lite');
const request = require('request');
const cheerio = require('cheerio');
const fs = require('fs');
const gm = require('gm');

// 登录函数
function login() {
  // 省略登录逻辑
}

// 获取文章列表
function getPosts() {
  // 省略获取文章列表逻辑
}

// 处理单篇文章
function getPost(board, file, replyCount) {
  // 省略获取单篇文章逻辑
}

// 获取用户信息
function getUser(userid) {
  // 省略获取用户信息逻辑
}

// 获取并处理图片
function getImage(id, url) {
  request.get(url, { encoding: null }, (err, res, data) => {
    if (err) {
      console.error(err);
      return;
    }

    if (res.statusCode === 200) {
      const tmpfile = `${id}.jpg`;

      // 使用gm生成缩略图
      gm(data)
        .resize(600, 600)
        .toBuffer('jpg', (err, buffer) => {
          if (err) throw err;

          fs.writeFileSync(tmpfile, buffer);
          console.log(`Thumbnailed image --- id: ${id} url: ${url}`);
        });
    }
  });
}

// 异步队列管理
const postQueue = async.queue((task, callback) => {
  getPost(task.board, task.file, task.replyCount);
  setTimeout(callback, 5000);
}, 1);

const userQueue = async.queue((task, callback) => {
  getUser(task.userid);
  setTimeout(callback, 5000);
}, 1);

const imageQueue = async.queue((task, callback) => {
  getImage(task.id, task.url);
  setTimeout(callback, 2000);
}, 5);

// 启动爬虫
exports.run = function () {
  login();
  async.whilst(
    () => true,
    (callback) => {
      if (postQueue.tasks.length === 0) {
        getPosts();
      }
      setTimeout(callback, 1000 * 60 * 5);
    },
    (err) => {
      console.error(err);
    }
  );
};

这段代码展示了如何使用async库来管理异步任务队列,并且通过request库来获取网页内容,使用cheerio解析HTML。对于缩略图生成,这里使用了gm库,并直接在内存中生成缩略图,避免了临时文件的创建与删除。

希望这段代码能帮助你理解Node.js网页爬虫的基本结构和工作流程。


nodejs专注于爬虫,图片交给云,我的小站就是这样处理~~图片我用了七牛云储存~ http://www.17qingsong.com

感觉不错的样子,不过官网怎么没找到价位表呢

爬虫用Ruby是不是更好呢?

根据你提供的代码片段,这是一个基于Node.js的网页爬虫程序,主要用于从某个论坛抓取帖子和用户信息,并处理图片以生成缩略图。下面是针对你的需求提供的一段简化版的Node.js网页爬虫代码,主要关注于获取网页内容、解析内容和生成缩略图的功能。

首先,安装必要的npm包:

npm install request iconv-lite cheerio gm

以下是简化后的爬虫代码:

const request = require('request');
const iconv = require('iconv-lite');
const cheerio = require('cheerio');
const gm = require('gm').subClass({ imageMagick: true });

const url = 'http://example.com'; // 目标网站URL

// 获取网页内容
request({ url, encoding: null }, (err, res, body) => {
  if (!err && res.statusCode === 200) {
    const html = iconv.decode(body, 'utf-8'); // 假设网页是UTF-8编码
    const $ = cheerio.load(html);

    // 解析网页内容
    $('a').each((index, element) => {
      const href = $(element).attr('href');
      console.log(href);
      
      // 下载图片并生成缩略图
      request(href, { encoding: null }, (err, res, body) => {
        if (!err && res.statusCode === 200) {
          const tmpfile = `${Date.now()}.jpg`;
          fs.writeFileSync(tmpfile, body);
          
          // 使用gm生成缩略图
          gm(body)
            .resize(300, 300) // 缩放至300x300像素
            .write(`${tmpfile}_thumbnail.jpg`, err => {
              if (!err) console.log('缩略图已生成');
              else console.error('生成缩略图时出错:', err);
            });
        }
      });
    });
  }
});

这段代码展示了如何下载网页内容,使用cheerio解析HTML,并下载图片以生成缩略图。注意这里假设网页是UTF-8编码,实际使用时可能需要根据目标网站的实际编码进行调整。

关于在内存中生成缩略图,上述代码使用了gm库,并通过调整resize()方法来改变图片尺寸。生成的缩略图会保存为临时文件,如果不需要保存,可以直接在内存中处理而不写入磁盘。

回到顶部