Nutch 1.3 學習筆記 5-1 FetchThread


分類: Nutch 433人閱讀 評論(1) 收藏 舉報
Nutch 1.3 學習筆記 5-1 FetchThread
-----------------------------------
上一節看了Fetcher中主要幾個類的實現,這一節會來分析一下其中用到的消費者FetcherThread,來看看它是幹嘛的。


1. Fetcher的Mapp模型

Fetcher.java代碼中可以看到,Fetcher繼承自MapRunable,它是Mapper的抽象接口,實現這個接口的子類能夠更好的對Map的流程進行控制,包括多線程與異步Maper。


1.1 Fetcher的入口函數fetch(Path segment,int threads, boolean parsing)

下面是它的源代碼,來分析一下:

[html] view plaincopy
  1. // 對配置進行檢測,看一些必要的配置是否已經配置了,如http.agent.name等參數  
  2.         checkConfiguration();  
  3.   
  4.   
  5.         // 記錄fetch的開始時間  
  6.         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
  7.         long start = System.currentTimeMillis();  
  8.         if (LOG.isInfoEnabled()) {  
  9.             LOG.info("Fetcher: starting at " + sdf.format(start));  
  10.           LOG.info("Fetcher: segment: " + segment);  
  11.         }  
  12.   
  13.   
  14.         // 這裏對抓取的時候進行限制,在FetchItemQueue中會用到這個參數  
  15.         // set the actual time for the timelimit relative  
  16.         // to the beginning of the whole job and not of a specific task  
  17.         // otherwise it keeps trying again if a task fails  
  18.         long timelimit = getConf().getLong("fetcher.timelimit.mins", -1);  
  19.         if (timelimit != -1) {  
  20.           timelimit = System.currentTimeMillis() + (timelimit * 60 * 1000);  
  21.           LOG.info("Fetcher Timelimit set for : " + timelimit);  
  22.           getConf().setLong("fetcher.timelimit", timelimit);  
  23.         }  
  24.           
  25.         // 生成一個Nutch的Map-Reduce配置  
  26.         JobConf job = new NutchJob(getConf());  
  27.         job.setJobName("fetch " + segment);  
  28.       
  29.         // 配置抓取線程數,  
  30.         job.setInt("fetcher.threads.fetch", threads);  
  31.         job.set(Nutch.SEGMENT_NAME_KEY, segment.getName());  
  32.         // 配置是否對抓取的內容進行解析  
  33.         job.setBoolean("fetcher.parse", parsing);  
  34.       
  35.         // for politeness, don't permit parallel execution of a single task  
  36.         job.setSpeculativeExecution(false);  
  37.       
  38.         // 配置輸出的路徑名  
  39.         FileInputFormat.addInputPath(job, new Path(segment, CrawlDatum.GENERATE_DIR_NAME));  
  40.         // 配置輸入的文件格式,這裏類繼承自SequenceFileInputFormat  
  41.         // 它主要是覆蓋了其getSplits方法,其作用是不對文件進行切分,以文件數量作爲splits的依據  
  42.         // 就是有幾個文件,就有幾個Map操作  
  43.         job.setInputFormat(InputFormat.class);  
  44.       
  45.         // 配置Map操作的類  
  46.         job.setMapRunnerClass(Fetcher.class);  
  47.       
  48.         // 配置輸出路徑  
  49.         FileOutputFormat.setOutputPath(job, segment);  
  50.         // 這裏配置輸出文件方法,這個類在前面已經分析過  
  51.         job.setOutputFormat(FetcherOutputFormat.class);  
  52.         // 配置輸出<key,value>類型  
  53.         job.setOutputKeyClass(Text.class);  
  54.         job.setOutputValueClass(NutchWritable.class);  
  55.       
  56.         JobClient.runJob(job);  

1.2 Fetcher的run方法分析

  這個是Map類的入口,用於啓動抓取的生產者與消費者,下面是部分源代碼:

