传统的搜索引擎,是将整个网页的文本内容进行索引。而垂直搜索引擎,则是对网页内容进行 进一步的分析,仅获取自己关心的数据。一般的垂直搜索引擎的蜘蛛程序,常采用正则表达式 匹配的方式定位并获取数据。这种方式属于模糊搜索,它从一堆网页源代码中搜索,运行效率 比较低,数据定位及抽取准确度也不高。网页稍有改动,正则表达式就极有可能重新编写,程序维护率高。
我们认为,对于垂直搜索来说,最好的方式是先把网页规格化、结构化,把HTML网页转化为XML网页,将操作HTML代码转为DOM(XML)文档对象, 这就可以利用DOM/XPATH/XSLT等技术来抽取、格式化数据了,使用灵活方便,从而使垂直搜索数据分析抽取水平提高若干个档次。如用//title可 抽取网页标题,用//meta[@name='keywords']/@content可抽取网页关键字,用//a/@href|//frame/@src|//iframe/@src可抽取全部 链接,包括超链接,frame/iframe的链接,用//img/@src可抽取全部图片地址,等等。
这项技术的关键点在于如何将HTML转化为XML。以前有开源的项目能做一点转换工作,但项目都停滞好些年了,无人维护了, 更重要的是其转换的质量、中文的支持、使用的方便程度等让人不敢恭维,完全达不到大规模商用的程度。现在好了,有了海星,HTML结构化的艰苦时代一去不复返了。
海星是什么?海星是资深的浏览器高级研发工程师继推出海葵垂直搜索专用网页抓取服务器、 海蛛垂直搜索专用网络蜘蛛系统之后,精心制作、最新推出的一款垂直搜索专用网页结构化服务 器。开发的目的,就是要为垂直搜索业界,提供最高质量的网页结构化工具,方便数据挖掘工作, 推动垂直搜索的快速发展。
1、基于FIREFOX浏览器代码实现,提供最高质量的转换结果
核心采用FIREFOX浏览器解析网页源码,网页编码自动探测,生成浏览器一级的XML结果,转换质量业界最高。
2、本地代码实现,后台多线程运行,效率成倍提升
海星是用C++实现的服务器端程序,运行于后台。它可同时接受多个请求,完全适应垂直 搜索蜘蛛同时解析若干个网页的要求,效率无忧。
3、支持多种操作系统
海星除可以运行在LINUX上,更可运行在WINDOWS上,用户使用更为容易。
4、接口协议简单,类HTTP协议,解析数据极易
通过PARSE命令,可以发送数据让海星解析,从而得到解析后的XML代码。这种方式速度最快, 最为常用,适合蜘蛛程序抓到网页数据后,交由海星解析,然后利用DOM/XPATH/XSLT得到所需的数据。 还可通过FETCH命令,获取指定网址的XML解析结果。这种方式速度稍慢,适合测试。
5、提供浏览器访问接口,方便数据分析
用户可通过浏览器访问海星所在6373端口,查看分析感兴趣的网页。如访问http://localhost:6373/fetch?type=xml&url=http://www.sohu.com 即可呈现搜狐首页转换为XML后的显示结果。若将type=xml改为type=html,则可显示出搜狐首页,其源码使用的是海星转换后的结果。
6、可利用FIREFOX浏览器进行所见即所得的开发工作,浏览分析两不误
若要分析数据,最好使用FIREFOX浏览器,并安装FIREBUG和XPATHER扩展。在浏览感兴趣的网页时, 先用FIREBUG定位数据所在节点,得到节点相关的ID值或其它属性值,然后在XPATHER中输入XPATH验证数据是否取得到。 XPATH在FIREFOX中验证成功后,就可以在蜘蛛程序中使用了。其原因就是海星使用FIREFOX代码做解析工作,静态内容网页解析 结果基本相同。这也是使用海星的优势所在,FIREFOX浏览器在这里相当于一个强大的开发工具了,既可浏览网页,又可分析数据。
海星与海葵的差别:海葵就如同多个可遥控的浏览器,使用海葵可以得到网页的全部数据,含有动态内容,如由javascript生成的内容, frame/iframe中的内容,更可执行javascript。海星则不同, 使用海星得到的网页结果则不包含动态内容,海星只做结构化的工作,将HTML转化为XML,它执行不了javascript,功能单一, 速度飞快,非常适合当前许多垂直搜索的数据抽取工作,并且我敢打赌,目前没有比海星更好的工具了。
选择海星菜单中选项,或者进入控制面板-管理工具-服务,选择seastar,可启动、停止海星服务。
service seastar start
service seastar stop
service seastar status
usage: seastarctl command where command is: 1) list list current settings 2) set [port|rcj] value set config 3) help print this help info
例1: seastarctl list 列出当前配置信息。 例2: seastarctl set port 4444 设定海星侦听4444端口, 在此端口中接收请求。 例3: seastarctl set rcj 4444-5555-6666 设置注册码为4444-5555-6666。
usage: struct [string | <url>
例1:结构化从标准输入输入的字符串 struct string <<!EOF hello,seastar! EOF 例2:利用管道,结构化文件内容 struct string < /root/a.html 例3:结构化网页内容 struct http://www.zhuatang.com
默认侦听端口: 6373
结构化字符串,客户端请发送如下请求,且字符串要使用utf-8编码(每行以换行符<LF>结束,遇到空行表示请求结束)
PARSE <length><LF>
<LF>
<contents>
注:
<length> - 字符串长度
<contents> - 字符串内容
结构化网页,客户端请发送如下请求(每行以换行符<LF>结束,遇到空行表示请求结束)
FETCH <url><LF>
<LF>
注:
<url> - 要结构化的网页地址
package com.zhsoft88.commons; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.net.Socket; import java.util.StringTokenizer; import org.apache.commons.lang.math.NumberUtils; /** * seastar web page structurize * @author zhsoft88 * @since 2008-03-28 * @update 2008-08-03 */ public class Seastar { public static final int PORT = 6373; /** * seastar result * @author zhsoft88 * @since 2008-3-28 */ public static class SeastarResult { private int status; private String contents; private long elapsedTime; public SeastarResult(int status,String contents,long elapsedTime) { this.status = status; this.contents = contents; this.elapsedTime = elapsedTime; } public int getStatus() { return status; } public String getContents() { return contents; } public long getElapsedTime() { return elapsedTime; } @Override public String toString() { return "[status="+status+",contents="+contents+",elapsedTime="+elapsedTime+"]"; } } private String host; private int port; /** * constructor: localhost */ public Seastar() { this("localhost"); } /** * constructor: host * @param host */ public Seastar(String host) { this(host,PORT); } /** * constructor for host,port * @param host * @param port */ public Seastar(String host,int port) { this.host = host; this.port = port; } /** * struct contents for specified url * @param url * @return * @throws Exception */ public SeastarResult structURL(String url) throws Exception { long t1 = System.currentTimeMillis(); Socket socket = new Socket(host,port); BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); bw.write("FETCH "+url+"\r\n"); bw.write("\r\n"); bw.flush(); BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(),"utf-8")); String line = br.readLine(); int status = -1; StringTokenizer st = new StringTokenizer(line," "); st.nextToken(); status = NumberUtils.toInt(st.nextToken()); while ((line=br.readLine())!=null) { if (line.length()==0) break; } StringBuilder sb = new StringBuilder(100); int c; while ((c=br.read())!=-1) { sb.append((char)c); } socket.close(); long t2 = System.currentTimeMillis(); return new SeastarResult(status,sb.toString(),t2-t1); } /** * struct string content * @param str * @return * @throws Exception */ public SeastarResult structString(String str) throws Exception { long t1 = System.currentTimeMillis(); Socket socket = new Socket(host,port); OutputStream out = socket.getOutputStream(); byte[] ba = str.getBytes("utf-8"); out.write(("PARSE "+ba.length+"\r\n\r\n").getBytes()); out.write(ba); out.flush(); BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(),"utf-8")); String line = br.readLine(); int status = -1; StringTokenizer st = new StringTokenizer(line," "); st.nextToken(); status = NumberUtils.toInt(st.nextToken()); while ((line=br.readLine())!=null) { if (line.length()==0) break; } StringBuilder sb = new StringBuilder(100); int c; while ((c=br.read())!=-1) { sb.append((char)c); } socket.close(); long t2 = System.currentTimeMillis(); return new SeastarResult(status,sb.toString(),t2-t1); } }
package com.zhsoft88.commons.tests; import com.zhsoft88.commons.Seastar; import com.zhsoft88.commons.Seastar.SeastarResult; /** * Test of Seastar * @author zhsoft88 * @since 2008-08-03 */ public class TestSeastar { /** * @param args */ public static void main(String[] args) throws Exception { Seastar ss = new Seastar(); { SeastarResult result = ss.structString("good<span>抓糖好<div>goodtest"); System.out.println(result); } { SeastarResult result = ss.structURL("http://localhost:8080/docs/"); System.out.println(result); } } }
package com.zhsoft88.commons.tests; import java.net.URL; import java.util.HashSet; import java.util.List; import java.util.Set; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.methods.GetMethod; import org.apache.commons.io.IOUtils; import org.dom4j.Attribute; import org.dom4j.Document; import org.dom4j.DocumentHelper; import com.zhsoft88.commons.Seastar; import com.zhsoft88.commons.Seastar.SeastarResult; /** * Test of Seastar * @author zhsoft88 * @since 2008-08-03 */ public class TestSeastar2 { /** * @param args */ public static void main(String[] args) throws Exception { String url = "http://www.sohu.com"; URL base = new URL(url); long t1 = System.currentTimeMillis(); HttpClient client = new HttpClient(); GetMethod get = new GetMethod(url); client.executeMethod(get); String origContent = IOUtils.toString(get.getResponseBodyAsStream(),"gbk"); long t2 = System.currentTimeMillis(); System.out.println("httpclient: "+(t2-t1)+" ms"); Seastar ss = new Seastar(); SeastarResult result = ss.structString(origContent); System.out.println("seastar: "+result.getElapsedTime()+" ms"); Document doc = DocumentHelper.parseText(result.getContents()); List<Attribute> list = doc.selectNodes("//a/@href|//frame/@src|//iframe/@src"); Set<String> set = new HashSet<String>(); for (Attribute a : list) { String v = a.getValue(); if (v.startsWith("javascript:")||v.startsWith("mailto:")||v.startsWith("#")) continue; set.add(new URL(base,v).toExternalForm()); } System.out.println("size="+set.size()); for (String s : set) { System.out.println(s); } } }