零基础写Java知乎爬虫之进阶篇(2)
首先,对于先设计一个数据结构用来存储TODO表, 考虑到需要先进先出所以采用队列,自定义一个Quere类:
import java.util.LinkedList;
/**
* 自定义队列类 保存TODO表
*/
public class Queue {
/**
* 定义一个队列,使用LinkedList实现
*/
private LinkedList<Object> queue = new LinkedList<Object>(); // 入队列
/**
* 将t加入到队列中
*/
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();
}
}
还需要一个数据结构来记录已经访问过的 URL,即Visited表。
考虑到这个表的作用,每当要访问一个 URL 的时候,首先在这个数据结构中进行查找,如果当前的 URL 已经存在,则丢弃这个URL任务。
这个数据结构需要不重复并且能快速查找,所以选择HashSet来存储。
综上,我们另建一个SpiderQueue类来保存Visited表和TODO表:
import java.util.HashSet;
import java.util.Set;
/**
* 自定义类 保存Visited表和unVisited表
*/
public class SpiderQueue {
/**
* 已访问的url集合,即Visited表
*/
private static Set<Object> visitedUrl = new HashSet<>();
/**
* 添加到访问过的 URL 队列中
*/
public static void addVisitedUrl(String url) {
visitedUrl.add(url);
}
/**
* 移除访问过的 URL
*/
public static void removeVisitedUrl(String url) {
visitedUrl.remove(url);
}
/**
* 获得已经访问的 URL 数目
*/
public static int getVisitedUrlNum() {
return visitedUrl.size();
}
/**
* 待访问的url集合,即unVisited表
*/
private static Queue unVisitedUrl = new Queue();
/**
* 获得UnVisited队列
*/
public static Queue getUnVisitedUrl() {
return unVisitedUrl;
}
/**
* 未访问的unVisitedUrl出队列
*/
public static Object unVisitedUrlDeQueue() {
return unVisitedUrl.deQueue();
}
/**
* 保证添加url到unVisitedUrl的时候每个 URL只被访问一次
*/
public static void addUnvisitedUrl(String url) {
if (url != null && !url.trim().equals("") && !visitedUrl.contains(url)
&& !unVisitedUrl.contians(url))
unVisitedUrl.enQueue(url);
}
/**
* 判断未访问的 URL队列中是否为空
*/
public static boolean unVisitedUrlsEmpty() {
return unVisitedUrl.empty();
}
}
上面是一些自定义类的封装,接下来就是一个定义一个用来下载网页的工具类,我们将其定义为DownTool类:
package controller;
import java.io.*;
import org.apache.commons.httpclient.*;
import org.apache.commons.httpclient.methods.*;
import org.apache.commons.httpclient.params.*;
public class DownTool {
/**
* 根据 URL 和网页类型生成需要保存的网页的文件名,去除 URL 中的非文件名字符
*/
private String getFileNameByUrl(String url, String contentType) {
// 移除 "http://" 这七个字符
url = url.substring(7);
// 确认抓取到的页面为 text/html 类型
if (contentType.indexOf("html") != -1) {
// 把所有的url中的特殊符号转化成下划线
url = url.replaceAll("[\\?/:*|<>\"]", "_") + ".html";
} else {
url = url.replaceAll("[\\?/:*|<>\"]", "_") + "."
+ contentType.substring(contentType.lastIndexOf("/") + 1);
}
return url;
}
/**
* 保存网页字节数组到本地文件,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.执行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("请检查你的http地址是否正确");
e.printStackTrace();
} catch (IOException e) {
// 发生网络异常
e.printStackTrace();
} finally {
// 释放连接
getMethod.releaseConnection();
}
return filePath;
}
}