@Julienが指摘したよう(html/select raw-html selectors)
に、生のhtmlに適用することで得られる、深くネストされたツリー構造を操作する必要があります。複数回適用しようとしているようですが、うまくいきhtml/select
ません。html/select
htmlをclojureデータ構造に解析するため、そのデータ構造に再度適用することはできません。
Webサイトの解析は実際には少し複雑であることがわかりましたが、マルチメソッドの良いユースケースになるかもしれないと思ったので、何かを一緒にハッキングしました。
(ここのコードは醜いです。この要点をチェックアウトすることもできます)
(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
<br/>
データ構造を取得し、そのコンテンツを文字列に収集して返します。これを、無視したいネストされたサブタグ(など)がまだ含まれている可能性のあるコンテンツに適用できるようにします。
派生ステートメントは、後でマルチメソッド解析ノードで使用するアドホック階層を確立します。これは、どのデータ構造に遭遇するかがまったくわからず、後で簡単にケースを追加できるため便利です。
このtag-type
関数は、実際には階層ステートメントを模倣するハックです。名前空間で修飾されていないキーワードから階層を作成することはできないので、このようにしました。
マルチメソッドparse-node
は、ノードのクラスにディスパッチし、ノードがマップである場合はさらににディスパッチしますtag-type
。
ここで行う必要があるのは、適切なメソッドを定義することだけです。ターミナルノードにいる場合は、コンテンツを文字列に変換します。それ以外の場合は、コンテンツを繰り返し処理するか、処理しているコレクションに解析ノード関数をマップします。 。の方法::String
は実際には使われていませんが、安全のために残しておきました。
関数は以前とほぼ同じです。h3+table
セレクターを少し簡略化してセットに入れましたが、意図したとおりにマップに入れるかどうかはわかりません。
ハッピースクレイピング!