English

关于海葵

想抓取类似下面的动态网页中的文本数据吗?
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>你好世界</title>
</head>
<body>
<script>document.write('你好,世界');</script>
</body>
</html>

对不起,常见的网页抓取器对此是无能为力的,它们执行不了javascript脚本,所以你也得不到脚本执行的结果。

但很幸运的是,我们可以使用海葵--这个全球顶尖的抓取工具。

海葵是什么?

海葵是全球第一款基于浏览器构建的顶尖的垂直搜索专用网页抓取服务器软件,运行于LINUX系统之上。 它不仅能抓取网页动态内容,还能将网页结构化为XML,垂直搜索引擎可以方便地通过DOM操作来提取所关心的数据了。

与海葵比较起来,一般的网页抓取器有以下重大缺陷:

一、动态内容很难抓到

一般的网页抓取器,不论是通过socket调用,还是通过httpclient等工具,抓的都是“死”的网页数据,其中的javascript脚本是不会 运行的。假如网页部分内容由javascript生成,那么这种抓取是很难抓到这类动态数据的。而在现今的互联网上,由于WEB2.0技术的流行, 用javascript生成内容的网页比比皆是,并越来越多,一般的网页抓取器对此只能是望洋兴叹了。

二、HTML格式编程分析不易

一般的网页抓取器得到的是HTML源码,而HTML规范不够严格,导致程序解析上的困难。有不少开源的HTML解析器,但效果不尽如人意, 更谈不上企业级的应用了。最好的解析器应该属IE/FIREFOX浏览器内置的解析器了,但不易利用。另外,想编程获取网页中某个位置的数据也不容易, 而这对于垂直搜索引擎是非常重要的。

三、不是通过真正的浏览器来抓取数据

一般的网页抓取器是通过模拟浏览器的行为来获取网页的,模拟达不到与浏览器完全相同的程度,因为不是通过真正的浏览器来抓取, 所以在抓取网页时会存在这样那样的问题,都需编程解决。

海葵完全克服了这些缺陷,它具备如下优点:

一、基于真正的浏览器构建

海葵基于功能强大的FIREFOX浏览器构建。它通过FIREFOX浏览器获取网页数据,其中javascript脚本是可执行的, 得到的网页数据是“鲜活”的。所有由javascript生成的内容可完全得到。

二、数据格式采用XML

XML是规范严格的标记语言,易于编程存取。海葵网页抓取返回的就是XML格式的结构化数据。这些数据可直接转为DOM(Document Object Model)对象。

利用XPATH,能对于这些数据进行绝对定位,存取十分方便。假如想得到文档的标题,执行XPATH命令/html/head/title即可。 假如想得到文档中全部链接,执行一下XPATH命令//a/@href即可。假如想得到文档中全部图片,执行XPATH命令//img/@src即可。

另外,若想得到整个文档的文本信息,也非常容易。利用dom4j的Document类的getStringValue()就可以得到。

这样一种领先的技术,对于垂直搜索引擎的数据抓取是非常重要的,能起到如虎添翼的作用。

三、网页信息完整

海葵返回的XML数据中,包含了javascript生成的内容,是完整的网页信息。

海葵还可以输出网页请求与回应数据,方便用户跟踪调试。

四、支持多种操作系统,后台多线程运行

由于采用了最新技术,海葵自6.0版开始,除能在linux上运行外,还能在windows上运行了。

海葵运行于后台,多线程运行,能同时接受多个抓取请求,工作效率很高。

五、抓取协议简单

海葵采用类似HTTP形式的协议,简单有效。发送一个GET指令即可得到指定URL的网页数据。

六、能抓取由javascript脚本实现翻页的下一个页面数据

有不少动态网站用javascript脚本来实现翻页的,一般抓取工具无法得到其下一个页面的数据。海葵在协议中增加了动态执行javascript 脚本的命令EXEC和保持连接的命令CONTINUE。再结合GET命令,用户可以持续获取多个动态页面的数据。

