这是计划的第1~2步
对比了各要求爬取的网站之后,先选择HTML结构简单的的雪球网进行尝试。

1)分析HTML结构

怎么爬取javascript页面 js爬取网页_nodejs


F12打开Chrome的控制台,可以看见其HTML源码;

其结构比较简单:首先,观察到每则新闻都在各自的class=AnonymousHome_home__timeline__item_3vU下,各种信息都以文本方式存储在结构中。

2)一级网址信息爬取

先试试能否爬取标题,

console.log($('.AnonymousHome_home__timeline__item_3vU','div').find('h3').children('a').text());

观察到一下结构,于是利用.find(‘h3’).children(‘a’).text()选出其中的标题并找出纯文本内容,并将这一行插入示例源码中。

怎么爬取javascript页面 js爬取网页_i++_02


怎么爬取javascript页面 js爬取网页_怎么爬取javascript页面_03


很明显所有标题都抓取成功,接下来整理标题

var html = body;
    var titles = []
    var $ = myCheerio.load(html, { decodeEntities: false });
    $('.AnonymousHome_home__timeline__item_3vU', 'div').find('h3').children('a').each(function (idx, element){
        titles.push({
            title:$(element).text()
        })
    })
    console.log(titles);

利用each()对每个标题做处理,利用一个匿名函数吧每个标题作为一个对象存入数组titles中(利用element变量传递函数外的查找路径)

怎么爬取javascript页面 js爬取网页_nodejs_04


接下来为了便于整理数据,将每篇文章的标题都作为一篇文章对象的属性

var html = body;
    var newsArr = [];
    var $ = myCheerio.load(html, { decodeEntities: false });

    var item = $('.AnonymousHome_home__timeline__item_3vU', 'div')

    item.map(function (idx, element) {
        var news = {};

        news.title = $(element).find('h3').children('a').text();

        newsArr.push(news);
    })
    console.log(newsArr);

将每一个标题存储为一个news中的title属性,并且存入数组newsArr中;
接下来,设计爬取其他信息的方式:

news.link = "https://xueqiu.com" + $(element).find('h3').children('a').attr('href');
		//链接
        news.editor = $(element).find('div').children('a').text();
		//编辑
        temp1 = $(element).find('.AnonymousHome_category_5zp').text().split(' ');
        news.from = temp1[temp1.length - 1];
		//来源
        temp2 = $(element).find('div').children('span').text();
        news.time = temp2.substr(temp2.length - 11, temp2.length);
		//发布时间
        news.read = $(element).find('.AnonymousHome_read_2t5').text();
		//阅读量
        news.contain = $(element).find('p').text();
        //摘要

基本是使用相同的方式,对于部分数据做了简单字符串操作,使其美观

怎么爬取javascript页面 js爬取网页_怎么爬取javascript页面_05


每项信息都可以正确的爬取到,接下来为了爬取每则新闻的正文内容,对获得的二级网址再进行爬取

3)二级网址信息爬取

首先同样分析HTML结构,非常之简洁

怎么爬取javascript页面 js爬取网页_html_06

//先单独写一个获取信息的脚本
var myRequest = require('request')
var myCheerio = require('cheerio')
var myURL = 'https://xueqiu.com/1333325987/142925519'
function request(url, callback) {//request module fetching url
    var options = {
        url: url, encoding: null, heagers: null
    }
    myRequest(options, callback)
}

request(myURL, function (err, res, body) {
	var html = body;
    var $ = myCheerio.load(html, { decodeEntities: false });
    var source = $('.article__bd__from', 'div').children('a').text();
	var texts = [];
	$('.article__bd__detail', 'div').children('p').each(function (idx, element){
		texts.push({
			texts:$(element).text()
		})
	})
    console.log(texts);
})

怎么爬取javascript页面 js爬取网页_怎么爬取javascript页面_07


可以看见正文内容可以正常地爬取到,接下来用简单的字符串操作删去不需要的首行和空文本

texts.shift();
	for(var i=0,n=0;i<texts.length;i++){
		if(texts[i].texts.length == 0) {  
			texts.splice(i, 1);
		}
	}

于是,接下来尝试利用函数将爬取二级网址的功能植入源码中

var myRequest = require('request')
var myCheerio = require('cheerio')
var Promise = require('bluebird');
var myURL = 'https://xueqiu.com'

function request(url, callback) {//request module fetching url
    var options = {
        url: url, encoding: null, heagers: null
    }
    myRequest(options, callback)
}

var newsArr = [];

