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写爬虫,可能我有新版本强迫症,老是喜欢用新版本的东西(虽说新版本不一定好用 ...
随机推荐
- Android拍照保存图片内存大小
图片拍摄的大小会随着硬件而变化,比如,像素高的相机拍出来的图片要比像素低的图片内存要大. 如此一来,针对机型可能调用camera app保存照片的时候,图片大小会不一样. 为了缩小图片大小,我们需要把 ...
- Codeforces Round #151 (Div. 2)
A. Buggy Sorting \(n \ge 3\)时,序列\(n.n-1.\cdots.1\)即可. B. Increase and Decrease 考虑和是否能被\(n\)整除. C. Be ...
- 归并排序 空间复杂度为O(1)的做法
#include <iostream> #include <cstdlib> using namespace std; void print(int *arr, int sta ...
- Javascript对象属性与方法汇总
Javascript对象属性与方法汇总 发布时间:2015-03-06 编辑:www.jquerycn.cn 详细介绍下,javascript对象属性与对象方法的相关知识,包括javascript字符 ...
- Kali linux渗透测试的艺术 思维导图
Kali Linux是一个全面的渗透测试平台,其自带的高级工具可以用来识别.检测和利用目标网络中未被发现的漏洞.借助于Kali Linux,你可以根据已定义的业务目标和预定的测试计划,应用合适的测试方 ...
- 复利计算- 结对2.0--复利计算WEB升级版
客户在大家的引导下,有了更多的想法: 这个数据我经常会填.....帮我预先填上呗?...... 把界面做得简单漂亮好操作一点呗? 能不能帮我转成个APP,我装到手机上就更方便了? 我觉得这个很有用,很 ...
- PHP pdao用法总结
$sql = 'SELECT name, colour, calories FROM fruit WHERE calories < :calories AND colour = ...
- IT运维管理市场
背景 http://www.cnitom.com/portal.php 中国it运维网 http://www.365master.com it运维网 http://www.51ou.com/ 51运维 ...
- css之padding,marging
padding:内边距,所有浏览器都支持,不允许使用负值 继承内部格式生成了10px的边距. 属性: auto:浏览器计算机内边距. length:规定以具体单位计的内边距值,比如像素.厘米等.默认值 ...
- .git 目录文件介绍
$>tree -L 1.|-- HEAD # 这个git项目当前处在哪个分支里|-- config # 项目的配置信息,git config命令会改动它|-- des ...