宽度优先遍历是爬虫中使用最广泛的一种爬虫策略,之所以使用宽度优先搜索策略,主要原因有三点:
1、重要的网页往往离种子比较近,例如我们打开的新闻网站的时候往往是最热门的新闻,随着不断的深入冲浪,所看到的网页的重要性越来越低。
2、万维网的实际深度最多能达到17层,但到达某个网页总存在着一条很短的路径。而宽度优先遍历会以最快的速度到达这个页面。
3、宽度优先有利于多爬虫的合作抓取,多爬虫合作通常先抓取站内的链接,抓取的封闭性很强。
本节需要多导入一个HtmlParser.jar 其他jar包从第一篇中下载
先画一个程序的结构
首先,需要定义一个图中所描述的“URL入队列”,这里使用了LinkedList来实现这个队列
Quequ类
package kuandu;
import java.util.LinkedList;
/**
* 队列,保存将要访问的URL
*/
public class Quequ {
//使用链表保存队列
private LinkedList quequ = new LinkedList();
//入队列
public void enQuequ(Object t) {
quequ.addLast(t);
}
//出队列
public Object deQuequ() {
return quequ.removeFirst();
}
//判断是否是空
public boolean isQuequEmpty() {
return quequ.isEmpty();
}
//判断队列是否包含t
public boolean contians(Object t) {
return quequ.contains(t);
}
public boolean empty() {
return quequ.isEmpty();
}
}
除了URL队列之外,在爬虫的过程中,还需要一个数据结构来记录已经访问过的URL。每当要访问一个URL时候,首先阿紫这个数据结构中进行查找,如果当前的URL已经存在,则丢弃它。这个数据结构要有两个特点:
1、结构中保存的URL不能重复。
2、能快速的查找。
针对以上两点我们选择HashSet作为存储结构。
LinkQuequ类
package kuandu;
import java.util.LinkedList;
/**
* 队列,保存将要访问的URL
*/
public class Quequ {
//使用链表保存队列
private LinkedList quequ = new LinkedList();
//入队列
public void enQuequ(Object t) {
quequ.addLast(t);
}
//出队列
public Object deQuequ() {
return quequ.removeFirst();
}
//判断是否是空
public boolean isQuequEmpty() {
return quequ.isEmpty();
}
//判断队列是否包含t
public boolean contians(Object t) {
return quequ.contains(t);
}
public boolean empty() {
return quequ.isEmpty();
}
}
下面的代码详细的说明了网页下载并处理的过程。
DownLoadFile类
package kuandu;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.params.HttpMethodParams;
public class DownLoadFile {
/**
* 根据URL和网页类型生成需要保存的网页的文件名,去除URL中非文件名字符
*/
public String getFileNameByUrl(String url, String contentType) {
//移除http
url = url.substring(7);
//text/html类型
if (contentType.indexOf("html") != -1) {
return url.replaceAll("[\\?/:*|<>\"]", "_") + ".html";
}
//如application/pdf类型
else {
return url.replaceAll("[\\?/:*|<>\"]", "_") + "." + contentType.substring(contentType.lastIndexOf("/") + 1);
}
}
/**
* 保存网页字节数组到本地文件,filePath为保存文件的相对地址
*/
private void saveToLoacl(byte[] data, String filePath) {
try {
DataOutputStream out = new DataOutputStream(new FileOutputStream(new File(filePath)));
for (int i = 0; i <data.length; i++) {
out.write(data[i]);
}
out.flush();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 下载URL指向的网页
*/
public String downLoadFile(String url) {
String filePath = null;
//1、生成HttppClient对象并生成参数
HttpClient httpClient = new HttpClient();
//设置http连接超时5s
httpClient.getHttpConnectionManager().getParams().setConnectionTimeout(5 * 1000);
httpClient.getParams().setParameter(HttpMethodParams.USER_AGENT, "Mozilla/5.0 (X11; U; Linux i686; zh-CN; rv:1.9.1.2) Gecko/20090803");
//2、生成GetMethod对象并生成参数
GetMethod getMethod = new GetMethod(url);
//设置请求超时5s
getMethod.getParams().setParameter(HttpMethodParams.SO_TIMEOUT, 5 * 1000);
//设置请求重试处理
getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, new DefaultHttpMethodRetryHandler());
//执行HttpGet请求
try {
int statusCode = httpClient.executeMethod(getMethod);
if (statusCode != HttpStatus.SC_OK) {
System.err.println("请求失败:" + getMethod.getStatusLine());
}
//4、处理http响应内容
byte[] responseBody = getMethod.getResponseBody();
//根据网页的URL生成保存时的文件名
filePath = "c:\\temp\\" + getFileNameByUrl(url, getMethod.getResponseHeader("Content-Type").getValue());
//5、保存
saveToLoacl(responseBody, filePath);
} catch (HttpException e) {
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return filePath;
}
}
接下来,演示如何从获得的网页中提取URL。JAVA有一个非常实用的开源工具包HtmlParser,它专门针对Html页面进行处理,不仅能提取URL,还能提取文本以及你想要的任何内容。
HtmlParserTool类
package kuandu;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.HashSet;
import java.util.Set;
import org.htmlparser.Node;
import org.htmlparser.NodeFilter;
import org.htmlparser.Parser;
import org.htmlparser.filters.NodeClassFilter;
import org.htmlparser.filters.OrFilter;
import org.htmlparser.tags.LinkTag;
import org.htmlparser.util.NodeList;
import org.htmlparser.util.ParserException;
public class HtmlParserTool {
/**
* 获取一个网站上的连接,filter用来过滤连接
*/
public static Set<String> extracLinks(String url, LinkFilter filter) {
Set<String> links = new HashSet<String>();
try {
URLConnection conn = null;
URL url1 = new URL(url);
conn = url1.openConnection();
conn.setRequestProperty("User-Agent","Mozilla/5.0 (X11; U; Linux i686; zh-CN; rv:1.9.1.2) Gecko/20090803");
Parser parser = new Parser();
parser.setConnection(conn);
parser.setEncoding("gb2312");
//过滤<frame>的filter,用来提取里面的src属性
NodeFilter frameFilter = new NodeFilter() {
@Override
public boolean accept(Node node) {
if (node.getText().startsWith("src=")) {
return true;
}
return false;
}
};
//OrFilter来设置过滤<a>和<frame>
OrFilter linkFilter = new OrFilter(new NodeClassFilter(LinkTag.class), frameFilter);
//得到所有经过过滤的标签
NodeList list = parser.extractAllNodesThatMatch(linkFilter);
for (int i = 0; i < list.size(); i++) {
Node tag = list.elementAt(i);
if (tag instanceof LinkTag) {//<a>标签
LinkTag link = (LinkTag)tag;
String linkUrl = link.getLink();//URL
if (filter.accept(linkUrl)) {
links.add(linkUrl);
}
}
else {//<frame>标签
//提取<frame>中的src
String frame = tag.getText();
int start = frame.indexOf("src=");
frame = frame.substring(start);
int end = frame.indexOf(" ");
if (end == -1) {
end = frame.indexOf(">");
}
String frameUrl = frame.substring(5, end - 1);
if (filter.accept(frameUrl)) {
links.add(frameUrl);
}
}
}
} catch (ParserException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return links;
}
}
最后再写一个爬虫的主函数就OK了。
MyCrawler类
package kuandu;
import java.util.Set;
public class MyCrawler {
/**
* 使用种子初始化URL队列
* @return
* @param seeds 种子URL
*/
private void initCrawlerWithSeeds(String[] seeds) {
for(int i = 0; i < seeds.length; i++) {
LinkQuequ.addUnVisitedUrl(seeds[i]);
}
}
/**
* 抓取过程
* @return
* @param seeds
*/
private void crawling(String[] seeds, final String headTo) {
//定义过滤器,定义以http://www.lietu.com开头的连接
LinkFilter filter = new LinkFilter() {
@Override
public boolean accept(String url) {
if (url.startsWith(headTo)) {
return true;
}
else {
return false;
}
}
};
//初始化URL队列
initCrawlerWithSeeds(seeds);
//循环条件:待抓取的连接不空且抓取的网页不超过1000个
while (!LinkQuequ.unVisitedUrlsdEmpty() && LinkQuequ.getVisitedUrlNum() < 1000) {
//对列头URL出列
String visitUrl = (String)LinkQuequ.unVisitedUrlDeQuequ();
if (visitUrl == null) {
continue;
}
DownLoadFile downLoadFile = new DownLoadFile();
//下载网页
downLoadFile.downLoadFile(visitUrl);
//放入已访问的URL队集合中
LinkQuequ.addVisitedUrl(visitUrl);
//提取出下载网页中的URL
Set<String> links = HtmlParserTool.extracLinks(visitUrl, filter);
//新的未访问的URL队列
for (String link : links) {
// System.out.println(link);
LinkQuequ.addUnVisitedUrl(link);
}
}
}
//程序入口
public static void main(String[] args) {
String[] seeds = new String[] {""};//要爬的网站
String headTo = "";//以他开头的
MyCrawler myCrawler = new MyCrawler();
myCrawler.crawling(seeds, headTo);
}
}