HtmlParser + HttpClient 实现爬虫
简易爬虫的实现
HttpClient 提供了便利的 HTTP 协议访问,使得我们可以很容易的得到某个网页的源码并保存在本地;HtmlParser 提供了如此简便灵巧的类库,可以从网页中便捷的提取出指向其他网页的超链接。笔者结合这两个开源包,构建了一个简易的网络爬虫。
爬虫 (Crawler) 原理
学过数据结构的读者都知道有向图这种数据结构。如下图所示,如果将网页看成是图中的某一个节点,而将网页中指向其他网页的链接看成是这个节点指向其他节点的边,那么我们很容易将整个 Internet 上的网页建模成一个有向图。理论上,通过遍历算法遍历该图,可以访问到Internet 上的几乎所有的网页。最简单的遍历就是宽度优先以及深度优先。以下笔者实现的简易爬虫就是使用了宽度优先的爬行策略。
图 2. 网页关系的建模图

简易爬虫实现流程
在看简易爬虫的实现代码之前,先介绍一下简易爬虫爬取网页的流程。
图 3. 爬虫流程图

各个类的源码以及说明
对应上面的流程图,简易爬虫由下面几个类组成,各个类职责如下:
Crawler.java:爬虫的主方法入口所在的类,实现爬取的主要流程。
LinkDb.java:用来保存已经访问的 url 和待爬取的 url 的类,提供url出对入队操作。
Queue.java: 实现了一个简单的队列,在 LinkDb.java 中使用了此类。
FileDownloader.java:用来下载 url 所指向的网页。
HtmlParserTool.java: 用来抽取出网页中的链接。
LinkFilter.java:一个接口,实现其 accept() 方法用来对抽取的链接进行过滤。
下面是各个类的源码,代码中的注释有比较详细的说明。
清单6 Crawler.java
package com.ie; import java.util.Set;
public class Crawler {
/* 使用种子 url 初始化 URL 队列*/
private void initCrawlerWithSeeds(String[] seeds)
{
for(int i=0;i<seeds.length;i++)
LinkDB.addUnvisitedUrl(seeds[i]);
} /* 爬取方法*/
public void crawling(String[] seeds)
{
LinkFilter filter = new LinkFilter(){
//提取以http://www.twt.edu.cn开头的链接
public boolean accept(String url) {
if(url.startsWith("http://www.twt.edu.cn"))
return true;
else
return false;
}
};
//初始化 URL 队列
initCrawlerWithSeeds(seeds);
//循环条件:待抓取的链接不空且抓取的网页不多于 1000
while(!LinkDB.unVisitedUrlsEmpty()&&LinkDB.getVisitedUrlNum()<=1000)
{
//队头 URL 出对
String visitUrl=LinkDB.unVisitedUrlDeQueue();
if(visitUrl==null)
continue;
FileDownLoader downLoader=new FileDownLoader();
//下载网页
downLoader.downloadFile(visitUrl);
//该 url 放入到已访问的 URL 中
LinkDB.addVisitedUrl(visitUrl);
//提取出下载网页中的 URL Set<String> links=HtmlParserTool.extracLinks(visitUrl,filter);
//新的未访问的 URL 入队
for(String link:links)
{
LinkDB.addUnvisitedUrl(link);
}
}
}
//main 方法入口
public static void main(String[]args)
{
Crawler crawler = new Crawler();
crawler.crawling(new String[]{"http://www.twt.edu.cn"});
}
}
清单7 LinkDb.java
package com.ie; import java.util.HashSet;
import java.util.Set; /**
* 用来保存已经访问过 Url 和待访问的 Url 的类
*/
public class LinkDB { //已访问的 url 集合
private static Set<String> visitedUrl = new HashSet<String>();
//待访问的 url 集合
private static Queue<String> unVisitedUrl = new Queue<String>(); public static Queue<String> getUnVisitedUrl() {
return unVisitedUrl;
} public static void addVisitedUrl(String url) {
visitedUrl.add(url);
} public static void removeVisitedUrl(String url) {
visitedUrl.remove(url);
} public static String unVisitedUrlDeQueue() {
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);
} public static int getVisitedUrlNum() {
return visitedUrl.size();
} public static boolean unVisitedUrlsEmpty() {
return unVisitedUrl.empty();
}
}
清单8 Queue.java
package com.ie; import java.util.LinkedList;
/**
* 数据结构队列
*/
public class Queue<T> { private LinkedList<T> queue=new LinkedList<T>(); public void enQueue(T t)
{
queue.addLast(t);
} public T deQueue()
{
return queue.removeFirst();
} public boolean isQueueEmpty()
{
return queue.isEmpty();
} public boolean contians(T t)
{
return queue.contains(t);
} public boolean empty()
{
return queue.isEmpty();
}
}
清单 9 FileDownLoader.java
package com.ie; 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 FileDownLoader { /**根据 url 和网页类型生成需要保存的网页的文件名
*去除掉 url 中非文件名字符
*/
public String getFileNameByUrl(String url,String contentType)
{
url=url.substring(7);//remove http://
if(contentType.indexOf("html")!=-1)//text/html
{
url= url.replaceAll("[\\?/:*|<>\"]", "_")+".html";
return url;
}
else//如application/pdf
{
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;
}
//测试的 main 方法
public static void main(String[]args)
{
FileDownLoader downLoader = new FileDownLoader();
downLoader.downloadFile("http://www.twt.edu.cn");
}
}
清单 10 HtmlParserTool.java
package com.ie; 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;
}
//测试的 main 方法
public static void main(String[]args)
{
Set<String> links = HtmlParserTool.extracLinks(
"http://www.twt.edu.cn",new LinkFilter()
{
//提取以http://www.twt.edu.cn开头的链接
public boolean accept(String url) {
if(url.startsWith("http://www.twt.edu.cn"))
return true;
else
return false;
} });
for(String link : links)
System.out.println(link);
}
}
清单11 LinkFilter.java
package com.ie; public interface LinkFilter {
public boolean accept(String url);
}
这些代码中关键的部分都在 HttpClient 和 HtmlParser 介绍中说明过了。
HtmlParser + HttpClient 实现爬虫的更多相关文章
- 使用 HttpClient 和 HtmlParser 实现简易爬虫
这篇文章介绍了 HtmlParser 开源包和 HttpClient 开源包的使用,在此基础上实现了一个简易的网络爬虫 (Crawler),来说明如何使用 HtmlParser 根据需要处理 Inte ...
- [转]使用 HttpClient 和 HtmlParser 实现简易爬虫
http://www.ibm.com/developerworks/cn/opensource/os-cn-crawler/ http://blog.csdn.net/dancen/article/d ...
- HttpClient&Jsoup爬虫的简单应用
详细的介绍已经有很多前辈总结,引用一下该篇文章:https://blog.csdn.net/zhuwukai/article/details/78644484 下面是一个代码的示例: package ...
- HtmlParser的使用-爬虫学习(三)
关于这个HtmlParser的学习资料,网上真的很匮乏,这个好用的东西不要浪费啊,所以我在这里隆重的介绍一下. HtmlParser是一个用来解析HTML文件的Java包,主要用于转换盒抽取两个方面. ...
- HttpClient和 HtmlParser实现爬虫
网络爬虫技术 1 什么叫网络爬虫 网络爬虫(又被称为网页蜘蛛,网络机器人,在FOAF社区中间,更经常的称为网页追逐者),是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本.另外一些不 ...
- webmagic的设计机制及原理-如何开发一个Java爬虫
之前就有网友在博客里留言,觉得webmagic的实现比较有意思,想要借此研究一下爬虫.最近终于集中精力,花了三天时间,终于写完了这篇文章.之前垂直爬虫写了一年多,webmagic框架写了一个多月,这方 ...
- webmagic的设计机制及原理-如何开发一个Java爬虫 转
此文章是webmagic 0.1.0版的设计手册,后续版本的入门及用户手册请看这里:https://github.com/code4craft/webmagic/blob/master/user-ma ...
- httpparase + httpclient 的运用
这篇文章介绍了 HtmlParser 开源包和 HttpClient 开源包的使用,在此基础上实现了一个简易的网络爬虫 (Crawler),来说明如何使用 HtmlParser 根据需要处理 Inte ...
- HttpClient 版本变化 转载
转载地址:http://my.oschina.net/u/577453/blog/173724 最近用到了HttpClient写爬虫,可能我有新版本强迫症,老是喜欢用新版本的东西(虽说新版本不一定好用 ...
随机推荐
- Mvc3.0_笔记
1.保留文本框中的值:<p>Find @Html.TextBox("searchKey", ViewBag.Filter as string)</p> 这 ...
- C++程序结构---1
C++ 基础教程Beta 版 原作:Juan Soulié 翻译:Jing Xu (aqua) 英文原版 本教程根据Juan Soulie的英文版C++教程翻译并改编. 本版为最新校对版,尚未定稿.如 ...
- Linux驱动设计—— 内外存访问
本节对内外存访问做详细的介绍. 驱动程序加载成功的一个关键因素,就是内核能够为驱动程序分配足够的内存空间.这些空间一部分用于驱动程序必要的数据结构,另一部分用于数据的交换.同时,内核也应该具有访问外部 ...
- Learn python the hard way. python test program 2016.04.27
# this will not be printed in python ! print "I could have code like this." # and the comm ...
- JVM 虚拟化
http://www.infoq.com/cn/news/2015/05/java20-multitenant-jvm http://2016.qconshanghai.com/presentatio ...
- Qt QTreeWidget节点的添加+双击响应+删除详解(转)
QTreeWidget是实现树形结构的类,在很多软件中都可以看到类似树形结构的界面. 我做的一个示例如下图,用来处理图像,最顶层节点是图像的路径名,子节点是图像的各个波段,双击各个波段会显示图像各波段 ...
- javascript 遍历object对象
(function(){ var str = ''; for(var i in obj){ //遍历object str += '\n'+(i+' : '+obj[i]); // i+' : '+ob ...
- why does txid_current() assign new transaction-id?
Naoya: Hi,hackers! I have a question about txid_current(). it is "Why does txid_current() assig ...
- python数据库连接池
python数据库连接池 import psycopg2 import psycopg2.pool dbpool=psycopg2.pool.PersistentConnectionPool(1,1, ...
- sql 2000 NOLOCK 和 ROWLOCK 和 UPDLOCK
关系型数据库,如SQL Server,使用锁来避免多用户修改数据时的并发冲突.当一组数据被某个用户锁定时,除非第一个用户结束修改并释放锁,否则其他用户就无法修改该组数据. 有些数据库,包括SQL Se ...