nodejs爬虫提取网站title之重定向

       在这里我曾经写的demo解决了提取网站title过程中的一些编码问题,主要包括utf-8以及gbk和gb2312的编码。但是那个程序在遇到重定向的问题就歇菜了,这里面补充了如何解决重定向问题的代码。主要思路就是服务器来了重定向,抓取重定向的地址。代码见文章的最后,这只是爬取一个网站的demo,至于批量的爬取,可以有多种方法,自行实现(例如数据库和async.forEachSeries循环等)。
       下面几个问题是有必要着重说明一下的。
       1,这个问题是在大量爬取网站的时候暴露出来的。由于开始的没有加入erro捕获连接错误,错误提示为events.js: 141 throw er; // Unhandled ‘error’ event具体还有提示为socket hang up或者其他的。关于此类问题,很多的解释是端口被占用的问题,但是在这里的最后有如下解释:

如果请求过程中遇到任何错误(DNS 解析错误、TCP 级的错误、或实际的 HTTP 解析错误),则在返回的请求对象中会触发 ‘error’事件。 对于所有的 ‘error’ 事件,如果没有注册监听器,则抛出错误。

       因此我在加入erro的错误捕获,就可以输出错误,让程序继续执行。其实就是捕获错误,不致使程序崩溃。
       2,这个问题也是在大量爬取网站的时候暴露出来的。无论是http还是https的请求,都注册了data,end以及erro事件。正常来释放socket,应该是在程序的结束时候,即我们或取了title之后,因此我在end事件中设置res.destroy的执行。但是由于http链接或者https链接会存在链接失败的情况(也就是回触发erro事件回调),因此使用erro事件处理,同时释放socket。
       3,如果爬虫中不加入UA,对于facebook这样具有一定反爬虫能力的网站 ,会一直重定向到这个地址https://www.facebook.com/unsupportedbrowser,在http请求头中加入UA,就可以解决此问题。
程序如下:

var async = require('async');
var cheerio = require('cheerio');
var http = require('http');
var https = require('https');
var iconv = require('iconv-lite');
var rowsOfMysqlTable = '';

var fs = require ('fs');
var g_strFilename = '../data/name.txt';
var g_iAllLineCount = 0;
var g_iNullLineCount = 0;

readDomainFromFile();

function readDomainFromFile(){
  console.log ("NAME_FILE:", g_strFilename);

  // 读入文件内容
  var g_strFileContent = fs.readFileSync(g_strFilename, 'utf-8');

  // 按行分割
  var g_strLineAry = new Array(); 
  g_strLineAry = g_strFileContent.split("\n"); 
  g_strFileContent = null;

  // 打印总行数(包括空行)
  console.log ('get', g_strLineAry.length, 'lines totally.');
  g_iAllLineCount = g_strLineAry.length;

  // 逐个处理每行内容
  async.eachLimit(g_strLineAry,100,funcGetOneLine, funcGotAllLines);
}




// 处理一行内容的动作
function funcGetOneLine(strLine, callback) {
  // console.log ('forEachSeries:' + strLine);
  // 不处理空行
  if (!strLine) {
    g_iNullLineCount ++;
    return;
  }

  g_iNotNullLineCount ++;
  var requestTimes = 0;//控制重定向的次数,防止死循环,例如ameba.jp
  //strLine = 'match.com';

  console.log('ccccccccccccccccccccccccccccccccccccccccccccccccc:'+strLine);

  GetHtmlTitle(strLine,requestTimes,function(){

  });

  setTimeout(function(){
    callback();
  },120000);
}

// 所有行完成处理后的动作
function funcGotAllLines(err){

  //if (err) throw err;
  if (err){
    console.log('erro');
    console.log(err);
  }

  console.log ('Done');
  console.log ('g_iAllLineCount:', g_iAllLineCount);
  console.log ('g_iNullLineCount:', g_iNullLineCount);
  console.log ('g_iNotNullLineCount:', g_iNotNullLineCount);
}

function GetHtmlTitle(strLine,requestTimes,callback){

  var domainName = strLine;
  var protocol = 'http';
  var filePath = '/';
  var serverPort = 80;

  domainName = domainName.trim();

  if(domainName.indexOf('http://') === 0){
    domainName = domainName.replace('http://','');
    protocol = 'http';
    serverPort = 80;

  }
  else if(domainName.indexOf('https://') === 0){
   domainName = domainName.replace('https://','');
   protocol = 'https';
   serverPort = 443;

  }else{
    protocol = 'http';
    serverPort = 80;

  }

  var pos = domainName.indexOf('/');
  if(pos != -1){
    filePath = domainName.substring(pos,domainName.length) ;
    domainName = domainName.substring(0,pos);

  }else{
    filePath = '/';

  }

  var options = {
    //hostname: 'www.'+domainName,
    hostname: domainName,
    port: serverPort,
    path: filePath,
    method: 'GET',
    agent: false,
    headers: {
      'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0',
      'Accept-Charset': 'utf-8;q=1',
      'Accept-Encoding': '*;q=0',
      'Connection':'close'
    }
  };

  //http.get('http://andersonjiang.blog.sohu.com/ ', function(res) {
  if(protocol === 'http'){
    HttpRequest(options,requestTimes,callback);
  }else{
    HttpsRequest(options,requestTimes,callback);
  }
}

