什么是Rss
简易信息聚合(也叫聚合内容)是一种RSS基于XML标准,在互联网上被广泛采用的内容包装和投递协议。RSS(Really Simple Syndication)是一种描述和同步网站内容的格式,是使用最广泛的XML应用。
通俗的说就是简易的数据,里面包含各种信息
为什么要获取并分析Rss
-
通过获取Rss可以将多数据集合(例如自动转载文章)
-
分析网站内容,获取有用信息
-
用于练习抓包,为以后进行数据分析做铺垫
使用SuperAgent 和 FeedParser 获取并分析 基地博文
SuperAgent 是nodeJs 的一个轻量级请求模块,可以用来方便发送HTTP请求,我们用它来获取Rss
FeedParser 是nodeJs 的一个关于Rss 的分析模块,我们可以使用它来分析获取到的数据
具体操作
当然首先安装这两个模块
npm install superagent feedparser --save-dev
使用superAgent
我们先尝试superAgent 模块使用
在根目录下,新建一个data.xml文件和sa.js
然后在sa.js中写获取的代码
var superagent = require("superagent"),
fs = require("fs"),
feedparser = require("feedparser")
var Url = "http://xgcyjd.com/feed.xml",
wStream = fs.createWriteStream("./data.xml") // 开辟写IO流
var req_stream = superagent.get(Url)
.buffer()
.type("xml")
.on("end", function (err, pres) {
if(err) {
console.log(err);
}
})
req_stream.pipe(wStream) // 数据写入
然后执行
node sa
你会发现数据都被写入到了xml中了
使用feedParser
根据刚刚获取的数据,我们就可以使用feedParser进行分析了,让我们尝试打印出博文聚合的文章列表吧
再新建一个 fp.js
并写入如下代码
var fs = require("fs"),
feedparser = require("feedparser"),
feed = __dirname + "/data.xml"
fs.createReadStream(feed)
.on("error", function (err) {
console.error(err);
})
.pipe(new feedparser())
.on("meta", function (meta) {
console.log("====== %s ======", meta.title);
})
.on("readable", function () {
var stream = this,
item
while(item = stream.read()) {
console.log("获取到了文章:%s", item.title || item.description);
}
})
然后
node fp
很优雅的打印出来了
当然,我们可以把superAgent 部分的代码 和 feedparser 的代码合起来
var superagent = require("superagent"),
fs = require("fs"),
feedparser = require("feedparser")
var Url = "http://xgcyjd.com/feed.xml",
fp = new feedparser()
var req_stream = superagent.get(Url)
.buffer()
.type("xml")
.end(function (err, pres) {
if(err) {
console.log(err);
}
})
req_stream.pipe(fp)
.on("error", function (err) {
console.error(err)
})
.on("meta", function (meta) {
console.log("===== %s =====", meta.title)
})
.on("readable", function () {
var $stream = this,
item
while(item = $stream.read()) {
console.log("获取到了文章: %s", item.title || item.description)
}
})
效果是一样
实战演示
上面我们只是很简单的尝试了这两个模块,现在让我们用这两个模块来干些事儿
例如百度的 Rss新闻集合
目标
“尝试获取百度 国内、国际、社会 三个方面的新闻Rss 转为JSON格式 并且汇总到mongoDB数据库中”
思路是先从 这三个方面分别异步的获取Rss资源,等全部获取完成后 再数据转化为JSON 并存入电脑
这种方式,业内一般专业的称呼为 “异步并发” 先分别异步获取,在一并执行下一步
首先,确保你已经装上了mongoDB 数据库
怎样实现nodeJs异步并发
由于抓取的数据所消耗的时间不一,我们常常需要写一个计数器 进行维护
例如下列伪代码:
var i = 0,
len = 3,
dataStack = []
$.get("国内Rss",function (data) {
i++;
dataStack.push(data);
timer();
})
$.get("国外Rss",function (data) {
i++;
dataStack.push(data);
timer();
})
$.get("社会Rss",function (data) {
i++;
dataStack.push(data);
timer();
})
function timer (data) {
if(i === len) {
handleData (dataStack); // 三个Rss都加载完成后,处理数据栈
}
}
这种方法不是不行,但是并不优雅,或许我们可以尝试另外一种方法
朴灵 给我们带来了他解决方案 eventproxy
eventproxy 也是一个nodeJs 模块,用于处理异步并发
将上面的代码转换成eventproxy方式
var eventproxy = require("eventproxy"),
ep = new eventproxy();
$.get("国内Rss",function (data) {
ep.emit("get_event",data)
})
$.get("国外Rss",function (data) {
ep.emit("get_event",data)
})
$.get("社会Rss",function (data) {
ep.emit("get_event",data)
})
// after 获取Rss后 进行处理
ep.after("get_event", 3 ,function (data) {
handleData(data)
})
希望大家详细的了解一下这个模块
准备工作
我们首先建立一个简单的文件路径
./
|
`--database
|
`--dao.js
|
`--app.js
|
`--service.js
|
`--package.Json
package.Json 如下
{
"name": "get_info",
"version": "1.0.0",
"description": "get_info by superagent, cheerio, eventproxy and mongoDB",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "babel index.js -d dist -s | node dist/index.js"
},
"author": "你的名儿",
"dependencies" : {
"eventproxy": "",
"superagent": "",
"superagent-charset": "",
"feedparser": "",
"iconv-lite": "",
"mongoose": ""
},
"license": "MIT"
}
service.js 用于实际的获取数据
app.js 是入口文件可以用来消费数据(这里暂时不涉及消费数据,所以app逻辑比较简单)
dao.js 用于配置数据库
实际演练
现在让我们投入实战,并且讲解一下一些坑
首先在 service 中我们可以新建一个class (开始涉及一点Es6语法),然后
class service {
constructor() {
this.result = []; // 数据暂存
}
getData (valueList, callback) {
}
saveData () {
}
}
主要逻辑是getData 和 saveData,我们先来比较简单的app.js
var fs = require("fs"),
service = require("./service.js")
var DataPort = [
{
name: "国内焦点",
category: "civilnews",
url: "http://news.baidu.com/n?cmd=1&class=civilnews&tn=rss"
},{
name: "国际焦点",
category: "internews",
url: "http://news.baidu.com/n?cmd=1&class=internews&tn=rss"
},{ name: "社会焦点",
category: "socianews",
url: "http://news.baidu.com/n?cmd=1&class=socianews&tn=rss"
}
];
service
.getData(DataPort,function ($self) {
$self.saveData();
})
很简单就是简单的引入模块,执行函数
superAgent 的两个大坑
再来看看获取数据
获取数据很简单,我们使用上文的方式处理就行了,我这次换成流的方式来写
var fp = new feedParser();
superAgent.get(url)
.type("xml")
.on("error", function (err) {
console.log("===== 获取数据失败 =====")
console.log(err);
})
.pipe(fp)
.on("error", function (err) {
console.log("===== 解析数据失败 =====");
console.error(err);
})
.on("readable", function () {
var $stream = this,
item
while(item = $stream.read()) {
// 处理你分析好的数据
}
})
.on("end", function () {
if(err) {
console.log(">>>>>> 出现错误!!!");
console.log(err);
} else {
console.log("===== 已解析完数据 =====");
}
})
上面的code 展示了获取单个数据的情况
注意,这里会有两个大坑,那就是转码问题
- 百度存储默认的是以GB2312存储,但是nodeJs 暂时并不支持GB2312编码 所以我们需要进行转码
- superAgent 暂时不支持除utf-8以外的其他格式, 对于任何非utf-8 的文档,会强行以utf-8进行编码
这两个问题加起来很坑(主要是一开始并没有意识到)找错误费了不少功夫
首先转码,我们选用 iconv-lite 模块
只需要在流入fp前 进行转码即可:
var iconv = require("iconv-lite")
...
.pipe(iconv.decodeStream("gb2312"))
.pipe(iconv.encodeStream("utf-8"))
.pipe(iconv.decodeStream("utf-8"))
再来就是 将superAgent 的文件修改编码
我们选用 一个国人写模块 superagent-charset
将superAgent 重新配置一下
var request = require("superagent"),
charsetCompoent = require('superagent-charset'),
superagent = charsetCompoent(request);
然后在发送请求之前
superagent.get(value.url)
.charset('gb2312')
.type("xml")
将编码设置为GB2312
feedparser 的一个大坑
feedparser 没什么,但是依然遇到了个BUG,跑去问公司大牛,弄了一个半小时解决~
主要就是获取多类新闻数据组的时候,解析出现了数据丢失,
经过分析,最终确认是,fp的问题
一开始写的是
var fp = new feedparser();
DataPort = forEach(function(value, idx) {
superAgent
.xxx
.xxx
.pipe(fp)
解决完后是
DataPort = forEach(function(value, idx) {
var fp = new feedparser();
superAgent
.xxx
.xxx
.pipe(fp)
因为 superAgent 获取数据是异步的,而单个fp解析能力是有限的
第一组内容完成后,fp开始解析,在解析过程中,第二组内容也获取完毕 依照一开始写的方式,fp就会丢掉正在解析的第一组数据,转而去解析第二组,从而产生数据丢失
而第二种方式,每次解析完成后都会新生成一个fp实例进行解析,2组对应2个fp 这样就不会发生数据丢失的行为!
数据存储
我们再来看saveData
for(var i = 0; i<$self.result.length; i++) {
// 存储数据
ep.emit("save_Data");
}
// 每组数据存储完毕后
ep.after("save_Data", $self.result.length , function () {
db.close(); // 关闭数据库
})
很简单的逻辑
具体源码
上文只是解析了部分代码,具体源码请见:Get_BaiDuNewsRss_By_NodeJS