function getTexts(url) {
    request(url, function (err, res, body) {
        var html = body;
        var $ = myCheerio.load(html, { decodeEntities: false });
        var source = $('.article__bd__from', 'div').children('a').text();
        var texts = [];
        $('.article__bd__detail', 'div').children('p').each(function (idx, element) {
            texts.push({
                texts: $(element).text()
            })
        })
        texts.shift();

        for (var i = 0, n = 0; i < texts.length; i++) {
            if (texts[i].texts.length == 0) {
                texts.splice(i, 1);
            }
        }
        console.log(source);
        return source;
    })
}


request(myURL, function (err, res, body) {
    var html = body;

    var $ = myCheerio.load(html, { decodeEntities: false });

    var item = $('.AnonymousHome_home__timeline__item_3vU', 'div')

    item.map(function (idx, element) {
        var news = {};

        news.title = $(element).find('h3').children('a').text();
        news.link = "https://xueqiu.com" + $(element).find('h3').children('a').attr('href');
        news.editor = $(element).find('div').children('a').text();
        news.source = '';
        temp1 = $(element).find('.AnonymousHome_category_5zp').text().split(' ');
        news.category = temp1[temp1.length - 1];
        temp2 = $(element).find('div').children('span').text();
        news.time = temp2.substr(temp2.length - 11, temp2.length);
        news.read = $(element).find('.AnonymousHome_read_2t5').text();
        news.contain = $(element).find('p').text();
        news.texts = '';
        news.source = getTexts(news.link);
        newsArr.push(news);
    })
    console.log(newsArr);
})

怎么爬取javascript页面 js爬取网页_nodejs_08


然而,二级网址的信息并没有爬取到;

查阅了资料,这是因为JavaScript具有异步行为,在执行到 news.source = getTexts(news.link); 的时候,程序并不等待二级网址的爬取,而是先执行 newsArr.push(news); 将news存入newsArr中,因此 source 依然是 undefined。

接下来对解决这个问题做了尝试,先使用了request-promise代替request,并将 newsArr.push(news); 作为 getText() 的回调函数,然而依然无法解决问题

var cheerio = require('cheerio')
var rp = require('request-promise')

var options = {
    uri: 'https://xueqiu.com',
    transform: function (body) {
        return cheerio.load(body);
    }
}


function getTexts(newsArr, news, callback) {
    var options = {
        uri: news.link,
        transform: function (body) {
            return cheerio.load(body);
        }
    }
    rp(options)
        .then(function ($) {
            news.source = $('.article__bd__from', 'div').children('a').text();
            var texts = news.texts;
            $('.article__bd__detail', 'div').children('p').each(function (idx, element) {
                texts.push({
                    texts: $(element).text()
                })
            })
            texts.shift();
    
            for (var i = 0, n = 0; i < texts.length; i++) {
                if (texts[i].texts.length == 0) {
                    texts.splice(i, 1);
                }
            }
        })
    callback(newsArr, news);
}

rp(options).then(function ($) {
        var newsArr = [];
        var item = $('.AnonymousHome_home__timeline__item_3vU', 'div')

        
        item.map(function (idx, element) {
            var news = {};
            news.title = $(element).find('h3').children('a').text();
            news.link = "https://xueqiu.com" + $(element).find('h3').children('a').attr('href');
            news.editor = $(element).find('div').children('a').text();
            news.source = '';
            temp1 = $(element).find('.AnonymousHome_category_5zp').text().split(' ');
            news.category = temp1[temp1.length - 1];
            temp2 = $(element).find('div').children('span').text();
            news.time = temp2.substr(temp2.length - 11, temp2.length);
            news.read = $(element).find('.AnonymousHome_read_2t5').text();
            news.contain = $(element).find('p').text();
            news.texts = [];

            getTexts(newsArr, news, function(newsArr, news) {
                newsArr.push(news);
            });
        })
        console.log(newsArr);
    })

怎么爬取javascript页面 js爬取网页_怎么爬取javascript页面_09


即使将push进行回调,依然没有source和texts的信息,推测是request-promise的异步行为所致。由于不深入了解其异步行为,暂时跳过这些内容,在获得二级目录下信息后立即输出,这样就不用考虑request和push的先后了

爬取部分的最终代码:

var cheerio = require('cheerio')
var rp = require('request-promise')
var options = {
    uri: 'https://xueqiu.com',
    transform: function (body) {
        return cheerio.load(body);
    }
}

