《搜索引擎零距离》第二章 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. ------------------------------------------------------------------- |