我需要抓取具有以下形式的html:
<div id='content'>
<h3>Headline1</h3>
<div>Text1</div>
<div>Text2</div>
<div>Text3</div>
<h3>Headline2</h3>
<div>Text4</div>
<div>Text5</div>
<h3>Headline3</h3>
<div>Text6</div>
<div>... and so on ...</div>
</div>
我需要将标题标记之间的内容作为单独的块。所以从一个标题到下一个标题。不幸的是,没有所需范围的容器标签。
我尝试了片段选择器{[:h3] [:h3]}
,但不知怎的,它只返回所有h3标签,它们之间没有标签:
(({:tag :h3, :attrs nil, :content ("Headline1")}) ({:tag :h3, :attrs nil, :content ("Headline2")}) ({:tag :h3, :attrs nil, :content ("Headline3")}))
有效的是{[[:h3 (html/nth-of-type 1)]] [[:h3 (html/nth-of-type 2)]]}
。这给了我第一个和第二个h3-tag之间的所有html。但是,这并没有给我一个选择器所需的所有块。
可以完全执行此操作,还是应该使用正则表达式?
谢谢!
答案 0 :(得分:0)
选择div.content中的所有内容,然后根据标记对它们进行分区。
这里有一个更通用的概念,即通过识别哪些东西是分隔符而哪些不是分隔符将一系列事物分成若干段:
(defn separate*
"Produces a sequence of (parent child*)*, coll must start with a parent"
[child? coll]
(lazy-seq
(when-let [s (seq coll)]
(let [run (cons (first s)
(take-while child? (next s)))]
(cons run (separate* child? (drop (count run) s)))))))
与partition-by
非常相似,但总是在父级上拆分:
(partition-by keyword? [:foo 1 2 3 :bar :baz 4 5])
;; => ((:foo) (1 2 3) (:bar :baz) (4 5))
(separate* (compliment keyword?) [:foo 1 2 3 :bar :baz 4 5])
;; => ((:foo 1 2 3) (:bar) (:baz 4 5))
如果你想在没有标题时处理:
(defn separate
[parent? coll]
(when-let [s (seq coll)]
(if (parent? (first coll))
(separate* (complement parent?) coll)
(let [child? (complement parent?)
run (take-while child? s)]
(cons (cons nil run)
(separate* child? (drop (count run) s)))))))
(separate keyword? [1 2 :foo 3 4])
;; => ((nil 1 2) (:foo 3 4))
回到手头的问题:
(def x [{:tag :h3 :content "1"}
{:tag :div :content "A"}
{:tag :div :content "B"}
{:tag :h3 :content "2"}
{:tag :div :content "C"}
{:tag :div :content "D"}])
(def sections (separate #(= :h3 (:tag %)) x))
=> (({:content "1", :tag :h3}
{:content "A", :tag :div
{:content "B", :tag :div})
({:content "2", :tag :h3}
{:content "C", :tag :div}
{:content "D", :tag :div}))
如果我们不关心保留h3标题的内容:
(map rest sections)
=> (({:content "A", :tag :div} {:content "B", :tag :div})
({:content "C", :tag :div} {:content "D", :tag :div}))