`

搜索引擎Nutch源代码研究之一 网页抓取(2)

阅读更多
 
今天我们来看看Nutch的源代码中的protocol-http插件,是如何抓取和下载web页面的。protocol-http就两个类HttpRespose和Http类,其中HttpRespose主要是向web服务器发请求来获取响应,从而下载页面。Http类则非常简单,其实可以说是HttpResponse的一个Facade,设置配置信息,然后创建HttpRespose。用户似乎只需要和Http类打交道就行了(我也没看全,所以只是猜测)。
我们来看看HttpResponse类:
看这个类的源码需要从构造函数
public HttpResponse(HttpBase http, URL url, CrawlDatum datum) throws ProtocolException, IOException开始
首先判断协议是否为http
Java代码 复制代码
  1. if (!"http".equals(url.getProtocol()))   
  2.       throw new HttpException("Not an HTTP url:" + url);  
if (!"http".equals(url.getProtocol()))
      throw new HttpException("Not an HTTP url:" + url);

获得路径,如果url.getFile()的为空直接返回”/”,否则返回url.getFile()
String path = "".equals(url.getFile()) ? "/" : url.getFile();
然后根据url获取到主机名和端口名。如果端口不存在,则端口默认为80,请求的地址将不包括端口号portString= "",否则获取到端口号,并得到portString
Java代码 复制代码
  1. String host = url.getHost();   
  2.     int port;   
  3.     String portString;   
  4.     if (url.getPort() == -1) {   
  5.       port= 80;   
  6.       portString= "";   
  7.     } else {   
  8.       port= url.getPort();   
  9.       portString= ":" + port;   
  10. }  
String host = url.getHost();
    int port;
    String portString;
    if (url.getPort() == -1) {
      port= 80;
      portString= "";
    } else {
      port= url.getPort();
      portString= ":" + port;
}

然后创建socket,并且设置连接超时的时间:
Java代码 复制代码
  1. socket = new Socket();                    // create the socket socket.setSoTimeout(http.getTimeout());  
socket = new Socket();                    // create the socket socket.setSoTimeout(http.getTimeout());

根据是否使用代理来得到socketHost和socketPort:
Java代码 复制代码
  1. String sockHost = http.useProxy() ? http.getProxyHost() : host;   
  2. int sockPort = http.useProxy() ? http.getProxyPort() : port;  
String sockHost = http.useProxy() ? http.getProxyHost() : host;
int sockPort = http.useProxy() ? http.getProxyPort() : port;

创建InetSocketAddress,并且开始建立连接:
Java代码 复制代码
  1. InetSocketAddress sockAddr= new InetSocketAddress(sockHost, sockPort);   
  2. socket.connect(sockAddr, http.getTimeout());  
InetSocketAddress sockAddr= new InetSocketAddress(sockHost, sockPort);
socket.connect(sockAddr, http.getTimeout());

获取输入流:
Java代码 复制代码
  1. // make request   
  2.       OutputStream req = socket.getOutputStream();  
// make request
      OutputStream req = socket.getOutputStream();

以下代码用来向服务器发Get请求:
Java代码 复制代码
  1. StringBuffer reqStr = new StringBuffer("GET ");   
  2.       if (http.useProxy()) {   
  3.          reqStr.append(url.getProtocol()+"://"+host+portString+path);   
  4.       } else {   
  5.          reqStr.append(path);   
  6.       }   
  7.   
  8.       reqStr.append(" HTTP/1.0\r\n");   
  9.       reqStr.append("Host: ");   
  10.       reqStr.append(host);   
  11.       reqStr.append(portString);   
  12.       reqStr.append("\r\n");   
  13.       reqStr.append("Accept-Encoding: x-gzip, gzip\r\n");   
  14.       String userAgent = http.getUserAgent();   
  15.       if ((userAgent == null) || (userAgent.length() == 0)) {   
  16.         if (Http.LOG.isFatalEnabled()) { Http.LOG.fatal("User-agent is not set!"); }   
  17.       } else {   
  18.         reqStr.append("User-Agent: ");   
  19.         reqStr.append(userAgent);   
  20.         reqStr.append("\r\n");   
  21.       }   
  22.       reqStr.append("\r\n");   
  23.       byte[] reqBytes= reqStr.toString().getBytes();   
  24.       req.write(reqBytes);   
  25.       req.flush();  
StringBuffer reqStr = new StringBuffer("GET ");
      if (http.useProxy()) {
         reqStr.append(url.getProtocol()+"://"+host+portString+path);
      } else {
         reqStr.append(path);
      }

      reqStr.append(" HTTP/1.0\r\n");
      reqStr.append("Host: ");
      reqStr.append(host);
      reqStr.append(portString);
      reqStr.append("\r\n");
      reqStr.append("Accept-Encoding: x-gzip, gzip\r\n");
      String userAgent = http.getUserAgent();
      if ((userAgent == null) || (userAgent.length() == 0)) {
        if (Http.LOG.isFatalEnabled()) { Http.LOG.fatal("User-agent is not set!"); }
      } else {
        reqStr.append("User-Agent: ");
        reqStr.append(userAgent);
        reqStr.append("\r\n");
      }
      reqStr.append("\r\n");
      byte[] reqBytes= reqStr.toString().getBytes();
      req.write(reqBytes);
      req.flush();

接着来处理相应,获得输入流并且包装成PushbackInputStream来方便操作:
Java代码 复制代码
  1. PushbackInputStream in =                  // process response   
  2.         new PushbackInputStream(   
  3.           new BufferedInputStream(socket.getInputStream(), Http.BUFFER_SIZE),    
  4.           Http.BUFFER_SIZE) ;  
PushbackInputStream in =                  // process response
        new PushbackInputStream(
          new BufferedInputStream(socket.getInputStream(), Http.BUFFER_SIZE), 
          Http.BUFFER_SIZE) ;

提取状态码和响应中的HTML的header:
Java代码 复制代码
  1. boolean haveSeenNonContinueStatus= false;   
  2.       while (!haveSeenNonContinueStatus) {   
  3.         // parse status code line   
  4.         this.code = parseStatusLine(in, line);    
  5.         // parse headers   
  6.         parseHeaders(in, line);   
  7.         haveSeenNonContinueStatus= code != 100// 100 is "Continue"   
  8.       }  
boolean haveSeenNonContinueStatus= false;
      while (!haveSeenNonContinueStatus) {
        // parse status code line
        this.code = parseStatusLine(in, line); 
        // parse headers
        parseHeaders(in, line);
        haveSeenNonContinueStatus= code != 100; // 100 is "Continue"
      }

接着读取内容:
Java代码 复制代码
  1. readPlainContent(in);  
readPlainContent(in);

获取内容的格式,如果是压缩的则处理压缩
Java代码 复制代码
  1. String contentEncoding = getHeader(Response.CONTENT_ENCODING);   
  2.       if ("gzip".equals(contentEncoding) || "x-gzip".equals(contentEncoding)) {   
  3.         content = http.processGzipEncoded(content, url);   
  4.       } else {   
  5.         if (Http.LOG.isTraceEnabled()) {   
  6.           Http.LOG.trace("fetched " + content.length + " bytes from " + url);   
  7.         }   
  8.       }  
String contentEncoding = getHeader(Response.CONTENT_ENCODING);
      if ("gzip".equals(contentEncoding) || "x-gzip".equals(contentEncoding)) {
        content = http.processGzipEncoded(content, url);
      } else {
        if (Http.LOG.isTraceEnabled()) {
          Http.LOG.trace("fetched " + content.length + " bytes from " + url);
        }
      }

整个过程结束。
下面我们来看看parseStatusLine parseHeaders  readPlainContent以及readChunkedContent的过程。
private int parseStatusLine(PushbackInputStream in, StringBuffer line)
throws IOException, HttpException:
这个函数主要来提取响应得状态,例如200 OK这样的状态码:
请求的状态行一般格式(例如响应Ok的话) HTTP/1.1 200" 或 "HTTP/1.1 200 OK
Java代码 复制代码
  1. int codeStart = line.indexOf(" ");   
  2. int codeEnd = line.indexOf(" ", codeStart+1);  
int codeStart = line.indexOf(" ");
int codeEnd = line.indexOf(" ", codeStart+1);

如果是第一种情况:
Java代码 复制代码
  1. if (codeEnd == -1)    
  2.       codeEnd = line.length();  
if (codeEnd == -1) 
      codeEnd = line.length();

状态码结束(200)位置便是line.length()
否则状态码结束(200)位置就是line.indexOf(" ", codeStart+1);
接着开始提取状态码:
Java代码 复制代码
  1. int code;   
  2.     try {   
  3.       code= Integer.parseInt(line.substring(codeStart+1, codeEnd));   
  4.     } catch (NumberFormatException e) {   
  5.       throw new HttpException("bad status line '" + line    
  6.                               + "': " + e.getMessage(), e);   
  7. }  
int code;
    try {
      code= Integer.parseInt(line.substring(codeStart+1, codeEnd));
    } catch (NumberFormatException e) {
      throw new HttpException("bad status line '" + line 
                              + "': " + e.getMessage(), e);
}

下面看看
Java代码 复制代码
  1. private void parseHeaders(PushbackInputStream in, StringBuffer line)   
  2. throws IOException, HttpException:  
private void parseHeaders(PushbackInputStream in, StringBuffer line)
throws IOException, HttpException:

这个函数主要是将响应的headers加入我们已经建立的结构header的Metadata中。
一个循环读取headers:
一般HTTP response的header部分和内容部分会有一个空行,使用readLine如果是空行就会返回读取的字符数为0,具体readLine实现看完这个函数在仔细看:
while (readLine(in, line, true) != 0)
   如果没有空行,那紧接着就是正文了,正文一般会以<!DOCTYPE、<HTML、<html开头。如果读到的一行中包含这个,那么header部分就读完了。
Java代码 复制代码
  1. // handle HTTP responses with missing blank line after headers   
  2. int pos;   
  3. if ( ((pos= line.indexOf("<!DOCTYPE")) != -1)    
  4.      || ((pos= line.indexOf("<HTML")) != -1)    
  5.      || ((pos= line.indexOf("<html")) != -1) )   
      // handle HTTP responses with missing blank line after headers
      int pos;
      if ( ((pos= line.indexOf("<!DOCTYPE")) != -1) 
           || ((pos= line.indexOf("<HTML")) != -1) 
           || ((pos= line.indexOf("<html")) != -1) ) 

   接着把多读的那部分压回流中,并设置那一行的长度为pos
Java代码 复制代码
  1. in.unread(line.substring(pos).getBytes("UTF-8"));   
  2.  line.setLength(pos);  
       in.unread(line.substring(pos).getBytes("UTF-8"));
        line.setLength(pos);

   接着把对一行的处理委托给processHeaderLine(line)来处理:
Java代码 复制代码
  1.   try {   
  2.       //TODO: (CM) We don't know the header names here   
  3.       //since we're just handling them generically. It would   
  4.       //be nice to provide some sort of mapping function here   
  5.       //for the returned header names to the standard metadata   
  6.       //names in the ParseData class   
  7.     processHeaderLine(line);   
  8.  } catch (Exception e) {   
  9.     // fixme:   
  10.     e.printStackTrace(LogUtil.getErrorStream(Http.LOG));   
  11.   }   
  12.   return;   
  13. }   
  14. processHeaderLine(line);  
        try {
            //TODO: (CM) We don't know the header names here
            //since we're just handling them generically. It would
            //be nice to provide some sort of mapping function here
            //for the returned header names to the standard metadata
            //names in the ParseData class
          processHeaderLine(line);
       } catch (Exception e) {
          // fixme:
          e.printStackTrace(LogUtil.getErrorStream(Http.LOG));
        }
        return;
      }
      processHeaderLine(line);

下面我们看看如何处理一行header的:
private void processHeaderLine(StringBuffer line)
throws IOException, HttpException
请求的头一般格式:
Cache-Control: private
Date: Fri, 14 Dec 2007 15:32:06 GMT
Content-Length: 7602
Content-Type: text/html
Server: Microsoft-IIS/6.0

这样我们就比较容易理解下面的代码了:
Java代码 复制代码
  1. int colonIndex = line.indexOf(":");       // key is up to colon  
int colonIndex = line.indexOf(":");       // key is up to colon

如果没有”:”并且这行不是空行则抛出HttpException异常
Java代码 复制代码
  1.     if (colonIndex == -1) {   
  2.       int i;   
  3.       for (i= 0; i < line.length(); i++)   
  4.         if (!Character.isWhitespace(line.charAt(i)))   
  5.           break;   
  6.       if (i == line.length())   
  7.         return;   
  8.       throw new HttpException("No colon in header:" + line);   
  9. }  
    if (colonIndex == -1) {
      int i;
      for (i= 0; i < line.length(); i++)
        if (!Character.isWhitespace(line.charAt(i)))
          break;
      if (i == line.length())
        return;
      throw new HttpException("No colon in header:" + line);
}

否则,可以可以提取出键-值对了:
key为0~colonIndex部分,然后过滤掉开始的空白字符,作为value部分。
最后放到headers中:
Java代码 复制代码
  1. String key = line.substring(0, colonIndex);   
  2.   
  3. int valueStart = colonIndex+1;            // skip whitespace   
  4. while (valueStart < line.length()) {   
  5.   int c = line.charAt(valueStart);   
  6.   if (c != ' ' && c != '\t')   
  7.    break;   
  8.   valueStart++;   
  9. }   
  10. String value = line.substring(valueStart);   
  11. headers.set(key, value);  
    String key = line.substring(0, colonIndex);

    int valueStart = colonIndex+1;            // skip whitespace
    while (valueStart < line.length()) {
      int c = line.charAt(valueStart);
      if (c != ' ' && c != '\t')
       break;
      valueStart++;
    }
    String value = line.substring(valueStart);
    headers.set(key, value);

下面我们看看用的比较多的辅助函数
private static int readLine(PushbackInputStream in, StringBuffer line,
                      boolean allowContinuedLine) throws IOException

代码的实现:
开始设置line的长度为0不断的读,直到c!=-1,对于每个c:
如果是\r并且下一个字符是\n则读入\r,如果是\n,并且如果line.length() > 0,也就是这行前面已经有非空白字符,并且还允许连续行,在读一个字符,如果是’ ’或者是\t说明此行仍未结束,读入该字符,一行结束,返回读取的实际长度。其他情况下直接往line追加所读的字符:
Java代码 复制代码
  1.   line.setLength(0);   
  2.   for (int c = in.read(); c != -1; c = in.read()) {   
  3.     switch (c) {   
  4.       case '\r':   
  5.         if (peek(in) == '\n') {   
  6.           in.read();   
  7.         }   
  8.       case '\n':    
  9.         if (line.length() > 0) {   
  10.           // at EOL -- check for continued line if the current   
  11.           // (possibly continued) line wasn't blank   
  12.           if (allowContinuedLine)    
  13.             switch (peek(in)) {   
  14.               case ' ' : case '\t':                   // line is continued   
  15.                 in.read();   
  16.                 continue;   
  17.             }   
  18.         }   
  19.         return line.length();      // else complete   
  20.       default :   
  21.         line.append((char)c);   
  22.     }   
  23.   }   
  24.   throw new EOFException();   
  25. }  
    line.setLength(0);
    for (int c = in.read(); c != -1; c = in.read()) {
      switch (c) {
        case '\r':
          if (peek(in) == '\n') {
            in.read();
          }
        case '\n': 
          if (line.length() > 0) {
            // at EOL -- check for continued line if the current
            // (possibly continued) line wasn't blank
            if (allowContinuedLine) 
              switch (peek(in)) {
                case ' ' : case '\t':                   // line is continued
                  in.read();
                  continue;
              }
          }
          return line.length();      // else complete
        default :
          line.append((char)c);
      }
    }
    throw new EOFException();
  }

接着看如何读取内容的,也就是
private void readPlainContent(InputStream in)
throws HttpException, IOException的实现:
首先从headers(在此之前已经读去了headers放到metadata中了)中获取响应的长度,
Java代码 复制代码
  1. int contentLength = Integer.MAX_VALUE;    // get content length   
  2.     String contentLengthString = headers.get(Response.CONTENT_LENGTH);   
  3.     if (contentLengthString != null) {   
  4.       contentLengthString = contentLengthString.trim();   
  5.       try {   
  6.         contentLength = Integer.parseInt(contentLengthString);   
  7.       } catch (NumberFormatException e) {   
  8.        throw new HttpException("bad content length: "+contentLengthString);   
  9.       }   
  10. }  
int contentLength = Integer.MAX_VALUE;    // get content length
    String contentLengthString = headers.get(Response.CONTENT_LENGTH);
    if (contentLengthString != null) {
      contentLengthString = contentLengthString.trim();
      try {
        contentLength = Integer.parseInt(contentLengthString);
      } catch (NumberFormatException e) {
       throw new HttpException("bad content length: "+contentLengthString);
      }
}

如果大于http.getMaxContent()(这个值在配置文件中http.content.limit来配置),
则截取maxContent那么长的字段:
Java代码 复制代码
  1. if (http.getMaxContent() >= 0  
  2.  && contentLength > http.getMaxContent())   // limit download size   
  3.   contentLength  = http.getMaxContent();   
  4.   
  5. ByteArrayOutputStream out = new ByteArrayOutputStream(Http.BUFFER_SIZE);   
  6. byte[] bytes = new byte[Http.BUFFER_SIZE];   
  7. int length = 0;                           // read content   
  8. for (int i = in.read(bytes); i != -1; i = in.read(bytes)) {   
  9.   out.write(bytes, 0, i);   
  10.   length += i;   
  11.   if (length >= contentLength)   
  12.     break;   
  13. }   
  14. content = out.toByteArray();   
    if (http.getMaxContent() >= 0
     && contentLength > http.getMaxContent())   // limit download size
      contentLength  = http.getMaxContent();

    ByteArrayOutputStream out = new ByteArrayOutputStream(Http.BUFFER_SIZE);
    byte[] bytes = new byte[Http.BUFFER_SIZE];
    int length = 0;                           // read content
    for (int i = in.read(bytes); i != -1; i = in.read(bytes)) {
      out.write(bytes, 0, i);
      length += i;
      if (length >= contentLength)
        break;
    }
    content = out.toByteArray();
  }

今天就写到这了。
分享到:
评论

相关推荐

    搜索引擎nutch配置

    从Apache官方网站下载Nutch的最新源代码,通常通过Git克隆仓库。解压后,进入Nutch的工作目录。 3. **配置Nutch** 打开`conf/nutch-site.xml`文件,这是Nutch的主要配置文件。以下是一些关键配置项: - `...

    Lucene+nutch搜索引擎开发(源代码)

    2. **Nutch源代码**:包括Nutch的爬虫模块、索引模块和搜索模块,可以帮助开发者学习如何配置和运行一个完整的网络爬虫,以及如何与Lucene集成进行全文检索。 3. **示例项目**:可能包含了一些示例应用,展示如何...

    nutch-2.1源代码

    Nutch不仅仅是一个搜索引擎,它还包含了一个Web爬虫,能够抓取互联网上的网页,并对抓取的数据进行索引和处理。 Nutch的源代码包含了整个项目的完整实现,包括爬虫、索引器、搜索器以及相关的配置和文档。这对于...

    nutch的源代码解析

    Nutch 的源代码解析对于深入理解搜索引擎的工作原理以及自定义搜索引擎的实现非常有帮助。下面我们将详细探讨 Nutch 的注入(Injector)过程,这是整个爬取流程的第一步。 Injector 类在 Nutch 中的作用是将输入的 ...

    Lucene+Nutch搜索引擎开发.王学松源代码

    总的来说,王学松的“Lucene+Nutch搜索引擎开发实例代码”是一份宝贵的教育资源,它可以帮助开发者快速入门搜索引擎开发,并深入了解这两个项目的内部工作机制。通过实践这些代码,不仅可以提升技术能力,还能为构建...

    lucene+nutch搜索引擎开发源码1

    《lucene+nutch搜索引擎开发源码1》是一个包含开源搜索引擎项目Lucene和Nutch源代码的压缩包,主要针对搜索引擎开发的学习和实践。这个压缩包是书籍《lucene+nutch搜索引擎开发》的一部分,由于源码量较大,因此分为...

    分布式搜索引擎nutch开发

    分布式搜索引擎Nutch开发详解 Nutch是一款开源的、基于Java实现的全文搜索引擎,它主要用于构建大规模的网络爬虫系统,并提供了对抓取的网页进行索引和搜索的功能。Nutch与Hadoop紧密集成,能够充分利用分布式计算...

    lucene+nutch开发自己的搜索引擎一书源代码

    《lucene+nutch开发自己的搜索引擎一书源代码》是一份专为初学者设计的资源,旨在教授如何利用Apache Lucene和Nutch构建自定义搜索引擎。Lucene是Java编写的一个高性能全文检索库,而Nutch则是一个开源的网络爬虫...

    nutch 1.5的源代码

    Nutch 1.5 是一个基于Java开发的开源搜索引擎项目,它主要负责网络抓取、索引和搜索等功能。这个源代码包包含了实现这些功能的所有模块和组件,为开发者提供了深入理解搜索引擎工作原理以及定制化搜索引擎的机会。接...

    nutch开发资料 搜索引擎

    Nutch是Apache软件基金会的一个开源项目,主要用于构建网络搜索引擎。这个开发资料压缩包包含了与Nutch相关的源代码和可能的配置文件,可以帮助开发者深入了解和学习Nutch的工作原理以及如何进行定制化开发。以下是...

    开发自己的搜索引擎 lunenc nutch

    Lucene 是一个全文搜索引擎库,而 Nutch 是一个完整的网络爬虫项目,两者结合可以提供从网页抓取到索引、搜索的一站式解决方案。 在开发自定义搜索引擎的过程中,首先我们需要了解 **Lucene** 的基本原理。Lucene ...

    Nutch 是一个开源Java 实现的搜索引擎(学习资料)----下载不扣分,回帖加1分,童叟无欺,欢迎下载 。不下也来看看啊!!

    1. **Java编程基础**:由于Nutch是用Java编写的,因此理解和修改Nutch源代码需要扎实的Java基础知识。 2. **搜索引擎原理**:理解搜索引擎的基本工作流程,包括爬虫、预处理、索引和查询处理。 3. **Hadoop和...

    Lucene.Nutch搜索引擎开发

    2. **Nutch**: Nutch是一个开放源代码的网络爬虫,主要用于抓取和索引网页内容。它基于Lucene,提供了完整的爬虫解决方案,包括网页抓取、预处理(如HTML解析、链接分析、去重等)、索引和搜索功能。Nutch的主要优势...

    nutch解决搜索结果高亮和网页快照链接无效及网页变形

    Nutch 是一个开源的搜索引擎项目,它提供了网络爬虫、索引和搜索的功能。在构建一个自定义的搜索引擎时,可能会遇到几个常见的问题,如搜索结果的关键词高亮、快照链接无效以及网页在预览时的变形。下面将详细讨论...

    Lucene+Nutch搜索引擎开发(配套光盘资源)

    《Lucene+Nutch搜索引擎开发》是一本专注于搜索引擎技术的书籍,配套光盘资源为学习者提供了丰富的实践材料,尤其对于想要深入理解Nutch搜索引擎开发的读者来说,这是一份不可多得的学习资料。Nutch是基于Apache ...

    Nutch:一个灵活可扩展的开源web搜索引擎

    总之,Nutch是一个强大的开源搜索引擎工具,它不仅适用于构建自己的搜索引擎,也是研究和学习搜索引擎技术的理想平台。通过熟悉其工作流程、分析源代码以及查阅相关文档,你将能够掌握搜索引擎的核心技术和实现细节...

    apache-nutch-1.4-src.tar.gz_nutch_搜索引擎

    Apache Nutch 是一款高度可扩展的开源全文搜索引擎框架,它为构建自定义的网络爬虫和搜索引擎提供了强大的工具集。Nutch 的设计目标是处理大量网页数据,进行高效的抓取、索引和搜索操作。在“apache-nutch-1.4-src....

    nutch网页爬取总结

    - **配置 Nutch 创建索引**:下载 Nutch 的源代码并解压,然后通过 Maven 进行编译。配置 Nutch 的 `conf/nutch-site.xml` 文件以设置存储路径、抓取策略等参数。 - **安装 Tomcat**:Tomcat 用于运行 Nutch 的 UI...

    nutch使用&Nutch;入门教程

    在使用Nutch之前,你需要配置Nutch的运行环境,包括安装Java、设置Hadoop(如果需要分布式爬取)、下载和编译Nutch源代码。还需要配置Nutch的`conf/nutch-site.xml`文件,指定抓取策略、存储路径、爬虫范围等参数。 ...

Global site tag (gtag.js) - Google Analytics