[ {"title": "【公告】发招聘帖的同学留意一下这里","href": "http://cnodejs.org/topic/541ed2d05e28155f24676a12","comment1": "呵呵呵呵" }, {"title": "发布一款 Sublime Text 下的 JavaScript 语法高亮插件","href": "http://cnodejs.org/topic/54207e2efffeb6de3d61f68f","comment1": "沙发!" }]挑战[ {"title": "【公告】发招聘帖的同学留意一下这里","href": "http://cnodejs.org/topic/541ed2d05e28155f24676a12","comment1": "呵呵呵呵","author1": "auser","score1": 80 }, ...]知识点var eventproxy = require("eventproxy");var superagent = require("superagent");var cheerio = require("cheerio");// url 模块是 Node.js 标准库里面的// http://nodejs.org/api/url.htmlvar url = require("url");var cnodeUrl = "https://cnodejs.org/";superagent.get(cnodeUrl) .end(function (err, res) {if (err) { return console.error(err);}var topicUrls = [];var $ = cheerio.load(res.text);// 获取首页所有的链接$("#topic_list .topic_title").each(function (idx, element) { var $element = $(element); // $element.attr("href") 本来的样子是 /topic/542acd7d5d28233425538b04 // 我们用 url.resolve 来自动推断出完整 url,变成 // https://cnodejs.org/topic/542acd7d5d28233425538b04 的形式 // 具体请看 http://nodejs.org/api/url.html#url_url_resolve_from_to 的示例 var href = url.resolve(cnodeUrl, $element.attr("href")); topicUrls.push(href);});console.log(topicUrls); });运行 node app.js
OK,这时候我们已经得到所有 url 的地址了,接下来,我们把这些地址都抓取一遍,就完成了,Node.js 就是这么简单。
抓取之前,还是得介绍一下 eventproxy 这个库。
用 js 写过异步的同学应该都知道,如果你要并发异步获取两三个地址的数据,并且要在获取到数据之后,对这些数据一起进行利用的话,常规的写法是自己维护一个计数器。
先定义一个 var count = 0,然后每次抓取成功以后,就 count++。如果你是要抓取三个源的数据,由于你根本不知道这些异步操作到底谁先完成,那么每次当抓取成功的时候,就判断一下 count === 3。当值为真时,使用另一个函数继续完成操作。
而 eventproxy 就起到了这个计数器的作用,它来帮你管理到底这些异步操作是否完成,完成之后,它会自动调用你提供的处理函数,并将抓取到的数据当参数传过来。
假设我们不使用 eventproxy 也不使用计数器时,抓取三个源的写法是这样的:
// 参考 jquery 的 $.get 的方法
$.get("http://data1_source", function (data1) { // something $.get("http://data2_source", function (data2) {// something$.get("http://data3_source", function (data3) { // something var html = fuck(data1, data2, data3); render(html);}); });});上述的代码大家都写过吧。先获取 data1,获取完成之后获取 data2,然后再获取 data3,然后 fuck 它们,进行输出。(function () { var count = 0; var result = {}; $.get("http://data1_source", function (data) {result.data1 = data;count++;handle();}); $.get("http://data2_source", function (data) {result.data2 = data;count++;handle();}); $.get("http://data3_source", function (data) {result.data3 = data;count++;handle();}); function handle() {if (count === 3) { var html = fuck(result.data1, result.data2, result.data3); render(html);} }})();丑的一逼,也不算丑,主要我写代码好看。var ep = new eventproxy();ep.all("data1_event", "data2_event", "data3_event", function (data1, data2, data3) { var html = fuck(data1, data2, data3); render(html);});$.get("http://data1_source", function (data) { ep.emit("data1_event", data); });$.get("http://data2_source", function (data) { ep.emit("data2_event", data); });$.get("http://data3_source", function (data) { ep.emit("data3_event", data); });好看多了是吧,也就是个高等计数器嘛。ep.all("data1_event", "data2_event", "data3_event", function (data1, data2, data3) {});这一句,监听了三个事件,分别是 data1_event, data2_event, data3_event,每次当一个源的数据抓取完成时,就通过 ep.emit() 来告诉 ep 自己,某某事件已经完成了。// 得到 topicUrls 之后// 得到一个 eventproxy 的实例var ep = new eventproxy();// 命令 ep 重复监听 topicUrls.length 次(在这里也就是 40 次) `topic_html` 事件再行动ep.after("topic_html", topicUrls.length, function (topics) { // topics 是个数组,包含了 40 次 ep.emit("topic_html", pair) 中的那 40 个 pair // 开始行动 topics = topics.map(function (topicPair) {// 接下来都是 jquery 的用法了var topicUrl = topicPair[0];var topicHtml = topicPair[1];var $ = cheerio.load(topicHtml);return ({ title: $(".topic_full_title").text().trim(), href: topicUrl, comment1: $(".reply_content").eq(0).text().trim(),}); }); console.log("final:"); console.log(topics);});topicUrls.forEach(function (topicUrl) { superagent.get(topicUrl).end(function (err, res) { console.log("fetch " + topicUrl + " successful"); ep.emit("topic_html", [topicUrl, res.text]);});});输出长这样:
完整的代码请查看 lesson4 目录下的 app.js 文件
总结
今天介绍的 eventproxy 模块是控制并发用的,有时我们需要同时发送 N 个 http 请求,然后利用得到的数据进行后期的处理工作,如何方便地判断数据已经全部并发获取得到,就可以用到该模块了。而模块不仅可以在服务端使用,也可以应用在客户端