2011-10-17 86 views
2

我試圖創建函數來從HTML頁面中抓取和標記,該頁面的URL提供給一個函數,並且它的工作方式應該如此。我得到<h3><table>元素的序列,當我嘗試使用select函數從結果序列中僅提取表或h3標記時,我得到(),或者如果我試圖映射那些我得到的標記(零無零... )。使用Enlive重新打印數據

你能幫我解決這個問題,或者說明我做錯了什麼?

下面是代碼:

(ns Test2 
    (:require [net.cgrand.enlive-html :as html]) 
    (:require [clojure.string :as string])) 

(defn get-page 
    "Gets the html page from passed url" 
    [url] 
    (html/html-resource (java.net.URL. url))) 

(defn h3+table  
    "returns sequence of <h3> and <table> tags" 
    [url] 
    (html/select (get-page url) 
{[:div#wrap :div#middle :div#content :div#prospekt :div#prospekt_container :h3] 
[:div#wrap :div#middle :div#content :div#prospekt :div#prospekt_container :table]} 
       )) 

(def url "http://www.belex.rs/trgovanje/prospekt/VZAS/show") 

此行讓我頭疼:

(html/select (h3+table url) [:table]) 

能否請你告訴我,我究竟做錯了什麼?

只是爲了澄清我的問題:是否有可能使用enlive的select函數從(h3 + table url)的結果中僅提取表標記?

回答

2

正如@Julien指出的那樣,您可能必須使用在原始html上應用(html/select raw-html selectors)時得到的深度嵌套樹結構。好像你試圖多次申請html/select,但這不起作用。 html/select將html解析爲clojure數據結構,因此您無法再將其應用於該數據結構。

我發現,解析該網站實際上是一個有點棘手,但我認爲這可能是一個很好的使用案例多方法,所以我砍死的東西在一起,也許這將讓你開始:

(代碼在這裏醜,你也可以簽上這是怎麼回事這個gist

(ns tutorial.scrape1 
    (:require [net.cgrand.enlive-html :as html])) 

(def *url* "http://www.belex.rs/trgovanje/prospekt/VZAS/show") 

(defn get-page [url] 
    (html/html-resource (java.net.URL. url))) 

(defn content->string [content] 
    (cond 
    (nil? content) "" 
    (string? content) content 
    (map? content) (content->string (:content content)) 
    (coll? content) (apply str (map content->string content)) 
    :else    (str content))) 

(derive clojure.lang.PersistentStructMap ::Map) 
(derive clojure.lang.PersistentArrayMap ::Map) 
(derive java.lang.String     ::String) 
(derive clojure.lang.ISeq    ::Collection) 
(derive clojure.lang.PersistentList  ::Collection) 
(derive clojure.lang.LazySeq    ::Collection) 

(defn tag-type [node] 
    (case (:tag node) 
    :tr ::CompoundNode 
    :table ::CompoundNode 
    :th ::TerminalNode 
    :td ::TerminalNode 
    :h3 ::TerminalNode 
    :tbody ::IgnoreNode 
    ::IgnoreNode)) 

(defmulti parse-node 
    (fn [node] 
    (let [cls (class node)] [cls (if (isa? cls ::Map) (tag-type node) nil)]))) 

(defmethod parse-node [::Map ::TerminalNode] [node] 
    (content->string (:content node))) 
(defmethod parse-node [::Map ::CompoundNode] [node] 
    (map parse-node (:content node))) 
(defmethod parse-node [::Map ::IgnoreNode] [node] 
    (parse-node (:content node))) 
(defmethod parse-node [::String nil] [node] 
    node) 
(defmethod parse-node [::Collection nil] [node] 
    (map parse-node node)) 

(defn h3+table [url] 
(let [ws-content (get-page url) 
     h3s+tables (html/select ws-content #{[:div#prospekt_container :h3] 
              [:div#prospekt_container :table]})] 
    (for [node h3s+tables] (parse-node node)))) 

的幾句話:

content->string需要一個數據結構,並收集其內容轉換爲字符串,返回是s o您可以將此應用於可能仍包含要忽略的嵌套子標記(如<br/>)的內容。

派生語句建立了一個特殊的層次結構,我們稍後將在多方法分析節點中使用它。這很方便,因爲我們不知道我們將遇到哪些數據結構,並且稍後我們可以輕鬆地添加更多的案例。

tag-type函數實際上是一個模仿層次結構語句的黑客 - AFAIK你不能從非命名空間限定的關鍵字創建一個層次結構,所以我這樣做了。

多方法parse-node調度節點的類,如果節點是tag-type附加地圖。

現在我們要做的就是定義合適的方法:如果我們在終端節點上,我們將內容轉換爲字符串,否則我們會在內容上重複或映射集合上的parse-node函數正在處理。 ::String的方法實際上甚至沒有使用,但爲了安全起見,我將它放在了上面。

h3+table函數幾乎是你以前的東西,我簡化了選擇器並將它們放入一個集合中,不確定是否將它們放入地圖中,就像您按照預期工作一樣。

快樂刮!

+0

感謝您的回覆。我仍然是Clojure世界的新手,特別是在這個特定的框架中,我還沒有使用multimethods。您的解決方案非常好,現在我正在測試它。我肯定會用你的方式(當然稍作修改),在我的項目的另一個用例中。再次感謝 !!! :D –

+0

嘿,非常歡迎。我不久前就在你的位置,如果你堅持下去,我想你會發現clojure很快就會在你身上增長。語言相當緊湊,抽象性強,我非常喜歡;) – Paul

1

你提的問題是很難理解的,但我覺得你的最後一行應該簡單地

(h3+table url) 

這將返回一個包含刮HTML,然後就可以潛入與通常的Clojure序列的API深度嵌套數據結構。祝你好運。

+0

嗨,謝謝你的回覆。我會嘗試澄清我的問題:是否可以使用enlive的select函數從(h3 + table url)的結果中僅提取表標記? –

+0

除了以前的評論:當我輸入類似這樣的'(地圖:表(h3 +表格url))'我得到(無零無...),或者當我嘗試用'(select(h3 + table url )[:table])'我得到()。這是我的主要問題。你能幫我解決這個問題嗎? –

+2

(h3 + table url)產生一個深度嵌套的clojure數據結構。看看它,看看它是否符合你的喜好。如果是這樣,開始拉開它,通過標準的Clojure序列API獲取你想要的。現在你不需要有活力了。或者改進你的選擇器來縮小你想要的html。我認爲你應該在REPL進行實驗,然後事情就會變得清晰。 –