[html] view plaincopy
  1. // 生成生產者,用於讀取Generate出來的CrawlDatum,把它們放到共享隊列中  
  2.     feeder = new QueueFeeder(input, fetchQueues, threadCount * 50);  
  3.     //feeder.setPriority((Thread.MAX_PRIORITY + Thread.NORM_PRIORITY) / 2);  
  4.     
  5.     // the value of the time limit is either -1 or the time where it should finish  
  6.     long timelimit = getConf().getLong("fetcher.timelimit", -1);  
  7.     if (timelimit != -1) feeder.setTimeLimit(timelimit);  
  8.     feeder.start();  
  9.   
  10.   
  11.     // set non-blocking & no-robots mode for HTTP protocol plugins.  
  12.     getConf().setBoolean(Protocol.CHECK_BLOCKING, false);  
  13.     getConf().setBoolean(Protocol.CHECK_ROBOTS, false);  
  14.     
  15. // 啓動消費者線程  
  16.     for (int i = 0; i < threadCount; i++) {       // spawn threads  
  17.       new FetcherThread(getConf()).start();  
  18.     }  
  19.   
  20.   
  21.     // select a timeout that avoids a task timeout  
  22.     long timeout = getConf().getInt("mapred.task.timeout", 10*60*1000)/2;  
  23.   
  24.   
  25. // 這裏用一個循環來等待線程結束  
  26.     do {                                          // wait for threads to exit  
  27.       try {  
  28.         Thread.sleep(1000);  
  29.       } catch (InterruptedException e) {}  
  30.   
  31.   
  32.     // 這個函數是得到相前線程的抓取狀態,如抓取了多少網頁,多少網頁抓取失敗,抓取速度是多少  
  33.       reportStatus();  
  34.         LOG.info("-activeThreads=" + activeThreads + "spinWaiting=" + spinWaiting.get()  
  35.             + ", fetchQueues.totalSize=" + fetchQueues.getTotalSize());  
  36.   
  37.   
  38. // 輸出抓取隊列中的信息  
  39.      if (!feeder.isAlive() && fetchQueues.getTotalSize() < 5) {  
  40.        fetchQueues.dump();  
  41.      }  
  42.       
  43.     // 查看timelimit的值,這裏只要返回的hitByTimeLimit不爲0,checkTimelimit方法會清空抓取隊列中的所有數據  
  44.      // check timelimit  
  45.      if (!feeder.isAlive()) {  
  46.         int hitByTimeLimit = fetchQueues.checkTimelimit();  
  47.          if (hitByTimeLimit != 0) reporter.incrCounter("FetcherStatus",  
  48.             "hitByTimeLimit", hitByTimeLimit);  
  49.      }  
  50.       
  51.     // 查看抓取抓取線程是否超時,如果超時,就退出等待  
  52.         // some requests seem to hang, despite all intentions  
  53.         if ((System.currentTimeMillis() - lastRequestStart.get()) > timeout) {  
  54.         if (LOG.isWarnEnabled()) {  
  55.           LOG.warn("Aborting with "+activeThreads+" hung threads.");  
  56.         }  
  57.         return;  
  58.         }  
  59.   
  60.   
  61.     } while (activeThreads.get() > 0);  
  62.     LOG.info("-activeThreads=" + activeThreads);  

2. Fetcher.FetcherThread

2.1 這個類主要是用來從隊列中得到FetchItem,下面來看一下其run方法,其大概做了幾件事:

  • 從抓取隊列中得到一個FetchItem,如果返回爲null,判斷生產者是否還活着或者隊列中是否還有數據,  如果隊列中還有數據,那就等待,如果上面條件沒有滿足,就認爲所有FetchItem都已經處理完了,退出當前抓取線程
  • 得到FetchItem, 抽取其url,從這個url中分析出所使用的協議,調用相應的plugin來解析這個協議
  • 得到相當url的robotRules,看是否符合抓取規則,如果不符合或者其delayTime大於我們配置的maxDelayTime,那就不抓取這個網頁
  • 對網頁進行抓取,得到其抓取的Content和抓取狀態,調用FetchItemQueues的finishFetchItem方法,表明當前url已經抓取完成
  • 根據抓取協議的狀態來進行下一步操作
    1. 如果狀態爲WOULDBLOCK,那就進行retry,把當前url放加FetchItemQueues中,進行重試
    2. 如果是MOVED或者TEMP_MOVED,這時這個網頁可以被重定向了,對其重定向的內容進行解析,得到重定向的網址,這時要生成一個新的FetchItem,根據其QueueID放到相應的隊列的inProgress集合中,然後再對這個重定向的網頁進行抓取
    3. 如果狀態是EXCEPTION,對當前url所屬的FetchItemQueue進行檢測,看其異常的網頁數有沒有超過最大異常網頁數,如果大於,那就清空這個隊列,認爲這個隊列中的所有網頁都有問題。
    4. 如果狀態是RETRY或者是BLOCKED,那就輸出CrawlDatum,將其狀態設置成STATUS_FETCH_RETRY,在下一輪進行重新抓取
    5. 如果狀態是GONE,NOTFOUND,ACCESS_DENIED,ROBOTS_DENIED,那就輸出CrawlDatum,設置其狀態爲STATUS_FETCH_GONE,可能在下一輪中就不進行抓取了,
    6. 如果狀態是NOTMODIFIED,那就認爲這個網頁沒有改變過,那就輸出其CrawlDatum,將其狀態設成成STATUS_FETCH_NOTMODIFIED.
    7. 如果所有狀態都沒有找到,那默認輸出其CrawlDatum,將其狀態設置成STATUS_FETCH_RETRY,在下一輪抓取中再重試
  • 判斷網頁重定向的次數,如果超過最大重定向次數,就輸出其CrawlDatum,將其狀態設置成STATUS_FETCH_GONE

