Jsoup代碼解讀之七-實現一個CSS Selector

street fighter

噹噹噹!終於來到了Jsoup的特點:CSS Selector部分。selector也是我寫的爬蟲框架webmagic開發的一個重點。附上一張street fighter的圖,但願之後webmagic也能挑戰Jsoup!html

select機制

Jsoup的select包裏,類結構以下:java

uml

在最開始介紹Jsoup的時候,就已經說過NodeVisitorSelector了。Selector是select部分的對外facade,而NodeVisitor則是遍歷樹的底層API,CSS Selector也是根據NodeVisitor實現的遍歷。git

Jsoup的select核心是Evaluator。Selector所傳遞的表達式,會通過QueryParser,最終編譯成一個EvaluatorEvaluator是一個抽象類,它只有一個方法:github

<!-- lang: java -->
public abstract boolean matches(Element root, Element element);

注意這裏傳入了root,是爲了某些狀況下對樹進行遍歷時用的。web

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樹結構進行篩選)。this

這裏咱們可能最關心的是,「div ul li」這樣的父子結構是如何實現的。這個的實現方式在StructuralEvaluator.Parent中,貼一下代碼了:lua

<!-- lang: java -->
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部分的研究,這部分應該算是得心應手了。code

關於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

相關文章
相關標籤/搜索