function HttpRequest(options,requestTimes,callback){
  var htmlData = [];
  var htmlDataLength = 0;
  const httpReq =  http.get(options, function(res) {

      var location  = res.headers['location'];
      var statusCode = res.statusCode;

      if((statusCode !== 200) && (location !== undefined)){
        requestTimes = requestTimes + 1;
        //console.log(location+'\t'+res.statusCode+'\t'+requestTimes);
        if(requestTimes < 5){
          res.resume();// 消耗响应数据以释放内存
          return GetHtmlTitle(location,requestTimes,callback);

        }
        res.resume();
        return;
      }

      res.on('data', function(data) {
        htmlData.push(data);
        htmlDataLength += data.length;


      });

      res.on('end',function(){

         if(htmlData.length > 0){
          parseHtmlTitle(options['hostname'],htmlData,htmlDataLength);

         }
         res.destroy(new Error('destroy the connection'));         
      });
    });
    // 错误处理
    httpReq.on('error', function (err) {
      if(!err){
        if(err.message !== 'destroy the connection'){
          console.log (err);
        }
        callback();
        httpReq.abort();
      }

    });

}

function HttpsRequest(options,requestTimes,callback){
  var htmlData = [];
  var htmlDataLength = 0;
  const httpsReq =  https.get(options, function(res) {

      var location  = res.headers['location'];
      var statusCode = res.statusCode;

      if((statusCode != 200) && (location != undefined)){ 
        requestTimes = requestTimes + 1;
        //console.log(location+'\t'+res.statusCode+'\t'+requestTimes);
        if(requestTimes < 5){
          res.resume();// 消耗响应数据以释放内存
          return GetHtmlTitle(location,requestTimes,callback);

        }
        res.resume();
        return;

      }

      res.on('data', function(data) {
        htmlData.push(data);
        htmlDataLength += data.length;

      });

      res.on('end',function(){

         if(htmlData.length > 0){
          parseHtmlTitle(options['hostname'],htmlData,htmlDataLength);

         }
         res.destroy(new Error('destroy the connection')); 
      });


    });
    // 错误处理
    httpsReq.on('error', function (err) {
      if(!err){
        if(err.message !== 'destroy the connection'){
          console.log (err);
        }
        callback();
        httpReq.abort();
      }

    });
}

function parseHtmlTitle(domainName,htmlData,htmlDataLength){

  var bufferHtmlData = Buffer.concat(htmlData,htmlDataLength);
  var charset = '';
  var decodeHtmlData;
  var htmlHeadTitle = '';
  var htmlHeadCharset = '';
  var htmlHeadContent = '';
  var separator = '\t';
  var index = 0;

  var $ = cheerio.load(bufferHtmlData, {decodeEntities: false});

  $('meta','head').each(function(i, e) {

    htmlHeadCharset = $(e).attr('charset');
    htmlHeadContent = $(e).attr('content');

    if(typeof(htmlHeadCharset) != 'undefined'){

      charset = htmlHeadCharset;
    }

    if(typeof(htmlHeadContent) != 'undefined'){

      if(htmlHeadContent.match(/charset=/ig)){

        index = htmlHeadContent.indexOf('=');
        charset = htmlHeadContent.substring(index+1);
      }
    }
  });

  //此处为什么需要对整个网页进行转吗,是因为cheerio这个组件不能够返回buffer,iconv则无法转换之
  if(charset.match(/gb/ig)){

    decodeHtmlData = iconv.decode(bufferHtmlData,'gbk');
  }
  else{//因为有可能返回的网页中不存在charset字段,因此默认都是按照utf8进行处理

    decodeHtmlData = iconv.decode(bufferHtmlData,'utf8');
  }

  var $ = cheerio.load(decodeHtmlData, {decodeEntities: false});

  $('title','head').each(function(i, e) {

    htmlHeadTitle = $(e).text();
    //console.log(htmlHeadTitle);
  });


  // title中含有回车换行,造成入库空行,排除之
  if (htmlHeadTitle) {
    htmlHeadTitle = htmlHeadTitle.replace (/\r/g, '');
    htmlHeadTitle = htmlHeadTitle.replace (/\n/g, '');
    htmlHeadTitle = htmlHeadTitle.replace (/\t/g, ''); // 制表符用于后续流程生成MAP.TXT文件的分隔符
  }

  if(domainName.indexOf('http://') === 0){
    domainName = domainName.replace('http://','');

  }
  if(domainName.indexOf('https://') === 0){
   domainName = domainName.replace('https://','');

  }
  if(domainName.indexOf('www.') === 0){
   domainName = domainName.replace('www.','');

  }

  rowsOfMysqlTable += (domainName + separator);
  rowsOfMysqlTable += (htmlHeadTitle + separator);
  rowsOfMysqlTable += '\n';
  //console.log(rowsOfMysqlTable);
  console.log(domainName + separator + htmlHeadTitle);
  //console.log(charset);

}

       本文为CSDN村中少年原创文章,转载记得加上小尾巴偶,博主链接这里

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

村中少年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值