海葵还为javascript脚本提供了getNodeByXPath方法,用户能得到指定的DOM节点,并能模拟节点的点击操作,得到点击后的页面数据。 这样就不必取出该节点对应的脚本了,如直接执行EXEC getNodeByXPath('//input[1]').onclick()便可模拟点击网页第一个input按钮, 得到更新后的页面数据,相当简单。

自2.0版开始,海葵协议新增nodata指令,使用此指令后,海葵将不返回网页数据,方便在此基础上使用javascript抓取下一页面的数据,节省了时间。 这非常适合基于海葵的网络蜘蛛使用:种子由URL加javascript脚本构成,当javascript脚本为空时,让海葵抓取指定URL并返回结果数据,否则,让海 葵以nodata方式访问指定URL,加快响应速度,然后再执行javascript脚本,得到动态页面的数据。

七、能定制HTTP网页请求数据

如可以发送HTTP-HEADER user-agent: xxx使WEB服务器认为客户端是xxx程序等等。

八、可以让网页不执行javascript

如发送disable javascript,网页便不执行javascript脚本,这非常适合特殊情况下的网页分析。

注:海葵不是网络蜘蛛(spider),不能自行分析URL并抓取。它是一个单纯的网页抓取工具,给出一个URL链接,海葵抓取后 返回结构化的XML格式的网页数据,最后其它程序对此数据进行处理。因为是XML格式,所以利用XPATH,处理是相当简单的。 网络蜘蛛系统我们推荐您使用海蛛--领先的垂直搜索专用网络蜘蛛系统!

下载

seaflower-6.1-installer.exe (适用于Windows系统)
seaflower-6.1-1.en_US.fc9.i386.rpm (适用于Fedora Core 9兼容的Linux系统)
seaflower-6.1-1.en_US.el5.i386.rpm (适用于RedHat EL 5/CentOS兼容的Linux系统)

安装

rpm -ivh seaflower*.rpm

海葵服务器管理

配置管理工具 - seaflowerctl

usage: seaflowertctl command
where command is: 
1)  list
list current settings
2) set [port|rcj|vxsmin|vxsmax|captureWaitTime] value
set config
3) proxy [ on <IP> <PORT> | off ]
set or clear proxy setting
4) help
print this help info
例1: seaflowerctl list
列出当前配置信息。
例2: seaflowerctl set port 4444
设定海葵侦听4444端口, 在此端口中接收请求。
例3: seaflowerctl set rcj 4444-5555-6666
设置注册码为4444-5555-6666。
例4: seaflowerctl set vxsmin 5
设定最少启动5个虚拟浏览器。此数字决定了海葵能并发接受多少个抓拍请求。
例5: seaflowerctl set captureWaitTime 2
设定默认抓取等待时间为2秒。
例6: seaflowerctl proxy on 192.168.28.91 8080
设定使用代理服务器192.168.28.91,端口8080。
例7: seaflowerctl proxy off
清除代理设置,不使用代理了。
注:vxsmax暂无意义,系统保留。

注册

海葵是个共享软件,未注册时只能使用30天,注册以后就无限制了。为了您的工作能正常进行,请及时注册。
联络方式: Email/MSN: zhsoft88@gmail.com QQ: 353239635. 注册版投资额人民币15000.00元。

通讯协议

相关文章: 如何使用海葵抓取动态网页

示范代码

Seaflower.java 下载

package com.zhsoft88.commons;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.StringReader;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.math.NumberUtils;

/**
 * seaflower crawler
 * @author zhsoft88
 * @since 2008-4-13
 * @update 2009-6-20
 */
public class Seaflower {

	public static final int PORT = 4050;
	
	/**
	 * crawl result
	 * @author zhsoft88
	 * @since 2008-4-13
	 */
	public static class SeaflowerResult {
		private int status;
		private String title;
		private String location;
		private String contents;
		private long time;
		private List<String> requestHeaders;
		private List<String> responseHeaders;
		
