教你编写简单的网络爬虫

http://blog.csdn.net/dreamrealised/article/details/12612349?ADUIN=283302847&ADSESSION=1392861528&ADTAG=CLIENT.QQ.5281_.0&ADPUBNO=26292#userconsent#

一、网络爬虫的基本知识

网络爬虫通过遍历互联网络,把网络中的相关网页全部抓取过来,这体现了爬的概念。爬虫如何遍历网络呢,互联网可以看做是一张大图,每个页面看做其中的一个节点,页面的连接看做是有向边。图的遍历方式分为宽度遍历和深度遍历,但是深度遍历可能会在深度上过深的遍历或者陷入黑洞。所以,大多数爬虫不采用这种形式。另一方面,爬虫在按照宽度优先遍历的方式时候,会给待遍历的网页赋予一定优先级,这种叫做带偏好的遍历。
实际的爬虫是从一系列的种子链接开始。种子链接是起始节点,种子页面的超链接指向的页面是子节点(中间节点),对于非html文档,如excel等,不能从中提取超链接,看做图的终端节点。整个遍历过程中维护一张visited表,记录哪些节点(链接)已经处理过了,跳过不作处理。
使用宽度优先搜索策略,主要原因有:
a、重要的网页一般离种子比较近,例如我们打开的新闻网站时候,往往是最热门的新闻,随着深入冲浪,网页的重要性越来越低。
b、万维网实际深度最多达17层,但到达某个网页总存在一条很短路径,而宽度优先遍历可以最快的速度找到这个网页
c、宽度优先有利于多爬虫合作抓取。
二、网络爬虫的简单实现

1、定义已访问队列,待访问队列和爬取得URL的哈希表,包括出队列,入队列,判断队列是否空等操作

[java]  view plain copy
  1. package webspider;  
  2. import java.util.HashSet;  
  3. import java.util.PriorityQueue;  
  4. import java.util.Set;  
  5. import java.util.Queue;  
  6.   
  7. public class LinkQueue {  
  8.     // 已访问的 url 集合  
  9.     private static Set visitedUrl = new HashSet();  
  10.     // 待访问的 url 集合  
  11.     private static Queue unVisitedUrl = new PriorityQueue();  
  12.   
  13.     // 获得URL队列  
  14.     public static Queue getUnVisitedUrl() {  
  15.         return unVisitedUrl;  
  16.     }  
  17.   
  18.     // 添加到访问过的URL队列中  
  19.     public static void addVisitedUrl(String url) {  
  20.         visitedUrl.add(url);  
  21.     }  
  22.   
  23.     // 移除访问过的URL  
  24.     public static void removeVisitedUrl(String url) {  
  25.         visitedUrl.remove(url);  
  26.     }  
  27.   
  28.     // 未访问的URL出队列  
  29.     public static Object unVisitedUrlDeQueue() {  
  30.         return unVisitedUrl.poll();  
  31.     }  
  32.   
  33.     // 保证每个 url 只被访问一次  
  34.     public static void addUnvisitedUrl(String url) {  
  35.         if (url != null && !url.trim().equals("") && !visitedUrl.contains(url)  
  36.                 && !unVisitedUrl.contains(url))  
  37.             unVisitedUrl.add(url);  
  38.     }  
  39.   
  40.     // 获得已经访问的URL数目  
  41.     public static int getVisitedUrlNum() {  
  42.         return visitedUrl.size();  
  43.     }  
  44.   
  45.     // 判断未访问的URL队列中是否为空  
  46.     public static boolean unVisitedUrlsEmpty() {  
  47.         return unVisitedUrl.isEmpty();  
  48.     }  
  49.   
  50. }  
2、定义DownLoadFile类,根据得到的url,爬取网页内容,下载到本地保存。此处需要引用commons-httpclient.jar,commons-codec.jar,commons-logging.jar。

