网络爬虫,使用NodeJs抓取RSS新闻

Node.js 是一个基于Chrome JavaScript 运行时建立的一个平台, 用来方便地搭建快速的, 易于扩展的网络应用· Node.js 借助事件驱动, 非阻塞 I/O 模型变得轻量和高效, 非常适合 run across distributed devices 的 data-intensive 的实时应用·


提供RSS服务的站点超级多,百度、网易、新浪、虎嗅网 等等站点,基于java  c++ php的rss抓取网上很多,今天说说NodeJs抓取RSS信息,

使用NodeJs做网络爬虫,抓取RSS新闻。各站点编码格式不一样 GBK,UTF-8,ISO8859-1等等,所以需要进行编码,对国人来说UTF-8是最酷的。抓取多站点,然后保存到数据库,充分利用javascript异步编程的特点,抓取速度超级快呀。

这个项目是为新闻android客户端实现的,以后我也会上传新闻客户端的源码。

本项目的源码托管在github:https://github.com/kissliux/rssSpider


环境需求:

NodeJs(必须), 我的版本是0.10.24

mongodb(可选),或者mysql等等其他数据库


编程工具:webStrom


第一步:新建nodejs项目,我一般建立express web项目

第二步: 在package.json文件添加依赖

 "dependencies": {
    "express": "3.4.8",
    "ejs": "*",
    "feedparser":"0.16.6",
    "request":"2.33.0",
    "iconv":"2.0.7",
    "mongoose":"3.8.7",
    "mongodb":"*"
  }

执行以下代码,导入相关的文件到项目node_modules中:

npm install -d 

第三步:

基本准备工作完毕,可以动手了写代码了。RSS抓取,主要依赖于feedparser 库,github地址:http://github.com/danmactough/node-feedparser

先配置下,需要抓取的站点信息。

建立一个rssSite.json文件

{

    "channel":[
        {
            "from":"baidu",
            "name":"civilnews",
            "work":false,       //false 则不抓取
            "title":"百度国内最新新闻",
            "link":"http://news.baidu.com/n?cmd=4&class=civilnews&tn=rss",
            "typeId":1
        },{
            "from":"netEase",
            "name":"rss_gn",
            "title":"网易最新新闻",
            "link":"http://news.163.com/special/00011K6L/rss_gn.xml",
            "typeId":2
        }
    ]
}

我要抓取的就是这两个站点,channel的值是一个对象数组,如果你需要多个站点,直接添加就行了。 

引入相关的包,

var request = require('request')
    , FeedParser = require('feedparser')
    , rssSite = require('../config/rssSite.json')
    , Iconv = require('iconv').Iconv;

需要遍历刚刚配置的channel,找到需要的url地址

var channels = rssSite.channel;
channels.forEach(function(e,i){
    if(e.work != false){
        console.log("begin:"+ e.title);
        fetch(e.link,e.typeId);
    }
});
work为false的站点,都不进行抓取。即黑名单吧,typeId是标识这个新闻是属于哪个栏目,社会,财经还是其他。

关键在于fetch函数,抓取和分析都在这里了。我先贴代码 再来解释

function fetch(feed,typeId) {
    var posts;
    // Define our streams
    var req = request(feed, {timeout: 10000, pool: false});
    req.setMaxListeners(50);
    // Some feeds do not response without user-agent and accept headers.
    req.setHeader('user-agent', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36')
        .setHeader('accept', 'text/html,application/xhtml+xml');

    var feedparser = new FeedParser();

    // Define our handlers
    req.on('error', done);
    req.on('response', function(res) {
        var stream = this
            , iconv
            , charset;
        posts = new Array();
        if (res.statusCode != 200) return this.emit('error', new Error('Bad status code'));
        charset = getParams(res.headers['content-type'] || '').charset;
        if (!iconv && charset && !/utf-*8/i.test(charset)) {
            try {
                iconv = new Iconv(charset, 'utf-8');
                iconv.on('error', done);
                stream = this.pipe(iconv);
            } catch(err) {
                this.emit('error', err);
            }
        }
        stream.pipe(feedparser);
    });

    feedparser.on('error', done);
    feedparser.on('end', function(err){
      //  postService.savePost(posts);    //存到数据库
    });
    feedparser.on('readable', function() {
        var post;
        while (post = this.read()) {
            posts.push(transToPost(post));//保存到对象数组
        }
    });
    function transToPost(post){
        var mPost = new Post({
            title : post.title,
            link : post.link,
            description : post.description,
            pubDate : post.pubDate,
            source : post.source,
            author : post.author,
            typeId : typeId
        });
        return mPost;
    }
}


1、关键函数: request(url,[option]); 这个是可以发送http请求的函数。 地址: http://github.com/mikeal/request.git

 var req = request(feed, {timeout: 10000, pool: false});

req 需要监听几个状态 response,error。请求发出后,会收到响应,有响应后,把接收到的数据进行拼接,拼接前需要进行编码转换。把非utf8的编码转换成utf8.这里利用到了库 ICONV。地址:http://github.com/bnoordhuis/node-iconv

res.headers['content-type'].charset;

这样获取你抓取站点的编码格式。


拼接前需要进行编码转换,当然这里用了高明点的作法,pipe管道

然后进行转换成我没可以操作的对象,这个时候需要feedparser出马了,

 var feedparser = new FeedParser();

feedparse也监听几个状态 readable,end,error

readable的回调方法 一次会读到一条记录,每次读到一条记录 我就存到数组对象中。

所有数据读完会调用 end的回调函数。到此,站点抓取完成了。 还是多站点呀



第四步:存到数据库

数据全部存在了posts 的数组对象中,要怎么处理,任凭您发落了。存到mongoDB也就几行代码的事情。这里用到了mongoose库

当然mongodb库也是必须要的。 mongoose类似于baseDao的感觉  操作mongodb数据库非常方便。

先建立模式:

var mongoose = require('mongoose');
var PostSchema = new mongoose.Schema({
    title:String,
    link :String,
    description :String,
    pubDate :String,
    source :String,
    author :String,
    typeId : Number
});
module.exports = PostSchema;

再建立模型:

var mongoose = require('mongoose');
var PostSchema = require('../schemas/PostSchema');
var Post = mongoose.model('Post',PostSchema);

module.exports = Post;

OK,可以保存到数据库了,mongoDB好像不能批量插入, 我这里就循环了,如果title不存在 则插进入,不然会有重复的新闻

var Post = require('../model/Post');

function savePost(posts){
    for(var i = 0 ;i


到此新闻抓取结束,欢迎拍砖。

下一篇文章我会介绍“新闻http服务,mongodb分页实现”,当然继续使用nodejs













你可能感兴趣的:(javascript,nodejs,rss)