		public SeaflowerResult() {
		}
		
		public int getStatus() {
			return status;
		}
		public String getTitle() {
			return title;
		}
		public String getLocation() {
			return location;
		}
		public String getContents() {
			return contents;
		}
		public long getTime() {
			return time;
		}
		public List<String> getRequestHeaders() {
			return requestHeaders;
		}
		public List<String> getResponseHeaders() {
			return responseHeaders;
		}
		protected void setStatus(int status) {
			this.status = status;
		}
		protected void setTitle(String title) {
			this.title = title;
		}
		protected void setLocation(String location) {
			this.location = location;
		}
		protected void setContents(String contents) {
			this.contents = contents;
		}
		protected void setTime(long time) {
			this.time = time;
		}
		protected void setRequestHeaders(List<String> requestHeaders) {
			this.requestHeaders = requestHeaders;
		}
		protected void setResponseHeaders(List<String> responseHeaders) {
			this.responseHeaders = responseHeaders;
		}
		@Override
		public String toString() {
			return "status="+status+",location="+location+",title="+title+",time="+time+",request-headers="+requestHeaders+",response-headers="+responseHeaders+",contents=["+contents+"]";
		}
	}
	
	/**
	 * crawl configuration
	 * @author zhsoft88
	 * @since 2008-4-13
	 * @update 2008-12-16
	 */
	public static class SeaflowerConf {
		private String url;
		private String exec;
		private int waitTime;
		private boolean cont;
		private boolean nodata;
		private List<String> httpHeaders;
		private boolean outputHttpHeaders;
		private boolean disableJavascript;
		
		public SeaflowerConf() {
		}

		public void addHttpHeader(String header) {
			if (httpHeaders==null) {
				httpHeaders = new ArrayList<String>();
			}
			httpHeaders.add(header);
		}
		
		public String getUrl() {
			return url;
		}

		public void setUrl(String url) {
			this.url = url;
		}

		public String getExec() {
			return exec;
		}

		public void setExec(String exec) {
			this.exec = exec;
		}

		public int getWaitTime() {
			return waitTime;
		}

		public void setWaitTime(int waitTime) {
			this.waitTime = waitTime;
		}

		public void setContinue(boolean cont) {
			this.cont = cont;
		}

		public boolean isNodata() {
			return nodata;
		}

		public void setNodata(boolean nodata) {
			this.nodata = nodata;
		}

		public boolean isContinue() {
			return cont;
		}

		public List<String> getHttpHeaders() {
			return httpHeaders;
		}

		public void setHttpHeaders(List<String> httpHeaders) {
			this.httpHeaders = httpHeaders;
		}

		public boolean isOutputHttpHeaders() {
			return outputHttpHeaders;
		}

		public void setOutputHttpHeaders(boolean outputHttpHeaders) {
			this.outputHttpHeaders = outputHttpHeaders;
		}

		public boolean isDisableJavascript() {
			return disableJavascript;
		}

		public void setDisableJavascript(boolean disableJavascript) {
			this.disableJavascript = disableJavascript;
		}
		
	}

	private Socket socket;
	
	public Seaflower() throws UnknownHostException, IOException {
		this("localhost");
	}
	
	public Seaflower(String host) throws UnknownHostException, IOException {
		this(host,PORT);
	}
	
	public Seaflower(String host,int port) throws UnknownHostException, IOException {
		socket = new Socket(host,port);
	}
	
