《搜索引擎零距离》第二章 IRS(7) 实例解析

roki 2009-06-19
实例解析
本节将用几个完整的IRS脚本编程语言实例来进一步呈现Web数据挖掘系统的功能特性。本节共3个实例,分别是“新浪读书频道”书籍信息挖掘,“灯火书城”书籍信息挖掘,“时光网”电影信息挖掘。其中,“新浪读书频道”这个实例中主要展示了“地址”扩展的特性,“灯火书城”实例主要展示${parentPage}以及“继承表”相关特性,“时光网”实例主要展现“独立表”相关特性。
1.新浪读书频道实例
-------------------------------------------------------------------
页面配置
	页面名 '新浪读书首页'
	爬虫配置名 'conf1'
	入口                                                                                         
	'http://3g.sina.com.cn/3g/book/index.php?tid=162&did=90&vid=96&cid=0&sid=42969&pos=15'
	配置:编码=GBK,JS=否,重连次数=5,重连时间=1000.
	
	匹配:'category' 
'<a href="http://3g.sina.com.cn/prog/wapsite/books/booktype.php[$catetype]">[$cateName]</a>'  
	'%cateNameEntrance,#post->http://3g.sina.com.cn/prog/wapsite/books/booktype.php$catetype'
 	
	次级 页内  '%cateNameEntrance'  '详细小说页Check' 
;

页面配置
	页面名 '详细小说页Check'

	匹配:'pageNum'  '</a> /[$pageNum]页[$]跳到'
'%novel,#extend->http://3g.sina.com.cn/prog/wapsite/books/booktype.php{
$新浪读书首页->catetype}&pn=[1..$pageNum];'

次级 页内 '%novel' '小说列表页'
;

页面配置
	页面名 '小说列表页'
	匹配:'bookname' '<a href="chlist.php[$bookenter]">[$bookName]</a>([$author])<br/>'  
	'%bookNameEntrance;#post->http://3g.sina.com.cn/prog/wapsite/books/chlist.php$bookenter'
	次级 页内  '%bookNameEntrance,#重连{5 1000};'  '详细小说页' 
;


页面配置
	页面名 '详细小说页'

	匹配:'book' '<card title="[$]-[$name]">
<p align="[$]">[$]简介:[$describe]<a href="[$readurl]">阅读该书</a><br/>[$]
<a href=[$]作者:[$author]
<br/>' 'NULL'

 	保存:'save'
	'NULL;
	小说列表页:P1,
	详细小说页->book:P2;
	select sprintf("http://3g.sina.com.cn/prog/wapsite/books/chlist.php%s",P1->bookenter) url,
	P2.name,P2.author,maxlength(P2.describe,"2000") describe,
	sprintf("http://3g.sina.com.cn/prog/wapsite/books/%s",P2.readurl) readUrl;
	novelDao->insert("publish"=>"N",
"siteName"=>"新浪读书", "siteUrl"=>"http://3g.sina.com.cn")' 
	
	 Ruby:'
		if($vm.match("book"))
		    $vm.save("save");
		end
		'
;
//////////////////////////////////////////////////
执行
	抓取 页面名 '新浪读书首页'
;
/////////////////////////////////////////////////
爬虫配置: 名称=conf1,编码=UTF-8,最大线程数=1,抓取延迟=20,输出=控制台.

系统配置 '代理服务器=221.130.176.xxx:80'
-------------------------------------------------------------------


功能解释:这个IRS程序所定义的任务的入口是http://3g.sina.com.cn/3g/book/ index.php?tid=162&did=90&vid=96&cid=0&sid=42969&pos=15,打开这个页面后,这个页面上的主要内容是以下一组的文字链接:

都市小说.言情小说
青春校园.两性激情
历史小说.名家经典
官场小说.人文历史
武侠天下.玄幻修真
恐怖惊悚.影视原著
人物传记.明星娱乐
生活时尚.小说其他
财经管理.时政商战
励志校园.教育科技

其中每个文字链接对应的都是一个类似如下内容小说列表页:

