1.自定义队列Queue,保存将要访问的URL:
package com.peng.queue;
import java.util.LinkedList;
//队列,保存将要访问的URL
public class Queue {
//使用链表实现队列
private LinkedList queue=new LinkedList();
//入队列
public void enQueue(Object t){
queue.addLast(t);
}
//出队列
public Object deQueue(){
return queue.removeFirst();
}
//判断队列为空
public boolean isQueueEmpty(){
return queue.isEmpty();
}
//判断队列是否包含t
public boolean contians(Object t){
return queue.contains(t);
}
public boolean empty(){
return queue.isEmpty();
}
}
2.定义数据结构LinkQueue ,维护已经访问过的URL和尚未访问过的URL:
package com.peng.queue;
import java.util.HashSet;
import java.util.PriorityQueue;
import java.util.Set;
//注意此时队列用的是自定义的队列
import com.peng.queue.*;
public class LinkQueue1 {
//已访问的url集合
private static Set visitedUrl=new HashSet();
//待访问的url集合
private static Queue unVisitedUrl=new Queue();
//获得URL队列
public static Queue getUnVisitedUrl(){
return unVisitedUrl;
}
//添加到访问过的URL队列中
public static void addVisitedUrl(String url){
visitedUrl.add(url);
}
//移除访问过的URL
public static void removeVisitedUrl(String url){
visitedUrl.remove(url);
}
//未访问过的URL出队列
public static Object unVisitedUrlDeQueue(){
//return unVisitedUrl.deQueue();
return unVisitedUrl.deQueue();
}
//保证每个URL只被访问一次
public static void addUnvisitedUrl(String url){
if(url!=null&&!url.trim().equals("")&&!visitedUrl.contains(url)
&&!unVisitedUrl.contians(url))
//unVisitedUrl.enQueue(url);
unVisitedUrl.enQueue(url);;
}
//获得已经访问的URL数目
public static int getVisitedUrlNum(){
return visitedUrl.size();
}
//判断未访问的URL队列中是否为空
public static boolean unVisitedUrlsEmpty(){
//return unVisitedUrl.empty();
return unVisitedUrl.empty();
}
}
3.DownLoadFile网页下载并处理:
package com.peng.download;
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)
{
//remove http://
url=url.substring(7);
//text/html类型
if(contentType.indexOf("html")!=-1)
{
url= url.replaceAll("[\\?/:*|<>\"]", "_")+".html";
return url;
}
//如application/pdf类型
else
{
return url.replaceAll("[\\?/:*|<>\"]", "_")+"."+
contentType.substring(contentType.lastIndexOf("/")+1);
}
}
/**
* 保存网页字节数组到本地文件 filePath 为要保存的文件的相对地址
*/
private void saveToLocal(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.生成 HttpClinet 对象并设置参数 */
HttpClient httpClient = new HttpClient();
// 设置 Http 连接超时 5s
httpClient.getHttpConnectionManager().getParams().setConnectionTimeout(
5000);
/* 2.生成 GetMethod 对象并设置参数 */
GetMethod getMethod = new GetMethod(url);
// 设置 get 请求超时 5s
getMethod.getParams().setParameter(HttpMethodParams.SO_TIMEOUT, 5000);
// 设置请求重试处理
getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
new DefaultHttpMethodRetryHandler());
/* 3.执行 HTTP GET 请求 */
try {
int statusCode = httpClient.executeMethod(getMethod);
// 判断访问的状态码
if (statusCode != HttpStatus.SC_OK) {
System.err.println("Method failed: "
+ getMethod.getStatusLine());
filePath = null;
}
/* 4.处理 HTTP 响应内容 */
byte[] responseBody = getMethod.getResponseBody();// 读取为字节数组
// 根据网页 url 生成保存时的文件名
filePath = "temp\\"
+ getFileNameByUrl(url, getMethod.getResponseHeader(
"Content-Type").getValue());
saveToLocal(responseBody, filePath);
} catch (HttpException e) {
// 发生致命的异常,可能是协议不对或者返回的内容有问题
System.out.println("Please check your provided http address!");
e.printStackTrace();
} catch (IOException e) {
// 发生网络异常
e.printStackTrace();
} finally {
// 释放连接
getMethod.releaseConnection();
}
return filePath;
}
}
4.从获得的网页中提取 URL—HtmlParserTool:
package com.peng.download;
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 {
Parser parser = new Parser(url);
parser.setEncoding("gb2312");
// 过滤 <frame >标签的 filter,用来提取 frame 标签里的 src 属性所表示的链接
NodeFilter frameFilter = new NodeFilter() {
public boolean accept(Node node) {
if (node.getText().startsWith("frame src=")) {
return true;
} else {
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 属性的链接如 <frame src="test.html"/>
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) {
e.printStackTrace();
}
return links;
}
}
5.主程序
package com.peng.download;
import java.util.Set;
import com.peng.queue.LinkQueue;
public class MyCrawler {
//使用种子初始化URL队列
private void initCrawlerWithSeeds(String[] seeds){
for(int i=0;i<seeds.length;i++)
LinkQueue.addUnvisitedUrl(seeds[i]);
}
//抓取过程
public void crawling(String[] seeds){
//定义过滤器,提取以http://www.lietuw.com开头的链接
LinkFilter filter=new LinkFilter() {
@Override
public boolean accept(String url) {
if(url.startsWith("http://www.baidu.com"))
return true;
else
return false;
}
};
//初始化URL队列
initCrawlerWithSeeds(seeds);
//循环条件:待抓取的链接不空且抓取的网页不多于1000
int i=0;
while(!LinkQueue.unVisitedUrlsEmpty()&&LinkQueue.getVisitedUrlNum()<=1000){
System.out.println();
System.out.println("执行了"+i+"次");
i++;
//队头URL出队列
String visitUrl=(String) LinkQueue.unVisitedUrlDeQueue();
if(visitUrl==null)
continue;
DownLoadFile downLoadFile=new DownLoadFile();
//下载网页
downLoadFile.downloadFile(visitUrl);
//该URL放入已访问的URL中
LinkQueue.addUnvisitedUrl(visitUrl);
//提取出下载网页中的URL
Set<String> links=HtmlParserTool.extracLinks(visitUrl, filter);
//新的未访问的URL队列
for(String link:links){
LinkQueue.addUnvisitedUrl(link);
}
}
}
public static void main(String[] args) {
MyCrawler myCrawler=new MyCrawler();
myCrawler.crawling(new String[]{"http://www.baidu.com"});
}
}
6.上面的主程序使用了一个 LinkFilter 接口,并且实现为一个内部类。这个接口的目的是为了过滤提取出来的 URL,它使得程序中提取出来的 URL 只会和猎兔网站相关。而不会提 取其他无关的网站
package com.peng.download;
public interface LinkFilter {
public boolean accept(String url);
}