简易爬虫的实现

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
  1. package com.ie;
  2.  
  3. import java.util.Set;
  4. public class Crawler {
  5. /* 使用种子 url 初始化 URL 队列*/
  6. private void initCrawlerWithSeeds(String[] seeds)
  7. {
  8. for(int i=0;i<seeds.length;i++)
  9. LinkDB.addUnvisitedUrl(seeds[i]);
  10. }
  11.  
  12. /* 爬取方法*/
  13. public void crawling(String[] seeds)
  14. {
  15. LinkFilter filter = new LinkFilter(){
  16. //提取以 http://www.twt.edu.cn 开头的链接
  17. public boolean accept(String url) {
  18. if(url.startsWith("http://www.twt.edu.cn"))
  19. return true;
  20. else
  21. return false;
  22. }
  23. };
  24. //初始化 URL 队列
  25. initCrawlerWithSeeds(seeds);
  26. //循环条件:待抓取的链接不空且抓取的网页不多于 1000
  27. while(!LinkDB.unVisitedUrlsEmpty()&&LinkDB.getVisitedUrlNum()<=1000)
  28. {
  29. //队头 URL 出对
  30. String visitUrl=LinkDB.unVisitedUrlDeQueue();
  31. if(visitUrl==null)
  32. continue;
  33. FileDownLoader downLoader=new FileDownLoader();
  34. //下载网页
  35. downLoader.downloadFile(visitUrl);
  36. //该 url 放入到已访问的 URL 中
  37. LinkDB.addVisitedUrl(visitUrl);
  38. //提取出下载网页中的 URL
  39.  
  40. Set<String> links=HtmlParserTool.extracLinks(visitUrl,filter);
  41. //新的未访问的 URL 入队
  42. for(String link:links)
  43. {
  44. LinkDB.addUnvisitedUrl(link);
  45. }
  46. }
  47. }
  48. //main 方法入口
  49. public static void main(String[]args)
  50. {
  51. Crawler crawler = new Crawler();
  52. crawler.crawling(new String[]{"http://www.twt.edu.cn"});
  53. }
  54. }
清单7 LinkDb.java
  1. package com.ie;
  2.  
  3. import java.util.HashSet;
  4. import java.util.Set;
  5.  
  6. /**
  7. * 用来保存已经访问过 Url 和待访问的 Url 的类
  8. */
  9. public class LinkDB {
  10.  
  11. //已访问的 url 集合
  12. private static Set<String> visitedUrl = new HashSet<String>();
  13. //待访问的 url 集合
  14. private static Queue<String> unVisitedUrl = new Queue<String>();
  15.  
  16. public static Queue<String> getUnVisitedUrl() {
  17. return unVisitedUrl;
  18. }
  19.  
  20. public static void addVisitedUrl(String url) {
  21. visitedUrl.add(url);
  22. }
  23.  
  24. public static void removeVisitedUrl(String url) {
  25. visitedUrl.remove(url);
  26. }
  27.  
  28. public static String unVisitedUrlDeQueue() {
  29. return unVisitedUrl.deQueue();
  30. }
  31.  
  32. // 保证每个 url 只被访问一次
  33. public static void addUnvisitedUrl(String url) {
  34. if (url != null && !url.trim().equals("")
  35. && !visitedUrl.contains(url)
  36. && !unVisitedUrl.contians(url))
  37. unVisitedUrl.enQueue(url);
  38. }
  39.  
  40. public static int getVisitedUrlNum() {
  41. return visitedUrl.size();
  42. }
  43.  
  44. public static boolean unVisitedUrlsEmpty() {
  45. return unVisitedUrl.empty();
  46. }
  47. }
清单8 Queue.java
  1. package com.ie;
  2.  
  3. import java.util.LinkedList;
  4. /**
  5. * 数据结构队列
  6. */
  7. public class Queue<T> {
  8.  
  9. private LinkedList<T> queue=new LinkedList<T>();
  10.  
  11. public void enQueue(T t)
  12. {
  13. queue.addLast(t);
  14. }
  15.  
  16. public T deQueue()
  17. {
  18. return queue.removeFirst();
  19. }
  20.  
  21. public boolean isQueueEmpty()
  22. {
  23. return queue.isEmpty();
  24. }
  25.  
  26. public boolean contians(T t)
  27. {
  28. return queue.contains(t);
  29. }
  30.  
  31. public boolean empty()
  32. {
  33. return queue.isEmpty();
  34. }
  35. }
