[Node.js] 練習題:將部落格文章依人氣數排序

[Node.js] 練習題:將部落格文章依人氣數排序

最近 PT 問我能不能將網誌文章,依人氣數排序列出來,

這樣如果有破圖的話,可以優先對高人氣的文章處理~

正好最近 Node.js 學到 使用 http 模組取得網頁內容,就順便拿來練習吧~

 

要取得某個痞客邦網誌的所有文章,

可以從 http://<name>.pixnet.net/blog/listall/<page> 得到~

這邊的 <page> 是從 1 開始的數字,可是要到多少呢?

觀察一下第一頁,它會有個指向下一頁的 next 連結,

而最後一頁就沒有 next 了,因此可以用這個來做為要不要取得下一頁的依據~

 

因此,我們可以先寫一個簡單的 get_page() 函式,用來取得某一頁的 HTML 資料~

這邊我們會先在 response 物件上註冊 “data” callback,收集伺服器回傳的片段資料,

而另外註冊的 “end” callback 則可以讓我們在收集到這個頁面完整的 HTML 後,

開始作處理,這邊的處理是呼叫另一個函式 handle_response():

// Load Node.js modules
var fs = require("fs");
var http = require("http");
// Global variables
var g_blog_name = "ephrain";
var g_base_url = "http://" + g_blog_name + ".pixnet.net/blog/listall/";
var g_all_articles = [];
function get_page(page)
{
// Fetch URL
var target_url = g_base_url + page;
console.log("Fetching " + target_url + " ...");
http.get(target_url, function(response) {
var full_response = "";
// Collect partial response
response.on("data", function(data) {
full_response += data.toString();
});
// All response from this URL is collected.
// Start analysis.
response.on("end", function() {
handle_response(page, full_response);
});
}).on("error", function(error) {
console.log("Error fetching " + target_url + ": " + error);
});
}

 

handle_response() 函式裡,

首先是呼叫 parse_articles() 來分析 HTML 中的文章,這個待會再提~

接下來就是檢查是不是有下一頁 next 的連結,有的話就繼續拜訪下一頁,

沒有的話,就可以排序所有的文章,顯示結果了:

function handle_response(page, full_response)
{
// Parse articles from the HTML response
parse_articles(full_response);
// Check if there is next page
if (full_response.indexOf('class="next"') >= 0)
{
// Visit next page
get_page(page+1);
}
else
{
// This is the final page.
// Sort all articles
sort_articles();
// Generate sorted html
generate_html_output();
}
}

 

parse_articles() 這個函式裡,主要就是要取出 HTML 裡面 <table> 到 </table> 內的資料,

而表格裡的每一個 <tr>…</tr> 都代表了一篇文章,

因此用 match() 加上 regular expression 來比對比較方便~

 這邊的 regular expression 在使用時學到了幾個新的用法:

 

  – 要 match 任意字元跨越多行的狀況時,可以用 [\s\S]*。

     \s 代表空白類的字元,\S 是非空白類的字元,合起來就是所有的字元了。

     這邊不能用 “.” 代表任意字元,因為 “.” 是不包含換行字元的~

     (參考資料:stackoverflow: How to use JavaScript regex over multiple lines?)

 

  – 要 match 特殊字元時,用 \xnn 的語法。

     這邊的 nn 代表那個字元的十六進位碼,像我想找 / 這個字元的話,

     跟 regular expression 的 / 衝到了,因此得用 \x2F 來代表 /~

     (參考資料:w3schools: JavaScript RegExp Reference)

 

  – 要做 non-greedy 的 match 時,加上 ? 字元。

    預設狀態下,JavaScript 會做 greedy match,因此 <tr>[\s\S]+\x2Ftr> 這個寫法,

    可能會把第一個 <tr> 和最後一個 </tr> 中間的東西,都算在一次 match 上,

     因此要用 non-greedy match,變成 <tr>[\s\S]+?\x2Ftr>,

     讓它找到最近的一個 </tr> 就停止~

 

當成功抓出文章的資訊時,就把它加到 g_all_articles 這個 global array 去:

function parse_articles(full_response)
{
var i = 0;
var matched_trs, matched_tokens;
// Get articles inside the table
matched_trs = full_response.match(/<table>[\s\S]*<\x2Ftable/);
if (matched_trs)
{
matched_trs = matched_trs[0].match(/<tr>[\s\S]+?\x2Ftr>/g);
if (matched_trs)
{
for (i = 1; i < matched_trs.length; i++)
{
matched_tokens = matched_trs[i].match(/<tr>[\s\S]+?href="(.+?)">(.+?)<\x2Fa>[\s\S]+?article_hit[^>]+?>(\d+)[\s\S]+?<\x2Ftr>/);
if (matched_tokens)
{
g_all_articles.push({
url: matched_tokens[1],
title: matched_tokens[2],
visit_count: parseInt(matched_tokens[3])
});
}
}
}
}
}

 

當所有的頁面都處理完畢後,在 g_all_articles 裡面就會有所有的文章,

下一步的 sort_articles() 就是依照人氣數來排序,

這動作很簡單,呼叫 sort() 並給它一個比較用的函式就可以了~

因為我們想要高人氣的文章排在前面,因此比較的時候,是用 b-a 而不是 a-b 的方式:

(參考資料:w3schools: JavaScript Array sort() Method)

function sort_articles()
{
// Sort articles by visit count
g_all_articles = g_all_articles.sort(function(a, b)
{
return (b.visit_count-a.visit_count);
});
}

 

最後,就是要呼叫 generate_html_output() 將排序後的結果輸出到檔案~ 

這邊因為我們需要寫出多篇文章的資料,因此不適合呼叫 fs.write(),

因為如果多次呼叫 fs.write() 的話,

並沒有辦法保證比較早呼叫的 fs.write() 一定會比較早執行,

這樣資料就會亂掉了…

根據 Node.js: fs.write() 這邊的建議,是用 fs.createWriteStream() 來作:

function generate_html_output()
{
var ws = fs.createWriteStream("result.html");
var encoding = "utf-8";
ws.write('<!DOCTYPE html>\n' +
'<meta charset="utf-8">\n' +
'<html>\n' +
'<body>\n' +
'<table>\n' +
'<tr><td>Count</td><td>Title</td></tr>\n', encoding);
for (var i = 0; i < g_all_articles.length; i++)
{
var splited = g_all_articles[i].url.split("/");
ws.write('<tr>' +
'<td>' + g_all_articles[i].visit_count + '</td>' +
'<td><a href="' + g_all_articles[i].url + '">' + g_all_articles[i].title + "</a></td>" + '</tr>\n', encoding); } ws.write('</body>\n' + '</html>\n', encoding); 
    }
}

 

最後只要記得呼叫 get_page(1) 開始從第一頁讀取,

程式就會把所有的文章都讀取並排序囉~

完成後把 result.html 用瀏覽器打開,就能看到成果啦:

Screen Shot 2015-04-28 at 上午12.09.48  

 

初學 Node.js 的應用,算是成功囉~^^/

 

 

 

(本頁面已被瀏覽過 286 次)

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

這個網站採用 Akismet 服務減少垃圾留言。進一步了解 Akismet 如何處理網站訪客的留言資料