2015年11月12日 星期四

Nutch 等 Web Crawler 的著作權問題

筆者雖然了解 Nutch 這類 Web Crawler 的運作模式和 Google 差不多, 既然 Google 可以營運, 想當然爾的, Nutch 應該也是合法的囉! 但是沒搞清楚, 心裏仍然有疑慮. 於是上網查了我國的法律, 以下幾個智財局的鏈結供讀者參考.

看過上述文章, 可以了解著作權在網路世界仍受到相當嚴密的保護, 並不是一般上網族所想到的那麼自由. 不過在 網路相關著作權問題之說明 這一篇文章有提到一段搜尋引擎的著作權放寬限制的說明, 使用 Nutch 抓取我國網站的夥伴們可以放心了. 此段文章內容如下 :

參、「搜尋引擎」
(一)網路服務業者提供的「搜尋引擎」服務,是透過軟體搜尋,將網路上所有資料下載儲存到其伺服器中,再透過自動編輯功能,使網友可以很快地找到所需要的資訊,即提供使用者有關網路資訊之索引、參考或連結之搜尋或連結之服務,例如Google、百度等,此種搜尋服務之業者所為下載儲存檔案的行為,當然是重製。然而網路業者事先未必取得所儲存檔案的著作財產權人的授權,有些人難免會產生是否會有侵害著作權的疑慮。
(二)在國際社會著作權領域的實務上,認為「搜尋引擎」重製他人著作,雖未取得著作人的授權,但因其利用的目的,是為了使網路的傳輸更有效率,而且對所重製之資料並未產生「市場替代」之效果,可以認為是合理使用。國際組織「萬維網聯盟(W3C)」對於搜尋引擎的運作,列有詳細的技術規範,基本上,搜尋引擎祇要符合該聯盟的技術規範所作的重製,應該都不會被認定為侵權行為。
(三)網路的世界是無國界的,在網路的秩序和規範方面,我國也必須遵循國際間的標準,因此,「搜尋引擎」之重製行為,不致對著作潛在市場與現在價值產生負面影響,應可認定為合理使用。
(四)又此種搜尋服務提供者對其使用者侵害他人著作權之行為,只要該等 業者對所搜尋或連結之資訊涉有侵權不知情(倘若使用者所連結之網 站張貼目前上映中的院線片供網友觀賞或下載者,難謂為不知情), 以及未直接自使用者之侵權行為獲有財產上利益者,並配合著作權人 之通知立即取下涉嫌侵權之內容,則該網路服務提供者亦可主張免除 「民事責任」。

不過, 筆者認為仍應注意以下 2 個問題 :

  1. 如果搜尋引擎的蒐集資料網站是註冊在國外的, 那麼是要遵守該國或我國的法律?
  2. 如果蒐集資料網站有公佈使用者條款限制對網站資料的使用, 是否應該要遵守? 如果未遵守是否違反著作權法? 或其他法律?

2015年11月7日 星期六

認識 Solr ( 5.4 ) -- 基礎概念

這一篇文章主要是要讓未接觸過 Solr 的讀者, 了解 Solr 是怎樣的一個軟體, 它的用途是什麼? 並將它和資料庫做個比較, 因為資料庫是程式開發人員常接觸到的平台.
或許經由這樣的比較, 讀者會有一個比較清楚的印象. 筆者參考的文件是尚未 release 的 5.4 版.

1. Solr 這類的平台的用途是什麼? Language support and linguistics in Lucene, Solr and ElasticSearch, and the eco-system 這篇文章可以看出這類平台的用途在於提供多種語言的文章快速全文檢索, 並支援透過語意查詢, 而不只是文字的相似比對.

2. Solr 的兩種模式: Standalone Solr, SolrCloud Cluster. 請看下表的比較.

表 1 : Standalone Solr 和 SolrCloud Cluster 的比較

  Standalone Solr SolrCloud Cluster
Solr 的一般功能
fault tolerance
high availability
load balancing
sharding
scale out

SolrCloud 的安裝可參考 nutch 1.8 + solr 4.9.0 探討系列三 : SolrCloud 安裝 這篇文章.

3. Solr 與 relational database 的比較 :

表 2 : Solr 與 relational database 的比較

