nodejs爬虫在linux上运行,10分钟教你撸一个nodejs爬虫系统

抓取首页文章列表20条数据

根目录创建一个app.js文件。

实现思路步骤

引入依赖

定义一个地址

发起请求

页面数据解析

分析页面数据

生成数据

1. 引入依赖:

const superagent = require('superagent');

const cheerio = require('cheerio');

2. 定义一个地址

const reptileUrl = "http://www.jianshu.com/";

3. 发起请求

superagent.get(reptileUrl).end(function (err, res) {

// 抛错拦截

if(err){

return throw Error(err);

}

// 等待 code

});

这个时候我们会向简书首页发一个请求,只要不抛错,走if,那么就可以继续往下看了。

4. 页面数据解析

superagent.get(reptileUrl).end(function (err, res) {

// 抛错拦截

if(err){

return throw Error(err);

}

/**

* res.text 包含未解析前的响应内容

* 我们通过cheerio的load方法解析整个文档,就是html页面所有内容,可以通过console.log($.html());在控制台查看

*/

let $ = cheerio.load(res.text);

});

注释已经说明这行代码的意思,就不在说明了。就下了就比较难了。

5. 分析页面数据

你需在浏览器打开简书官网,简书是后台渲染部分可见的数据,后续数据是通过ajax请求,使用js填充。我们爬数据,一般只能爬到后台渲染的部分,js渲染的是爬不到,如果ajax,你可以直接去爬api接口,那个日后再说。

言归正传,简书首页文章列表,默认会加载20条数据,这个已经够我用了,你每次刷新,如果有更新就会更新,最新的永远在最上面。

这20条数据存在页面一个类叫.note-list的ul里面,每条数据就是一个li,ul父级有一个id叫list-container,学过html的都知道id是唯一,保证不出错,我选择id往下查找。

$('#list-container .note-list li')

上面就是cheerio帮我们获取到说有需要的文章列表的li,是不是和jq写一样。我要获取li里面内容就需要遍历 Element.each(function(i, elem) {}) 也是和jq一样

$('#list-container .note-list li').each(function(i, elem) {

// 拿到当前li标签下所有的内容,开始干活了

});

以上都比较简单,复杂的是下面的,数据结构。我们需要怎么拼装数据,我大致看了一下页面,根据经验总结了一个结构,还算靠谱。

{

id: 每条文章id

slug:每条文章访问的id (加密的id)

title: 标题

abstract: 描述

thumbnails: 缩略图 (如果文章有图,就会抓第一张,如果没有图就没有这个字段)

collection_tag:文集分类标签

reads_count: 阅读计数

comments_count: 评论计数

likes_count:喜欢计数

author: { 作者信息

id:没有找到

slug: 每个用户访问的id (加密的id)

avatar:会员头像

nickname:会员昵称(注册填的那个)

sharedTime:发布日期

}

}

基本数据结构有了,先定义一个数组data,来存放拼装的数据,留给后面使用。

随便截取一条文章数据

300

96

xxx

xxxxxxx

xxxxxxxxx...

xxxx

414

2

16

1

我们就拿定义的数据结构和实际的页面dom去一一比对,去获取我们想要的数据。

id: 每条文章id

li上有一个 data-note-id="12732916"这个东西就是文章的id,

怎么获取:$(elem).attr('data-note-id'),这样就完事了

slug:每条文章访问的id (加密的id)

如果你点文章标题,或者带缩略图的位置,都会跳转一个新页面 http://www.jianshu.com/p/xxxxxx 这样的格式。标题是一个a链接,链接上有一个href属性,里面有一段 /p/xxxxxx 这样的 /p/是文章详情一个标识,xxxxxx是标识哪片文章。而我们slug就是这个xxxxxx,就需要处理一下。$(elem).find('.title').attr('href').replace(//p//, ""),这样就可以得到xxxxxx了。

title: 标题

