顯示具有 WebHarvest 標籤的文章。 顯示所有文章
顯示具有 WebHarvest 標籤的文章。 顯示所有文章

2012年3月6日 星期二

Web-Harvest 挖掘需要的數據


首先,在官方網站下載web-harvest,目前最新版本是1.0,下載頁面分三個下載包,分別是webharvest1-ex​​e.zip,webharvest1-bin.zip,webharvest1-project.zip,他們沒實質區別,第一個是包含了全部第三方包(一起打入了同一個jar文件直接可運行),第二個做為一個中間件出現,附帶了所有獨立的第三方jar包,第三個則是源碼,當然要最大的靈活性自然選擇下載源碼了:》

下載下來後再eclipse建立一個空項目,把所有解壓出來的文件夾仍進去,然後把src和config設為source folder (源碼目錄) 然後看到default包下面有個Test.java的文件,下面我們來看看這個文件:

        ScraperConfiguration config = new ScraperConfiguration("c:/temp/scrapertest/dddd.xml");
        Scraper scraper = new Scraper(config, "c:/temp/scrapertest/");
        scraper.setDebug(true);
        long startTime = System.currentTimeMillis();
        scraper.execute();
        System.out.println("time elapsed: " + (System.currentTimeMillis() - startTime));

除去log4j的註解外,剩下的就是這幾行代碼啦,我們看到運行挖掘任務只需要3行代碼,創建config,用config和挖取到數據存儲目錄做參數創建Scraper,然後執行就OK。

好了,先試試,它能不能工作,在剛才解壓出來的文件中,有一個examples文件夾下面有很多,配置文件可以先試試手,首先,看看圖片挖取的例子google_images.xml ,更改上面的兩行代碼

        ScraperConfiguration config = new ScraperConfiguration("E:/workspace/webharvest/examples/google_images.xml");

        Scraper scraper = new Scraper(config, " E:/workspace/webharvest/examples/");

然後運行這個Test在console窗口會看到抓取過程,完成後再E:\workspace\webharvest\examples\google_images目錄下就能看到剛才抓取到的所有圖片了,抓取非常簡單,到此為止,我們看看他的配置文件,google_images.xml到底有什麼魔法。

在google_images.xml中,這個配置文件中首先引用了一個functions.xml,在google_images.xml同級目錄下,我們先看看這個文件。這個文件中定義了一個function(函數)名字是download-multipage-list雖然不符合java函數定義規則,但是這裡只是一個函數名,函數return了一個循環出來的結果集,empty標記表示其中的執行結果不包含在返回結果集內,那麼主要返回的就是:
                <xpath expression="${itemXPath}">
                    <var name="content"/>
                </xpath>