清单 9 FileDownLoader.java
  1. package com.ie;
  2.  
  3. import java.io.DataOutputStream;
  4. import java.io.File;
  5. import java.io.FileOutputStream;
  6. import java.io.IOException;
  7. import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;
  8. import org.apache.commons.httpclient.HttpClient;
  9. import org.apache.commons.httpclient.HttpException;
  10. import org.apache.commons.httpclient.HttpStatus;
  11. import org.apache.commons.httpclient.methods.GetMethod;
  12. import org.apache.commons.httpclient.params.HttpMethodParams;
  13.  
  14. public class FileDownLoader {
  15.  
  16. /**根据 url 和网页类型生成需要保存的网页的文件名
  17. *去除掉 url 中非文件名字符
  18. */
  19. public String getFileNameByUrl(String url,String contentType)
  20. {
  21. url=url.substring(7);//remove http://
  22. if(contentType.indexOf("html")!=-1)//text/html
  23. {
  24. url= url.replaceAll("[\\?/:*|<>\"]", "_")+".html";
  25. return url;
  26. }
  27. else//如application/pdf
  28. {
  29. return url.replaceAll("[\\?/:*|<>\"]", "_")+"."+ \
  30. contentType.substring(contentType.lastIndexOf("/")+1);
  31. }
  32. }
  33.  
  34. /**保存网页字节数组到本地文件
  35. * filePath 为要保存的文件的相对地址
  36. */
  37. private void saveToLocal(byte[] data,String filePath)
  38. {
  39. try {
  40. DataOutputStream out=new DataOutputStream(
  41. new FileOutputStream(new File(filePath)));
  42. for(int i=0;i<data.length;i++)
  43. out.write(data[i]);
  44. out.flush();
  45. out.close();
  46. } catch (IOException e) {
  47. e.printStackTrace();
  48. }
  49. }
  50.  
  51. /*下载 url 指向的网页*/
  52. public String downloadFile(String url)
  53. {
  54. String filePath=null;
  55. /* 1.生成 HttpClinet 对象并设置参数*/
  56. HttpClient httpClient=new HttpClient();
  57. //设置 Http 连接超时 5s
  58. httpClient.getHttpConnectionManager().getParams().
  59. setConnectionTimeout(5000);
  60.  
  61. /*2.生成 GetMethod 对象并设置参数*/
  62. GetMethod getMethod=new GetMethod(url);
  63. //设置 get 请求超时 5s
  64. getMethod.getParams().setParameter(HttpMethodParams.SO_TIMEOUT,5000);
  65. //设置请求重试处理
  66. getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
  67. new DefaultHttpMethodRetryHandler());
  68.  
  69. /*3.执行 HTTP GET 请求*/
  70. try{
  71. int statusCode = httpClient.executeMethod(getMethod);
  72. //判断访问的状态码
  73. if (statusCode != HttpStatus.SC_OK)
  74. {
  75. System.err.println("Method failed: "+ getMethod.getStatusLine());
  76. filePath=null;
  77. }
  78.  
  79. /*4.处理 HTTP 响应内容*/
  80. byte[] responseBody = getMethod.getResponseBody();//读取为字节数组
  81. //根据网页 url 生成保存时的文件名
  82. filePath="temp\\"+getFileNameByUrl(url,
  83. getMethod.getResponseHeader("Content-Type").getValue());
  84. saveToLocal(responseBody,filePath);
  85. } catch (HttpException e) {
  86. // 发生致命的异常,可能是协议不对或者返回的内容有问题
  87. System.out.println("Please check your provided http
  88. address!");
  89. e.printStackTrace();
  90. } catch (IOException e) {
  91. // 发生网络异常
  92. e.printStackTrace();
  93. } finally {
  94. // 释放连接
  95. getMethod.releaseConnection();
  96. }
  97. return filePath;
  98. }
  99. //测试的 main 方法
  100. public static void main(String[]args)
  101. {
  102. FileDownLoader downLoader = new FileDownLoader();
  103. downLoader.downloadFile("http://www.twt.edu.cn");
  104. }
  105. }
