《搜索引擎零距离》第二章 IRS(6)
roki
2009-06-18
3.2.22 IRQL内部函数
在上节中的IRQL中,为了限制字段值的长度,使用了一个名叫maxlength的内部函数。事实上,IRQL语言还有多种其他的内部函数,具体包括add,replace,fullurl,sprintf,cleartag。这种经过IRQL内部函数处理之后的字段,称为“函数型字段”。函数型字段的格式为: functionName(PageAlias.srcFieldName, parameter1,…paremeterN) fieldAlias 也就是函数名(页面别名.原始字段名,参数1, …, 参数N) 字段别名。其中“字段别名”的含义是:该数据存储在关系型数据库中的字段名,“字段别名”在select子句的普通字段中也有效,也就是说,类似“select P1.f f_alias;”的select子句,处理过后,数据f字段的数据最终将存放在数据库的f_alias字段。 下文分别介绍这些函数的作用与用法。 1.Add函数 Add函数的作用是对数据作算术操作。假设页面SamplePage上的一个结果数据集如图3.2所示。
图3.2 结果数据集 如果希望在保存进数据库之前,在以上每个数字上加1或者-1,可以这样编写select子句: samplePage: P; select add(P.count,"1") count; 或者: samplePage: P;select add(P.count,"-1") count; 则上述数据表的内容就如图3.3所示。
(a) 公式1 add(P.count,"1") (b) 公式2 add(P.count,"-1") 图3.3 调用Add函数后的数据表内容 2.Replace函数 Replace函数的作用是字符串替换。假设从testPage页面上获取的数据集如下: brief <font color=“red”>some test brief</font> <font color=red>another test brief</font> 如果希望去掉其中的font标签,则可以编写如下select子句: testPage:P;select replace(P.brief ," <font color=\"red\">|</font>">", ") 上述语句中的第二个参数是一个正则表达式,另外用斜杠把参数中的双引号转义掉了,另一个需要转义的符号是单引号。上述的正则表达式<font color=\“red\”>|</font>可以同时匹配到<font color=“red”>和“</font>”这两段文本,因此这两段文本都会被替换成replace函数的第三个参数——空字符串。于是,经过replace函数处理过的数据集就变成如下形式: brief some test brief another test brief 3.FullUrl函数 FullUrl函数的作用是把相对URL地址补齐为绝对URL地址,假设爬虫在URL地址 http://abc.com/book/list.jsp上获取到了如下内容: <a href= “detail1.jsp”>book1</a> <a href= “detail2.jsp”>book2</a> <a href= “detail3.jsp”>book3</a> 则我们可以用IEE表达式 ------------------------------------ 页面配置 页面名 '列表页' 匹配:'book' '<a href="[$url]">[$book]</a>' 'NULL' ; ------------------------------------- 来获取如表3.15所示的数据集。 表3.15 获取的数据集
上述数据中的url字段是相对地址,如果我们需要转换为绝对地址,就要这样编写select子句: 列表页:P;select fullurl(P.url) url ,P.book; 结果数据集将变成如表3.16所示的形式。 表3.16 结果数据集 url book http://abc.com/book/detail1.jsp book1 http://abc.com/book/detail2.jsp book2 http://abc.com/book/detail3.jsp book3 4. Sprintf函数 IRQL中的Sprintf与Java和C语言的中的Sprintf函数有相同的作用——格式化字符串。假设在testPage上获得了如表3.17所示的数据集。 表3.17 在testPage获得的数据集
我们希望用cateID和bookID这两个字段来构造出一个新的targetURL字段,可以这样写: testPage:P; select sprintf("http://mydomain.com/targeturl.jsp?cateid=%s&bookid=%s", P.cateID,P.bookID) targetURL; sprintf函数将把 P.cateID和 P.bookID这两个字段的值分别代入函数第一个参数的格式字符串,从而得到如下数据集: targetURL http://mydomain.com/targeturl.jsp?cateid=1&bookid=101 http://mydomain.com/targeturl.jsp?cateid=2&bookid=123 http://mydomain.com/targeturl.jsp?cateid=3&bookid=356 因此使用Sprintf函数可以方便地用结果数据集中的数据来构造目标数据。 5.ClearTag函数 如果希望去除结果数据集中的各种HTML和WML标签,则可以使用ClearTag函数。ClearTag函数的功能是把<br>标签换成换行符,并去除所有的HTML和WML标签,使得一个HTML和WML变成一个纯文本文件。其用法如下: select cleartag(P.brief) brief; 6.Recursive函数 很多情况下,会有嵌套调用多个函数的需求,比如调用ClearTag函数之后再调用Sprintf函数,IRQL中提供了这样的支持。其用法如下: recursive("sprintf cleartag", "samplehead %s tail", P1.content) content 假设这里P1.content 的内容是“<html><img…>示例正文<H/><href…> </html>”,那么,经过上述的Recursive函数处理之后,得到的结果是“samplehead 示例正文 tail”,也就是先调用函数ClearTag把HTML标签去掉,再调用Sprintf把samplehead %s tail中的%d替换成ClearTag函数的执行结果。 事实上,Recursive函数的内部实际使用了逆波兰式的思想。逆波兰式也叫后缀表达式(将运算符写在操作数之后)。如:我们平时写a+b,这是中缀表达式,写成后缀表达式就是ab+。 例如,(a+b)*c-(a+b)/e的后缀表达式为: (a+b)*c-(a+b)/e →((a+b)*c)((a+b)/e)- →((a+b)c*)((a+b)e/)- →(ab+c*)(ab+e/)- →ab+c*ab+e/- 来看一下Recursive.java的主要代码: import java.util.ArrayList; import java.util.List; import org.apache.commons.collections.ArrayStack; public class RecursiveFunction implements Function { //保存函数列表,比如"sprintf cleartag" private String functions; //参数栈 ArrayStack paramStack = new ArrayStack(); //操作符栈 ArrayStack operatorStack = new ArrayStack(); public String operator(Object[] args) { String[] ops = functions.split("\\s+"); //把sprintf和cleartag函数压栈 for (String op : ops) { operatorStack.push(op); } //把参数压栈 for (int i = 1; i < args.length; i++) { paramStack.push(args[i]); } while (!operatorStack.empty()) { //总栈顶取出一个操作符 String op = (String) operatorStack.pop(); if (op.equalsIgnoreCase("cleartag")) { //如果是cleartag函数,那么, 取参数栈栈顶的一个参数 ClearTagFunction f = new ClearTagFunction(); String value = (String) paramStack.pop(); //调用cleartag函数,并把结果压入参数栈 String result = f.operator(new String[] { value }); paramStack.push(result); } else if (op.equalsIgnoreCase("sprintf")) { //如果是sprintf函数,那么取出参数栈上所有的参数 SprintfFunction sf = new SprintfFunction(); List<String> ps = new ArrayList(); while (paramStack.size() > 1) { ps.add((String) paramStack.pop()); } String format = (String) paramStack.pop(); String[] fs = ps.toArray(new String[0]); sf.setFormat(format); //调用sprintf函数并把参数压栈 String result = sf.operator(fs); paramStack.push(result); }else if( …){ //处理其他函数 } } //返回参数栈栈顶的元素 return (String) paramStack.pop(); } } 7.Varconcat函数族 这里的Varconcat是Variable Concatenate的缩减,意思是把变量的值串连在一起。Varconcat函数族里有3个函数,分别是varenque, varjoin, varclear,通过使用这3个函数,可以解决这样的一类问题: 假设我们需要对某个新闻站进行抓取,但是新闻内容是有翻页的,并且页数不确定,当页面内容里有“下一页”的标志时,就需要进入下一个页面继续获取这个新闻的内容,当页面内容里没有“下一页”标志时,说明这个新闻内容已经获取完毕,可以保存为一条记录了。下面是一个IRS代码片段,展示了如何使用这3个函数: 页面配置: 页面名 'newsInfoPage' 匹配: 'contentMatch' '<div id="ArticleCnt">[$news_content]</div>' '' 匹配: 'nextPage' ']</a><span class="next"><a href="[$nextURL]"><img alt="浏览下一页' '%nextPageEntrance,#post->http://lady.qq.com$nextURL;' 次级 页内 '%nextPageEntrance' 'newsInfoPage' 保存: 'save' 'NULL; newsInfoPage->contentMatch:P0, newsInfoPage->fullContentMatch:P1; select cleartag(P1.full_news_content) news_content; newsDao->insert()' Ruby:' #先试图匹配"下一页"标志 hasNext = $vm.match("nextPage"); # 试图用名为" contentMatch"的IEE表达式来匹配页面 if ($vm.match("contentMatch ")) #如果匹配到contentMatch的内容的话,把newsInfoPage页面上的 #contentMatch表达式上的news_content字段的值放入一个叫做 #content的数组 $vm.enquevar("content","newsInfoPage->contentMatch.news_content"); #如果有下一页的话,继续处理,也就是把下一页的内容放入content数组#里面去 if (hasNext) $vm.enter("%nextPageEntrance"); else #如果没有"下一页"了,就把数组content的字符串列表拼接成一个字符串, #并把结果值放入 #"newsInfoPage->fullContentMatch.full_news_content"这个位置上去 $vm.joinvar("content","newsInfoPage-> fullContentMatch.full_news_content"); $vm.save("save"); #清空content数组 $vm.clearvar("content"); end end ' ; 通过上面这段IRS,我们就能够让智能爬虫一边翻页,一边把一个新闻的所有内容连接在一起,翻到最后一页之后,把完整的新闻内容保存下来,并且是不包含HTML标签的(在IRQL中调用了ClearTag函数)。 |