转载: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文件添加依赖
- "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);
- }
- });
关键在于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
- <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
- res.headers['content-type'].charset;
这样获取你抓取站点的编码格式。
拼接前需要进行编码转换,当然这里用了高明点的作法,pipe管道
然后进行转换成我没可以操作的对象,这个时候需要feedparser出马了,
- var feedparser = new FeedParser();
feedparse也监听几个状态 readable,end,error
readable的回调方法 一次会读到一条记录,每次读到一条记录 我就存到数组对象中。
所有数据读完会调用 end的回调函数。到此,站点抓取完成了。 还是多站点呀
第四步:存到数据库
数据全部存在了posts 的数组对象中,要怎么处理,任凭您发落了。存到mongoDB也就几行代码的事情。这里用到了mongoose库
当然mongodb库也是必须要的。 mongoose类似于baseDao的感觉 操作mongodb数据库非常方便。
先建立模式:
- <span style="font-size:18px;">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;</span>
再建立模型:
- <span style="font-size:18px;">var mongoose = require('mongoose');
- var PostSchema = require('../schemas/PostSchema');
- var Post = mongoose.model('Post',PostSchema);
- module.exports = Post;</span>
OK,可以保存到数据库了,mongoDB好像不能批量插入, 我这里就循环了,如果title不存在 则插进入,不然会有重复的新闻
- <span style="font-size:18px;">var Post = require('../model/Post');
- function savePost(posts){
- for(var i = 0 ;i<posts.length;i++){
- var post = posts[i];
- console.log(post.title||"");
- Post.find({"title":post.title||""},function(err,r){ // 不存在,则插入
- if(err){
- console.error(err.stack);
- return;
- };
- if(r == null){
- post.save(function(err){
- if(err){
- console.error(err.stack);
- return;
- };
- });
- }
- });
- }
- }
- exports.savePost = savePost;</span>