清单 10 HtmlParserTool.java
  1. package com.ie;
  2.  
  3. import java.util.HashSet;
  4. import java.util.Set;
  5.  
  6. import org.htmlparser.Node;
  7. import org.htmlparser.NodeFilter;
  8. import org.htmlparser.Parser;
  9. import org.htmlparser.filters.NodeClassFilter;
  10. import org.htmlparser.filters.OrFilter;
  11. import org.htmlparser.tags.LinkTag;
  12. import org.htmlparser.util.NodeList;
  13. import org.htmlparser.util.ParserException;
  14.  
  15. public class HtmlParserTool {
  16. // 获取一个网站上的链接,filter 用来过滤链接
  17. public static Set<String> extracLinks(String url,LinkFilter filter) {
  18.  
  19. Set<String> links = new HashSet<String>();
  20. try {
  21. Parser parser = new Parser(url);
  22. parser.setEncoding("gb2312");
  23. // 过滤 <frame >标签的 filter,用来提取 frame 标签里的 src 属性所表示的链接
  24. NodeFilter frameFilter = new NodeFilter() {
  25. public boolean accept(Node node) {
  26. if (node.getText().startsWith("frame src=")) {
  27. return true;
  28. } else {
  29. return false;
  30. }
  31. }
  32. };
  33. // OrFilter 来设置过滤 <a> 标签,和 <frame> 标签
  34. OrFilter linkFilter = new OrFilter(new NodeClassFilter(
  35. LinkTag.class), frameFilter);
  36. // 得到所有经过过滤的标签
  37. NodeList list = parser.extractAllNodesThatMatch(linkFilter);
  38. for (int i = 0; i < list.size(); i++) {
  39. Node tag = list.elementAt(i);
  40. if (tag instanceof LinkTag)// <a> 标签
  41. {
  42. LinkTag link = (LinkTag) tag;
  43. String linkUrl = link.getLink();// url
  44. if(filter.accept(linkUrl))
  45. links.add(linkUrl);
  46. } else// <frame> 标签
  47. {
  48. // 提取 frame 里 src 属性的链接如 <frame src="test.html"/>
  49. String frame = tag.getText();
  50. int start = frame.indexOf("src=");
  51. frame = frame.substring(start);
  52. int end = frame.indexOf(" ");
  53. if (end == -1)
  54. end = frame.indexOf(">");
  55. String frameUrl = frame.substring(5, end - 1);
  56. if(filter.accept(frameUrl))
  57. links.add(frameUrl);
  58. }
  59. }
  60. } catch (ParserException e) {
  61. e.printStackTrace();
  62. }
  63. return links;
  64. }
  65. //测试的 main 方法
  66. public static void main(String[]args)
  67. {
  68. Set<String> links = HtmlParserTool.extracLinks(
  69. "http://www.twt.edu.cn",new LinkFilter()
  70. {
  71. //提取以 http://www.twt.edu.cn 开头的链接
  72. public boolean accept(String url) {
  73. if(url.startsWith("http://www.twt.edu.cn"))
  74. return true;
  75. else
  76. return false;
  77. }
  78.  
  79. });
  80. for(String link : links)
  81. System.out.println(link);
  82. }
  83. }
  84. 清单11 LinkFilter.java
  85. package com.ie;
  86.  
  87. public interface LinkFilter {
  88. public boolean accept(String url);
  89. }

这些代码中关键的部分都在 HttpClient 和 HtmlParser 介绍中说明过了。