這裏有一些細節沒有說明,如網頁被重定向以後如果操作,相應的協議是如果產生的,這個是通過插件產生的,具體插件是怎麼調用的,這裏就不說了,以後有機會會再分析一下。


2.2 下面分析FetcherThread中的另外一個比較重要的方法,就是output

具體這個output大概做了如下幾件事:
  • 判斷抓取的content是否爲空,如果不爲空,那調用相應的解析插件來對其內容進行解析,然後就是設置當前url所對應的CrawlDatum的一些參數,如當前內容的MD5碼,分數等信息
  • 然後就是使用FetchOutputFormat輸出當前url的CrawlDatum,Content和解析的結果ParseResult
下面分析一下FetcherOutputFormat中所使用到的ParseOutputFormat.RecordWriter
在生成相應的ParseOutputFormat的RecordWriter過程中,這個RecordWriter會再生成三個RecordWriter來寫出parse_text(MapFile),parse_data(MapFile)和crawl_parse(SequenceFile),我們在segments下具體的segment中看到的三個這樣的目錄就是這個對象生成的,分別輸出了網頁的源代碼;網頁的解析數據,如網頁title、外鏈接、元數據、狀態等信息,這裏會對外鏈接進行過濾、規格化,並且用插件計算每一個外鏈接的初始分數;另一個是網頁解析後的CrawlDatum對象,這裏會分析當前CrawlDatum中的metadata,從中生成兩種新的CrawlDatum,還有就是它會對外鏈接生成相應的CrawlDatum,放入crawl_parse目錄中,這裏我還沒有看明白。




3. 總結

有點暈了,這裏的代碼有點複雜,我們來整理一下思路。

3.1 從目錄生成的角度 

  • 從Generate後會在segments目錄下生成一些要抓取的具體的segment,這裏每一個segment下會有一個叫crawl_generate的目錄,其中放着要抓取CrawlDatum信息
  • 在Fetch的時候,會輸出另外五個目錄
    1. content: 這個目錄只有在配置了要輸出抓取內容時纔會輸出
    2. crawl_fetch: 這個目錄是輸出抓取成功後的CrawlDatum信息,這裏是對原來crawl_generate目錄中的信息進行了一些修改,下面三個目錄只有配置瞭解析參數後纔會輸出,如果後面調用bin/nutch parse命令
    3. parse_text: 這個目錄存放了抓取的網頁內容,以提後面建立索引用
    4. parse_data: 這裏存入了網頁解析後的一些數據,如網頁title,外鏈接信息等
    5. crawl_parse: 這裏存儲了一些新生成的CrawlDatum信息,如外鏈接等,以供下一次迭代抓取使用


3.2 從數據流的角度

  • Generate生成的CrawlDatum數據首先經過QueueFeeder生產者,放入共享隊列
  • 多個消費者(FetcherThread)從共享隊列中取得要抓取的FetchItem數據
  • 對FetchItem所對應的url進行抓取,得到相應的抓取內容,對抓取的狀態進行判斷,回調相應的操作
  • 對抓取的內容進行解析,產生網頁的外鏈接,生成新的CrawlDatum抓取數據,產生解析後的數據
  • 調用FetcherOutputFormat.Writer對象,把CrawlDatum,Content,ParseResult分別寫入crawl_fetch,content,(parse_text,parse_data,crawl_parse)目錄中

好了,Fetcher的分析也差不多了,可能有一些細節還沒有分析到,下面有機會再補上吧。
發佈了11 篇原創文章 · 獲贊 6 · 訪問量 18萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章