//获得二级目录信息
function getTexts(newsArr, news, callback) {
    var options = {
        uri: news.link,
        transform: function (body) {
            return cheerio.load(body);
        }
    }
    rp(options).then(function ($) {
            //获得source
            news.source = $('.article__bd__from', 'div').children('a').text();
            //获得texts,并且清除无效部分
            var texts = news.texts;
            $('.article__bd__detail', 'div').children('p').each(function (idx, element) {
                texts.push({
                    texts: $(element).text()
                })
            })
            texts.shift();
            for (var i = 0, n = 0; i < texts.length; i++) {
                if (texts[i].texts.length == 0) {
                    texts.splice(i, 1);
                }
            }
            console.log(news); //直接输出news,不再push入newsArr中
        })
    //callback(newsArr, news);
}

rp(options).then(function ($) {
        var newsArr = [];
        var item = $('.AnonymousHome_home__timeline__item_3vU', 'div')
        item.map(function (idx, element) {
            var news = {};
            news.title = $(element).find('h3').children('a').text();
            news.link = "https://xueqiu.com" + $(element).find('h3').children('a').attr('href');
            news.editor = $(element).find('div').children('a').text();
            news.source = '';
            temp1 = $(element).find('.AnonymousHome_category_5zp').text().split(' ');
            news.category = temp1[temp1.length - 1];
            temp2 = $(element).find('div').children('span').text();
            news.time = temp2.substr(temp2.length - 11, temp2.length);
            news.read = $(element).find('.AnonymousHome_read_2t5').text();
            news.contain = $(element).find('p').text();
            news.texts = [];
            getTexts(newsArr, news, function(newsArr, news) {
                newsArr.push(news);
            });
        })
        //console.log(newsArr);
    })

怎么爬取javascript页面 js爬取网页_html_10


可以看到,所有信息都可以被正确的爬取到,最后一步是将其存储至文件中

4)存储信息

需要用到的语法

var fs = require('fs') //导入文件操作模块

var str = JSON.stringify(object) //将对象信息转换为JSON文本

fs.writeFile('./xueqiu.json', JSON.stringify(news) + '\n', {'flag': 'a'}, function(err) {
        if (err) {
            return console.error(err);
        }
})
//以追加方式存入文件

最终的效果:

怎么爬取javascript页面 js爬取网页_i++_11


最终的代码:

var cheerio = require('cheerio')
var rp = require('request-promise')
var fs = require('fs')
var options = {
    uri: 'https://xueqiu.com',
    transform: function (body) {
        return cheerio.load(body);
    }
}

fs.unlink('./xueqiu.json', function(err) {
    if (err) {
        return console.error(err);
    }
})



//获得二级目录信息
function getTexts(newsArr, news, callback) {
    var options = {
        uri: news.link,
        transform: function (body) {
            return cheerio.load(body);
        }
    }
    rp(options).then(function ($) {
            //获得source
            news.source = $('.article__bd__from', 'div').children('a').text();
            //获得texts,并且清除无效部分
            var texts = news.texts;
            $('.article__bd__detail', 'div').children('p').each(function (idx, element) {
                texts.push({
                    texts: $(element).text()
                })
            })
            texts.shift();
            for (var i = 0, n = 0; i < texts.length; i++) {
                if (texts[i].texts.length == 0 || texts[i].texts == ' ') {
                    texts.splice(i, 1);
                }
            }
            fs.writeFile('./xueqiu.json', JSON.stringify(news) + '\n', {'flag': 'a'}, function(err) {
                if (err) {
                    return console.error(err);
                }
            })
            console.log(news); //直接输出news,不再push入newsArr中
        })
    //callback(newsArr, news);
}

rp(options).then(function ($) {
        var newsArr = [];
        var item = $('.AnonymousHome_home__timeline__item_3vU', 'div')
        item.map(function (idx, element) {
            var news = {};
            news.title = $(element).find('h3').children('a').text();
            news.link = "https://xueqiu.com" + $(element).find('h3').children('a').attr('href');
            news.editor = $(element).find('div').children('a').text();
            news.source = '';
            temp1 = $(element).find('.AnonymousHome_category_5zp').text().split(' ');
            news.category = temp1[temp1.length - 1];
            temp2 = $(element).find('div').children('span').text();
            news.time = temp2.substr(temp2.length - 11, temp2.length);
            news.read = $(element).find('.AnonymousHome_read_2t5').text();
            news.contain = $(element).find('p').text();
            news.texts = [];
            getTexts(newsArr, news, function(newsArr, news) {
                newsArr.push(news);
            });
        })
        //console.log(newsArr);
    })

本实验项目源码:https://github.com/AquariusAQ/Web-Crawler-in-Node.js