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

Count
2
4
7

图3.2  结果数据集
如果希望在保存进数据库之前,在以上每个数字上加1或者-1,可以这样编写select子句:

samplePage: P; select add(P.count,"1")  count; 


或者:

samplePage: P;select add(P.count,"-1") count;


则上述数据表的内容就如图3.3所示。

Count
3
5
8

Count
1
3
6


         (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 book
detail1.jsp book1
detail2.jsp book2
detail3.jsp book3

上述数据中的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
1 101
2 123
3 356


我们希望用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函数)。
Global site tag (gtag.js) - Google Analytics