relational database Solr
relational database 是將資料正規化, 儲存在 database 裏的 table. 比如說公司收到多張訂單, 訂單裏有訂單編號, 客戶的名稱, 送貨地址, 訂單的明細等等.正規化後, 會將訂單明細存到訂單明細 table, 而將其他資料存到訂單 table, 2 個 table 之間用訂單編號做關聯, 而 1 筆訂單 table 的資料可能關連到 1 到多筆訂單明細的資料. 如果公司有將客戶的資料儲存到客戶的 table, 那訂單 table 可能用客戶代號跟客戶 table 做關聯. 而客戶 table 除了地址之外, 可能還有聯絡電話, 聯絡人等等資料. 當我們要查詢一張訂單, 就可以用這些關聯的欄位, 將整張訂單的資料取出來或只取其中一些欄位的資料. 我們也可以用 SQL 語法一次查詢多張訂單的資料. Solr 是全文檢索軟體. 我們可以想像一個活頁簿, 當我們將一張張食譜加入這個活頁簿, 會將新加入食譜的某一欄位或多個欄位的各個詞語加入到活頁簿的第一頁做索引, 這個索引很特別, 記錄著各個詞語分別發生在那些食譜. 想像我們在 google 輸入一個或多個關鍵字時, 它就會列出有這些關鍵字的文章.
資料庫的資料儲存架構由大至小為 database(-->schema or user)-->table-->column 不像 relational database 一個資料庫有多個 table, 一個 core(SolrCloud 改稱為 collection)會有各個欄位的定義, 請想像上述所說的, 一張張的食譜有同樣的料理名稱, 食材, 製作步驟等欄位, 而這些食譜(documents)就是一個 collecton. 一個 solr server 可以有許多 core(在 SolrCloud 稱為 collection), 可能有食譜的 core, 人才資源庫的 core, 而這些 core 不像 relational database 的 table 可以做 join, 它們是各自獨立的.
relational database 使用 SQL 語法中的資料定義語言(Data Definition Language, DDL) 來建立或修改 database, schema, user, table, index, stored procedure, function, trigger 等等. 而使用資料操縱語言(Data Manipulation Language, DML) 來新增, 刪除, 修改 table 的資料. 另資料庫軟體常提供指令來 import /export 資料庫的資料及 schema, 也提供 backup/restore 資料庫的指令. 也提供圖形化介面方便使用者做上述的所有操作. Solr schema(field 等)的定義儲存在 schema.xml 中, 除了在啟動 server 前修改, 啟動 server 後亦可透過 Schema API 讀取或修改 schema. Solr server 主要的 configuration file solrconfig.xml 儲存 server 的 configuration. 而 solr.xml, core.properties 定義 core 的 configuration. Solr 提供 Configuration APIs 修改 solrconfig.xml 的設定. Solr 提供很多方式將資料加入 index. Solr 亦提供 Client APIs(有多種程式語言版本) 來執行 query, index, delete, commit, and optimize. Solr 亦提供 Backup/Restore API, 也提供簡易的 Admin 介面, 但大多是讀取系統狀態, 訊息而無法進行管理. 上述種種 APIs 都用 http request 來與 server 溝通執行.
relational database 的 table 的 column 需指定 data type, user 可以定義自己的 data type, 且每一 column 可以設定預設值. 除了在 insert 或 update data 的同時可以對資料做處理再存入 table, 也可以在 table 設定 trigger, 在資料 insert, update, delete 的前後做一些這個 table or 其他 table 資料的處理. 上述機制中除了呼叫 server 本身提供的預設 function 以外, 也可以呼叫使用者自訂 stored procedure 或 function 來處理資料. Solr 的 field 需指明是哪一種 field type. field type 也可自己定義. 一般而言, field type 定義時, class 選為 solr.TextField 時會定義 analyzer. analyzer 是由一個 charFilter(非必要), 一個 tokenizer(必要), 一至多個 filter(非必要) 依序來組成, 每一個都是一個 Java Class 實作. tokenizer 用來將field 傳入的文章斷詞, 分成一個個詞語. filter 則用來對詞語做進一步的處理, 如篩選不要的文字, 將字母轉成小寫, 同意詞的轉換, 不同國家語言的特別處理等等. 讀者可以參考以下的說明, 進一步了解. Char Filters can add, change, or remove characters while preserving the original character offsets to support features like highlighting. The job of a tokenizer is to break up a stream of text into tokens, where each token is (usually) a sub-sequence of the characters in the text. An analyzer is aware of the field it is configured for, but a tokenizer is not. Tokenizers read from a character stream (a Reader) and produce a sequence of Token objects (a TokenStream). Like tokenizers, filters consume input and produce a stream of tokens. Filters also derive from org.apache.lucene.analysis.TokenStream. Unlike tokenizers, a filter's input is another TokenStream. The job of a filter is usually easier than that of a tokenizer since in most cases a filter looks at each token in the stream sequentially and decides whether to pass it along, replace it or discard it.
relational database 的 table column 可以定義為 primary key, 確保該欄位的資料不會重複, 也可多個 column 共同定義為 primary key, 則這些 column 組合的資料在 table 裏不會重複. Solr 每一個 core 可以定義一個field 為 unique key, 也可以不設 unique key. 但一般會設 unique key, 且 unique key 只能為單一 field. 讀者可參考以下說明:The uniqueKey element specifies which field is a unique identifier for documents. Although uniqueKey is not required, it is nearly always warranted by your application design. For example, uniqueKey should be used if you will ever update a document in the index. 大部分 data import 時建 index, 都會檢查有沒有設 unique key. 筆者試出使用 Data Import Handler 的方式可以去掉 unique key.

field 的定義

<field name="my_text" type="text_general" indexed="true" stored="true"/>

field Type 的定義 -- index, query 使用不同的 analyzer

<fieldType name="text_general" class="solr.TextField" positionIncrementGap="100">
  <analyzer type="index">
    <charFilter class="solr.MappingCharFilterFactory" mapping="mapping-FoldToASCII.txt"/>
    <tokenizer class="solr.StandardTokenizerFactory"/>
    <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" />
    <!-- in this example, we will only use synonyms at query time
    <filter class="solr.SynonymFilterFactory" synonyms="index_synonyms.txt" ignoreCase="true" expand="false"/>
    -->
    <filter class="solr.LowerCaseFilterFactory"/>
  </analyzer>
  <analyzer type="query">
    <tokenizer class="solr.StandardTokenizerFactory"/>
    <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" />
    <filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
    <filter class="solr.LowerCaseFilterFactory"/>
  </analyzer>
</fieldType>

field Type 的定義 -- index, query 使用相同的 analyzer

<fieldType name="nametext" class="solr.TextField">
  <analyzer>
    <tokenizer class="solr.StandardTokenizerFactory"/>
    <filter class="solr.StandardFilterFactory"/>
    <filter class="solr.LowerCaseFilterFactory"/>
    <filter class="solr.StopFilterFactory"/>
    <filter class="solr.EnglishPorterFilterFactory"/>
  </analyzer>
</fieldType>

field Type 的定義 -- 使用一個 analyzer(Java class) 來完成 charFilter, tokenizer, filter 對文字的的處理.

<fieldType name="nametext" class="solr.TextField">
  <analyzer class="org.apache.lucene.analysis.WhitespaceAnalyzer"/>
</fieldType>

2015年10月23日 星期五

無線網路連接成功, 卻顯示無法連至網際網路

最近的 NoteBook 的無線網路連接成功, 卻顯示無法連至網際網路. 以前最多都是重新啟動電腦就可恢復正常, 但這次始終連不上. 因此在 google 找了一下,
找到一篇文章 "為什麼將 TP-LINK 路由器連接到數據機後,無法存取網際網路", 照著文章內容檢查無線路由器. 發現是 WAN 設定跑掉了. 重新設定後即恢復正常. 在此分享鏈結給大家.

2015年8月25日 星期二

weblogic 使用時遇到的幾個難解問題 ( weblogic 12c )

最近的工作使用到 weblogic 12c , 過程中遇到幾個瓶頸, 都花了不少時間處理, 在此提供給諸位參考, 如果有人遇到類似這樣的情況, 或許可以啟發解決問題的思路.

  • 1. 一個 war 檔, 更新版本, deploy 後, 功能運作有問題. 但退回原先的版本, 卻無法如同先前一樣正常的運作.
    解決方法為: 從 weglogic console remove 此 war 檔的 deployment. 再檢查 OS weblogic 目錄下有無殘留的 war 檔, 有則刪除. restart weblogic, 從 weblogic console 再 create 此 war 檔的 deployment, 即可恢復舊版本的正常運作.
  • 2. 一個 war 檔, 更新版本, deploy 後, 一直出現 java.lang.NoSuchMethodError Exception. 這個問題的原因是 OS 目錄裏有舊版的 war 檔在干擾. 解決方法為: 從 weglogic console remove 此 war 檔的 deployment. 刪除 OS weblogic 目錄下殘留的 war 檔. restart weblogic, 從 weblogic console 再 create 此 war 檔的 deployment, 新版本即可正常運作.

