本来是要发布在cnblog的,奇葩的是找不到发布入口,上次还发表过一篇文章,现在竟然找不到了,就发在csdn了。
之前经常用nodejs+express搭建http框架,发现其实不用,可以做到简洁的完成自己的需求,参考文档。
https://www.nodebeginner.org/index-zh-cn.html.
https://www.cnblogs.com/coco1s/p/4954063.html.
这个教程如果不用router而是直接调用server.
start_listen()
会有两次调用onRequest的问题。
下面说下项目步骤:
准备阶段的安装nodejs步骤略去,就是网上下载nodejs.msi然后安装到默认目录就行。
1,建立手动目录,d:\myproject\js\monitor_jx
2,打开cmd命令行来到monitor_jx目录,执行一些下载需要使用的模块的命令,
因为公司网络使用npm拉包总是失败,使用cnpm,不过初始化还是可以使用npm,一路回车就行。
3,然后命令行下拉几个对应的modules
这里说下cnpm的安装方法:npm install cnpm -g --registry=https://registry.npm.taobao.org
以上安装的是需要的几个包:
sa = require('superagent'),
cheerio = require('cheerio'),
async = require('async'),
eventproxy = require('eventproxy');
4,在项目目录下手动建index.js文件,server.js文件,两个目录,modules,router。
modules目录下面建文件product.js,router目录下建route.js文件
index.js文件简单,三行即可。
var server = require("./server");
var router = require("./routers/router");
server.start_listen(router.route);
5,server.js文件如下
var http = require('http'); objProduct= require('./modules/product'), sa = require('superagent'), cheerio = require('cheerio'), async = require('async'), eventproxy = require('eventproxy'); var ep = new eventproxy(), urlsArray = [], //存放爬取网址 pageUrls = [], //存放收集页面网址 pageNum = 10, //要爬取的页数总数 pageItems=40; //每页的商品数目 totalPageSkus = []; var ep2=new eventproxy(); var nameMap = {}; // Map map = new HashMap(); var allProducts={}; for(var i=1 ; i<= pageNum ; i++){ pageUrls.push('http://list.jiuxian.com/1-0-0-0-0-0-0-0-0-0-0-0.htm?pageNum='+i+'&&area=2'); } // 主start程序 function start(route){ function onRequest(req, res){ // 轮询 所有文章列表页 res.setHeader('Content-Type', 'text/html; charset=utf-8'); pageUrls.forEach(function(pageUrl){ sa.get(pageUrl) .end(function(err,pres){ // pres.text 里面存储着请求返回的 html 内容,将它传给 cheerio.load 之后 // 就可以得到一个实现了 jquery 接口的变量,我们习惯性地将它命名为 `$` // 剩下就都是利用$ 使用 jquery 的语法了 //<a class="proName" href="*" title="*" var $ = cheerio.load(pres.text); //var curPageUrls = $('.titlelnk'); //var curPageNodes=$('li[product-box]');//所以包含“product-box”属性的li元素 var products=$('.proName');//所有包含class="proName"的元素 for(var i = 0 ; i < products.length ; i++){ var goodsUrl=products.eq(i).attr('href'); var name=products.eq(i).attr('title'); var reg=/http:\/\/www.jiuxian.com\/goods-(.*?).html/; var goodsArr = goodsUrl.match(reg); if(goodsArr.length>0){ var goodsArr = goodsUrl.match(reg); var sku=goodsArr[1]; nameMap[sku] = name;//as in java: map.put(key, value); } ep.emit('GoodsHtml', goodsUrl); } }); });//forEach ep.after('GoodsHtml', pageNum*pageItems ,function(goodsUrls){ // 当所有 'BlogArticleHtml' 事件完成后的回调触发下面事件 // ... res.write('<br/>'); res.write('total goods is '+goodsUrls.length+'<br/>'); var skus=''; var onePageSkus=[]; var reqPricesUrls=[]; for(var i=0;i<goodsUrls.length; i++){ //var goods_url = "http://www.jiuxian.com/goods-10.html"; var reg=/http:\/\/www.jiuxian.com\/goods-(.*?).html/; var url= goodsUrls[i]; var goodsArr = url.match(reg); var sku=''; if(goodsArr.length>0){ sku=goodsArr[1]; } onePageSkus.push(sku); if((i+1)%pageItems==0){ totalPageSkus.push(onePageSkus); onePageSkus=[]; } }//for if(onePageSkus.length>0){ totalPageSkus.push(onePageSkus); onePageSkus=[]; } res.write('<br/>'); //res.write('http://list.jiuxian.com/act/selectPriceAndClubPriceByProIds.htm?ids='+skus.join(',')+'<br/>'); totalPageSkus.forEach(function(onePageSkus){ var requrl='http://list.jiuxian.com/act/selectPriceAndClubPriceByProIds.htm?ids='+onePageSkus.join(','); reqPricesUrls.push(requrl); //res.write(url+'<br/>'); }); var curCount = 0; var funcReqOnePagePrice = function(url,callback){ //延迟毫秒数 var delay = parseInt((Math.random() * 30000000) % 1000, 10); curCount++; console.log('现在的并发数是', curCount, ',正在抓取的是', ',耗时' + delay + '毫秒'); sa.get(url) .end(function(err,sres){ var $ = cheerio.load(sres.text); var data = sres.text; var obj = JSON.parse(data); // res.write('status'+obj.status+"<br/>"); //解析数组 obj.data.forEach(function(item) { var clubPrice=0.0; var price=0.0; if (typeof(item.clubPrice) != 'undefined') { clubPrice=parseFloat(item.clubPrice); } if(typeof(item.price) != 'undefined'){ price=parseFloat(item.price); } var name=''; if(typeof(nameMap[item.productId])!='undefined'){ name=nameMap[item.productId]; } var p=objProduct.createProduct(item.productId,price,clubPrice); p.name=name; allProducts[item.productId]=p; res.write(p.name+':'+ clubPrice + "," + item.price + ",id:" + item.productId + '<br/>'); }); }); setTimeout(function() { curCount--; callback(null,url +'Call back content'); console.log('剩余请求数为:'+curCount); }, delay); }; // 使用async控制异步抓取 // mapLimit(arr, limit, iterator, [callback]) // 异步回调 async.mapLimit(reqPricesUrls, 10 ,function (url, callback) { funcReqOnePagePrice(url, callback); }, function (err,result) { // pageNum 个 URL 访问完成的回调函数 console.log('抓取结束'); }); })//after };//onRequest http.createServer(onRequest).listen(3000); } exports.start_listen= start;
6,route.js文件如下
7,product.js文件如下function route(pathname) { console.log("About to route a request for " + pathname); } exports.route = route;
function createProduct(productId,price,clubPrice) { var oTempProduct = new Object; oTempProduct.proId = productId; oTempProduct.proPrice = price; oTempProduct.proClubPrice = clubPrice; oTempProduct.name=''; return oTempProduct; } exports.createProduct= createProduct;//暴露出来才能在外面使用
8。文件建立完毕,命令行执行node index,然后浏览器里输入 http://localhost:3000/
命令行打印如下。
网页显示的一部分如下
主要工作在server.js里面,代码分析有时间再写一篇文章。
这只是初步工作剩下还有存储历史数据和定时刷新比较出新低价的工作未做。