Jsoup代碼解讀之七-實現一個CSS Selector
來自: http://www.importnew.com/17854.html
當當當!終于來到了Jsoup的特色:CSS Selector部分。selector也是我寫的爬蟲框架webmagic開發的一個重點。附上一張street fighter的圖,希望以后webmagic也能挑戰Jsoup!
select機制
Jsoup的select包里,類結構如下:
在最開始介紹Jsoup的時候,就已經說過NodeVisitor和Selector了。Selector是select部分的對外facade,而NodeVisitor則是遍歷樹的底層API,CSS Selector也是根據NodeVisitor實現的遍歷。
Jsoup的select核心是Evaluator。Selector所傳遞的表達式,會經過QueryParser,最終編譯成一個Evaluator。Evaluator是一個抽象類,它只有一個方法:
public abstract boolean matches(Element root, Element element);
注意這里傳入了root,是為了某些情況下對樹進行遍歷時用的。
Evaluator的設計簡潔明了,所有的Selector表達式單詞都會編譯到對應的Evaluator。例如#xx對應Id,.xx對應Class,[]對應Attribute。這里補充一下w3c的CSS Selector規范:http://www.w3.org/TR/CSS2/selector.html
當然,只靠這幾個還不夠,Jsoup還定義了CombiningEvaluator(對Evaluator進行And/Or組合),StructuralEvaluator(結合DOM樹結構進行篩選)。
這里我們可能最關心的是,“div ul li”這樣的父子結構是如何實現的。這個的實現方式在StructuralEvaluator.Parent中,貼一下代碼了:
static class Parent extends StructuralEvaluator { public Parent(Evaluator evaluator) { this.evaluator = evaluator; } public boolean matches(Element root, Element element) { if (root == element) return false; Element parent = element.parent(); while (parent != root) { if (evaluator.matches(root, parent)) return true; parent = parent.parent(); } return false; } }
這里Parent包含了一個evaluator屬性,會根據這個evaluator去驗證所有父節點。注意Parent是可以嵌套的,所以這個表達式”div ul li”最終會編譯成And(Parent(And(Parent(Tag(“div”)),Tag(“ul”)),Tag(“li”)))這樣的Evaluator組合。
select部分比想象的要簡單,代碼可讀性也很高。經過了parser部分的研究,這部分應該算是駕輕就熟了。
關于webmagic的后續打算
webmagic是一個爬蟲框架,它的Selector是用于抓取HTML中指定的文本,其機制和Jsoup的Evaluator非常像,只不過webmagic暫時是將Selector封裝成較簡單的API,而Evaluator直接上了表達式。之前也考慮過自己定制DSL來寫一個HTML,現在看了Jsoup的源碼,實現能力算是有了,但是引入DSL,實現只是一小部分,如何讓DSL易寫易懂才是難點。
其實看了Jsoup的源碼,精細程度上比webmagic要好得多了,基本每個類都對應一個真實的概念抽象,可能以后會在這方面下點工夫。
下篇文章將講最后一部分:白名單及HTML過濾機制。
最后依然附上這系列文章和代碼的github地址:https://github.com/code4craft/jsoup-learning