SipSoup基于Jsoup的xpath實現(xiàn)
sipsoup是一個基于Jsoup的xpath實現(xiàn),他將Jsoup的cssQuery變成了xpath語法的一部分,可以實現(xiàn)在xpath內(nèi)部執(zhí)行cssQuery和xpath混合模式的鏈式文檔查詢
是一款純Java開發(fā)的使用xpath解析html的解析器,xpath語法分析與執(zhí)行完全獨立,html的DOM樹生成借助Jsoup。
sipSoup本身也應(yīng)該叫做Xsoup,JsoupXpath之類的,但是他出身太晚了,名字被占了,但是我覺得還是應(yīng)該喝湯,所以我喜歡sipSoup這個名字。
每一個爬蟲框架作者都應(yīng)該實現(xiàn)一個xpath, sipsoup 出現(xiàn)也是如此,在我設(shè)計爬蟲框架vscrawler的時候,考慮如何定位抽取數(shù)據(jù),甚至如何整合其他爬蟲框架的抽取API, 然后調(diào)研了Xsoup和JsoupXpath,看到JsoupXpath眼前一亮,我覺得他肯定適合我的需要,所以嘗試使用JsoupXpath對接了Xsoup,因為JsoupXpath實現(xiàn)了xpath2.0的絕大部分語法功能, 使用JsoupXpath可以非常靈活自由的實現(xiàn)xpath。但是在整合的過程,我發(fā)現(xiàn)JsoupXpath有一點不符合我的需求,就是他都是運行的適合拋錯,我希望能夠在編譯xpath表達式的時候,就可以做語法 檢查,我希望JsoupXpath可以類似Xsoup一樣做鏈式抽取,我希望規(guī)則模型可以緩存。所以嘗試著對JsopXpath進行重構(gòu)。
SipSoup本質(zhì)是對JsoupXpath重構(gòu)而來的,里面少部分代碼仍然是JsoupXpath的,這里非常感謝JsoupXpath,但是絕大部分代碼都被替換了,所以本身SipSoup不算依賴JsoupXpath(組件結(jié)構(gòu)幾乎還和JsoupXpath保持了一致)。 目前看來,SipSoup完全兼容JsoupXpath,因為它本身自JsoupXpath發(fā)展而來。關(guān)于JsoupXpath的相關(guān)資料,參考 JSoupXpath
以下為改動點
函數(shù)重構(gòu),JsoupXpath使用反射加載一個類里面的靜態(tài)函數(shù),SipSoup則是使用接口實現(xiàn)的方式擴展
類掃描,默認函數(shù)注冊,是通過掃描器自動注冊,如果你需要擴展自己的函數(shù),可以將自己的函數(shù)實現(xiàn)放到com.virjar.sipsoup.function,就能夠自動注冊
軸函數(shù),SipSoup不光支持一般的謂語函數(shù)擴展,同時支持軸函數(shù),抽取函數(shù)擴展。而且允許函數(shù)帶參數(shù)
css('cssQuery') css是SipSoup內(nèi)置的重要軸函數(shù),它可以將css查詢表達式傳遞給css軸,這是SipSoup一個大的突破,他比Xsoup更加容易的實現(xiàn)了css和xpath混合鏈式抽取
復(fù)雜謂語,目前來說,SipSoup對謂語的支持是我發(fā)現(xiàn)的最完善的,支持函數(shù)嵌套,表達式嵌套,括號嵌套,處理空格,轉(zhuǎn)義等問題。SipSoup的謂語模塊,是我花了整整一天寫出來的計算器
謂語數(shù)據(jù)類型擴展,在xpath語法中,我定義了數(shù)據(jù)類型分為: 字符串,數(shù)字,運算符,xpath,函數(shù),屬性取值動作,布爾類型,復(fù)合表達式 這幾種類型,其中除運算符以外的其他數(shù)據(jù)類型都是可以擴展或者卸載的。(boolean類型是開始設(shè)計的時候沒有的類型,然后通過這個機制注冊了boolean,后來又將其添加到了默認類型了)
運算符重載,運算符重載一般c++ 聽得挺多,含義就是可以通過重載運算符實現(xiàn)操作符的行為改寫。比如"+"加法操作,遇到數(shù)字執(zhí)行加法,遇到字符串執(zhí)行字符串鏈接,你可以重寫他,遇到數(shù)字轉(zhuǎn)化數(shù)字,失敗使用默認值等等
多xpath組合邏輯,可以實現(xiàn)多個xpath的多重與或計算,對應(yīng)集合實現(xiàn)交集,并集計算
總之,SipSoup已經(jīng)是一個高度擴展的Xpath語法分析器,通過靈活的擴展以及Jsoup整合,成為了一個異常強大的xpath工具
軟件主頁:http://git.oschina.net/virjar/sipsoup
demo如下:
格式化輸出節(jié)點文本 allText allText()
position使用,選取所有偶數(shù)節(jié)點 position
注冊新的函數(shù) RegisterNewFunctionTest.java
注冊新的操作符 運算符重載
福利,美女爬蟲 實際demo
風(fēng)騷語法展示:
<ul class="ad-thumb-list"> <a href="www.java1234.com/test.jpg">這是一個混淆的圖片數(shù)據(jù)</a> <li> <div class="inner"> <a /></a> </div> </li> <li> <div class="inner"> <a /></a> </div> </li> <li> <div class="inner"> <a /></a> </div> </li> <ul></ul> </ul>
這個文本,需要提取所有偶數(shù)行的a標簽的圖片的鏈接信息,對應(yīng)xpath表達式可以這么寫
//css('.ad-thumb-list .inner')::a[position(parent(2)) %2 =0]/@href 語法解釋
css('.ad-thumb-list .inner')::這是css軸的運用,這個表達式定位到了所有的圖片數(shù)據(jù)(其中"<a href="www.java1234.com/test.jpg"\>這是一個混淆的圖片數(shù)據(jù)"將會被過濾)a[position(parent(2)) %2 =0]這是復(fù)雜謂語的一個簡單應(yīng)用,首先a[xxx]定位到a標簽,然后使用parent函數(shù)得到他的爺爺節(jié)點,(parent函數(shù)可以帶參數(shù),必須是一個數(shù)字,2代表父親的父親,也就是得到了li標簽)然后使用position函數(shù)得到這個li元素的position偏移,也就是他是第幾個li。
最后,讓他和2取模,如果結(jié)果為0,代表他就是偶數(shù)資源
maven坐標
<!-- xpath --> <dependency> <groupId>com.virjar</groupId> <artifactId>sipsoup</artifactId> <version>RELEASE</version> </dependency>
具體文檔
函數(shù)
所有支持的函數(shù),可以通過如下demo得到 解析函數(shù)的demo
軸函數(shù) :ancestor 軸函數(shù) :ancestorOrSelf 軸函數(shù) :cacheCss 軸函數(shù) :child 軸函數(shù) :css 軸函數(shù) :descendant 軸函數(shù) :descendantOrSelf 軸函數(shù) :followingSibling 軸函數(shù) :followingSiblingOne 軸函數(shù) :parent 軸函數(shù) :precedingSibling 軸函數(shù) :precedingSiblingOne 軸函數(shù) :self 軸函數(shù) :sibling 謂語過濾函數(shù) :|abs 謂語過濾函數(shù) :|allText 謂語過濾函數(shù) :|boolean 謂語過濾函數(shù) :|concat 謂語過濾函數(shù) :|contains 謂語過濾函數(shù) :|false 謂語過濾函數(shù) :|first 謂語過濾函數(shù) :|hasClass 謂語過濾函數(shù) :|last 謂語過濾函數(shù) :|lower-case 謂語過濾函數(shù) :|matches 謂語過濾函數(shù) :|name 謂語過濾函數(shù) :|not 謂語過濾函數(shù) :|nullToDefault 謂語過濾函數(shù) :|parent 謂語過濾函數(shù) :|position 謂語過濾函數(shù) :|root 謂語過濾函數(shù) :|string 謂語過濾函數(shù) :|string-length 謂語過濾函數(shù) :|substring 謂語過濾函數(shù) :|text 謂語過濾函數(shù) :|toDouble 謂語過濾函數(shù) :|toInt 謂語過濾函數(shù) :|true 謂語過濾函數(shù) :|try 謂語過濾函數(shù) :|upper-case 抽取函數(shù) :allText 抽取函數(shù) :@ 抽取函數(shù) :html 抽取函數(shù) :node 抽取函數(shù) :num 抽取函數(shù) :outerHtml 抽取函數(shù) :self 抽取函數(shù) :tag 抽取函數(shù) :text
軸函數(shù)
軸函數(shù)是定義節(jié)點塞選域的函數(shù),在一個xpath表達式節(jié)點中,軸是可選的。但是如果存在軸,那么他的作用則是根據(jù)當(dāng)前的節(jié)點集產(chǎn)生新的一片候選節(jié)點集。 SipSoup的軸和標準Xpath的軸保持兼容,但是也有一個地方不一樣,就是SipSoup的軸允許帶有參數(shù),軸參數(shù)目前只支持字符串,可以支持多個字符串的參數(shù)。
軸函數(shù)列表
| 函數(shù)名稱 | 參數(shù) | 作用 |
|---|---|---|
| ancestor | 無 | 全部祖先節(jié)點 父親,爺爺 , 爺爺?shù)母赣H... |
| ancestorOrSelf | 無 | 全部祖先節(jié)點和自身節(jié)點 |
| cacheCss | css query表達式 | 內(nèi)部實現(xiàn)路由至Jsoup的select,和css軸不一樣的是,cacheCss會對css規(guī)則進行緩存,在遇到大量同類型網(wǎng)頁的解析的時候,可能一個css規(guī)則被多次使用,緩存css能夠減少css規(guī)則編譯的消耗,提升性能 |
| child | 無 | 直接子節(jié)點 |
| css | css query表達式 | 內(nèi)部實現(xiàn)路由至Jsoup的select |
| descendant | 無 | 全部子代節(jié)點 兒子,孫子,孫子的兒子... |
| descendantOrSelf | 無 | 全部子代節(jié)點和自身 |
| following-sibling | 無 | 節(jié)點后面的全部同胞節(jié)點following-sibling |
| following-sibling-one | 無 | 返回下一個同胞節(jié)點(擴展) 語法 following-sibling-one |
| parent | 無 | 父節(jié)點 |
| preceding-sibling | 無 | 節(jié)點前面的全部同胞節(jié)點,preceding-sibling |
| preceding-sibling-one | 無 | 返回前一個同胞節(jié)點(擴展),語法 preceding-sibling-one |
| self | 無 | 自身 |
| sibling | 無 | 全部同胞(擴展) |
抽取函數(shù)
抽取函數(shù)用來對結(jié)果集進行轉(zhuǎn)換,他是一些簡單的數(shù)據(jù)抽取函數(shù),如提取所有文本,提取某個屬性等等,抽取函數(shù)目前不會太多。抽取結(jié)果可能為元素,也能為字符串。請注意,如果xpath節(jié)點中,存在抽取函數(shù),而且抽取函數(shù)返回類型為字符串,那么這個函數(shù)應(yīng)該是這個xpath節(jié)點鏈的末尾節(jié)點。因為每個抽取節(jié)點開始的時候,將會執(zhí)行過濾,只會將element作為下一輪抽取的輸入。
軸函數(shù)列表(哪位大神能幫我調(diào)整以下表格布局,真丑,我不喜歡)
| 函數(shù)名稱 | 參數(shù) | 作用 |
|---|---|---|
| allText | 無 | 遞歸獲取節(jié)點內(nèi)全部的純文本,將block的html元素轉(zhuǎn)化為換行符,能夠保持原有的html的段落結(jié)構(gòu),但是不能保持布局結(jié)構(gòu) |
| @ | 無 | 抽取當(dāng)前節(jié)點的某個屬性,數(shù)據(jù)內(nèi)部函數(shù),外界不可直接調(diào)用,原因是函數(shù)語法識別器不會識別"@"函數(shù),但是"@"操作符本身內(nèi)部是轉(zhuǎn)化抽取函數(shù)了 |
| html | 無 | 獲取全部節(jié)點的內(nèi)部的html |
| node | 無 | 獲取全部節(jié)點的html |
| num | 無 | 抽取節(jié)點自有文本中全部數(shù)字 |
| outerHtml | 無 | 獲取全部節(jié)點的 包含節(jié)點本身在內(nèi)的全部html |
| self | 無 | 放棄抽取,保留自身。xpath默認會對當(dāng)前節(jié)點的子節(jié)點進行操作,而有些時候軸函數(shù)定位到的節(jié)點就是需要的數(shù)據(jù)了,所以不需要在使用抽取函數(shù)計算新節(jié)點了,主要用在tag函數(shù)不能解決的場景下 |
| tag | tagName | 內(nèi)部函數(shù),xpath 語法中的tag字段,默認路由到此函數(shù)處理//div[@class] 這里的div在運行時會轉(zhuǎn)化為 //tag('div')[@'class']這兩個表達式等價,但是第一個表達式才是xpath常規(guī)思路,而且對SipSoup這兩個表達式性能完全一樣 |
| text | 無 | 只獲取節(jié)點自身的子文本 |
過濾函數(shù)
過濾函數(shù)用在謂語中,過濾函數(shù)非常強大,因為它能夠遞歸的使用函數(shù),配合操作符,實現(xiàn)復(fù)雜的表達式計算,
過濾函數(shù)列表
| 函數(shù)名稱 | 參數(shù) | 作用 |
|---|---|---|
| abs | 數(shù)字,可以是整數(shù)或者小數(shù) | 返回參數(shù)的絕對值。例子:abs(-3.14) 結(jié)果:3.14 |
| allText | 無 | 獲取元素下面的全部文本 |
| boolean | 數(shù)字,boolean,字符串 | 返回數(shù)字、字符串的布爾值。如果沒有參數(shù),則默認返回false,如果本身是boolean,返回boolean,如果是字符串,嘗試轉(zhuǎn)化為boolean,如果是整數(shù),非零為真。其他類型的數(shù)字,大于零為真。剩余場景全部返回false |
| concat | 多個參數(shù),可以為任意類型 | 返回字符串的拼接,例子:concat('XPath ','is ','FUN!') 結(jié)果:'XPath is FUN!',如果非字符串,則轉(zhuǎn)化為字符串后執(zhí)行字符串拼接 |
| contains | 兩個參數(shù),左右都為字符串,測試左側(cè)字符串中是否包含右側(cè)字符串 | 如果 string1 包含 string2,則返回 true,否則返回 false |
| false | 無 | 返回布爾值 false |
| first | 無 | 判斷一個元素是不是同名同胞中的第一個,可以使用position函數(shù)代替,遷移自JSoupXpath的函數(shù) |
| hasClass | 字符串,為css的class樣式名稱 | css規(guī)則的className,實際上會調(diào)用Jsoup的hasClass方法(Jsoup本身的抽取器也是調(diào)用這個方法的) |
| last | 無 | 判斷一個元素是不是最后一個同名同胞中的 |
| lower-case | 一個參數(shù),類型為字符串 | 轉(zhuǎn)化字符串為大寫 |
| matches | 兩個參數(shù),類型為字符串 | 如果 string 參數(shù)匹配指定的模式,則返回 true,否則返回 false,例子:matches("Merano", "ran") 結(jié)果:true |
| name | 無 | 獲取當(dāng)前節(jié)點的節(jié)點名稱 |
| not | 和boolean的參數(shù)相同 | 首先通過 boolean() 函數(shù)把參數(shù)還原為一個布爾值。如果該布爾值為 false,則返回 true,否則返回 true |
| nullToDefault | 兩個參數(shù),任意類型 | 如果第一個參數(shù)的值為null,則第三個參數(shù)作為本函數(shù)的返回值,否則第一個參數(shù)為本函數(shù)的返回值 |
| parent | 一個或者零個參數(shù),均為正整數(shù)類型 | 如果沒有參數(shù),則獲取當(dāng)前節(jié)點的父親節(jié)點,返回值為element類型,如果傳遞了參數(shù),則指定n代祖先對應(yīng)節(jié)點 |
| position | 一個或者零個參數(shù),為節(jié)點類型 | 如果沒有傳遞參數(shù),或者第一個參數(shù)計算結(jié)果為null,則返回當(dāng)前節(jié)點在兄弟節(jié)點中的位置。否則返回傳入的節(jié)點相對于傳入節(jié)點兄弟節(jié)點的位置 |
| root | 無 | 返回當(dāng)前節(jié)點的根節(jié)點,一般是document節(jié)點 |
| string | 一個參數(shù),任意類型 | 返回參數(shù)的字符串值。參數(shù)可以是數(shù)字、邏輯值 |
| string-length | 一個參數(shù),字符串類型 | 返回字符的長度 |
| substring | 兩個或者參數(shù) | 第一個參數(shù)為字符串類型,第二個參數(shù)為數(shù)字,第三個參數(shù)如果存在,也必須是數(shù)字類型。本函數(shù)返回字符串對應(yīng)子串。第二個參數(shù)為起始偏移,如果第三個參數(shù)存在,則第三個參數(shù)為結(jié)尾偏移,否則結(jié)尾為字符串末尾 |
| text | 無 | 獲取元素自己的子文本 |
| toDouble | 一個或者兩個參數(shù),第一個需要是數(shù)字,第二個如果存在,則需要是double類型 | 將一個字符串轉(zhuǎn)化為double,如果轉(zhuǎn)化失敗,嘗試使用默認值返回 |
| toInt | 個或者兩個參數(shù),第一個需要是數(shù)字,第二個如果存在,則需要是int類型 | 將一個字符串轉(zhuǎn)化為int,如果轉(zhuǎn)化失敗,嘗試使用默認值返回 |
| true | 無 | 返回布爾值 true |
| try | 異常處理,0,1,2個參數(shù) | 如果參數(shù)為空,返回true,如果第一個參數(shù)對應(yīng)語法節(jié)點無異常執(zhí)行,則返回第一個參數(shù)。否則返回第二個參數(shù)(此時如果第二個參數(shù)不存在,返回false) |
| upper-case | 一個參數(shù),類型為字符串 | 轉(zhuǎn)化字符串為小寫 |
操作符
目前操作符相關(guān),遷移自JSoupXpath其運算規(guī)則和JSoupXpath保持一致,SipSoup支持多個運算符組合,所以涉及操作符優(yōu)先級問題(除非非常常見的用法,一般建議使用括號手動確定優(yōu)先級) 操作符和對應(yīng)優(yōu)先級如下表,其中優(yōu)先級高越優(yōu)先的到計算,如果操作符優(yōu)先級相同,則按照從左往右的順序計算。
各運算符優(yōu)先級定義 + :20 加法運算 - :20 減法運算 * :30 乘法運算 / :30 除法運算 % :30 取模運算 && :0 邏輯且運算 || :0 邏輯或運算 and :0 邏輯且運算,等價于&& or :0 邏輯或運算,等價于|| = :10 相等判斷,邏輯運算 > :10 大于,邏輯運算 < :10 小于,邏輯運算 >= :10 大于等于,邏輯運算 <= :10 小于等于,邏輯運算 *= :10 包含,測試表達式左側(cè)數(shù)據(jù)是否包含表達式右側(cè)數(shù)據(jù),僅適用于字符串,邏輯運算 $= :10 以xxx結(jié)尾,測試左側(cè)數(shù)據(jù)是否以表達式右側(cè)數(shù)據(jù)結(jié)尾,僅適用于字符串,邏輯運算 ^= :10 以xxx開始,測試左側(cè)數(shù)據(jù)是否以表達式右側(cè)數(shù)據(jù)開始,僅適用于字符串,邏輯運算 != :10 不等于,和"="作用相反,邏輯運算 ~= :10 模式匹配,測試左側(cè)數(shù)據(jù)是否符合右側(cè)字符串代表的正則表達式模式,僅適用于字符串,邏輯運算 !~ :10 不匹配,與"~="相反,如果左側(cè)數(shù)據(jù)不匹配右側(cè)字符串代表的正則表達式,則返回true
操作符類型
操作符計算涉及到數(shù)據(jù)類型升級問題,如一個整形數(shù)據(jù)和一個長整形計算,將會轉(zhuǎn)化為長整形。SipSoup默認支持較好的數(shù)字類型有:int,long,double(float按double處理),BigDecimal,并且會根據(jù)一般的慣例處理轉(zhuǎn)型計算的問題
特別說明,對于+有兩個動作,如果遇到數(shù)字,則執(zhí)行數(shù)字加法,如果遇到字符串,則執(zhí)行字符串鏈接。如果你覺得不應(yīng)該有這個表現(xiàn),可以通過重寫操作符的方式修改
高級用法
注冊或重寫新的函數(shù) RegisterNewFunctionTest.java
注冊或重寫新的操作符 運算符重載
注冊新的數(shù)據(jù)類型 TokenAnalysisRegistry.java 這里不展示細節(jié)了,如果你需要這個功能了,那么你對SipSoup的源代碼應(yīng)該足夠了解,應(yīng)該可以獨立駕馭這個類,并根據(jù)SipSoup定義的規(guī)范識識別新數(shù)據(jù)類型的格式,以及消費轉(zhuǎn)化為特性計算節(jié)點模型
