获取BBS十大热门话题方法探讨

最近有兴趣对饮水思源BBS的源代码进行了分析,尝试用Node.js抓取了其十大热门话题的标题和链接。

获取整个网页

先尝试获取整个网页内容,用https模块的get()方法:

var https = require('https');
var indexUrl = "https://bbs.sjtu.edu.cn/frame2.html";
https.get(indexUrl, function(res) {
var source = "";
res.on('data', function(data) {
source += data;
});
res.on('end', function() {
console.log(source);
});
});

结果如下:

<html>
<meta HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=gb2312" />
<link rel="shortcut icon" type="image/x-icon" href="favicon.ico" />
<script src="/js/jquery.js"></script>
<script src="/js/jquery_cookie.js"></script>
<script type="text/javascript" src="frame2.js"></script>
<head>
<title>��ˮ˼Դ</title>
</head>
<frameset name="topframe" border="0" frameborder="0" framespacing="2" framemargin="0" cols="150,*">
<frame name="f2" framespcing="2" src="bbsleftnew">
<frameset onload="rightframe_onload()" id="rframe" name="rightframe" rows="0, *, 20" >
<frame scrolling="no" marginwidth="4" marginheight="0" framespacing="0" name="fmsg" src='getmsg.html'>
<frame framespacing="2" name="f3" >
<frame scrolling="no" marginwidth="4" marginheight="1" framespacing="1" name="f4" src='foot.html'>
</frameset>
</frameset>
</html>

中文的乱码暂时先不管,重点在于框架的内容,并没有成功获取到。十大热门话题所在的name="f3"frame,还有一个onload事件,更增加了难度。

我又想到了superagent,官方对其有这样的介绍:

SuperAgent is a small progressive client-side HTTP request library, and Node.js module with the same API, sporting many high-level HTTP client features.

猜测可能还不行,但还是验证一下吧。代码改写如下:

var superagent = require("superagent");
var indexUrl = "https://bbs.sjtu.edu.cn/frame2.html";
superagent.get(indexUrl).end(function(err,data) {
console.log(data.text);
});

结果果然和https一模一样,还是不行。

直接获取frame内容

查阅了一些资料,我把目光转向了PhantomJS,官方介绍如下:

PhantomJS is a headless WebKit scriptable with a JavaScript API. It has fast and native support for various web standards: DOM handling, CSS selector, JSON, Canvas, and SVG.

代码如下,直接尝试获取name="f3"frame

// test.js
var page = require('webpage').create();
page.open('https://bbs.sjtu.edu.cn/frame2.html', function(status) {
console.log("Status: " + status);
if (status === "success") {
window.setTimeout(function() {
page.switchToFrame('f3');
console.log(page.frameContent);
phantom.exit();
}, 500);
}
});

运行结果很理想,这里面setTimeout()很关键,保证了页面加载完成后才获取frame内容。

但现在还和Node.js没什么关系,因为上述代码是用phantomjs test.js执行的,但发现有phantomjs-node这个东西,很给力,官方这样描述道:

Fast NodeJS API for PhantomJS

于是,代码改写成了下面的:

// test2.js
var phantom = require('phantom');
var indexUrl = "https://bbs.sjtu.edu.cn/frame2.html";

phantom.create().then(function(ph) {
ph.createPage().then(function(page) {
page.open('https://bbs.sjtu.edu.cn/frame2.html').then(function(status) {
// console.log("Status: " + status);
if(status === "success") {
setTimeout(function () {
page.switchToFrame('f3');
page.evaluate(function() {
return document.body.innerHTML;
}).then(function(html) {
console.log(html);
});
ph.exit();
}, 500);
}
});
});
});

node test2.js执行上述代码,结果和上面用phantomjs test.js执行一样,可以进行下一步了。

获取十大热门话题标题和链接

源代码关键部分如下:

<tbody>
<tr>
<td width="110">[
<a href="/bbsdoc?board=RealEstate" target="_self">RealEstate</a> ]
</td>
<td>
<a href="/bbstcon?board=RealEstate&amp;reid=1467765268" target="_self">田朴珺的一整层楼全部抛售,房价大跌已成共识</a>
</td>
<td width="110">floraxi</td>
</tr>
<tr>
<td width="110">[
<a href="/bbsdoc?board=LoveBridge" target="_self">LoveBridge</a> ]
</td>
<td>
<a href="/bbstcon?board=LoveBridge&amp;reid=1467756629" target="_self">[代挂]氧气美女诚征男友</a>
</td>
<td width="110">aqiao</td>
</tr>
...
</tbody>

这个引入cheerio,轻松搞定,最终代码如下:

var phantom = require('phantom');
var cheerio = require('cheerio');
var publicUrl = "https://bbs.sjtu.edu.cn";
var indexUrl = "https://bbs.sjtu.edu.cn/frame2.html";
var topicTitles = [];
var topicUrls = [];

phantom.create().then(function(ph) {
ph.createPage().then(function(page) {
page.open('https://bbs.sjtu.edu.cn/frame2.html').then(function(status) {
// console.log("Status: " + status);
if(status === "success") {
setTimeout(function () {
page.switchToFrame('f3');
page.evaluate(function() {
return document.body.innerHTML;
}).then(function(html){
// console.log(html);
var $ = cheerio.load(html);
var $topicBody = $("img[src='iconT.gif']").parent().parent().parent().parent().parent().parent().parent().next().children().children().children();
$topicBody.children('tr').each(function() {
var $topicA = $(this).children().eq(1).children();
var topicTitle = $topicA.text();
var topicTitleStop = topicTitle.length-3;
topicTitle = topicTitle.substring(0, topicTitleStop);
var topicUrl = publicUrl + $topicA.attr("href");
topicTitles.push(topicTitle);
topicUrls.push(topicUrl);
});
console.log(topicTitles);
console.log(topicUrls);
});
ph.exit();
}, 1000);
}
});
});
});

最后,声明一下,仅供学习交流哦。