这个简单,$(elem).find('.title').text()就好了。

abstract: 描述

这个简单,$(elem).find('.abstract').text()就好了。

thumbnails: 缩略图 (如果文章有图,就会抓第一张,如果没有图就没有这个字段)

这个存在.wrap-img这a标签里面img里,如果没有就不显示,$(elem).find('.wrap-img img').attr('src'),如果取不到就是一个undefined,那正合我意。

下面4个都在.meta的div里面 (我没有去打赏的数据,因为我不需要这个数据)

collection_tag:文集分类标签

有对应的class,$(elem).find('.collection-tag').text()

reads_count: 阅读计数

这个就比较麻烦了,它的结构是这样的

414

还要有一个字体图标的class可以使用,不然还真不好玩,那需要怎么获取了,$(elem).find('.ic-list-read').parent().text(),先去查找这个字体图标i标签,然后去找它的父级a标签,获取里面text文本,标签就不被获取了,只剩下数字。

接下来2个一样处理的。

comments_count: 评论计数

$(elem).find('.ic-list-comments').parent().text()

likes_count:喜欢计数

$(elem).find('.ic-list-like').parent().text()

接来就是会员信息,全部都在.author这个div里面

id:没有找到

slug: 每个用户访问的id (加密的id)

这个处理方式和文章slug一样,$(elem).find('.avatar').attr('href').replace(//u//, ""),唯一不同的需要吧p换成u。

avatar:会员头像

$(elem).find('.avatar img').attr('src')

nickname:会员昵称(注册填的那个)

昵称存在一个叫.blue-link标签里面,$(elem).find('.blue-link').text()

sharedTime:发布日期

这个发布日期,你看到页面是个性化时间,xx小时前啥的,如果直接取就是一个坑爹的事了,在.time的span上有一个data-shared-at="2017-05-24T08:05:12+08:00"这个才是正真的时间,你会发现它一上来是空的,是js来格式化的。$(elem).find('.time').attr('data-shared-at')

以上就是所有字段来源的。接下来要说一个坑爹的事,text()获取出来的,有回车符/n和空格符/s。所以需要写一个方法把它们去掉。

function replaceText(text){

return text.replace(/\n/g, "").replace(/\s/g, "");

}

组装起来的数据代码:

let data = [];

// 下面就是和jQuery一样获取元素,遍历,组装我们需要数据,添加到数组里面

$('#list-container .note-list li').each(function(i, elem) {

let _this = $(elem);

data.push({

id: _this.attr('data-note-id'),

slug: _this.find('.title').attr('href').replace(/\/p\//, ""),

author: {

slug: _this.find('.avatar').attr('href').replace(/\/u\//, ""),

avatar: _this.find('.avatar img').attr('src'),

nickname: replaceText(_this.find('.blue-link').text()),

sharedTime: _this.find('.time').attr('data-shared-at')

},

title: replaceText(_this.find('.title').text()),

abstract: replaceText(_this.find('.abstract').text()),

thumbnails: _this.find('.wrap-img img').attr('src'),

collection_tag: replaceText(_this.find('.collection-tag').text()),

reads_count: replaceText(_this.find('.ic-list-read').parent().text()) * 1,

comments_count: replaceText(_this.find('.ic-list-comments').parent().text()) * 1,

likes_count: replaceText(_this.find('.ic-list-like').parent().text()) * 1

});

});

let _this = $(elem); 先把$(elem);存到一个变量里面,jq写习惯了。

有几个*1是吧数字字符串转成数字,js小技巧,不解释。

6. 生成数据

数据已经可以获取了,都存在data这个数据里面,现在是20条数据,我们理想的数据,那么放在node里面,我们还是拿不到,怎么办,一个存在数据库(还没有弄到哪里,我都还没有想好怎么建数据库表设计),一个就存在本地json文件。

那就存在本地json文件。nodejs是一个服务端语言,就说可以访问本地磁盘,添加文件和访问文件。需要引入nodejs内置的包fs。

const fs = require('fs');

它的其他用法不解释了,只说一个创建一个文件,并且在里面写内容

这是写文件的方法:

fs.writeFile(filename,data,[options],callback);

/**

* filename, 必选参数,文件名

* data, 写入的数据,可以字符或一个Buffer对象

* [options],flag 默认‘2’,mode(权限) 默认‘0o666’,encoding 默认‘utf8’

* callback 回调函数,回调函数只包含错误信息参数(err),在写入失败时返回。

*/

我们需要这样来写了:

// 写入数据, 文件不存在会自动创建

fs.writeFile(__dirname + '/data/article.json', JSON.stringify({

status: 0,

data: data

}), function (err) {

if (err) throw err;

console.log('写入完成');

});

注意事项

我方便管理数据,放在data文件夹,如果你也是这样,记得一定先要在根目录建一个data文件夹不然就会报错

默认utf-8编码;

写json文件一定要JSON.stringify()处理,不然就是[object Object]这货了。

如果是文件名可以直接article.json会自动生成到当前项目根目录里,如果要放到某个文件里,例如data,一定要加上__dirname + '/data/article.json'。千万不能写成3. 如果是文件名可以直接article.json会自动生成到当前项目根目录里,如果要放到某个文件里,例如data,一定要加上__dirname + '/data/article.json'。千万不能写成'/data/article.json'不然就会抛错,找不到文件夹,因为文件夹在你所在的项目的盘符里。例如G:/data/article.json。

以上基本就完成一个列表页面的抓取。看下完整代码:

/**

* 获取依赖

* @type {*}

*/

const superagent = require('superagent');

const cheerio = require('cheerio');

const fs = require('fs');

/**

* 定义请求地址

* @type {*}

*/

const reptileUrl = "http://www.jianshu.com/";

/**

* 处理空格和回车

* @param text

* @returns {string}

*/

function replaceText(text) {

return text.replace(/\n/g, "").replace(/\s/g, "");

}

/**

* 核心业务

* 发请求,解析数据,生成数据

*/

superagent.get(reptileUrl).end(function (err, res) {

// 抛错拦截

if (err) {

return throw Error(err);

}

// 解析数据

let $ = cheerio.load(res.text);

/**

* 存放数据容器

* @type {Array}

*/

let data = [];

// 获取数据

$('#list-container .note-list li').each(function (i, elem) {

let _this = $(elem);

data.push({

id: _this.attr('data-note-id'),

slug: _this.find('.title').attr('href').replace(/\/p\//, ""),

author: {

slug: _this.find('.avatar').attr('href').replace(/\/u\//, ""),

avatar: _this.find('.avatar img').attr('src'),

nickname: replaceText(_this.find('.blue-link').text()),

sharedTime: _this.find('.time').attr('data-shared-at')

},

title: replaceText(_this.find('.title').text()),

abstract: replaceText(_this.find('.abstract').text()),

thumbnails: _this.find('.wrap-img img').attr('src'),

collection_tag: replaceText(_this.find('.collection-tag').text()),

reads_count: replaceText(_this.find('.ic-list-read').parent().text()) * 1,

comments_count: replaceText(_this.find('.ic-list-comments').parent().text()) * 1,

likes_count: replaceText(_this.find('.ic-list-like').parent().text()) * 1

});

});

// 生成数据

// 写入数据, 文件不存在会自动创建

fs.writeFile(__dirname + '/data/article.json', JSON.stringify({

status: 0,

data: data

}), function (err) {

if (err) throw err;

console.log('写入完成');

});

});

一个简书首页文章列表的爬虫就大工告成了,运行代码,打开Terminal运行node app.js或者node app都行。或者在package.json的scripts对象下添加一个"dev": "node app.js",然后用webstorm的npm面板运行。

有文章列表就有对应的详情页面,后面继续讲解怎么爬详情。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值