美国中文网拥有一批不离不弃的高水平的摄影博主,他们应该算是美国中文网博客区里最大的财富。站方也在多个网页里为这些摄影博文里的精品提供了展示的地盘,特别是在网站首页的右上角(我们称之为头版头条)的滚动图片新闻里为摄影博文保留了一方之地:七幅滚动新闻的最后一幅是一个博文图片。在任何时刻只有一篇博文图片显示在那里,可想而知那应该是当前博客区里的精品中的精品,代表了网站里博文的最高水平。
在这里我们探讨一下如何用计算机程序来观察和统计头版头条的内容。有这样几个技术要点要解决:
1)如何能得到一个网页的内容,并进而选取网页上要观察的部分
2)如何储存观察结果
3)如何提取和统计观察结果
4)如何显示统计结果
5)如何定期观察
每个问题的回答还取决于选择的程序运行环境,这里我们讨论的是一个在用Discuz建造的网站里用插件形式来实现的方案。虽然这个功能有多种途径可以实现。但是Discuz软件提供的多种设施给实现这个功能带来了很大的便利。下面是这个名为mzw_toppicblogs的插件的文件结构:
当我们在Discuz的管理中心里设置了这个插件后,脚本文件blogs.inc.php里的内容就可以通过链接http://www.imyoonasite.com/plugin.php?id=mzw_toppicblogs:blogs来运行,这里和下面为叙述方便起见我们假设这个网站是http://www.imyoonasite.com。
1. 如何得到和选取网页内容
PHP提供了两个相关的类。一个叫DOMDocument可以得到网页内容,另一个叫DOMXPath可以按用HTML/XML文本查询语言XPath得到该网页上指点位置上的内容。为了得知我们要选取的内容在文本上的确切位置,可以Firefox里用Inspect Element来查看:
$doc = new DOMDocument;
$doc->loadHTMLFile('http://www.sinovision.net/');
$xpath = new DOMXPath($doc);
$entries = $xpath->query('//ul[@id="top-slide-list"]/li[position()=7]');
foreach($entries as $entry) {
$as = $entry->getElementsByTagName('a');
foreach ($as as $a) {
if ($a->nodeValue) $blogTitle = substr($a->nodeValue, 27);
$blogUrl = $a->getAttribute('href');
$pics = $a->getElementsByTagName('img');
foreach ($pics as $pic) $blogPicUrl = $pic->getAttribute('src');
}
}
$doc->loadHTMLFile($blogUrl);
$xpath = new DOMXPath($doc);
$entries = $xpath->query('//div[@id="pcd"]/div/div/h2/a');
foreach($entries as $entry) $author = $entry->nodeValue;
上面是PHP脚本文件blogs.inc.php里的一段代码。这里我们不仅得到了首页上的头版头条里的博文标题,博文链接以及图片链接,为方便起见,还去该博文页得到了作者名。
2. 如何储存观察结果
当我们得到了所要的信息后,需要有地方保存这样的信息因为这个头版头条的内容在不断变化。既然Discuz软件用了数据库来存储网站内容,我们不妨在其中加一个数表存储这个信息。我们叫这个数表ext_mzw_toppicblog:
CREATE TABLE `ext_mzw_toppicblog` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`dateline` int(10) unsigned NOT NULL DEFAULT '0',
`author` varchar(255) NOT NULL DEFAULT '',
`url` varchar(255) NOT NULL DEFAULT '',
`title` varchar(255) NOT NULL DEFAULT '',
`picurl` varchar(255) NOT NULL DEFAULT '',
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
我们再按Discuz的规矩在文件table_ext_mzw_toppicblog.php里来建造接触该数表的类。注意这里我们没有定义储存数据的方法,因为Discuz提供的基类已经有了相应的方法。我们只添加了一个提取数据的方法,在下一步里要用到。
_table = 'ext_mzw_toppicblog';
$this->_pk = 'id';
parent::__construct();
}
public function fetch_blogs() {
return DB::fetch_all(@'select author, title, url, min(dateline) firstrecordtime, max(dateline) lastrecordtime
from ext_mzw_toppicblog group by author, title, url order by max(dateline)');
}
}
?>
这样我们就能在脚本文件blogs.inc.php里将在前面得到的信息储存在数据库里了:
$blogdata = array(
'dateline' => TIMESTAMP,
'author' => $author,
'url' => $blogUrl,
'title' => $blogTitle,
'picurl' => $blogPicUrl
);
C::t('#mzw_toppicblogs#ext_mzw_toppicblog')->insert($blogdata);
3. 如何提取和统计观察结果
上面数表类的定义里已经给出了从数据库里提取相关数据的方法。下面是脚本文件里利用那个方法的代码。为简便起见我们将这些代码放在前面提到的文件blogs.inc.php里,也就是说运行这个脚本文件同时起了两个作用:一是观察和储存当前观察的结果,二是显示以往观察的统计结果。
$data_blog = C::t('#mzw_toppicblogs#ext_mzw_toppicblog')->fetch_blogs();
foreach($data_blog as $blog) {
$firstrecordtime = date("Y-m-d H:00",$blog['firstrecordtime']);
$lastrecordtime = date("Y-m-d H:00",$blog['lastrecordtime']);
$blog['firstrecordtime'] = $firstrecordtime;
$blog['hours'] = (strtotime($lastrecordtime)-strtotime($firstrecordtime))/3600+1;
$blogs[] = $blog;
$key = $blog['author'];
if (!isset($hourscount[$key])) $hourscount[$key] = 0;
$hourscount[$key] += $blog['hours'];
}
arsort($hourscount);
include template('mzw_toppicblogs:blog_list');
上面的代码的最后引用了模板文件blog_list.htm来显示统计结果,这是下一步要讨论的内容。
4. 如何显示统计结果
我们可以将观察到的头版头条内容简单的按先后次序列表:
刊登时间 |
刊登小时数 |
作者 |
标题 |
$blog[firstrecordtime] |
$blog[hours] |
$blog[author] |
< a href="$blog[url]">$blog[title] |
也可以用其它方式来显示统计结果。下面我们用Google Charts(
链接)来按作者所有在头版头条被推荐的时间总数来做个图示:
5. 如何定期观察
前述的脚本文件每被运行一次,就观察和记录一次头版头条的内容。那么我们怎么才能通过定期运行这个脚本文件来定期收集内容呢?这就要依靠Linux机器上的定时任务服务(Cron Service)或Windows上的Task Scheduler了:
wget http://www.imyoonasite.com/plugin.php?id=mzw_toppicblogs:blogs > /dev/null 2>&1
比如我们可以在那里设定每小时在整点时运行那个脚本文件一次。
注:本文中的代码里的<符号如果后面的字符是a的话,在它们中间加了一个不应该有的空格,以避免Discuz在保存日志时自动改变日志内容。