	private String readUTFLine(InputStream in) throws IOException {
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		int c;
		while ((c=in.read())!=-1) {
			if (c=='\r') continue;
			if (c=='\n') break;
			baos.write(c);
		}
		return baos.toString("utf-8");
	}
	/**
	 * crawl
	 * @param conf
	 * @return
	 * @throws Exception
	 */
	public SeaflowerResult crawl(SeaflowerConf conf) throws Exception {
		if (socket==null) {
			throw new Exception("socket closed");
		}
		long t1 = System.currentTimeMillis();
		BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
		if (conf.getUrl()!=null) {
			bw.write("GET "+conf.getUrl()+"\r\n");
		}
		if (conf.getExec()!=null) {
			bw.write("EXEC "+conf.getExec()+"\r\n");
		}
		if (conf.getWaitTime()!=-1) {
			bw.write("WAIT-TIME "+conf.getWaitTime()+"\r\n");
		}
		if (conf.getHttpHeaders()!=null) {
			for (String s : conf.getHttpHeaders()) {
				bw.write("HTTP-HEADER "+s+"\r\n");
			}
		}
		if (conf.isOutputHttpHeaders()) {
			bw.write("OUTPUT http-headers\r\n");
		}
		if (conf.isDisableJavascript()) {
			bw.write("DISABLE javascript\r\n");
		}
		if (conf.isContinue()) {
			bw.write("CONTINUE\r\n");
		}
		if (conf.isNodata()) {
			bw.write("NODATA\r\n");
		}
		bw.write("\r\n");
		bw.flush();
		InputStream in = socket.getInputStream();
		String line = readUTFLine(in);
		int status = -1;
		StringTokenizer st = new StringTokenizer(line," ");
		st.nextToken();
		status = NumberUtils.toInt(st.nextToken());
		String tagTitle = "Current-Title: ";
		String tagLocation = "Current-Location: ";
		String tagLength = "Content-Length: ";
		String title = null;
		String location = null;
		int length = 0;
		while ((line=readUTFLine(in))!=null) {
			if (line.length()==0) break;
			if (line.startsWith(tagTitle)) {
				title = line.substring(tagTitle.length());
			} else if (line.startsWith(tagLocation)) {
				location = line.substring(tagLocation.length());
			} else if (line.startsWith(tagLength)) {
				length = NumberUtils.toInt(line.substring(tagLength.length()));
			}
		}
		ByteArrayOutputStream baos = new ByteArrayOutputStream(length);
		byte[] ba = new byte[4096];
		while (length>0) {
			int len = in.read(ba);
			baos.write(ba, 0, len);
			length -= len;
		}
		String contents = baos.toString("utf-8");
		if (!conf.isContinue()) {
			socket.close();
			socket = null;
		}
		long t2 = System.currentTimeMillis();
		SeaflowerResult result = new SeaflowerResult();
		result.setStatus(status);
		result.setTitle(title);
		result.setLocation(location);
		result.setTime(t2-t1);
		result.setContents(contents);
		List<String> requestHeaders = new ArrayList<String>();
		List<String> responseHeaders = new ArrayList<String>();
		if (conf.isOutputHttpHeaders()) {
			BufferedReader hbr = new BufferedReader(new StringReader(result.getContents()));
			boolean found = false;
			while ((line=hbr.readLine())!=null) {
				if (line.equals("<!-- @REQUEST-HEADERS [")) {
					found = true;
					break;
				}
			}
			if (found)
			{
				while ((line=hbr.readLine())!=null) {
					if (line.equals("] -->")) break;
					requestHeaders.add(line);
				}
			}
			found = false;
			while ((line=hbr.readLine())!=null) {
				if (line.equals("<!-- @RESPONSE-HEADERS [")) {
					found = true;
					break;
				}
			}
			if (found)
			{
				while ((line=hbr.readLine())!=null) {
					if (line.equals("] -->")) break;
					responseHeaders.add(line);
				}
			}
			result.setContents(IOUtils.toString(hbr));
		}
		result.setRequestHeaders(requestHeaders);
		result.setResponseHeaders(responseHeaders);
		return result;
	}
	
}

TestSeaflower.java 下载

package com.zhsoft88.commons.tests;

import com.zhsoft88.commons.Seaflower;
import com.zhsoft88.commons.Seaflower.SeaflowerConf;
import com.zhsoft88.commons.Seaflower.SeaflowerResult;

