在这里我曾经写的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村中少年原创文章,转载记得加上小尾巴偶,博主链接这里。