[java]  view plain copy
  1. package webspider;  
  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 DownLoadFile {  
  15.     /** 
  16.      * 根据 url 和网页类型生成需要保存的网页的文件名 去除掉 url 中非文件名字符 
  17.      */  
  18.     public String getFileNameByUrl(String url, String contentType) {  
  19.         // remove http://  
  20.         url = url.substring(7);  
  21.         // text/html类型  
  22.         if (contentType.indexOf("html") != -1) {  
  23.             url = url.replaceAll("[\\?/:*|<>\"]""_") + ".html";  
  24.             return url;  
  25.         }  
  26.         // 如application/pdf类型  
  27.         else {  
  28.             return url.replaceAll("[\\?/:*|<>\"]""_") + "."  
  29.                     + contentType.substring(contentType.lastIndexOf("/") + 1);  
  30.         }  
  31.     }  
  32.   
  33.     /** 
  34.      * 保存网页字节数组到本地文件 filePath 为要保存的文件的相对地址 
  35.      */  
  36.     private void saveToLocal(byte[] data, String filePath) {  
  37.         try {  
  38.             DataOutputStream out = new DataOutputStream(new FileOutputStream(  
  39.                     new File(filePath)));  
  40.             for (int i = 0; i < data.length; i++)  
  41.                 out.write(data[i]);  
  42.             out.flush();  
  43.             out.close();  
  44.         } catch (IOException e) {  
  45.             e.printStackTrace();  
  46.         }  
  47.     }  
  48.   
  49.     /* 下载 url 指向的网页 */  
  50.     public String downloadFile(String url) {  
  51.         String filePath = null;  
  52.         /* 1.生成 HttpClinet 对象并设置参数 */  
  53.         HttpClient httpClient = new HttpClient();  
  54.         // 设置 Http 连接超时 5s  
  55.         httpClient.getHttpConnectionManager().getParams()  
  56.                 .setConnectionTimeout(5000);  
  57.   
  58.         /* 2.生成 GetMethod 对象并设置参数 */  
  59.         GetMethod getMethod = new GetMethod(url);  
  60.         // 设置 get 请求超时 5s  
  61.         getMethod.getParams().setParameter(HttpMethodParams.SO_TIMEOUT, 5000);  
  62.         // 设置请求重试处理  
  63.         getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,  
  64.                 new DefaultHttpMethodRetryHandler());  
  65.   
  66.         /* 3.执行 HTTP GET 请求 */  
  67.         try {  
  68.             int statusCode = httpClient.executeMethod(getMethod);  
  69.             // 判断访问的状态码  
  70.             if (statusCode != HttpStatus.SC_OK) {  
  71.                 System.err.println("Method failed: "  
  72.                         + getMethod.getStatusLine());  
  73.                 filePath = null;  
  74.             }  
  75.   
  76.             /* 4.处理 HTTP 响应内容 */  
  77.             byte[] responseBody = getMethod.getResponseBody();// 读取为字节数组  
  78.             // 根据网页 url 生成保存时的文件名  
  79.             filePath = "f:\\spider\\"  
  80.                     + getFileNameByUrl(url,  
  81.                             getMethod.getResponseHeader("Content-Type")  
  82.                                     .getValue());  
  83.             saveToLocal(responseBody, filePath);  
  84.         } catch (HttpException e) {  
  85.             // 发生致命的异常,可能是协议不对或者返回的内容有问题  
  86.             System.out.println("Please check your provided http address!");  
  87.             e.printStackTrace();  
  88.         } catch (IOException e) {  
  89.             // 发生网络异常  
  90.             e.printStackTrace();  
  91.         } finally {  
  92.             // 释放连接  
  93.             getMethod.releaseConnection();  
  94.         }  
  95.         return filePath;  
  96.     }  
  97. }  

3、定义HtmlParserTool类,用来获得网页中的超链接(包括a标签,frame中的src等等),即为了得到子节点的URL。需要引入htmlparser.jar