HtmlParser + HttpClient 实现爬虫的更多相关文章

  1. 使用 HttpClient 和 HtmlParser 实现简易爬虫

    这篇文章介绍了 HtmlParser 开源包和 HttpClient 开源包的使用,在此基础上实现了一个简易的网络爬虫 (Crawler),来说明如何使用 HtmlParser 根据需要处理 Inte ...

  2. [转]使用 HttpClient 和 HtmlParser 实现简易爬虫

    http://www.ibm.com/developerworks/cn/opensource/os-cn-crawler/ http://blog.csdn.net/dancen/article/d ...

  3. HttpClient&Jsoup爬虫的简单应用

    详细的介绍已经有很多前辈总结,引用一下该篇文章:https://blog.csdn.net/zhuwukai/article/details/78644484 下面是一个代码的示例: package ...

  4. HtmlParser的使用-爬虫学习(三)

    关于这个HtmlParser的学习资料,网上真的很匮乏,这个好用的东西不要浪费啊,所以我在这里隆重的介绍一下. HtmlParser是一个用来解析HTML文件的Java包,主要用于转换盒抽取两个方面. ...

  5. HttpClient和 HtmlParser实现爬虫

    网络爬虫技术 1       什么叫网络爬虫 网络爬虫(又被称为网页蜘蛛,网络机器人,在FOAF社区中间,更经常的称为网页追逐者),是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本.另外一些不 ...

  6. webmagic的设计机制及原理-如何开发一个Java爬虫

    之前就有网友在博客里留言,觉得webmagic的实现比较有意思,想要借此研究一下爬虫.最近终于集中精力,花了三天时间,终于写完了这篇文章.之前垂直爬虫写了一年多,webmagic框架写了一个多月,这方 ...

  7. webmagic的设计机制及原理-如何开发一个Java爬虫 转

    此文章是webmagic 0.1.0版的设计手册,后续版本的入门及用户手册请看这里:https://github.com/code4craft/webmagic/blob/master/user-ma ...

  8. httpparase + httpclient 的运用

    这篇文章介绍了 HtmlParser 开源包和 HttpClient 开源包的使用,在此基础上实现了一个简易的网络爬虫 (Crawler),来说明如何使用 HtmlParser 根据需要处理 Inte ...

  9. HttpClient 版本变化 转载

    转载地址:http://my.oschina.net/u/577453/blog/173724 最近用到了HttpClient写爬虫,可能我有新版本强迫症,老是喜欢用新版本的东西(虽说新版本不一定好用 ...

随机推荐

  1. Android拍照保存图片内存大小

    图片拍摄的大小会随着硬件而变化,比如,像素高的相机拍出来的图片要比像素低的图片内存要大. 如此一来,针对机型可能调用camera app保存照片的时候,图片大小会不一样. 为了缩小图片大小,我们需要把 ...

  2. Codeforces Round #151 (Div. 2)

    A. Buggy Sorting \(n \ge 3\)时,序列\(n.n-1.\cdots.1\)即可. B. Increase and Decrease 考虑和是否能被\(n\)整除. C. Be ...

  3. 归并排序 空间复杂度为O(1)的做法

    #include <iostream> #include <cstdlib> using namespace std; void print(int *arr, int sta ...

  4. Javascript对象属性与方法汇总

    Javascript对象属性与方法汇总 发布时间:2015-03-06 编辑:www.jquerycn.cn 详细介绍下,javascript对象属性与对象方法的相关知识,包括javascript字符 ...

  5. Kali linux渗透测试的艺术 思维导图

    Kali Linux是一个全面的渗透测试平台,其自带的高级工具可以用来识别.检测和利用目标网络中未被发现的漏洞.借助于Kali Linux,你可以根据已定义的业务目标和预定的测试计划,应用合适的测试方 ...

  6. 复利计算- 结对2.0--复利计算WEB升级版

    客户在大家的引导下,有了更多的想法: 这个数据我经常会填.....帮我预先填上呗?...... 把界面做得简单漂亮好操作一点呗? 能不能帮我转成个APP,我装到手机上就更方便了? 我觉得这个很有用,很 ...

  7. PHP pdao用法总结

    $sql = 'SELECT name, colour, calories     FROM fruit     WHERE calories < :calories AND colour =  ...

  8. IT运维管理市场

    背景 http://www.cnitom.com/portal.php 中国it运维网 http://www.365master.com it运维网 http://www.51ou.com/ 51运维 ...

  9. css之padding,marging

    padding:内边距,所有浏览器都支持,不允许使用负值 继承内部格式生成了10px的边距. 属性: auto:浏览器计算机内边距. length:规定以具体单位计的内边距值,比如像素.厘米等.默认值 ...

  10. .git 目录文件介绍

    $>tree -L 1.|-- HEAD         # 这个git项目当前处在哪个分支里|-- config       # 项目的配置信息,git config命令会改动它|-- des ...