node.js爬虫01

转载:http://blog.csdn.net/kissliux/article/details/19560603

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文件添加依赖

[javascript] view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1. "dependencies": {  
  2.    "express""3.4.8",  
  3.    "ejs""*",  
  4.    "feedparser":"0.16.6",  
  5.    "request":"2.33.0",  
  6.    "iconv":"2.0.7",  
  7.    "mongoose":"3.8.7",  
  8.    "mongodb":"*"  
  9.  }  

执行以下代码,导入相关的文件到项目node_modules中:
  1. npm install -d   

第三步:

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

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

建立一个rssSite.json文件

[javascript] view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1. {  
  2.   
  3.     "channel":[  
  4.         {  
  5.             "from":"baidu",  
  6.             "name":"civilnews",  
  7.             "work":false,       //false 则不抓取  
  8.             "title":"百度国内最新新闻",  
  9.             "link":"http://news.baidu.com/n?cmd=4&class=civilnews&tn=rss",  
  10.             "typeId":1  
  11.         },{  
  12.             "from":"netEase",  
  13.             "name":"rss_gn",  
  14.             "title":"网易最新新闻",  
  15.             "link":"http://news.163.com/special/00011K6L/rss_gn.xml",  
  16.             "typeId":2  
  17.         }  
  18.     ]  
  19. }  

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

引入相关的包,

[javascript] view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1. var request = require('request')  
  2.     , FeedParser = require('feedparser')  
  3.     , rssSite = require('../config/rssSite.json')  
  4.     , Iconv = require('iconv').Iconv;  

需要遍历刚刚配置的channel,找到需要的url地址
[javascript] view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1. var channels = rssSite.channel;  
  2. channels.forEach(function(e,i){  
  3.     if(e.work != false){  
  4.         console.log("begin:"+ e.title);  
  5.         fetch(e.link,e.typeId);  
  6.     }  
  7. });  
work为false的站点,都不进行抓取。即黑名单吧,typeId是标识这个新闻是属于哪个栏目,社会,财经还是其他。

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

[javascript] view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1. function fetch(feed,typeId) {  
  2.     var posts;  
  3.     // Define our streams  
  4.     var req = request(feed, {timeout: 10000, pool: false});  
  5.     req.setMaxListeners(50);  
  6.     // Some feeds do not response without user-agent and accept headers.  
  7.     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')  
  8.         .setHeader('accept''text/html,application/xhtml+xml');  
  9.   
  10.     var feedparser = new FeedParser();  
  11.   
  12.     // Define our handlers  
  13.     req.on('error', done);  
  14.     req.on('response'function(res) {  
  15.         var stream = this  
  16.             , iconv  
  17.             , charset;  
  18.         posts = new Array();  
  19.         if (res.statusCode != 200) return this.emit('error'new Error('Bad status code'));  
  20.         charset = getParams(res.headers['content-type'] || '').charset;  
  21.         if (!iconv && charset && !/utf-*8/i.test(charset)) {  
  22.             try {  
  23.                 iconv = new Iconv(charset, 'utf-8');  
  24.                 iconv.on('error', done);  
  25.                 stream = this.pipe(iconv);  
  26.             } catch(err) {  
  27.                 this.emit('error', err);  
  28.             }  
  29.         }  
  30.         stream.pipe(feedparser);  
  31.     });  
  32.   
  33.     feedparser.on('error', done);  
  34.     feedparser.on('end'function(err){  
  35.       //  postService.savePost(posts);    //存到数据库  
  36.     });  
  37.     feedparser.on('readable'function() {  
  38.         var post;  
  39.         while (post = this.read()) {  
  40.             posts.push(transToPost(post));//保存到对象数组  
  41.         }  
  42.     });  
  43.     function transToPost(post){  
  44.         var mPost = new Post({  
  45.             title : post.title,  
  46.             link : post.link,  
  47.             description : post.description,  
  48.             pubDate : post.pubDate,  
  49.             source : post.source,  
  50.             author : post.author,  
  51.             typeId : typeId  
  52.         });  
  53.         return mPost;  
  54.     }  
  55. }  


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

[javascript] view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1. <span style="font-size:18px;"var req = request(feed, {timeout: 10000, pool: false});</span>  

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

[javascript] view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1. res.headers['content-type'].charset;  

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


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

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

[javascript] view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1. var feedparser = new FeedParser();  

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

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

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



第四步:存到数据库

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

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

先建立模式:

[javascript] view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1. <span style="font-size:18px;">var mongoose = require('mongoose');  
  2. var PostSchema = new mongoose.Schema({  
  3.     title:String,  
  4.     link :String,  
  5.     description :String,  
  6.     pubDate :String,  
  7.     source :String,  
  8.     author :String,  
  9.     typeId : Number  
  10. });  
  11. module.exports = PostSchema;</span>  

再建立模型:
[javascript] view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1. <span style="font-size:18px;">var mongoose = require('mongoose');  
  2. var PostSchema = require('../schemas/PostSchema');  
  3. var Post = mongoose.model('Post',PostSchema);  
  4.   
  5. module.exports = Post;</span>  

OK,可以保存到数据库了,mongoDB好像不能批量插入, 我这里就循环了,如果title不存在 则插进入,不然会有重复的新闻
[javascript] view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1. <span style="font-size:18px;">var Post = require('../model/Post');  
  2.   
  3. function savePost(posts){  
  4.     for(var i = 0 ;i<posts.length;i++){  
  5.         var post = posts[i];  
  6.         console.log(post.title||"");  
  7.         Post.find({"title":post.title||""},function(err,r){ // 不存在,则插入  
  8.             if(err){  
  9.                 console.error(err.stack);  
  10.                 return;  
  11.             };  
  12.             if(r == null){  
  13.                 post.save(function(err){  
  14.                     if(err){  
  15.                         console.error(err.stack);  
  16.                         return;  
  17.                     };  
  18.                 });  
  19.             }  
  20.   
  21.         });  
  22.     }  
  23. }  
  24. exports.savePost = savePost;</span>  



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值