1.爱,在你转身时盛开(千寻千寻)
2.歌尽桃花(靡宝)
3.地水鸾宫明月姬(桂圆八宝)
4.大象的眼泪([美]莎拉•格鲁恩)
5.十年时间,我还是超不过她,我恨她(阡陌风不定)
6.绾青丝(绝胜篇)1(波波)
7.当时明月在(匪我思存)
8.清风吹散往事如烟灭2之子于归(楚湘云)
9.清风吹散往事如烟灭1桃李不言(楚湘云)
10.寻樱丝(淡樱)
[1] 2 ... 88 /88页 跳到[ ]页 Go!

而上述内容的每一小说条目都指向一个详细介绍信息页,如:

爱,在你转身时盛开(78评)(连载中)

简介:她本不相信人生的奇遇,却被召去意大利继承姐姐碧昂的遗产,威尼斯的叹息桥上,和姐姐有过十年之约的JAN,竟然就是她的新上司。JAN要她以碧昂的名义爱他,她没有当真,...阅读该书
最后更新:08年01月09日
(未完待续,后面将有更精彩的内容!温馨提示:本书每日更新5页,01月23日全书连载完!)

    以上网站页面结构就是一个Web数据挖掘任务的目标,而IRS脚本就是爬虫执行这个任务的行动指南。按照IRS脚本的指示执行任务,就能够得到新浪读书频道里所有书籍的完整结构化信息。

这个Web数据挖掘任务的执行逻辑是:
(1) 执行第63~65行“执行块”中的“抓取页面”语句,从第3~27行名为“新浪读书首页”的“页面配置”中获得入口地址,并获得第一个页面“新浪读书首页”的内容。
(2) 执行第11~14行的“模式匹配语句”,获得一组 $catetype的值,然后利用第13行上的#post->http://3g.sina.com.cn/prog/wapsite/books/booktype.php$catetype拼接成正确的次级地址,把这组地址存入变量 %cateNameEntrance。
(3) 执行第16行的“次级页内”语句,用 %cateNameEntrance中的地址,使用“详细小说页Check”的页面配置来处理,目的是从小说列表页的内容中获取这个分类的小说共有多少页,以便使用extend语句来生成所有这些小说类表页的地址。
(4) 使用%cateNameEntrance变量中的URL地址,打开小说列表页小的页面,根据第19~27行的“分类列表页“页面配置,执行第22行的模式匹配语句,获得这个分类下总共有多少个列表页(这个例子中第一个分类页是88页)。
(5) 执行第23行的“#extend->http://3g.sina.com.cn/prog/wapsite/books/ booktype.php{$新浪读书首页->catetype}&pn=[1..$pageNum];”语句, 根据“$新浪读书->catetype”和“$pageNum”这两个变量的内容,扩展成N条入口地址,存入变量%novel。
假设
{$新浪读书首页->catetype}="?cateid=1", $pageNum="88",

则扩展后的地址为:

http://3g.sina.com.cn/prog/wapsite/books/booktype.php?cateid=1&pn=1
http://3g.sina.com.cn/prog/wapsite/books/booktype.php?cateid=1&pn=2

http://3g.sina.com.cn/prog/wapsite/books/booktype.php?cateid=1&pn=88

(6) 执行第26行的“次级 页内 '%novel' '小说列表页'”语句,使用第29~35行的页面配置,打开变量%novel中存储的N条URL地址,并使用第31行上的模式匹配语句来获得小说详细信息页的URL地址列表,存储进 %bookNameEntrance。
(7) 执行第34行“次级 页内  '%bookNameEntrance,#重连{5 1000};’ 详细小说页 ”这条语句,用详细小说页的配置来处理%bookNameEntrance里的地址列表。
(8) 从%bookNameEntrance里的URL地址进入详细小说页,执行第56~60行的Ruby语句,先使用第41~44行上的模式匹配语句,处理小说列表页中的内容,获得小说的详细信息,在执行第46~54行上的保存语句,把最终的数据结果集保存进数据库。
2.灯火书城实例
本例中的说明性语句将直接写在IRS代码中,作为注释出现,IRS里的注释和Java的注释格式相同,用//和/**/标识。
图3.4是以表格形式表现的页面内容模板(模式匹配的主要目的就是匹配页面模板)。


