`

搜索引擎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
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
String host = url.getHost();
    int port;
    String portString;
    if (url.getPort() == -1) {
      port= 80;
      portString= "";
    } else {
      port= url.getPort();
      portString= ":" + port;
}

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

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

创建InetSocketAddress,并且开始建立连接:
InetSocketAddress sockAddr= new InetSocketAddress(sockHost, sockPort);
socket.connect(sockAddr, http.getTimeout());

获取输入流:
// make request
      OutputStream req = socket.getOutputStream();

以下代码用来向服务器发Get请求:
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来方便操作:
PushbackInputStream in =                  // process response
        new PushbackInputStream(
          new BufferedInputStream(socket.getInputStream(), Http.BUFFER_SIZE), 
          Http.BUFFER_SIZE) ;

提取状态码和响应中的HTML的header:
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"
      }

接着读取内容:
readPlainContent(in);

获取内容的格式,如果是压缩的则处理压缩
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
int codeStart = line.indexOf(" ");
int codeEnd = line.indexOf(" ", codeStart+1);

如果是第一种情况:
if (codeEnd == -1) 
      codeEnd = line.length();

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

下面看看
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部分就读完了。
      // 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
       in.unread(line.substring(pos).getBytes("UTF-8"));
        line.setLength(pos);

   接着把对一行的处理委托给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

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

如果没有”:”并且这行不是空行则抛出HttpException异常
    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中:
    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追加所读的字符:
    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中了)中获取响应的长度,
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那么长的字段:
    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();
  }

今天就写到这了。
分享到:
评论
3 楼 wind_bell 2008-06-25  
楼主研究的应该是nutch0.7
2 楼 fuliang 2008-03-17  
我是用svn直接checkout出来的源代码.
svn地址:http://svn.apache.org/repos/asf/lucene/nutch
直接下载的没有插件那部分的源代码.
1 楼 sharong 2008-03-17  
楼主研究的nutch是哪个版本啊,在nutch0.9里怎么找不到相应代码

相关推荐

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

    《Lucene+nutch搜索引擎开发》书附带的源代码

    nutch的源代码解析

    nutch 源代码的详细分析,对于自己实现自己的搜索引擎很有帮助,尤其是将nutch项目嵌入到 自己的项目 当中很有帮助!

    lucene nutch 搜索引擎 开发 实例 源代码 源码

    lucene nutch 搜索引擎 开发 实例 源代码 源码 包含lucene使用的所有源代码,从建立索引,搜索,删除,排序,都有,非常齐全 还有PDF 解析,WORD解析 ,EXCEL,ppt,xml解析等,,都有源码实现 还有nutch源码,spider...

    Nutch搜索引擎的页面排序修改方法研究.kdh

    Nutch是一个优秀的开放源代码的Web搜索引擎。虽然Nutch的页面排序方法比较合理,但是很多情况下仍然不能 满足需要。分析开源搜索引擎Nutch代码,研究了Nutch的页面排序方法。在Nutch原有的结构基础上提出了3种修改...

    图解搜索引擎nutch配置

    图解搜索引擎nutch配置,自己制作的教程。因为在网上搜索到的教程很多都是粗略,对于初学nutch搜索引擎很难配置好,所以自己亲自打造了一篇图解教程!希望你能够配置成功!

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

    licene 实例代码 nutch实例代码 lucene+nutch搜索引擎开发实例代码(王学松版)

    基于Java的搜索引擎Nutch中文搜索技术研究

    基于Java的搜索引擎Nutch中文搜索技术研究 摘要:Nutch是一个优秀的基于Java的开放源码搜索引擎,为了使它能够支持中文搜索,本文在分析了Nutch结构的基础上,采用词表分词技术和前向匹配分词算法对中文信息进行分词...

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

    Lucene+nutch搜索引擎开发(源代码),内含本书的PDF电子下载地址。

    开源搜索引擎nutch-1.0.part09.rar

    Nutch 是一个开源的、Java 实现的搜索引擎。它提供了我们运行自己的搜索引擎所需的全部工具。 nutch 1.0

    nutch-2.1源代码

    Nutch 是一个开源Java 实现的搜索引擎。它提供了我们运行自己的搜索引擎所需的全部工具。包括全文搜索和Web爬虫。 本资源官网上下的源代码。 nutch-2.1 适用于windows系统

    apache-nutch-2.3.1 源码和构建好的库文件等 (part 4)

    相对于那些商用的搜索引擎, Nutch作为开放源代码 搜索引擎将会更加透明, 从而更值得大家信赖. 现在所有主要的搜索引擎都采用私有的排序算法, 而不会解释为什么一个网页会排在一个特定的位置. 除此之外, 有的搜索...

    nutch 1.5的源代码

    nutch源代码,共享给大家。nutch是一个开源的搜索引擎

    搜索引擎nutch配置

    这里是在网上搜到的Nutch配置的博客,比较详细,担心自己以后配置的时候忘了,所以传到csdn,顺便分享给大家。

    基于lucene和nutch的开源搜索引擎资料集合

    其中内容均为前段时间研究开源搜索引擎时搜集参考的资料,非常齐全包含的内容有: Computing PageRank Using Hadoop.ppt Google的秘密PageRank彻底解说中文版.doc JAVA_Lucene_in_Action教程完整版.doc Java开源搜索...

    Nutch搜索引擎(1-5期)

    Nutch搜索引擎·Nutch简介及安装(第1期) Nutch搜索引擎·Solr简介及安装(第2期) Nutch搜索引擎·Nutch简单应用(第3期) Nutch搜索引擎·Eclipse开发配置(第4期) Nutch搜索引擎·Nutch浅入分析(第5期)

    基于Nutch的搜索引擎系统的研究与实现

    基于Nutch的搜索引擎系统的研究与实现

    Lucene+Nutch搜索引擎开发

    Lucene+Nutch搜索引擎开发

    分布式搜索引擎nutch开发

    非常实用的分布式搜索引擎开发工具nutch,有兴趣的赶紧下吧!

    nutch搜索引擎数据获取

    Nutch搜索引擎数据获取1、 基本原理2、网络蜘蛛3、局域网抓取

Global site tag (gtag.js) - Google Analytics