import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Queue;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.GetMethod;
/**
* 下载类 完成 下载给定URL的网页
* 实现下载函数 download()
*/
public class DownloadPage implements Runnable{
int statusCode = 200; // 执行 下载返回的状态码200为执行成功
String path = ""; // 下载之后保存的本地地址
// HttpClient httpClient; // 用于执行下载的 httpClient
String url = ""; // 要下载的地址
Queue<String> newPath;
public DownloadPage(String url, Queue<String> table){
this.url = url;
newPath = table;
}
/**
* 下载制定URL的网页
* @param url 下载地址
* @return 本地地址
*/
public void download() {
try {
HttpClient httpClient = new HttpClient();
InputStream input = null;
OutputStream output = null; // 初始化 输入输出流
HttpMethod getMethod = new GetMethod(url);
statusCode = httpClient.executeMethod(getMethod);
if (statusCode == HttpStatus.SC_OK) {// 针对状态码200进行处理
input = getMethod.getResponseBodyAsStream(); // 将得到的返回用流表达
// String filename = path.substring(path.lastIndexOf('/')+1); //得到文件名
path = "e:\\" + (Spider.i++) +".html";
output = new FileOutputStream(path); // 获得文件输出流
int tempByte = -1;
while((tempByte=input.read())>0){ // 输出到文件
output.write(tempByte); // 开始写
}
input.close();
output.close();// 关闭输入输出流
}
updateGetPageURL();
}
catch (Exception e) {
e.printStackTrace();
}
}
/**
* start 执行的方法
*/
public void run(){
download();
}
public synchronized void updateGetPageURL(){
newPath.add(path);
}
}
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Queue;
import org.htmlparser.Parser;
import org.htmlparser.filters.NodeClassFilter;
import org.htmlparser.tags.LinkTag;
import org.htmlparser.util.NodeList;
import org.htmlparser.util.ParserException;
/**
* 下载类 包含 一个文件路径和一个 URL链表
* 实现 了从 一个html文件中获取 超链接的功能
*/
public class GetPageURL implements Runnable{
Queue<String> pageToGetURL; // 带分析的 网页 路径队列
URLQueue urlQueue; // 将 得到的 URL 加入该队列中
/**
* 构造方法
* @param urlQueue
*/
public GetPageURL(URLQueue urlQueue){
pageToGetURL = new LinkedList<String>(); // 实例化网页路径队列
this.urlQueue = urlQueue; // 接收 存储 URL的队列
}
/**
* 最重要的一个函数从给定的html文件中获取URL
*/
public void getURLFromFile(String filePath){
NodeList nodeList = null;
try{
Parser parser = new Parser(filePath); // 分析类parser 将会把网页分析成树
parser.setEncoding("GB2312"); // 设置编码格式
nodeList = parser.parse(new NodeClassFilter(LinkTag.class)); // 根据过滤器的要求分析html 形成节点
} // 要求tag 是 LinkTag 即我们要求的超链接
catch(ParserException e){
e.printStackTrace();
}
if(nodeList != null && nodeList.size() > 0){ // 遍历 节点
String urlLink;
for(int i = 0;i < nodeList.size();i++){
urlLink = ((LinkTag)nodeList.elementAt(i)).extractLink(); // 得到超链接
String linkName = ((LinkTag)nodeList.elementAt(i)).getLinkText(); // 得到超链接的名字即在网页显示的展现
urlLink = URLUtility.Encode(urlLink);
urlQueue.add(urlLink); // 添加到 ArrayList 里面
System.out.println(linkName+":"+urlLink);
}
}
}
/**
* 为了方便测试添加的打印函数
* 完成打印已经分析完成的超链接功能
*/
public void print(){
Iterator<String> urlIterator = pageToGetURL.iterator(); // 设置迭代器
while(urlIterator.hasNext()){
System.out.println(urlIterator.next());
}
}
/**
* start 执行的方法
*/
public void run(){
try{
String filePath = null;
while(true){
if(pageToGetURL.isEmpty()){ // 如果队列为空的话则暂停1S然后继续检查
Thread.sleep(1000);
continue;
}
while(!pageToGetURL.isEmpty()){
filePath = pageToGetURL.poll();// 从队列中取出来一个路径
getURLFromFile(filePath); // 从该文件中获取URL
}
}
}
catch(Exception e){
e.printStackTrace();
}
}
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 主类 Spider 实现多线程爬虫
*/
public class Spider implements Runnable{
URLQueue urlQueue; // 放置URL的队列
GetPageURL getPageURL; // 从下载页面中获取URL
private static int NumberofMaxThreads = 4; // 线程池中线程的最大个数
static int i = 1; // 用于对网页进行编号
/**
* 构造方法
* @param url
*/
public Spider(String url){
urlQueue = new URLQueue(); // 实例化 URLQueue
urlQueue.add(url); // 初始化 URLQueue
getPageURL = new GetPageURL(urlQueue); // 实例化 GetPageURL
new Thread(getPageURL).start(); // 开始运行 GetPageURL
}
/**
* 实现Runnable的run方法
*/
public void run(){
ExecutorService pool = null; // 声明线程池
try{
pool = Executors.newFixedThreadPool(NumberofMaxThreads); // 实例化一个确定的线程池
while (true) {
if(!urlQueue.isEmpty()){// 如果 队列不为空的话 则从线程池中开启一个线程
DownloadPage download = new DownloadPage(urlQueue.get(),getPageURL.pageToGetURL);
pool.execute(download); //将URL交给下载类
}
else{
Thread.sleep(2000); // 如果队列不为空的话 sleep 2S 再次运行
continue;
}
}
}
catch(Exception e){
e.printStackTrace();
// System.exit(0);
}
finally{pool.shutdown();}
}
public static void main(String []args){
Spider spider = new Spider("http://news.baidu.com/");
Thread t = new Thread(spider);
t.start();
}
}
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Queue;
/**
* 队列类 包含 一个队列和一个 哈希集合
* 实现 添加元素 读取元素 是否为空等函数
*/
public class URLQueue {
private Queue<String> urlQueue; // URL的队列 爬虫将从此队列中取得下一个下载的地址
private HashSet<String> visitedURL; // 已经访问过的地址集合 每次添加的需要根据此集合进行判断
/**
* 构造方法 初始化 队列和 集合
*/
public URLQueue(){
urlQueue = new LinkedList<String>();
visitedURL = new HashSet<String>();
}
/**
* 向队列中添加一个地址
* @param url
* @return
*/
public boolean add(String url){
if(visitedURL.contains(url)){ // 如果已经访问过 则不添加返回
return false;
}
urlQueue.offer(url); // 添加到队列中
return true;
}
/**
* 添加一系列的地址 根据需要所添加的函数
* @param urls
*/
public void add(ArrayList<String> urls){
Iterator<String> urlIterator = urls.iterator();
while(urlIterator.hasNext()){
add(urlIterator.next());
}
}
/**
* 返回 是否为空
* @return
*/
public boolean isEmpty(){
return urlQueue.isEmpty();
}
/**
* 返回一个元素
* @return
*/
public String get(){
String firstMember = urlQueue.poll();// 从队列中读取第一个地址数据
if(!visitedURL.contains(firstMember)){ // 将该即将被访问的地址加入到已经访问的集合中
visitedURL.add(firstMember);
}
return firstMember;
}
/**
* 返回 队列的长度
* @return
*/
public int size(){
return urlQueue.size();// 返回队列的大小
}
}
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.regex.Pattern;
public class URLUtility {
private static String m_urlPatternString = "(?i)(?s)<\\s*?a.*?href=\"(.*?)\".*?>";
private static Pattern m_urlPattern = Pattern.compile(m_urlPatternString);
/**
* 将"url"变为编码为合法的URL
*/
public static String Encode(String url) {
String res = "";
for(char c : url.toCharArray()) {
if( !":/.?&#=".contains("" + c) ) {// 这些字符不能出现于URL中
try {
res += URLEncoder.encode("" + c, "UTF-8"); // 转成UTF-8
} catch (UnsupportedEncodingException e){}
} else {
res += c;
}
}
return res;
}
public static String Normalizer(String url) {
url = url.replaceAll("&", "&");
if( url.endsWith("/") ) {
url = url.substring(0, url.length() - 1);
}
return url;
}
//拼接URL
public static String Refine(String baseUrl, String relative) {
if( baseUrl == null || relative == null ) {
return null;
}
final Url base = Parse(baseUrl), url = Parse(relative);
if( base == null || url == null ) {
return null;
}
if( url.scheme == null ) {
url.scheme = base.scheme;
if( url.host == null ) {
url.host = base.host;
}
}
if( url.path.startsWith("../") ) {
String prefix = "";
int idx = base.path.lastIndexOf('/');
if( (idx = base.path.lastIndexOf('/', idx - 1)) > 0 ) prefix = base.path.substring(0, idx);
url.path = prefix + url.path.substring(3);
}
return Normalizer(url.ToUrl());
}
//拆分URL成scheme, host, path
private static Url Parse(String link) {
int idx, endIndex;
final Url url = new Url();
if( (idx = link.indexOf("#")) >= 0 ) { //ignore fragment
if( idx == 0 ) return null;
else link = link.substring(0, idx - 1);
}
if( (idx = link.indexOf(":")) > 0 ) {
url.scheme = link.substring(0, idx).trim();
if( IsLegalScheme(url.scheme) ) {
link = link.substring(idx + 1);
}
else {
return null;
}
}
if( link.startsWith("//") ) {
if( (endIndex = link.indexOf('/', 2)) > 0 ) {
url.host = link.substring(2, endIndex).trim();
link = link.substring(endIndex + 1);
}
else {
url.host = link.substring(2).trim();
link = null;
}
}
if( link != null )
url.path = link.trim();
else
url.path = "";
return url;
}
/**
* 判断scheme是否合法(要处理的scheme类型)
*/
private static boolean IsLegalScheme(String scheme) {
if( scheme.equals("http") || scheme.equals("https") || scheme.equals("ftp") )
return true;
else
return false;
}
private static class Url {
public Url() {}
public String ToUrl() {
String prefix = null;
if( path.startsWith("/") )
prefix = scheme + "://" + host;
else
prefix = scheme + "://" + host + "/";
return prefix + path;
}
public String scheme;
public String host;
public String path;
}
}