這幾天有一個朋友遇到一個問題: 一個 jar 檔放在 weblogic domain 的 system library 目錄下, 卻一直發生 java.lang.NoSuchMethodError Exception, 各位猜猜是什麼原因? 這個問題目前尚未解決.

2014年10月25日 星期六

PostgreSQL Buffer Cache 的運作機制

前幾天因為要了解 PostgreSQL Buffer Cache 的運作機制, 所以找了一下資料, 找到一個網站, 分享給大家, Monitoring PostgreSQL Buffer Cache Internals. 其中這個檔案 Presentation slides (application/pdf - 188.9 KB) 可以看一下, 很清楚地說明了 PostgreSQL Buffer Cache 內部的運作機制.

讀過之後再來看 PostgreSQL: Documentation: 9.2: The Statistics Collector 裏的 pg_stat_bgwriter View 的欄位說明, 就可以明白了解. 以下這些表格的內容是取自 PostgreSQL: Documentation: 9.2: The Statistics Collector 加以補充的.

Table 27-4. pg_stat_database View

Column Type Description
buffers_checkpoint bigint Number of buffers written during checkpoints. 系統每隔一段時間(checkpoint_timeout)或 checkpoint_segments 寫滿時, 會觸發 checkpoint, 此時要將記憶體修改過的 page, 寫回硬碟. 寫入的 block 數目, 就統計在這個數據.
buffers_clean bigint Number of buffers written by the background writer. PostgreSQL 設計上就有一個常駐的 bgwriter process 定期地在檢查, 把一些 dirty 的 page 寫回硬碟, 以免 checkpoint 時需要寫入一大堆 block, 造成整個系統 freeze. 這些被 clean 的 block 數目, 就統計在這個數據.
buffers_backend bigint Number of buffers written directly by a backend. 當Buffer Cache 找不到要的資料, 需要從硬碟讀入新的 block, 但剛好要 swap out 的資料, 需要寫入硬碟, 就統計在這個數據.

所以 block_write = buffers_checkpoint + buffers_clean + buffers_backend.

我們再來看 read 部份, buffer read 的資訊在 pg_stat_database, 不過要記得 pg_stat_database 是 database level 的, 所以要加總之後, 才是整個 server 的統計數據. 而 pg_stat_writer 是 server level 的.

Table 27-4. pg_stat_database View