图3.4  页面内容模板
图3.4就是这个站点树状的页面结构,从“分类列表页”,到“小说列表页”,再到“详细信息页”,成树状扩散。在“小说列表页”上还能用“下页”这个链接取到翻页后的第二个“小说列表页”,然后可以继续扩散,对于这个站点的Web数据挖掘任务,需要从根页面顺着每条链接,走到每个叶子页面,代码如下所示:
-------------------------------------------------------------------
//灯火分类列表页->小说列表页->小说详细页
页面配置
	页面名 '灯火分类列表页'
	爬虫配置名 'conf1'
	入口 'http://bookwap.net/showbookclass.asp' 配置:编码=UTF-8.

	匹配:'cate' '    <a href="showbooklist.asp[$cateUrl]">-[$category]</a><br/>'  
	'%cateNameEntrance,#post->http://bookwap.net/showbooklist.asp$cateUrl;'
	次级 页内  '%cateNameEntrance'  '小说列表页' 
	/*在图3.4格式的页面上获得一组$cateUrl变量,并用其来拼接成:
	http://bookwap.net/showbooklist.asp?shouji=&pwd=&classid=8
	http://bookwap.net/showbooklist.asp?shouji=&pwd=&classid=10
	http://bookwap.net/showbooklist.asp?shouji=&pwd=&classid=12
	…
这组URL地址,然后进入图3.4格式的“分类页” */ 

这时,独立表“UniTable: 灯火分类列表页-> cate”里的数据如表3.18所示。
表3.18  独立表CATE里的数据
	cateUrl	category
?shouji=&pwd=&classid=8	玄幻奇幻
?shouji=&pwd=&classid=10	武侠修真
?shouji=&pwd=&classid=12	都市言情

共享表“ShareTable: 灯火分类列表页”里的数据和独立表“UniTable: 灯火分类列表页-> cate”里相同。继承表“HierTable”为空。

...*/
页面配置:
页面名 '小说列表页'
匹配:'book' '<a href="showarticlelist.asp[$bookUrl]">[$bookName]([$]) </a><br/>'  
'%BookNameEntrance,#post->http://bookwap.net/showarticlelist.asp$bookUrl;'
/*
匹配出bookName和bookUrl,并拼接出“小说详细页”将要处理的入口URL
http://bookwap.net/showarticlelist.asp?shouji=&pwd=&bookid=5
http://bookwap.net/showarticlelist.asp?shouji=&pwd=&bookid=6
http://bookwap.net/showarticlelist.asp?shouji=&pwd=&bookid=7
...

这时,独立表“UniTable: 小说列表页-> book”里的数据如表3.19所示。
表3.19  独立表里book的数据
bookUrl	bookName
?shouji=&pwd=&bookid=5	紫血游侠
?shouji=&pwd=&bookid=6	异界之春光乍泄
?shouji=&pwd=&bookid=7	退魔英雄传

共享表“ShareTable: 小说列表页”里的数据同独立表“UniTable: 小说列表页-> book”里的数据相同。继承表里这个时候有数据,由于爬虫分别从灯火分类列表页上的%cateNameEntrance变量里的N个地址进入当前的“小说列表页”,因此从每个入口进入当前页面时,当前页面的继承表里就会加上入口URL上的附带的字段值,假设爬虫是从<a href=“http://bookwap.net/showbooklist.asp?shouji=&pwd=&classid=8” > 奇幻玄幻</a>进入当前页面的,那么继承表里的信息是:

HierTable:{小说列表页=>{cateUrl =>' ?shouji=&pwd=&classid=8',cateName=>'奇幻玄幻'}}

匹配页面地址及页面配置:

*/
	
	匹配:'next' '上页</a><a href="/showbooklist.asp[$nextUrl]">下页</a>'
	'%nextTo,#post->http://bookwap.net/showbooklist.asp$nextUrl;'
/*
匹配出下页的地址,以便爬虫可以自动翻页:
*/
	
	次级 页内  '%BookNameEntrance'  '详细小说页'   //进入详细信息页
	次级 页内  '%nextTo'  '小说列表页' //自动翻页,处理下一个"小说列表页"
;
页面配置:
页面名 '小说详细页'
		匹配:'info' '书名:[$bookName][Bid:[$]<br/>[$]作者: [$author]<br/>[$]状态:[$state]<br/>[$]<br/>[$]<anchor>推荐送花[$]</go></anchor>]<br/>