[java]  view plain copy
  1. package webspider;  
  2.   
  3. import java.util.HashSet;  
  4. import java.util.Set;  
  5. import org.htmlparser.Node;  
  6. import org.htmlparser.NodeFilter;  
  7. import org.htmlparser.Parser;  
  8. import org.htmlparser.filters.NodeClassFilter;  
  9. import org.htmlparser.filters.OrFilter;  
  10. import org.htmlparser.tags.LinkTag;  
  11. import org.htmlparser.util.NodeList;  
  12. import org.htmlparser.util.ParserException;  
  13.   
  14. public class HtmlParserTool {  
  15.     // 获取一个网站上的链接,filter 用来过滤链接  
  16.     public static Set<String> extracLinks(String url, LinkFilter filter) {  
  17.   
  18.         Set<String> links = new HashSet<String>();  
  19.         try {  
  20.             Parser parser = new Parser(url);  
  21.             //parser.setEncoding("utf-8");  
  22.             // 过滤 <frame >标签的 filter,用来提取 frame 标签里的 src 属性所表示的链接  
  23.             NodeFilter frameFilter = new NodeFilter() {  
  24.                 public boolean accept(Node node) {  
  25.                     if (node.getText().startsWith("frame src=")) {  
  26.                         return true;  
  27.                     } else {  
  28.                         return false;  
  29.                     }  
  30.                 }  
  31.             };  
  32.             // OrFilter 来设置过滤 <a> 标签,和 <frame> 标签  
  33.             OrFilter linkFilter = new OrFilter(new NodeClassFilter(  
  34.                     LinkTag.class), frameFilter);  
  35.             // 得到所有经过过滤的标签  
  36.             NodeList list = parser.extractAllNodesThatMatch(linkFilter);  
  37.             for (int i = 0; i < list.size(); i++) {  
  38.                 Node tag = list.elementAt(i);  
  39.                 if (tag instanceof LinkTag)// <a> 标签  
  40.                 {  
  41.                     LinkTag link = (LinkTag) tag;  
  42.                     String linkUrl = link.getLink();// url  
  43.                     if (filter.accept(linkUrl))  
  44.                         links.add(linkUrl);  
  45.                 } else// <frame> 标签  
  46.                 {  
  47.                     // 提取 frame 里 src 属性的链接如 <frame src="test.html"/>  
  48.                     String frame = tag.getText();  
  49.                     int start = frame.indexOf("src=");  
  50.                     frame = frame.substring(start);  
  51.                     int end = frame.indexOf(" ");  
  52.                     if (end == -1)  
  53.                         end = frame.indexOf(">");  
  54.                     String frameUrl = frame.substring(5, end - 1);  
  55.                     if (filter.accept(frameUrl))  
  56.                         links.add(frameUrl);  
  57.                 }  
  58.             }  
  59.         } catch (ParserException e) {  
  60.             e.printStackTrace();  
  61.         }  
  62.         return links;  
  63.     }  
  64. }  
4、编写测试类MyCrawler,用来测试爬取效果
[java]  view plain copy
  1. package webspider;  
  2.   
  3. import java.util.Set;  
  4.   
  5. public class MyCrawler {  
  6.     /** 
  7.      * 使用种子初始化 URL 队列 
  8.      *  
  9.      * @return 
  10.      * @param seeds 
  11.      *            种子URL 
  12.      */  
  13.     private void initCrawlerWithSeeds(String[] seeds) {  
  14.         for (int i = 0; i < seeds.length; i++)  
  15.             LinkQueue.addUnvisitedUrl(seeds[i]);  
  16.     }  
  17.   
  18.     /** 
  19.      * 抓取过程 
  20.      *  
  21.      * @return 
  22.      * @param seeds 
  23.      */  
  24.     public void crawling(String[] seeds) { // 定义过滤器,提取以http://www.lietu.com开头的链接  
  25.         LinkFilter filter = new LinkFilter() {  
  26.             public boolean accept(String url) {  
  27.                 if (url.startsWith("http://www.baidu.com"))  
  28.                     return true;  
  29.                 else  
  30.                     return false;  
  31.             }  
  32.         };  
  33.         // 初始化 URL 队列  
  34.         initCrawlerWithSeeds(seeds);  
  35.         // 循环条件:待抓取的链接不空且抓取的网页不多于1000  
  36.         while (!LinkQueue.unVisitedUrlsEmpty()  
  37.                 && LinkQueue.getVisitedUrlNum() <= 1000) {  
  38.             // 队头URL出队列  
  39.             String visitUrl = (String) LinkQueue.unVisitedUrlDeQueue();  
  40.             if (visitUrl == null)  
  41.                 continue;  
  42.             DownLoadFile downLoader = new DownLoadFile();  
  43.             // 下载网页  
  44.             downLoader.downloadFile(visitUrl);  
  45.             // 该 url 放入到已访问的 URL 中  
  46.             LinkQueue.addVisitedUrl(visitUrl);  
  47.             // 提取出下载网页中的 URL  
  48.             Set<String> links = HtmlParserTool.extracLinks(visitUrl, filter);  
  49.             // 新的未访问的 URL 入队  
  50.             for (String link : links) {  
  51.                 LinkQueue.addUnvisitedUrl(link);  
  52.             }  
  53.         }  
  54.     }  
  55.   
  56.     // main 方法入口  
  57.     public static void main(String[] args) {  
  58.         MyCrawler crawler = new MyCrawler();  
  59.         crawler.crawling(new String[] { "http://www.baidu.com" });  
  60.     }  
  61.   
  62. }  

至此,可以看到f:\spider文件夹下面已经出现了很多html文件,都是关于百度的,以“www.baidu.com”为开头。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值