public class TestSeaflower {

	/**
	 * @param args
	 */
	public static void main(String[] args) throws Exception {
		SeaflowerConf conf = new SeaflowerConf();
		conf.setUrl("http://www.google.com");
		conf.addHttpHeader("user-agent: jiong/0.1");
		conf.addHttpHeader("kkkkkkk: vvvvvvvvvvvvvvv");
		conf.setOutputHttpHeaders(true);
		Seaflower sf = new Seaflower();
		SeaflowerResult result = sf.crawl(conf);
		System.out.println(result.getRequestHeaders());
		System.out.println(result.getResponseHeaders());
		System.out.println(result.getContents());
	}

}

TestSeaflower4.java 下载

分析酷基金网站(www.ourku.com)、取出基金数据的JAVA示例代码(注:一般抓取器抓不到这些数据,因为它们是js生成的)
package com.zhsoft88.commons.tests;

import java.util.List;

import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Node;

import com.zhsoft88.commons.Seaflower;
import com.zhsoft88.commons.Seaflower.SeaflowerConf;
import com.zhsoft88.commons.Seaflower.SeaflowerResult;

/**
 * Test of Seaflower: 抓取酷基金网站数据
 * @author zhsoft88
 * @since 2009-6-20
 */
public class TestSeaflower4 {

	/**
	 * @param args
	 */
	public static void main(String[] args) throws Exception {
		Seaflower s = new Seaflower();
		{
			SeaflowerConf conf = new SeaflowerConf();
			conf.setUrl("http://www.ourku.com/index_kfjj.html");
			conf.setWaitTime(10);
			SeaflowerResult result = s.crawl(conf);
			Document doc = DocumentHelper.parseText(result.getContents());
			List<Node> list = doc.selectNodes("//table[@id='ilist']//tr[@class='tr']");
			System.out.println("total size="+list.size());
			for (Node no : list) {
				for (int i=1;i<=8;i++) {
					System.out.print(no.selectSingleNode("td["+i+"]").getStringValue());
					if (i!=8) System.out.print(", ");
				}
				System.out.println();
			}
		}
		
	}

}

TestSeaflower3Cont.java 下载

模拟点击页面元素、取出相关图片地址的JAVA示例代码(注:一般抓取器抓不到这些数据,因为它们是js生成的)
package test;

import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;

import com.zhsoft88.commons.Seaflower;
import com.zhsoft88.commons.Seaflower.SeaflowerConf;
import com.zhsoft88.commons.Seaflower.SeaflowerResult;

public class TestSeaflower3Cont {

	/**
	 * @param args
	 */
	public static void main(String[] args) throws Exception {
		Seaflower sf = new Seaflower();
		{
			SeaflowerConf conf = new SeaflowerConf();
			conf.setContinue(true);
			conf.setNodata(true);
			conf.setWaitTime(10);
			conf.setUrl("http://www.gap.com/browse/product.do?cid=8793&vid=1&pid=655053");
			sf.crawl(conf);
		}
		for (char c='0';c<='4'; c++)
		{
			SeaflowerConf conf = new SeaflowerConf();
			conf.setContinue(c!='4');
			conf.setExec("document.getElementById('colorSwatch_"+c+"').onclick()");
			SeaflowerResult result = sf.crawl(conf);
			Document doc = DocumentHelper.parseText(result.getContents());
			Attribute a = (Attribute)doc.selectSingleNode("//img[@id='dragImg']/@src");
			System.out.println(a.getValue());
		}
	}

}

抓取用javascript实现分页的下一页网页数据的步骤

产品族: 海狮 海猫 海葵 海蛛 海鹞 海星 海狗 WBXL Xultray webapp
iDocSet iDocSetHelper Blink浏览器 templateJS 布偶猫 skiafy tranid
(C) 2018 抓糖网 版权所有

update: 2013-06-07