這段了,這段代碼表示,用xpath方式(解析規則是expression表達式決定的)解析引用的content內容,做為一個行記錄(這個行記錄會被默認封裝成一個List(不是普通的list而是:org.webharvest.runtime.variables.ListVariable);

循環的主體主要實現讀取數頁(多少頁由$maxloops變量決定)的數據.


現在看看empty內部的功能:

首先
<var-def name="content">
    <html-to-xml>
        <http url="${pageUrl}"/>
    </html-to-xml>
</var-def>
是定義一個content變量,內容是訪問${pageUrl}頁面內容。

接著:
<var-def name="nextLinkUrl">
    <xpath expression="${nextXPath}">
        <var name="content"/>
    </xpath>
</var-def>

是用xpath表達式(${nextXPath})取content變量內部的值(也就是剛才${pageUrl}頁面解析)得到下一頁的URL地址。

<var-def name="pageUrl">
      <template>${sys.fullUrl(pageUrl.toString(), nextLinkUrl.toString())}</template>
</var-def>

對pageUrl重新賦值,將下一頁賦值給pageUrl下次循環讀下一頁數據解析。

好了這個函數簡單的說就是用來分頁取數據的。

回到google-images.xml文件我們繼續看:

      <!-​​- defines search keyword and start URL -->
      <var-def name="search" overwrite="false">platon</var-def>
      <var-def name="url">
            <xpath expression="//noscript//a/@href[1]">
                  <html-to-xml>
                   <http url="http://images.google.com/images?q=${search}&amp;hl=en&amp;btnG=Search+Images"/>
                  </html-to-xml>
            </xpath>
    </var-def>

很簡單,定義一個變量做搜索條件給url中,然後用html-to-xml標記把http標記訪問url的頁面html內容轉換為xml,最後用xpath一個表達式得到頁面真實的url(看來google確實比較麻煩,其他網站可以直接得到url的)。

      <!-​​- collects all image URLs -->
      <var-def name="imgLinks">
        <call name="d​​ownload-multipage-list">
            <call-param name="pageUrl"><var name="url"/></call-param>
            <call-param name="nextXPath">//td[.='Next']/a/@href</call-param>
            <call-param name="itemXPath">//img[contains(@src, 'images?q=tbn')]/@src</call-param>
            <call-param name="maxloops">5</call-param>
        </call>
      </var-def>
    這段的功能就是調用分頁函數,把itemXPath參數中的xpath表達式內容,限定解析的頁面內容封裝成一個list啦

首先定義了4個參數,然後調用functions定義的download-multipage-list函數,最後將返回值賦給imgLinks(這就完成了圖片url的抓取工作了)。

    <!-​​- download images and saves them to the files -->
    <loop item="link" index="i" filter="unique">
        <list>
            <var name="imgLinks"/>
        </list>
        <body>
            <file action="write" type="binary" path="google_images/${search}_${i}.gif">
                <http url="${sys.fullUrl(url, link)}"/>
            </file>
        </body>
    </loop>

雖然已經完成了抓取過程,但是圖片還沒有保存在本地呢,這裡我們就要循環調用file標籤,來把url中的圖片逐一保存在本地啦。
這裡很簡單,需要注意的就是loop循環,比較不同的是loop必須包含一個list和body,會把list逐一迭代出來,body代碼中有句sys.fullUrl函數,是用來處理抓到的相對地址修正為完整的http開頭的絕對地址,參數url是前面我們定義的,link是loop循環體變量在循環裡面定義的。

看完了這個我們做個我們看到只要用xpath表達式,對任何頁面的相對位置抓取都不是問題了。

Web-harvest已經解決了大部分問題,我們要做的就是寫配置文件了。

Web-Harvest爬取yahoo!answers數據


本文主要以web-harvest爬取yahoo! answers的數據為例,說明在使用過程中需要注意的問題。當然,最好的使用文檔就是官方網站的user manual。

web-harvest有三個版本,這裡用的是源碼包。要完成數據的爬取,最重要的是配置config文件。源碼包中有個Java類,Test.java,源代碼如下:

public class Test {

    public static void main(String[] args) throws IOException {

        ScraperConfiguration config = new ScraperConfiguration("e:/temp/yahooanswer/auto racing.xml"); //line a
        Scraper scraper = new Scraper(config, "e:/temp/wikianswer"); //line b

        scraper.setDebug(true);

        long startTime = System.currentTimeMillis();
        scraper.execute();
        System.out.println("time elapsed: " + (System.currentTimeMillis() - startTime));
    }

}

 line a中的.xml文件即抓取配置數據,line b 為抓取後數據的存放路徑。其功能是完成yahoo! answers分類中sports/auto racing的resolved問題中的前5頁內容,每頁20條,以如下格式寫入文件中:

下面主要來分析一下auto racing.xml,xml文件如下:

<?xml version="1.0" encoding="utf-8"?>
<config charset="utf-8">
    <include path="functions.xml"/>
    <var-def name="home">http://answers.yahoo.com</var-def>
    <var-def name="QALinks"> //定義變量QALinks,其值為函數download-multipage-list的返回值。
        <call name="d​​ownload-multipage-list">
            <call-param name="pageUrl">http://answers.yahoo.com/dir/index;_ylt=AnRU11UwwAiICNV69Xv._0HzDH1G;_ylv=3?sid=396545601&amp;link=resolved#yan-questions</call- param>
            <call-param name="nextXPath">//li[@rel="next"]/@href</call-param>
            <call-param name="itemXPath">//ul[@class="questions"]//h3//a/@href</call-param>
            <call-param name="maxloops">5</call-param>
        </call>
    </var-def>

    <!-​​- According the link, get all questions -->
    <var-def name="questions">
        <loop item="item" index="i">
            <list><var name="QALinks"/></list>
            <body>
                <html-to-xml>
                    <http url="${sys.fullUrl(home, item)}"/>
                </html-to-xml><script><![CDATA[print("item"+i+":"+item);]]></script>
            </body>
        </loop>
    </var-def>

    <!-​​- iterates over all collected products and extract desired data -->
    <file action="write" path="sports/auto racing.xml" charset="utf-8">
        <![CDATA[ <questionanswers> ]]>
        <loop item="item" index="i">
            <list><var name="questions"/></list>
            <body>
                <template>
                     <![CDATA[<number>]]><var name="i"/><![CDATA[</number>]]>
                </template>
                <xquery>
                    <xq-param name="item" type="node()"><var name="item"/></xq-param>
                    <xq-expression><![CDATA[declare variable $item as node() external;
                        let $subject := data($item//h1[@class='subject'])
                            return
                                 <questionanswer>
                                    <subject>{normalize-space($subject)}</subject>
                                   { for $x at $count in data($item//div[@class="content"])
                            return if($count eq 1)
                        then <questioncontent>{$x}</questioncontent>
                        else <answer>{$x}</answer>
            }
                                </questionanswer>
                ]]></xq-expression>
                </xquery>
            </body>
        </loop>
        <![CDATA[ </questionanswers> ]]>
    </file>
</config>

functions.xml源代碼:

<?xml version="1.0" encoding="UTF-8"?>
<config>
    <!-​​-
        Download multi-page list of items.
      
        @param pageUrl - URL of starting page
        @param itemXPath - XPath expression to obtain single item in the list
        @param nextXPath - XPath expression to URL for the next page
        @param maxloops - maximum number of pages downloaded
      
        @return list of all downloaded items
     -->
    <function name="d​​ownload-multipage-list">
        <return>
            <while condition="${pageUrl.toString().length() != 0}" maxloops="${maxloops}" index="i">
                <empty> //函數中<empty></empty>中的內容表示不用返回。
                    <var-def name="content"> //定義了變量content,其內容是pageUrl返回的網頁內容
                        <html-to-xml>
                            <http url="${pageUrl}"/>
                        </html-to-xml>
                    </var-def>
                    <script><![CDATA[ // <script>中是調試用的print,將輸入內容顯示在Java的控制台。
                        print("pageUrl:"+pageUrl);
                    ]]></script>

                    <var-def name="nextLinkUrl"> //定義了變量nextLinkUrl,其值是根據nextXPath從content中獲取的數據
                        <xpath expression="${nextXPath}">
                            <var name="content"/>
                        </xpath>
                    </var-def>

                    <var-def name="pageUrl"> //重新定義pageUrl,其值為原來的pageUrl和nextLinkUrl的連接。
                        <template>${sys.fullUrl(pageUrl.toString(), nextLinkUrl.toString())}</template>
                    </var-def>

                </empty>
  
                <xpath expression="${itemXPath}"> //要返回的值,根據itemXPath從content中獲取的數據

                    <var name="content"/>
                </xpath>
            </while>
        </return>
    </function>
</config>

 functions.xml定義了一個函數,4個輸入參數,1個輸出。 pageUrl表示起始的抓取url;nextXPath是從本頁抓取的內容中獲取下一頁url的xpath表達式,也就是如何在本頁中獲取next所對應的href;function包含一個while循環,maxloops是在其他條件滿足是最多循環次數;itemXPath是每次循環時從抓取的內容中獲取返回的列表的xpath表達式,本例中是從每頁獲得answer對應的href。最後返回的是根據itemXPath獲取的所有內容的列表。

2012年3月4日 星期日

WebHarvest採集網路數據


一、背景

在當前信息空前爆炸的時代,人們不再擔心信息的匱乏,而是為篩選有用的信息付出大量的代價。那麼如何採集有用的信息呢? 


現在有 RSS、博客等服務,但是並不能完全滿足我們的需求,因為很多信息並不是以格式化的數據形式提供出來,於是聰明的工程師想出了精確搜索的方法,從而出現大量的垂直搜索網站 (比如酷訊),確實火了一把。當然我們無法得知他們是怎麼實現的,但是我們也可以實現這種精確採集,開源的 Web-Harvest 就是類似的技術,之前曾經接觸過,故寫出來分享給大家。

二、WebHarvest 簡介


Web-Harvest 是一個用 J​​ava 寫的開源的 Web 數據提取工具。它提供了一種從所需的頁面上提取有用數據的方法。為了達到這個目的,你可能需要用到如 XSLT, XQuery, 和正則表達式等操作 text/xml 的相關技術。 Web-Harvest 主要著眼於目前仍佔大多數的基於 HMLT/XML 的頁面內容。另一方面,它也能通過寫自己的 Java 方法來輕易擴展其提取能力。


Web-Harvest 的主要目的是加強現有數據提取技術的應用。它的目標不是創造一種新方法,而是提供一種更好地使用和組合現有方法的方式。它提供了一個處理器集用於處理數據和控制流程,每一個處理器被看作是一個函數,它擁有參數和執行後同樣有結果返回。而且處理是被組合成一個管道的形式,這樣使得它們可以以鍊式的形式來執行,此外為了更易於數據操作和重用,Web-Harvest 還提供了變量上下方用於存儲已經聲明的變量。


上述流程的執行結果可以存儲在執行中創建的文件中或者是編程時的上下文環境中使用。


一、配置語言


每個提取過程都被定義在了一個或多個基於 XML 的配置文件中,而且被描述為特定的或是結構化的 XML 元素中。為了更好地說明,下面列舉了一個配置文件來進行說明:


<config charset="gbk">
         
<!– 頁面爬取開始,按照關鍵詞:"玩具"來搜索–>
         
<var-def name="start" >
                  
<html-to-xml>
                            
<http url="http://www.baidu.com/s?wd=玩具"/>
                  
</html-to-xml>
         
</var-def>
         
<!– 獲取競價排名的企業網站列表–>
         
<var-def name="urlList" >
                  
<xpath expression="//div[@class='r']">
                            
<var name="start"/>
                  
</xpath>
         
</var-def>
         
<!– 循環urlList ,並把結果寫入到XML文件中–>
    
<file action="write" path="baidu/catalog.xml" charset="utf-8″>
        
<![CDATA[ <catalog> ]]>
        
<loop item="item" index="i">
            
<list><var name="urlList"/></list>
            
<body>
                
<xquery>
                    
<xq-param name="item" type="node()"><var name="item"/></xq-param>
                    
<xq-expression><![CDATA[
                            
declare variable $item as node() external;
                            
let $name := data($item//span/font[1]/text()[1])
                            
let $url := data($item//span/font[2]/text())
                                
return
                                    
<website>
                                        
<name>{normalize-space($name)}</name>
                                        
<url>{normalize-space($url)}</url>
                                    
</website>
                    
]]></xq-expression>
                
</xquery>
            
</body>
        
</loop>
        
<![CDATA[ </catalog> ]]>
    
</file></config>


上述的配置文件包含了三段。


第一段的執行步驟:1. 下載 http://www.baidu.com/s?wd=玩具 裡面的內容;2. 清除下載內容裡面的 HTML 以產生 XHTML;


第二段的執行步驟:1. 用 XPath 表達式從所給的 URL 裡面提取搜索結果;2. 用一個新的變量 "urlList" 來保存上面的搜索結果;


第三段是利用上一段的搜索結果來提取相應的信息:1. 循環裡面迭代每一個 item;2. 獲取每個 item 的 name 和 url;3. 將其保存在文件系統裡;


有了配置文件 (把該配置文件保存為
baidu.xml),我們再往前一步,寫幾行代碼:import java.io.IOException;import org.webharvest.definition.ScraperConfiguration;import org.webharvest.runtime.Scraper;public class Test {

    
public static void main(String[] args) throws IOException {

        
ScraperConfiguration config = new ScraperConfiguration("c:/baidu.xml");
        
Scraper scraper = new Scraper(config, "c:/tmp/");
        
scraper.setDebug(true);
    
        
long startTime = System.currentTimeMillis();
        
scraper.execute();
        
System.out.println("time elapsed: " + (System.currentTimeMillis() - startTime));
    
}}


讓我們執行一下,看看結果:<catalog>
         
<website>
                   
<name>上海麗強 專業大型</name>
                   
<url>www.liqiang-toy.com</url>
         
</website>
         
<website>
                   
<name>多樣型大型</name>
                   
<url>www.yonglangplay.com</url>
         
</website>
         
<website>
                   
<name>童博士卡通</name>
                   
<url>www.tbs88.com</url>
         
</website>
         
<website>
                   
<name>芝麻街</name>
                   
<url>c49.txooo.js.cn</url>
         
</website>
         
<website>
                   
<name>童博士, 中國平價學生用品..</name>
                   
<url>www.cfsj8.cn</url>
         
</website>
         
<website>
                   
<name>充氣</name>
                   
<url>www.xmcaili.com</url>
         
</website>
         
<website>
                   
<name>找木製</name>
                   
<url>www.tengyuetoys.com</url>
         
</website>
         
<website>
                   
<name>米多迪</name>
                   
<url>b146.txooo.com</url>
         
</website></catalog> 

是不是很酷。爬蟲就這麼簡單。

二、深入考慮


不知道大家看到上面的配置、代碼和結果是否感覺很熟悉。是否和 Java 通過 Ibatis 讀取數據庫數據的方式類似。


那我們是否可以實現這樣的機制呢,把整個互聯網作為我們的龐大的數據庫,我們隨意的讀取。


Web-Harvest 提供了一個 ScraperContext
可以在該上下文中設置 Java 對象,可以通過 Java 對象收集相應的結果數據,(比如:設置 Map,可以通過 Map 收集數據)Scraper 提供了這樣的方法:
    
scraper.getContext().put("resDataSet", new ResultDataSet());ResultDataSet 是收集數據的  Java 對象。


那麼我們就可以這麼做: 


1. 首先設置要訪問的網頁的路徑scraper.getContext().put("startPageHref", "http://www.baidu.com/s?cl=3&wd=兒童玩具");

2. 第二步,設置要收集返回數據的容器scraper.getContext().put("resDataSet", new ResultDataSet());


3. 在配置文件中就可以這樣設置數據${resDataSet.addRecord("searchResult","totalSearchResult",totalSearchResult)};d) 爬取操作執行完畢後,即可返回數據:ResultDataSet resultDataSet = (ResultDataSet)scraper.getContext().get("resDataSet");Ok,我們就可以隨心所欲的使用這些數據,詳細請看附件。


三、分頁機制處理


1. 來由介紹現在的信息量很大,在展示的時候都是通過分頁處理的。


2. 實現機制那我們怎麼處理呢?分頁提取數據我們得明確幾件事情a. 分頁器的處理,比如:頁碼、頁大小、記錄數或頁數。b. "下一頁"的地址的構造c. 每頁數據的爬取不同的網站的分頁機制都不一樣,我們如何處理呢?當然我們不能通過硬編碼的方式來處理,我們就通過 Web-Harvest 的配置文件來實現。
Web-Harvest 本身的配置文件結構為:<config charset="gbk">
    
配置信息
 
</config>對這個結構進行擴展:<web-harvest-config>
  
<!– 生成分頁器配置 –>
  
<config charset="gbk" id="pagination">
          
配置信息
  
</config>
  
<!– 組裝下一頁地址 –>
  
<config charset="gbk" id="urlnav">
          
配置信息
  
</config>
  
<!– 抓取列表數據 –>
  
<config charset="gbk" id="listData">
          
配置信息
  
</config></web-harvest-config>


我們就可以通過三個config項來處理


第一步,通過 id="pagination" 的配置生成分頁器


第二步,通過已經生成的分頁器加上 id="urlnav" 的配置構造下一頁的 URL


第三步,通過 id="listData" 的配置提取需要的數據


一、Web-Harvest的優缺點


優點:1. Web-Harvest 是一個使用比較方便的抓取信息的 API 庫,目前是 1.0 版本2. 擴展性好,只要修改配置文件即可3. 上手較快,使用方便。


缺點:1. 處理過程比較多,對應的速度較慢


二、其他使用過或者正在嘗試的精確抓取數據的方式


1. 使用 HTMLParserHTMLParser 可以分析 HTML 源碼中的 TAG (比如 Table,DIV 等),還可以自己定義 TAG (比如:ENET),通過查找特定的 Tag,提取相應的數據。由於沒有很多的中間處理過程,速度較快,缺點是有很多的硬編碼,難以擴展。或許能找出一個特定的表達式可以快速的提取數據。


2. 使用 HTMLClean該方式還是走 HTML->XML 的路線,首先通過 HtmlClean 把抓取的網頁內容轉化為 XML 格式數據,然後通過 XPATH、XSL 等方式對 XML 數據進行轉化,達到收集數據的目的。 Web-Harvest 是類似的方式,但是我們可以精簡化,提高抓取的效率。


三、使用爬蟲碰到的問題


1. 網站對頻繁抓取數據的爬蟲進行IP限制問題考慮使用 IP 代理,但是速度難以忍受,故現在在考慮分佈式的抓取數據的方式。