[$describe]href="[$readUrl]"[$]>点击阅读' 'NULL'

/*
	匹配到如下结果数据集:
	UniTable: 小说详细页-> info

bookName	author	state	describe	readUrl
紫血游侠	苍月孤魂	完全	3025年人类早已统一,成立联邦政府.人口数量接近千亿	showarticlelist.asp?
shouji=&pwd=&bookid=5


ShareTable: 小说详细页
数据同"UniTable: 小说详细页-> info"的数据相同
HierTable:{
		分类列表页=>{
			cateUrl =>' ?shouji=&pwd=&classid=8',cateName=>'奇幻玄幻',
			cateName=>' 玄幻奇幻'
					},
		小说列表页=>{
					bookUrl=>" showarticlelist.asp?shouji=&pwd=&bookid=5",
					bookName=' 紫血游侠'
					}
			}

*/

	保存:'save'
    'NULL;
    灯火分类列表页:P0,
    ${parentPage}:P2,
	小说详细页:P1;

	select P0->cateName, sprintf("http://bookwap.net/showarticlelist.asp%s",P2->bookUrl) url,P1.bookName name,P1.state,P1.author,P1.describe,fullurl(P1.readUrl) readUrl;

	novelDao->insert("siteName"=>"灯火书城","siteUrl"=>"http://bookwap.net/")' 
	
/*
	
保存语句将从各个页面的所有UniTable和ShareTable,以及本页面的HierTable里的所有数据中,整理出完整的结构化信息。首先,别名语句中定义了P0,P1,P2这些页面别名所代表的页面,其中比较特别的是 ${parentPage},代表本页面的父页面,比如本页面是从“小说列表页”进入的,那么本页面的 ${parentPage}就是“小说列表页”.
上述的IRQL语句是以“爬虫从第一个分类,进入第一部书籍,然后进入它的详细信息页”这样一个场景为例。
首先,“ P0-> cateName”:从“HierTable->灯火分类列表页”上取cateName字段,也就是如图3.5所示。

cateName
玄幻奇幻
图3.5  cateName字段
其次,“sprintf("http://bookwap.net/showarticlelist.asp%s",P2->bookUrl) url”:从“HierTable->小说列表页”上取出bookUrl字段的值,并调用Sprintf函数把值代入http://bookwap.net/showarticlelist.asp%s,最终得到如下所示的结果:

url
http://bookwap.net/showarticlelist.asp? showarticlelist.asp?shouji=&pwd=&bookid=5

然后,“P1.bookName name,P1.state,P1.author,P1.describe,fullurl(P1.readUrl) readUrl”:从ShareTable:详细信息页上取出bookName,state,author,describe字段,并补齐readUrl字段,最终获得如表3.20所示的结果。
表3.20  爬虫取得的各个字段的值
bookName	author	state	describe	readUrl
紫血游侠	苍月孤魂	完全	3025年人类早已统一,成立联邦政府.人口数量接近千亿	http://bookwap.net/showarticlelist.asp?
shouji=&pwd=&bookid=5

最后,把上述一系列从各个页面取到的各个临时数据表作表连接操作,得到最终完整的一条小说信息数据,如表3.21所示。
表3.21  最终小说信息数据
cateName	url	bookName	author	state	describe	readUrl
玄幻奇幻	http://bookwap.net/showarticlelist.asp? showarticlelist.asp?shouji=&pwd=&bookid=5	紫血
游侠	苍月
孤魂	完全	3025年人类早已统一,成立联邦政府.人口数量接近千亿	http://bookwap.net /showarticlelist.asp?shouji=&pwd=&bookid=5

另外,由于 insert("siteName"=>"灯火书城","siteUrl"=>"http://bookwap.net/")语句中加入了两个常量字段,因此,上述数据在保存进数据库的时候,还会多出siteName和siteUrl这两个字段,总共是9个字段的结构化数据。

...
*/

	Ruby:	'
  		if($vm.match("info"))
			$vm.save("save");
		else
			$requestMatch()
		end
	     '
	