Column Type Description
blks_read bigint Number of disk blocks read in this database. 這個數據統計的是從 disk 讀取的 block 數, 因為在buffer cache 找不到相關的資料.
blks_hit bigint Number of times disk blocks were found already in the buffer cache, so that a read was not necessary (this only includes hits in the PostgreSQL buffer cache, not the operating system's file system cache). 需要的資料已存在 buffer cache 中, 所以不用再從 disk read.
blk_read_time double precision Time spent reading data file blocks by backends in this database, in milliseconds. 從 disk 讀取資料所花的時間.
blk_write_time double precision Time spent writing data file blocks by backends in this database, in milliseconds. 這個數據我試過了, checkpoint 循環發生時, 有 dirty page, 這個數據也不會增加, 似乎 bgwriter clean 也不會來更新這個數據, 只有大量新增才會被統計, 所以推估這是只統計 dirty page 不得不 swap out 的 write time.
stats_reset timestamp with time zone Time at which these statistics were last reset. 請記得 pg_stat_ 系列的一些 view 是統計一段時間的, 會不斷地自動累加. 除非你 reset 統計起始時間.

Table 27-13. Additional Statistics Functions

Function Return Type Description
pg_backend_pid() integer Process ID of the server process handling the current session
pg_stat_get_activity(integer) setof record Returns a record of information about the backend with the specified PID, or one record for each active backend in the system if NULL is specified. The fields returned are a subset of those in the pg_stat_activity view.
pg_stat_clear_snapshot() void Discard the current statistics snapshot
pg_stat_reset() void Reset all statistics counters for the current database to zero (requires superuser privileges)
pg_stat_reset_shared(text) void Reset some cluster-wide statistics counters to zero, depending on the argument (requires superuser privileges). Calling pg_stat_reset_shared('bgwriter') will zero all the counters shown in the pg_stat_bgwriter view.
pg_stat_reset_single_table_counters(oid) void Reset statistics for a single table or index in the current database to zero (requires superuser privileges)
pg_stat_reset_single_function_counters(oid) void Reset statistics for a single function in the current database to zero (requires superuser privileges)

所以要 reset 統計起始時間, 要考慮一起 reset, 這樣統計會有一致的起始點, 但也可以只 reset 想 reset 的那一小部分.以下介紹 reset pg_stat_database, pg_stat_bgwriter 的方法.

#!/bin/bash
username=postgres


#reset pg_stat_database, 要每一個 database reset 一次.

OUTPUT=$( psql -U $username -tc "select datname from pg_database;"
)
echo "$OUTPUT" | while read dbname
do
    psql -U $username -d $dbname -c 'select pg_stat_reset();'
done



#reset pg_stat_bgwriter, 只要下一次 command 即可.

psql -U $username -c "select pg_stat_reset_shared('bgwriter');"

11. 參考文章

2014年7月24日 星期四

nutch 1.8 + solr 4.9.0 探討系列三 : SolrCloud 安裝

參考了 Apache Solr Reference Guide / Apache Solr Reference Guide / SolrCloud, 打算用 4 個 centos 6.3 VM 來安裝 SolrCloud. 在上述文章中可以看到 SolrCloud 的簡介.

Apache Solr includes the ability to set up a cluster of Solr servers that combines fault tolerance and high availability. Called SolrCloud, these capabilities provide distributed indexing and search capabilities, supporting the following features:

  • Central configuration for the entire cluster
  • Automatic load balancing and fail-over for queries
  • ZooKeeper integration for cluster coordination and configuration.

SolrCloud is flexible distributed search and indexing, without a master node to allocate nodes, shards and replicas. Instead, Solr uses ZooKeeper to manage these locations, depending on configuration files and schemas. Documents can be sent to any server and ZooKeeper will figure it out.

ZooKeeper 會安裝成獨立的 server, 不會用 solr 提供的 embedded ZooKeeper. 以下是 4 個 VM 安裝的配置 :

solr1(192.168.0.11) : 安裝 ZooKeeper, solr
solr2(192.168.0.12) : 安裝 ZooKeeper, solr
solr3(192.168.0.13) : 安裝 ZooKeeper, solr
solr4(192.168.0.14) : 安裝 solr
1. solr 安裝
先在 solr1 VM 上安裝 solr, 再 scp 到其他 VM. solr 的安裝請參考 nutch 1.8 + solr 4.9.0 探討系列一 : 基礎安裝篇. 先在 4 台 VM 安裝 scp.

yum install openssh-clients

以下是在 solr1 VM 上傳 solr example/ 目錄 到 solr2, solr3, solr4, 並且 solr1 本身也 copy 一份到 local /root

cd /root/solr-4.9.0/solr
cp -r example/ /root/
scp -r example/ solr2:/root
scp -r example/ solr3:/root
scp -r example/ solr4:/root

在 4 台 VM solr1, solr2, solr3, solr4 更改目錄名.

mv /root/example/ /root/solr-node
2. ZooKeeper 安裝
在 4 台 VM solr1, solr2, solr3, solr4 執行以下指令 :

cd ~
wget http://ftp.twaren.net/Unix/Web/apache/zookeeper/zookeeper-3.4.6/zookeeper-3.4.6.tar.gz
tar zxvf zookeeper-3.4.6.tar.gz
cd zookeeper-3.4.6
cp zoo_sample.cfg zoo.cfg
vi zoo.cfg

修改並儲存 :

# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just
# example sakes.
dataDir=/root/zookeeper-3.4.6/data
# the port at which the clients will connect
clientPort=2181

server.1=solr1:2888:3888
server.2=solr2:2888:3888
server.3=solr3:2888:3888

建立 data 目錄 :

cd ..
mkdir data

solr1 VM 新增 myid 檔案 :

echo "1" >> data/myid

solr2 VM 新增 myid 檔案 :

echo "2" >> data/myid

solr3 VM 新增 myid 檔案 :

echo "3" >> data/myid

solr4 VM 新增 myid 檔案 :

echo "4" >> data/myid
3. 啟動 solr1, solr2, solr3 的 zookeeper server
cd /root/zookeeper-3.4.6
bin/zkServer.sh start
4. 啟動 solr1 的 solr
cd /root/solr-node
java -DnumShards=2 -Dbootstrap_confdir=./solr/collection1/conf \
-Dcollection.configName=myconf -DzkHost=solr1:2181,solr2:2181,solr3:2181 \
-jar start.jar

可連接以下網址查看 :

http://192.168.0.11:8983/solr/#/~cloud

可看到 :

5. 啟動 solr2, solr3, solr4 的 solr
cd /root/solr-node
java -Djetty.port=7574 -DzkHost=solr1:2181,solr2:2181,solr3:2181 \
-jar start.jar

請記得第 2, 3, 4 VM 的 jetty.port 不要和第一台一樣, 在此設為 7574.可連接以下任一網址查看, 請記得這 4 個網址都可以當做這一個 SolrCloud 的入口.

http://192.168.0.11:8983/solr/#/~cloud
or
http://192.168.0.12:7574/solr/#/~cloud
or
http://192.168.0.13:7574/solr/#/~cloud
or
http://192.168.0.14:7574/solr/#/~cloud

可看到 :

請忽略圖中灰色看不到的 端點和文字, 這是在測試過程, 啟動 server 留下的痕跡, 沒有意義.

6. SolrCloud index, query, shards, replicas, fault tolerance and high availability 測試
請注意, 此 solr 的 schema.xml 是來自 Nutch, 所以以下的資料 import 有其特殊性, 若為 solr 的 預設 schema.xml, 可參考 Updating a Solr Index with JSON. 起初以 csv 匯入, 因為測試後發現了問題, 可能是個 bug, 所以改採 json 匯入. 以下指令中的網址可使用任一個 solr node, 任一 node 都是指令入口.
curl "http://192.168.0.14:7574/solr/update" -H 'Content-type:application/json' -d '
[
 {"id" : "book1",
  "url" : "url1",
  "title" : "A Game of Thrones",
  "author" : "George R.R. Martin"
 },
 {"id" : "book2",
  "url" : "url2",
  "title" : "A Clash of Kings",
  "author" : "George R.R. Martin"
 },
 {"id" : "book3",
  "url" : "url3",
  "title" : "Foundation",
  "author" : "Isaac Asimov"
 },
 {"id" : "book4",
  "url" : "url4",
  "title" : "Foundation and Empire",
  "author" : "Isaac Asimov"
 }
]'

然後使用以下 8 個指令看回傳的結果, 其中 distrib=false 是代表只要查詢此一 shard node 上的資料, 而不是全部資料. 請記得要用 "" 括住整個網址.

curl "http://192.168.0.11:8983/solr/collection1/select?q=*:*"
curl "http://192.168.0.12:7574/solr/collection1/select?q=*:*"
curl "http://192.168.0.13:7574/solr/collection1/select?q=*:*"
curl "http://192.168.0.14:7574/solr/collection1/select?q=*:*"

curl "http://192.168.0.11:8983/solr/collection1/select?q=*:*&distrib=false"
curl "http://192.168.0.12:7574/solr/collection1/select?q=*:*&distrib=false"
curl "http://192.168.0.13:7574/solr/collection1/select?q=*:*&distrib=false"
curl "http://192.168.0.14:7574/solr/collection1/select?q=*:*&distrib=false"

接下來再依序 stop 其中 solr4, solr3, solr2 3 個 node, 並同時執行以上指令, 看會有甚麼影響.

我們從上面的 shard node 結構圖可以看出, shard1, shard2 為一個完整的資料, 整個 cluster 有 2 份資料, 自動保持同步. 所以 shard1, shard2 至少各要保持一個 node, 如果無法達成此條件, 則上述的查詢會得到 503 的錯誤訊息. Leader shard node 如果 stop 後再啟動, 因為它 stop, 所以 Leader 身份自動轉移出去. 例如:如果 stop solr1, 再啟動, 可以發現, solr1 已失去 Leader 的身份, 改為 solr4.

接下來我們來測試 ZooKeeper 的 high availability. 從 Getting Started with SolrCloud 的 Using Multiple ZooKeepers in an Ensemble 段落內容可看出, 3 台 ZooKeeper Server 至少要保持 2 台 ZooKeeper live, 才可以達到 high availability.

To truly provide high availability, we need to make sure that not only do we also have at least one shard server running at all times, but also that the cluster also has a ZooKeeper running to manage it. To do that, you can set up a cluster to use multiple ZooKeepers. This is called using a ZooKeeper ensemble. A ZooKeeper ensemble can keep running as long as more than half of its servers are up and running, so at least two servers in a three ZooKeeper ensemble, 3 servers in a 5 server ensemble, and so on, must be running at any given time. These required servers are called a quorum.

經過測試, 任 2 台 ZooKeeper stop 時, SolrCloud 就無法使用, 所以真的必須保留一半以上的 ZooKeepers.

7. Nutch 抓取的資料給 SolrCloud 做 index
前面有說 4 個 node 都可以做 SolrCloud 的入口, 所以可以執行以下任一指令 :
bin/crawl urls crawl http://192.168.0.11:8983/solr/ 2
or
bin/crawl urls crawl http://192.168.0.12:7574/solr/ 2
or
bin/crawl urls crawl http://192.168.0.13:7574/solr/ 2
or
bin/crawl urls crawl http://192.168.0.14:7574/solr/ 2

再用上述的 query 指令, 查詢一下 Nutch 抓取的資料在各個 shard 的分布.

Nutch 的安裝請參考 nutch 1.8 + solr 4.9.0 探討系列一 : 基礎安裝篇.

8. 用 Tomcat 取代 jetty
Tomcat 的安裝, 及與 solr 的整合與設定請參考 nutch 1.8 + solr 4.9.0 探討系列一 : 基礎安裝篇 中 6~8 步驟. 先在 solr1 安裝和設定. 其中步驟 7 改成以下方式處理.
yum install unzip
unzip /root/solr-node/webapps/solr.war -d /usr/local/apache-tomcat-8.0.9/webapps/solr
vi /usr/local/apache-tomcat-8.0.9/webapps/solr/WEB-INF/web.xml

修改以下內容 :

  <!--
    <env-entry>
       <env-entry-name>solr/home</env-entry-name>
       <env-entry-value>/put/your/solr/home/here</env-entry-value>
       <env-entry-type>java.lang.String</env-entry-type>
    </env-entry>
   -->

    <env-entry> 
       <env-entry-name>solr/home</env-entry-name>
       <env-entry-value>/root/solr-node/solr</env-entry-value>
       <env-entry-type>java.lang.String</env-entry-type>
    </env-entry>

修改 server.xml :

vi /usr/local/apache-tomcat-8.0.9/conf/server.xml

修改 8080 port 成 8983 :

<Connector port="8983" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />

修改 cataina.sh :

vi /usr/local/apache-tomcat-8.0.9/bin/cataina.sh

加入以下內容 :

JAVA_OPTS="$JAVA_OPTS -Djetty.port=8983 -DnumShards=2 -Dbootstrap_confdir=/root/solr-node/solr/collection1/conf -Dcollection.configName=myconf -DzkHost=solr1:2181,solr2:2181,solr3:2181"

solr2, solr3, solr4 建立 /usr/local 目錄

mkdir /usr/local

solr1 設定好後, scp 到 solr2, solr3, solr4

scp -r /usr/local/apache-tomcat-8.0.9/ solr2:/usr/local/
scp -r /usr/local/apache-tomcat-8.0.9/ solr3:/usr/local/
scp -r /usr/local/apache-tomcat-8.0.9/ solr4:/usr/local/

solr2, solr3, solr4 修改 server.xml

vi /usr/local/apache-tomcat-8.0.9/conf/server.xml

修改 8983 port 成 7574 :

<Connector port="7574" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />

修改 cataina.sh :

vi /usr/local/apache-tomcat-8.0.9/bin/cataina.sh

修改 jetty.port 成 7574 :

JAVA_OPTS="$JAVA_OPTS -Djetty.port=7574 -DnumShards=2 -Dbootstrap_confdir=/root/solr-node/solr/collection1/conf -Dcollection.configName=myconf -DzkHost=solr1:2181,solr2:2181,solr3:2181"

-Djetty.port=7574 也可以不指定, 而直接修改 /root/solr-node/solr/solr.xml 的 hostPort, 由 ${jetty.port:8983} 改為 7574 :

vi /root/solr-node/solr/solr.xml

修改如下 :

  <solrcloud>
    <str name="host">${host:}</str>
    <int name="hostPort">7574</int>
    <str name="hostContext">${hostContext:solr}</str>
    <int name="zkClientTimeout">${zkClientTimeout:30000}</int>
    <bool name="genericCoreNodeNames">${genericCoreNodeNames:true}</bool>
  </solrcloud>

請記得 solr2, solr3, solr4 的 port 要跟 solr1 不一樣. 讀者可以試試看所有 VM 的 port 都一樣的結果.

solr1, solr2, solr3, solr4 startup tomcat

cd /usr/local/apache-tomcat-8.0.9
bin/startup.sh

連上以下網址驗證 SolrCloud 是否正常

http://192.168.0.11:8983/solr/#/~cloud
or
http://192.168.0.12:7574/solr/#/~cloud
or
http://192.168.0.13:7574/solr/#/~cloud
or
http://192.168.0.14:7574/solr/#/~cloud
9. multicore 的設定
因為要改用 multicore, 所以將先前的 collection1 刪除. 否則以下的操作完成後會不正常. 在 solr1 執行以下指令 :
curl "http://192.168.0.11:8983/solr/admin/collections?action=DELETE&name=collection1"

solr1, solr2, solr3, solr4 shutdown tomcat
cd /usr/local/apache-tomcat-8.0.9
bin/shutdown.sh

SolrCloud是透過 ZooKeeper 集群來保證 conf/ 文件的變更及時同步到各個節點上,所以,需要將 conf/* 上傳到 ZooKeeper 集群中, 我們共有 core0, core1 2 個 core(以前只有 collection1):

在 solr1 上執行以下指令 :

core0 :

java -classpath .:/root/solr-node/solr-webapp/webapp/WEB-INF/lib/*:/root/solr-node/lib/ext/* \
     org.apache.solr.cloud.ZkCLI -cmd upconfig -zkhost solr1:2181,solr2:2181,solr3:2181 \
     -confdir /root/solr-node/multicore/core0/conf -confname conf0 \
     -solrhome /root/solr-node/multicore

java -classpath .:/root/solr-node/solr-webapp/webapp/WEB-INF/lib/*:/root/solr-node/lib/ext/* \
     org.apache.solr.cloud.ZkCLI -cmd linkconfig -zkhost solr1:2181,solr2:2181,solr3:2181 \
     -collection core0 -confname conf0 -solrhome /root/solr-node/multicore

core1 :

java -classpath .:/root/solr-node/solr-webapp/webapp/WEB-INF/lib/*:/root/solr-node/lib/ext/* \
     org.apache.solr.cloud.ZkCLI -cmd upconfig -zkhost solr1:2181,solr2:2181,solr3:2181 \
     -confdir /root/solr-node/multicore/core1/conf -confname conf1 \
     -solrhome /root/solr-node/multicore

java -classpath .:/root/solr-node/solr-webapp/webapp/WEB-INF/lib/*:/root/solr-node/lib/ext/* \
     org.apache.solr.cloud.ZkCLI -cmd linkconfig -zkhost solr1:2181,solr2:2181,solr3:2181 \
     -collection core1 -confname conf1 -solrhome /root/solr-node/multicore

上傳完成以後,我們查一下 ZooKeeper 上的儲存情況, 可以選擇 solr, solr2, solr3 任一台 VM 執行以下指令:

cd /root/zookeeper-3.4.6
bin/zkCli.sh -server solr1:2181
...

[zk: solr1:2181(CONNECTED) 0] ls /
[configs, zookeeper, clusterstate.json, aliases.json, live_nodes, overseer, overseer_elect, collections]
[zk: solr1:2181(CONNECTED) 1] ls /configs
[conf0, conf1, myconf]
[zk: solr1:2181(CONNECTED) 2] ls /configs/conf0
[admin-extra.menu-top.html, currency.xml, protwords.txt, mapping-FoldToASCII.txt, _schema_analysis_synonyms_english.json, solrconfig.xml, _schema_analysis_stopwords_english.json, stopwords.txt, lang, schema.xml.bak, spellings.txt, mapping-ISOLatin1Accent.txt, admin-extra.html, xslt, synonyms.txt, scripts.conf, update-script.js, velocity, elevate.xml, admin-extra.menu-bottom.html, schema.xml, clustering]
[zk: solr1:2181(CONNECTED) 3]quit

也可以查詢一下 solr2, solr3 的情況 :

bin/zkCli.sh -server solr2:2181
or
bin/zkCli.sh -server solr3:2181

在 solr1, solr2, solr3, solr4 修改 tomcat 的設定並重新啟動 :

cd /usr/local/apache-tomcat-8.0.9
vi bin/cataina.sh

刪除先前所輸入的 -Dbootstrap_confdir=/root/solr-node/solr/collection1/conf -Dcollection.configName=myconf

JAVA_OPTS="$JAVA_OPTS -Djetty.port=7574 -DnumShards=2 -Dbootstrap_confdir=/root/solr-node/solr/collection1/conf -Dcollection.configName=myconf -DzkHost=solr1:2181,solr2:2181,solr3:2181"

刪除之後的內容 :

JAVA_OPTS="$JAVA_OPTS -Djetty.port=7574 -DnumShards=2 -DzkHost=solr1:2181,solr2:2181,solr3:2181"

在 solr1 上的 cataina.sh 是 -Djetty.port=8983, 為了方便就不另外貼指令.

修改solr1, solr2, solr3, solr4 的各個 VM 上的 web.xml :

vi /usr/local/apache-tomcat-8.0.9/webapps/solr/WEB-INF/web.xml

修改以下內容 :

  <!--
    <env-entry>
       <env-entry-name>solr/home</env-entry-name>
       <env-entry-value>/root/solr-node/solr</env-entry-value>
       <env-entry-type>java.lang.String</env-entry-type>
    </env-entry>
   -->

    <env-entry> 
       <env-entry-name>solr/home</env-entry-name>
       <env-entry-value>/root/solr-node/multicore</env-entry-value>
       <env-entry-type>java.lang.String</env-entry-type>
    </env-entry>

/root/solr-node/multicore/ 下的 core0/conf/, core1/conf/, 讀者可自行定義相關 xml, 或從 collection1/conf copy 過來再修改.

solr1, solr2, solr3, solr4 startup tomcat

bin/startup.sh
10. 參考文章

2014年7月8日 星期二

nutch 1.8 + solr 4.9.0 探討系列二 : nutch url filter, re-crawl, crawl script

這一篇有三個想要了解的重點, urlfilter 的選用, re-crawl 的設定, 及 crawl script 的內容.

1. urlfilter 的選用
NutchTutorial 或是一些 Nucth 的安裝文章裏, 都可以看到一個步驟, 就是如果想要對所抓取的網址做 filter 的話, 可以修改 regex-urlfilter.txt 的最後一行, 達到這個目的. 但是我們可以發現, conf/ 目錄下有許多 urlfilter.txt, 其他的 urlfilter.txt 有沒有作用呢? 要如何使用?

打開 regex-urlfilter.txt, 可以看到內容裏說明 :

vi /root/apache-nutch-1.8/runtime/local/conf/regex-urlfilter.txt

# The default url filter.
# Better for whole-internet crawling.

所以我們可以知道 regex-urlfilter.txt 是 default 的 url filter. 但是如果你打開 automaton-urlfilter.txt, 也可以發現以下描述 :

vi /root/apache-nutch-1.8/runtime/local/conf/automaton-urlfilter.txt

# The default url filter.
# Better for whole-internet crawling.

所以 regex-urlfilter.txt, automaton-urlfilter.txt 兩個是 default 的 url filter, 其他的不是. 真的是這樣嗎?

如果我們觀察 nutch-default.xml 的內容, 可以發現以下幾個 property

vi /root/apache-nutch-1.8/runtime/local/conf/nutch-default.xml

<!-- indexingfilter plugin properties -->

<property>
  <name>indexingfilter.order</name>
  <value></value>
  <description>The order by which index filters are applied.
  If empty, all available index filters (as dictated by properties
  plugin-includes and plugin-excludes above) are loaded and applied in system
  defined order. If not empty, only named filters are loaded and applied
  in given order. For example, if this property has value:
  org.apache.nutch.indexer.basic.BasicIndexingFilter org.apache.nutch.indexer.more.MoreIndexingFilter
  then BasicIndexingFilter is applied first, and MoreIndexingFilter second.

  Filter ordering might have impact on result if one filter depends on output of
  another filter.
  </description>
</property>

<property>
  <name>plugin.includes</name>
  <value>protocol-http|urlfilter-regex|parse-(html|tika)|index-(basic|anchor)|indexer-solr|scoring-opic|urlnormalizer-(pass|regex|basic)</value>
  <description>Regular expression naming plugin directory names to
  include.  Any plugin not matching this expression is excluded.
  In any case you need at least include the nutch-extensionpoints plugin. By
  default Nutch includes crawling just HTML and plain text via HTTP,
  and basic indexing and search plugins. In order to use HTTPS please enable
  protocol-httpclient, but be aware of possible intermittent problems with the
  underlying commons-httpclient library.
  </description>
</property>

<property>
  <name>plugin.excludes</name>
  <value></value>
  <description>Regular expression naming plugin directory names to exclude.
  </description>
</property>

<!-- urlfilter plugin properties -->

<property>
  <name>urlfilter.domain.file</name>
  <value>domain-urlfilter.txt</value>
  <description>Name of file on CLASSPATH containing either top level domains or
  hostnames used by urlfilter-domain (DomainURLFilter) plugin.</description>
</property>

<property>
  <name>urlfilter.regex.file</name>
  <value>regex-urlfilter.txt</value>
  <description>Name of file on CLASSPATH containing regular expressions
  used by urlfilter-regex (RegexURLFilter) plugin.</description>
</property>

<property>
  <name>urlfilter.automaton.file</name>
  <value>automaton-urlfilter.txt</value>
  <description>Name of file on CLASSPATH containing regular expressions
  used by urlfilter-automaton (AutomatonURLFilter) plugin.</description>
</property>

<property>
  <name>urlfilter.prefix.file</name>
  <value>prefix-urlfilter.txt</value>
  <description>Name of file on CLASSPATH containing url prefixes
  used by urlfilter-prefix (PrefixURLFilter) plugin.</description>
</property>

<property>
  <name>urlfilter.suffix.file</name>
  <value>suffix-urlfilter.txt</value>
  <description>Name of file on CLASSPATH containing url suffixes
  used by urlfilter-suffix (SuffixURLFilter) plugin.</description>
</property>

<property>
  <name>urlfilter.order</name>
  <value></value>
  <description>The order by which url filters are applied.
  If empty, all available url filters (as dictated by properties
  plugin-includes and plugin-excludes above) are loaded and applied in system
  defined order. If not empty, only named filters are loaded and applied
  in given order. For example, if this property has value:
  org.apache.nutch.urlfilter.regex.RegexURLFilter org.apache.nutch.urlfilter.prefix.PrefixURLFilter
  then RegexURLFilter is applied first, and PrefixURLFilter second.
  Since all filters are AND'ed, filter ordering does not have impact
  on end result, but it may have performance implication, depending
  on relative expensiveness of filters.
  </description>
</property>

仔細看各個 property 的描述, 可以發現 urlfilter.*.file 的 property 是在定義 url filter 的檔名, 而會引用哪些 filter, 是在 plugin.includes 定義, plugin.excludes 則是排除使用哪些 plugin(url filter 也是 plugin 的一種), 而 urlfilter.order 和 indexingfilter.order 則是讓使用者自行定義 filter 的調用順序, 如果空白, 則系統自己會排順序. 從這一系列的 property 的設定看來, 只有 regex-urlfilter.txt 會被調用. 也因此我們可以自己決定要使用哪幾個 url filter 和順序, 不過筆者覺得, regex-urlfilter.txt 就夠用了, 甚至筆者也不會想要去修改 regex-urlfilter.txt 來進一步 filter 抓取的網址.

2. re-crawl 的設定
How to re-crawl with Nutch 可以了解 re-crawl 的機制設計和幾個設定, 但系統究竟會用 db.fetch.interval.default 或 db.fetch.schedule.adaptive.* 的設定, 此篇文章並沒有說明. 但我在另一篇文章 RE: Question about fetch interval value 有說到修改以下這個 property 可以指定使用 db.fetch.schedule.adaptive.* 的設定. 各位可以試試看.
vi /root/apache-nutch-1.8/runtime/local/conf/nutch-site.xml

<property>
  <name>db.fetch.schedule.class</name>
  <value>org.apache.nutch.crawl.AdaptiveFetchSchedule</value>
</property>

但如果要 "auto" re-crawl, 還是得自己設定 crontab. 記得請不要直接修改 nutch-default.xml, 而是將要修改的 property 放到 nutch-site.xml 檔, 這樣 nutch-site.xml 的 properties 就會 override nutch-default.xml 的值.

3. bin/crawl script 的內容
在 nutch 1.8 之後已經不可以使用 bin/nutch crawl 的指令, 因為 NUTCH 1.8 及 NUTCH 2.3 之後已經 remove 這個用法. 請參考 bin/nutch crawl . 所以我們改用 bin/crawl 來替代 bin/nutch crawl. 或者也可以參考 NutchTutorial 的做法, step by step 下指令. 其實 bin/crawl script 也是將這樣的指令組合起來在一起執行而已. 我們來看一下 bin/crawl 的內容.
#!/bin/bash
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License.  You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# 
# The Crawl command script : crawl <seedDir> <crawlDir> <solrURL> <numberOfRounds>
#
# 
# UNLIKE THE NUTCH ALL-IN-ONE-CRAWL COMMAND THIS SCRIPT DOES THE LINK INVERSION AND 
# INDEXING FOR EACH SEGMENT

SEEDDIR="$1"
CRAWL_PATH="$2"
SOLRURL="$3"
LIMIT="$4"

# 做輸入參數的檢查
if [ "$SEEDDIR" = "" ]; then
    echo "Missing seedDir : crawl <seedDir> <crawlDir> <solrURL> <numberOfRounds>"
    exit -1;
fi

if [ "$CRAWL_PATH" = "" ]; then
    echo "Missing crawlDir : crawl <seedDir> <crawlDir> <solrURL> <numberOfRounds>"
    exit -1;
fi

if [ "$SOLRURL" = "" ]; then
    echo "Missing SOLRURL : crawl <seedDir> <crawlDir> <solrURL> <numberOfRounds>"
    exit -1;
fi

if [ "$LIMIT" = "" ]; then
    echo "Missing numberOfRounds : crawl <seedDir> <crawlDir> <solrURL> <numberOfRounds>"
    exit -1;
fi

#############################################
# MODIFY THE PARAMETERS BELOW TO YOUR NEEDS #
#############################################

# set the number of slaves nodes
numSlaves=1

# and the total number of available tasks
# sets Hadoop parameter "mapred.reduce.tasks"
numTasks=`expr $numSlaves \* 2`

# number of urls to fetch in one iteration
# 250K per task?
# 這個參數就是 -topN $numSlaves*50 , 本來是 $numSlaves*50000 , 
# 但是考量硬碟的大小及測試階段不要花費太長的時間做抓取, 所以設為 50

sizeFetchlist=`expr $numSlaves \* 50`

# time limit for feching
timeLimitFetch=180

# num threads for fetching
numThreads=50

#############################################

# determines whether mode based on presence of job file
# 看起來如果要執行 distributed mode, 要執行 runtime/deploy/bin/crawl, 
# 該檔內容與此檔 runtime/local/bin/crawl 一樣.
# 在 runtime/deploy 下有

mode=local
if [ -f ../*nutch-*.job ]; then
    mode=distributed
fi

bin=`dirname "$0"`
bin=`cd "$bin"; pwd`

# note that some of the options listed here could be set in the 
# corresponding hadoop site xml param file 
commonOptions="-D mapred.reduce.tasks=$numTasks -D mapred.child.java.opts=-Xmx1000m -D mapred.reduce.tasks.speculative.execution=false -D mapred.map.tasks.speculative.execution=false -D mapred.compress.map.output=true"

 # check that hadoop can be found on the path 
if [ $mode = "distributed" ]; then
 if [ $(which hadoop | wc -l ) -eq 0 ]; then
    echo "Can't find Hadoop executable. Add HADOOP_HOME/bin to the path or run in local mode."
    exit -1;
 fi
fi

# initial injection
$bin/nutch inject $CRAWL_PATH/crawldb $SEEDDIR

if [ $? -ne 0 ] 
  then exit $? 
fi


# main loop : rounds of generate - fetch - parse - update
# 第 4 個輸入參數 LIMIT , 適用來控制 loop 的次數

for ((a=1; a <= LIMIT ; a++))
do
  if [ -e ".STOP" ]
  then
   echo "STOP file found - escaping loop"
   break
  fi

  echo `date` ": Iteration $a of $LIMIT"

  echo "Generating a new segment"

  # 請注意 -topN $sizeFetchlist , -numFetchers $numSlaves 
  # 這 2 個上面設的環境變數, 在這裡被用到了
  $bin/nutch generate $commonOptions $CRAWL_PATH/crawldb $CRAWL_PATH/segments -topN $sizeFetchlist -numFetchers $numSlaves -noFilter
  
  if [ $? -ne 0 ] 
  then exit $? 
  fi

  # capture the name of the segment
  # call hadoop in distributed mode
  # or use ls

  if [ $mode = "local" ]; then
   SEGMENT=`ls $CRAWL_PATH/segments/ | sort -n | tail -n 1`
  else
   SEGMENT=`hadoop fs -ls $CRAWL_PATH/segments/ | grep segments |  sed -e "s/\//\\n/g" | egrep 20[0-9]+ | sort -n | tail -n 1`
  fi
  
  echo "Operating on segment : $SEGMENT"

  # fetching the segment
  echo "Fetching : $SEGMENT"

  # 請注意 -threads $numThreads 這個上面設的環境變數, 在這裡被用到了
  $bin/nutch fetch $commonOptions -D fetcher.timelimit.mins=$timeLimitFetch $CRAWL_PATH/segments/$SEGMENT -noParsing -threads $numThreads

  if [ $? -ne 0 ] 
  then exit $? 
  fi

  # parsing the segment
  echo "Parsing : $SEGMENT"
  # enable the skipping of records for the parsing so that a dodgy document 
  # so that it does not fail the full task
  skipRecordsOptions="-D mapred.skip.attempts.to.start.skipping=2 -D mapred.skip.map.max.skip.records=1"
  $bin/nutch parse $commonOptions $skipRecordsOptions $CRAWL_PATH/segments/$SEGMENT

  if [ $? -ne 0 ] 
  then exit $? 
  fi

  # updatedb with this segment
  echo "CrawlDB update"
  $bin/nutch updatedb $commonOptions $CRAWL_PATH/crawldb  $CRAWL_PATH/segments/$SEGMENT

  if [ $? -ne 0 ] 
  then exit $? 
  fi

# note that the link inversion - indexing routine can be done within the main loop 
# on a per segment basis
  echo "Link inversion"
  $bin/nutch invertlinks $CRAWL_PATH/linkdb $CRAWL_PATH/segments/$SEGMENT

  if [ $? -ne 0 ] 
  then exit $? 
  fi

  echo "Dedup on crawldb"
  # Once indexed the entire contents, it must be disposed of 
  # duplicate urls in this way ensures that the urls are unique.
  # <-- from http://wiki.apache.org/nutch/NutchTutorial

  $bin/nutch dedup $CRAWL_PATH/crawldb
  
  if [ $? -ne 0 ] 
   then exit $? 
  fi

  echo "Indexing $SEGMENT on SOLR index -> $SOLRURL"
  $bin/nutch index -D solr.server.url=$SOLRURL $CRAWL_PATH/crawldb -linkdb $CRAWL_PATH/linkdb $CRAWL_PATH/segments/$SEGMENT
  
  if [ $? -ne 0 ] 
   then exit $? 
  fi

  echo "Cleanup on SOLR index -> $SOLRURL"

  # The class scans a crawldb directory looking for entries 
  # with status DB_GONE (404) and sends delete requests to 
  # Solr for those documents. Once Solr receives the request 
  # the aforementioned documents are duly deleted. 
  # This maintains a healthier quality of Solr index. 
  # <-- from http://wiki.apache.org/nutch/NutchTutorial

  $bin/nutch clean -D solr.server.url=$SOLRURL $CRAWL_PATH/crawldb
  
  if [ $? -ne 0 ] 
   then exit $? 
  fi

done

exit 0
4. 參考文章