;
/*
	
使用Ruby语句精确控制爬虫在“详细信息页”上的行为:如果执行名为info的模式匹配语句成功(匹配到正确的模板,得到正确的数据),那么就调用名为save的保存语句,否则,跳出调试界面,以供观察为何没有匹配成功。

*/
	
//////////////////////////////////////////////////
执行

	抓取 页面名 '灯火分类列表页'
;

爬虫配置: 名称=conf1,编码=UTF-8,最大线程数=5,抓取延迟=10.

-------------------------------------------------------------------


以上就是一个带有注释与解释的IRS语言实例。下面把简化后的不带注释的原始IRS代码列在下面,便于阅读:
-------------------------------------------------------------------
页面配置
	页面名 '灯火分类列表页'
	爬虫配置名 'conf1'
	入口 'http://bookwap.net/showbookclass.asp' 配置:编码=UTF-8.
	匹配:'cate' '    <a href="showbooklist.asp[$cateUrl]">-[$category]</a><br/>'  
	'%cateNameEntrance,#post->http://bookwap.net/showbooklist.asp$cateUrl;'
	次级 页内  '%cateNameEntrance'  '小说列表页' 
;

页面配置
	页面名 '小说列表页'
     匹配:'book' '<a href="showarticlelist.asp[$bookUrl]">[$bookName]([$])</a><br/>'  
	'%BookNameEntrance,#post->http://bookwap.net/showarticlelist.asp$bookUrl;'
	
	匹配:'next' '上页</a><a href="/showbooklist.asp[$nextUrl]">下页</a>'
	'%nextTo,#post->http://bookwap.net/showbooklist.asp$nextUrl;'
	
	次级 页内  '%BookNameEntrance'  '详细小说页'   //进入详细信息页
	次级 页内  '%nextTo'  '小说列表页' //自动翻页,处理下一个"小说列表页"
;
页面配置
	页面名 '小说详细页'
	
	匹配:'info' '书名:[$bookName][Bid:[$]<br/>[$]作者:[$author]<br/>[$]状态:[$state]<br/>[$]<br/>[$]<anchor>推荐送花[$]</go></anchor>]<br/>
[$describe]href="[$readUrl]"[$]>点击阅读' 'NULL'

	保存:'save'
    'NULL;
    灯火分类列表页:P0,
    ${parentPage}:P2,
	小说详细页:P1;
	select P0->cateName,sprintf("http://bookwap.net/showarticlelist.asp%s",P2->bookUrl) 	url,P1.bookName name,P1.state,P1.author,P1.describe,fullurl(P1.readUrl) readUrl;
	novelDao->insert("siteName"=>"灯火书城","siteUrl"=>"http://bookwap.net/")' 
	
	Ruby:	'
  		if($vm.match("info"))
			$vm.save("save");
		else
			$requestMatch()
		end
	     '
;
	
//////////////////////////////////////////////////
执行
	抓取 页面名 '灯火分类列表页'
;

爬虫配置: 名称=conf1,编码=UTF-8,最大线程数=5,抓取延迟=10.

-------------------------------------------------------------------

3. 时光网实例
在这个实例中,我们将使用IRS语言来描述时光网上的电影信息的Web数据挖掘。以URL地址为 http://www.mtime.com/movie/10057的页面为例,页面的大致模板格式如下。

导演:
许鞍华 Ann Hui

主演:
杨紫琼 Michelle Yeoh
洪金宝 Sammo Hung
黄家诺 Jimmy Wong Ga Lok ...

国家/地区: 香港

对白语言:粤语

发行公司: 嘉禾娱乐事业有限公司 ...

更多中文片名:
阿金的故事

更多外文片名:
Ah Kam
A Jin de gu shi
Ah Kam: Story of a Stuntwoman
A Jin

类型:动作/剧情

片长:96 min

剧情
  剧情讲述习武出身的大陆姑娘阿金只身来到香港,为了生活,她不得不做了电影替身演员。由于她身手不凡很快就成为武师。武术指导霹雳与儿子亚朗相依为命,阿金与霹雳在工作中配合默契。后来阿金与阿山相爱,从此离...

幕后/花絮
《阿金》在中国内地上映时片名为《阿金的故事》,这是一部关于“电影特技人”的影片。惯于创新的许鞍华把影片分为三段,用了三种不同的表现方式来讲述阿金不同的生活侧面,使本片成为当年香港不可多得的实验电影。同...

这个实例与前面的两个实例的最大差别是:在这目标页面上的各种目标字段的出现与否是不确定的,出现的位置也是不确定的,为了匹配到页面上可能出现的“电影名,导演,主演,国家/地区,上映日期,发行公司,更多中文片名,更多外文片名,官方网站,剧情,幕后/花絮,类型”这些字段,需要把各个字段的匹配模式写在单独的“模式匹配语句”中,然后在IRQL语句中把匹配到的字段串起来,如果某字段未出现,则结果数据中就没有这个字段,不影响数据的保存。
IRS代码:
-----------------------------------------------------------------

页面配置
	页面名 '时光网电影'

	爬虫配置名 'index'

	入口 'http://www.mtime.com/movie/'  [10000..11220]
	配置:编码=UTF-8,JS=否.

	匹配:'title'	'<title>[$title]</title>' 'NULL'

	匹配:'actor' 'h4 class="mt10">主演:</h4>
[%actor=<a href="[$]" title="[$]">[$actor]</a>%]更多'
	'#组模式{= \s };'

	匹配:'director' 'h4>导演:</h4>
<p><a href="[$]" title="[$]">[$director]</a></p>'
	'NULL'

	匹配:'region' '国家/地区:</strong>
<a href="[$]>[$region]</a></p>'
	'NULL'

	匹配:'releasetime' '上映日期:</strong>
[$releasetime]</a>'
	'NULL'

	匹配:'company' '发行公司:</strong>[%company=<a href="[$] title="">[$company]</a>%]更多'
	'#组模式{= \s };'

	匹配:'chinaname' '<h4>更多中文片名:</h4>[%chinaname=<p>[$chinaname]</p>%]<h4'
	'#组模式{= \s};'
	匹配:'forename' '<h4 class="mt10">更多外文片名:</h4>
[%forename=<p>[$forename]</p>%]<p class="mt10">'
	'#组模式{= \s };'

	匹配:'website' '官方网站:</strong>
<a href="[$website]"[$]>[$siteName]</a></p>' 
	'NULL'

	匹配:'plot' '<h3 class="mt10">剧情</h3>
<p class="mt10">[$plot]</p>'
	'NULL'

	匹配:'backstage' '<h3 class="mt10">幕后/花絮</h3>
<p class="mt10">[$backstage]</p>'
	'NULL'

	匹配:'type' '类型:</strong><a href="[$]" class="normal">[$type]</a></p>' 'NULL'
/*
 以上的各个模式匹配语句是用来提取电影的各个字段用的,其中的"组模式"相关
的概念前文已经介绍过了,见组匹配模式。
*/

保存:'save1' 'NULL;
时光网电影->title:title,
时光网电影->director:director,
时光网电影->actor:actor,
时光网电影->region:region,
时光网电影->releasetime:releasetime,
时光网电影->company:company,
时光网电影->chinaname:chinaname,
时光网电影->forename:forename,
时光网电影->plot:plot,
时光网电影->backstage:backstage,
时光网电影->type:type,
时光网电影->website:website;
    select title.title,director.director,actor.actor,region.region,releasetime.releasetime,
	company.company,plot.plot,backstage.backstage,website.website, chinaname.chinaname,
	forename.forename,type.type;
filmDao->insert()'

/*
以上这句IRQL中,每个字段都在一个UniTable中,所有表连接起来,就能得到目标结构化数据。
*/


	Ruby:'
		ary =["actor","forename","title","director","region","releasetime", "company","plot",
		"backstage","website","chinaname","type"];

		i = 0;
		while i < ary.length
  			$vm.match(ary[i])
			i+=1;
		end

		$vm.save("save1");
	     '
;
/*
	以上这段Ruby脚本的功能: 按照匹配名依次执行所有的模式匹配语句, 从页面中提取出所有的所需字段,然后调用保存语句,存储数据。
*/

//////////////////////////////////////////////////
执行
	抓取 页面名 '时光网电影'
;

爬虫配置: 名称=index ,最大抓取线程数=1,抓取延迟=0,编码=UTF-8.
-------------------------------------------------------------------


Global site tag (gtag.js) - Google Analytics