聶琨琳,黃 蔚,胡國(guó)超
(華北計(jì)算技術(shù)研究所信息技術(shù)應(yīng)用系統(tǒng)部,北京 100083)
互聯(lián)網(wǎng)現(xiàn)已成為人們生活的重要組成部分,并且隨著異步JavaScript與XML技術(shù)(Ajax)的出現(xiàn),Web 2.0技術(shù)的興起,網(wǎng)頁(yè)設(shè)計(jì)領(lǐng)域發(fā)生了巨大的變革。網(wǎng)頁(yè)設(shè)計(jì)者更加關(guān)注網(wǎng)頁(yè)的用戶體驗(yàn)、與用戶的交互等功能。JavaScript作為一門(mén)優(yōu)秀的腳本語(yǔ)言,可以滿足網(wǎng)頁(yè)設(shè)計(jì)者的各種使用需求,被廣泛用于網(wǎng)頁(yè)的開(kāi)發(fā)之中。
JavaScript的廣泛使用對(duì)JavaScript腳本的解析提出了很大的需求。無(wú)論是瀏覽器,還是網(wǎng)絡(luò)爬蟲(chóng)軟件對(duì)于JavaScript的解析都是其核心功能之一。特別是對(duì)于搜索引擎而言,對(duì)JavaScript的解析是獲取“深層網(wǎng)絡(luò)”(Deep Web)信息的重要途徑[1,14]。綜上所述,對(duì)于JavaScript解析技術(shù)的研究具有巨大的現(xiàn)實(shí)意義。
目前,已經(jīng)有一些成熟的標(biāo)準(zhǔn)JavaScript解析引擎,本文重點(diǎn)研究的Rhino引擎就是其中之一。但是,這類(lèi)僅僅可以解析標(biāo)準(zhǔn)JavaScript腳本的引擎還是遠(yuǎn)遠(yuǎn)不能滿足應(yīng)用的需求,需要對(duì)其進(jìn)行改進(jìn)。關(guān)于Rhino引擎的改進(jìn),已經(jīng)有學(xué)者做過(guò)研究,如上海交通大學(xué)的金曉鷗、鐘寶燕等提出使用本地創(chuàng)建HTML DOM的方法提供Document支持[2];北京郵電大學(xué)的李蕊、魏更宇等提出直接使用腳本語(yǔ)言在JavaS-cript運(yùn)行環(huán)境中創(chuàng)建Window對(duì)象的方法[3];浙江大學(xué)的羅兵在其碩士論文中,使用JavaScript解析引擎制作了可以抓取Ajax網(wǎng)頁(yè)超鏈接的爬蟲(chóng)[4,13]。本文主要從軟件工程的方向出發(fā),重點(diǎn)研究對(duì)引擎的改進(jìn)與擴(kuò)展,提出一種低耦合、易擴(kuò)展的引擎改進(jìn)方法,使其能夠滿足實(shí)際應(yīng)用需求。
標(biāo)準(zhǔn)JavaScript是指經(jīng)過(guò)歐洲計(jì)算機(jī)制造商協(xié)會(huì)(European Computer Manufacturer’s Association,ECMA)進(jìn)行標(biāo)準(zhǔn)化的一種具有面向?qū)ο竽芰Φ摹⒔忉屝偷某绦蛟O(shè)計(jì)語(yǔ)言[5]。ECMA發(fā)布的ECMA-262標(biāo)準(zhǔn)是JavaScript的官方標(biāo)準(zhǔn),在這個(gè)標(biāo)準(zhǔn)中JavaScript的官方名稱(chēng)是ECMAScript。
目前廣泛使用于網(wǎng)頁(yè)設(shè)計(jì)行業(yè)的是客戶端JavaScript,可以認(rèn)為客戶端JavaScript是標(biāo)準(zhǔn) JavaS-cript的擴(kuò)展??蛻舳薐avaScript主要擴(kuò)展對(duì)象包括Window對(duì)象、Document對(duì)象、Navigator對(duì)象、Screen對(duì)象、History對(duì)象和Location對(duì)象等。其中Window對(duì)象的屬性包含 document、navigator、screen、history 和location等,分別對(duì)應(yīng)各個(gè)對(duì)象的只讀引用[7]。其中W3C組織發(fā)布了Document對(duì)象的標(biāo)準(zhǔn),完成了Document對(duì)象的標(biāo)準(zhǔn)化。
通過(guò)擴(kuò)展為JavaScript增加對(duì)DOM對(duì)象的操作功能,以使得DOM對(duì)象具有動(dòng)態(tài)特性,也使得JavaS-cript成為DHTML和Ajax的技術(shù)的核心。
1.2.1 Rhino 引擎簡(jiǎn)介
Rhino是一個(gè)JavaScript腳本的解析引擎,可以認(rèn)為Rhino是標(biāo)準(zhǔn)JavaScript腳本語(yǔ)言(或者說(shuō)ECMA-262標(biāo)準(zhǔn))的Java實(shí)現(xiàn),通過(guò)使用Rhino引擎解析JavaScript腳本,可以將JavaScript腳本代碼轉(zhuǎn)化為對(duì)本地Java對(duì)象的操作。
1.2.2 Rhino 引擎特點(diǎn)
Rhino引擎主要有以下3個(gè)優(yōu)點(diǎn):
(1)作為JavaScript的Java實(shí)現(xiàn),Rhino引擎完全由Java語(yǔ)言編寫(xiě)完成,繼承了Java語(yǔ)言跨平臺(tái)特性,故本文的研究可以不涉及特殊平臺(tái)的兼容問(wèn)題。
(2)Rhino引擎的實(shí)現(xiàn)核心是JavaScript對(duì)象和Java對(duì)象之間的映射,對(duì)腳本運(yùn)行的監(jiān)控可以通過(guò)監(jiān)控其對(duì)應(yīng)Java對(duì)象來(lái)獲得。
(3)Rhino引擎是完全開(kāi)源的JavaScript引擎,而且其基于Mozilla Public License協(xié)議(MPL協(xié)議),相比于GNU General Public License協(xié)議(GPL協(xié)議)而言,代碼的使用者擁有更大的自由和權(quán)限。
與Mozilla另外一個(gè)經(jīng)典的JavaScript解析引擎SpiderMonkey相同,Rhino是一個(gè)運(yùn)行隱式字節(jié)碼的快速的 JavaScript Interpreter[6]。一個(gè)典型的 JavaS-cript代碼段解釋執(zhí)行流程如圖1所示。
圖1 Rhino引擎解析過(guò)程
在執(zhí)行 JavaScript腳本過(guò)程中,Rhino引擎的Context對(duì)象和ScriptRuntime對(duì)象負(fù)責(zé)運(yùn)行管理功能。Scriptable對(duì)象是腳本解析的核心,其負(fù)責(zé)將需要解析的JavaScript對(duì)象映射到與之相關(guān)聯(lián)的Java對(duì)象[3]。
在進(jìn)行語(yǔ)法分析得到語(yǔ)法分析樹(shù)后,得到需求資源的Token鍵值,然后Rhino引擎就會(huì)通過(guò)Token鍵值查詢Scriptable對(duì)象中存儲(chǔ)的映射關(guān)系,并將對(duì)JavaScript對(duì)象的各種操作轉(zhuǎn)化為對(duì)相關(guān)Java對(duì)象的操作。在當(dāng)前作用域的Scriptable對(duì)象中不存在映射的Java對(duì)象時(shí),Rhino引擎就會(huì)發(fā)出警告,停止解析。具體過(guò)程如圖2所示。
標(biāo)準(zhǔn)的Rhino引擎提供了ECMA-262標(biāo)準(zhǔn)中規(guī)定的JavaScript對(duì)象的相應(yīng)實(shí)現(xiàn),但是沒(méi)有提供客戶端JavaScript的實(shí)現(xiàn)對(duì)象。
由以上分析可以得知,Rhino引擎對(duì)于標(biāo)準(zhǔn)JavaScript的實(shí)現(xiàn)非常優(yōu)秀,但是,如果將其應(yīng)用于具體網(wǎng)頁(yè)信息的采集,則Rhino缺少客戶端JavaScript的擴(kuò)展支持。
圖2 Rhino引擎執(zhí)行過(guò)程
根據(jù)對(duì)Rhino引擎解析和執(zhí)行JavaScript腳本的分析,要使Rhino引擎可以支持客戶端JavaScript的解析,必須改進(jìn)Rhino引擎。
根據(jù)上海交通大學(xué)的金曉鷗[2]提出的解決方法,可以本地創(chuàng)建如Window對(duì)象、Document對(duì)象等JavaScript內(nèi)置對(duì)象,并將創(chuàng)建的本地對(duì)象大部分方法直接設(shè)置為空,只實(shí)現(xiàn)如Window.open()之類(lèi)的可能含有超鏈接的方法。這種方法可以很好地提取網(wǎng)頁(yè)中含有的URL,但是難以得到使用JavaScript腳本展現(xiàn)的網(wǎng)頁(yè)內(nèi)容。
根據(jù)北京郵電大學(xué)的李蕊[3]提出的解決方法,可以使用開(kāi)源工具NekoHtml生成Document對(duì)象,再將生成的Document對(duì)象映射到Rhino引擎的執(zhí)行域中。由于NekoHtml生成Document對(duì)象很好地實(shí)現(xiàn)了W3C的DOM接口,這種方式可以為Rhino引擎提供很好的客戶端JavaScript內(nèi)置對(duì)象支持,但是由于JavaScript語(yǔ)言的特性,這種方法不能提供對(duì)象屬性或方法的動(dòng)態(tài)添加。如JavaScript腳本中存在的document.myValue=0之類(lèi)的語(yǔ)句就不能解析,因?yàn)镹ekoHtml生成的Document對(duì)象沒(méi)有myValue的屬性。
由于NekoHtml生成的Document對(duì)象沒(méi)有實(shí)現(xiàn)Scriptable接口,故無(wú)法實(shí)現(xiàn)動(dòng)態(tài)添加屬性或方法,則也可以考慮直接實(shí)現(xiàn)W3C標(biāo)準(zhǔn)的接口并將所有實(shí)現(xiàn)類(lèi)都實(shí)現(xiàn)Scriptable接口。但是,這種方法首先需要實(shí)現(xiàn)所有W3C接口,工作量比較大;并且這樣的編碼方式和Rhino引擎有很大的耦合性,加大了代碼維護(hù)的難度。
根據(jù)對(duì)Rhino引擎介紹和分析,可知其可以完成JavaScript腳本對(duì)象和Java本地對(duì)象的映射,而Rhino的標(biāo)準(zhǔn)實(shí)現(xiàn)中沒(méi)有提供客戶端JavaScript內(nèi)置對(duì)象的擴(kuò)展,故改進(jìn)Rhino引擎的重點(diǎn)就在于以某種方式為Rhino引擎提供客戶端JavaScript內(nèi)置對(duì)象擴(kuò)展。
根據(jù)Rhino工作過(guò)程,本小節(jié)主要研究Rhino對(duì)一段JavaScript腳本的調(diào)用過(guò)程。Rhino引擎雖然沒(méi)有提供對(duì)于客戶端JavaScript的擴(kuò)展,但是其設(shè)計(jì)者為Rhino使用者提供了一個(gè)方便的擴(kuò)展接口——Scriptable,并且為此接口設(shè)計(jì)了一個(gè)實(shí)現(xiàn)類(lèi)ScriptableObject。Scriptable主要提供put和get 2個(gè)方法,這2個(gè)方法實(shí)現(xiàn)對(duì)象屬性或方法的動(dòng)態(tài)添加和訪問(wèn),所以通過(guò)實(shí)現(xiàn)Scriptable接口或者繼承ScriptableObject類(lèi)可以使用戶自定義的對(duì)象具有動(dòng)態(tài)添加屬性或方法的特性[8]。
假設(shè)有以下示例代碼(示例1):
本示例首先初始化運(yùn)行環(huán)境,然后在JavaScript腳本域中加入繼承于ScriptableObject的對(duì)象c0,之后運(yùn)行JavaScript腳本ss。
通過(guò)跟蹤 Rhino源代碼可以得到 Rhino運(yùn)行JavaScript腳本過(guò)程如下:
(1)首先將腳本ss編譯為Script對(duì)象,然后調(diào)用Script對(duì)象的方法exec進(jìn)行執(zhí)行腳本;
(2)執(zhí)行腳本時(shí)(以本ss腳本為例),由于scope是ScriptableObject對(duì)象,故Rhino引擎調(diào)用scope的get方法首先試圖獲得c0對(duì)象,由于c0對(duì)象已經(jīng)加入scope域,獲取對(duì)象成功(使用get方法獲取對(duì)象時(shí)主要查詢ScriptableObject維護(hù)的 slots數(shù)組,slots數(shù)組的下標(biāo)是調(diào)用put方法時(shí)給定下標(biāo)或者加入對(duì)象name屬性的hash值);
(3)由于本小節(jié)JavaScript腳本是設(shè)置屬性w的值,所以Rhino引擎繼續(xù)遞歸尋找w,這時(shí)同樣調(diào)用get方法,不過(guò)此時(shí)作用域?yàn)閏0作用域,則調(diào)用c0對(duì)象(c0繼承于ScriptableObject)的get方法。由于代碼中并沒(méi)有加入w屬性,這是get方法返回Scriptable.NOT_FOUND;
(4)由于屬性值不存在,Rhino引擎調(diào)用c0的put方法將屬性w加入slots數(shù)組中,并將其值設(shè)置為7;
(5)再次執(zhí)行下一句腳本,同上,這次通過(guò)get方法查詢到w屬性,則直接修改w屬性的值為0。
通過(guò)對(duì)上面簡(jiǎn)單的Rhino引擎過(guò)程分析,可以知在Rhino引擎解析JavaScript腳本并與Java本地對(duì)象進(jìn)行交互時(shí),最主要的方法為get和put,這2個(gè)方法作為JavaScript腳本和Java本地類(lèi)交互的入口和出口決定了如何得到和修改(調(diào)用)Java本地類(lèi)的屬性(方法)。
由于對(duì)Rhino引擎應(yīng)用或改進(jìn)的常用方法存在一些缺陷,不能完全滿足應(yīng)用需求,故本文提出一種為Rhino引擎提供低侵入式客戶端JavaScript內(nèi)置對(duì)象擴(kuò)展(以下簡(jiǎn)稱(chēng)低侵入擴(kuò)展)的方法。
3.2.1 低侵入擴(kuò)展特點(diǎn)
低侵入式擴(kuò)展是借鑒Hibernate框架的設(shè)計(jì)思想。使用本文提出的方法可以使擴(kuò)展類(lèi)的開(kāi)發(fā)人員面向W3C組織公布的標(biāo)準(zhǔn)Document接口進(jìn)行編程,無(wú)需知道Rhino引擎的運(yùn)行機(jī)制或理論,只需實(shí)現(xiàn)W3C組織標(biāo)準(zhǔn)化的接口,為Rhino引擎提供簡(jiǎn)單的Java類(lèi),之后Rhino引擎通過(guò)反射和自動(dòng)封裝可以將開(kāi)發(fā)好的簡(jiǎn)單 Java類(lèi)集成使用。這樣就實(shí)現(xiàn)了Rhino引擎和擴(kuò)展類(lèi)的開(kāi)發(fā)人員解耦和,簡(jiǎn)化了擴(kuò)展的開(kāi)發(fā)和維護(hù)。
3.2.2 低侵入擴(kuò)展總體結(jié)構(gòu)
要從含有JavaScript腳本的網(wǎng)頁(yè)獲取信息,主要有2個(gè)重要步驟:(1)需要獲取網(wǎng)頁(yè)的源碼;(2)使用Rhino引擎解析網(wǎng)頁(yè)源碼中的JavaScript腳本,將解析之后的源代碼轉(zhuǎn)化為HTML代碼提取信息。其中難點(diǎn)是步驟(2),在步驟(2)中本文借鑒北京郵電大學(xué)的李蕊[3]提出的解決方案,先將網(wǎng)頁(yè)源代碼通過(guò)NekoHtml工具生成Document對(duì)象,并將其提供給Rhino引擎使用。其中為了使Document對(duì)象具有動(dòng)態(tài)添加屬性的功能,本文作了進(jìn)一步的改進(jìn),使用Rhino引擎提取JavaScript網(wǎng)頁(yè)信息,總體結(jié)構(gòu)如圖3所示。
圖3 網(wǎng)頁(yè)信息獲取總體結(jié)構(gòu)
3.2.3 低侵入擴(kuò)展實(shí)現(xiàn)類(lèi)的簡(jiǎn)單封裝
使用低侵入式方式使得普通類(lèi)可以如同Scriptable類(lèi)一樣在Rhino引擎中被調(diào)用和修改。本文考慮從put和get方法入手,使用一個(gè)ScriptableObject對(duì)象封裝普通對(duì)象。具體過(guò)程如下:
(1)假設(shè)a為一普通對(duì)象,則新建s對(duì)象繼承于ScriptableObject且在s中存儲(chǔ)a的引用;
(2)在 s中重寫(xiě) ScriptableObject的 get方法,使得Rhino引擎調(diào)用get方法時(shí)首先查詢a對(duì)象是否有相應(yīng)屬性或方法,如有則返回a中屬性或方法,否則調(diào)用s的父對(duì)象(ScriptableObject對(duì)象)的get方法(put方法類(lèi)似)。
如示例2:
測(cè)試的主方法:
運(yùn)行后返回值為OK,則顯示使用對(duì)象s封裝對(duì)象a成功。但是,僅僅使用本小節(jié)的封裝方式在實(shí)際使用中是沒(méi)有意義的,在s的代碼中可以發(fā)現(xiàn),s對(duì)a的封裝是以硬編碼的方式維護(hù)一個(gè)a的引用得到的,使得對(duì)于每一個(gè)需要封裝的對(duì)象都要提供一個(gè)封裝對(duì)象,即使得封裝變得困難和無(wú)意義。
3.2.4 低侵入擴(kuò)展實(shí)現(xiàn)類(lèi)的低侵入式通用封裝
低侵入擴(kuò)展對(duì)于普通類(lèi)的封裝是可行的,但是簡(jiǎn)單封裝在實(shí)際使用中幾乎沒(méi)有價(jià)值,故本文將考慮一種低侵入通用封裝方式。使用低侵入通用封裝方式可以將任何自定義類(lèi)在不經(jīng)過(guò)修改的情況下,加入Rhino的映射中,且具有動(dòng)態(tài)添加屬性或方法的特性。
目前難點(diǎn)在于:由于多個(gè)普通類(lèi)不盡相同,在封裝之前程序并不知道各個(gè)類(lèi)的屬性和方法信息。所以本文考慮使用Java提供的反射[9]功能得到普通類(lèi)的方法再進(jìn)行封裝。研究Java的反射機(jī)制可知,通過(guò)Java反射得到類(lèi)方法,之后使用invoke方法可以調(diào)用相應(yīng)方法并返回運(yùn)行結(jié)果。invoke原型為public Object invoke(Object obj,Object[]args),其中 obj為調(diào)用方法的對(duì)象,args為方法所需參數(shù)??梢钥紤]使用一個(gè)對(duì)象儲(chǔ)存普通類(lèi)的所有公共方法,然后在封裝類(lèi)的對(duì)象中同時(shí)維護(hù)普通類(lèi)的對(duì)象和存儲(chǔ)普通類(lèi)對(duì)象方法的對(duì)象。
1.方法存儲(chǔ)類(lèi)設(shè)計(jì)。
綜合以上分析,首先設(shè)計(jì)一個(gè)類(lèi)用于存儲(chǔ)普通類(lèi)的方法。通過(guò)研究可以發(fā)現(xiàn),本儲(chǔ)存類(lèi)的輸入為一個(gè)普通類(lèi)對(duì)象,輸出為一個(gè)儲(chǔ)存類(lèi)對(duì)象。輸出的儲(chǔ)存類(lèi)對(duì)象擁有普通類(lèi)所有公共方法的引用。故可以設(shè)計(jì)儲(chǔ)存類(lèi)擁有一個(gè)私有的哈希表用于以名稱(chēng)為鍵值存儲(chǔ)普通類(lèi)方法。流程為:
(1)首先初始化一個(gè)普通類(lèi)對(duì)象;
(2)通過(guò)反射得到普通類(lèi)對(duì)象的類(lèi),將此類(lèi)傳入存儲(chǔ)類(lèi)的構(gòu)造函數(shù)用于構(gòu)造存儲(chǔ)類(lèi)對(duì)象;
(3)存儲(chǔ)類(lèi)構(gòu)造函數(shù)得到傳入的普通類(lèi)參數(shù)后,再通過(guò)反射獲得普通類(lèi)的所有方法;
(4)以方法的名稱(chēng)為鍵值,將方法存入存儲(chǔ)類(lèi)私有的哈希表。
根據(jù)以上流程發(fā)現(xiàn),反射時(shí)沒(méi)有考慮函數(shù)重載,即同名函數(shù)的情況。為了解決此問(wèn)題,可以將哈希表的元素值設(shè)為一個(gè)鏈表,如有同名方法則在鏈表處增加方法。由于JavaScript腳本是不支持函數(shù)重載[10]的,對(duì)于JavaScript腳本而言函數(shù)名是函數(shù)對(duì)象的標(biāo)識(shí),參數(shù)數(shù)量只是這個(gè)函數(shù)的屬性,所以靠定義參數(shù)數(shù)量不同的函數(shù)實(shí)現(xiàn)重載是不行的。故處理同名函數(shù)時(shí),本文參考JavaScript腳本對(duì)于同名函數(shù)的處理方法,即將被調(diào)用函數(shù)的參數(shù)列表與同名函數(shù)鏈表中的各個(gè)方法參數(shù)列表進(jìn)行比對(duì),以最大匹配原則尋找可執(zhí)行的方法,若參數(shù)數(shù)量不足則以默認(rèn)值補(bǔ)齊,參數(shù)數(shù)量過(guò)多則舍去多余參數(shù)。由此獲得一個(gè)普通類(lèi)對(duì)象和一個(gè)存儲(chǔ)該對(duì)象所有方法的存儲(chǔ)類(lèi)對(duì)象。
2.封裝類(lèi)設(shè)計(jì)。
通過(guò)Java反射機(jī)制可得到一個(gè)普通類(lèi)對(duì)象和一個(gè)存儲(chǔ)該對(duì)象所有方法的存儲(chǔ)類(lèi)對(duì)象。設(shè)計(jì)一個(gè)封裝類(lèi),將普通類(lèi)對(duì)象封裝為繼承ScriptableObject類(lèi)的對(duì)象。由于此時(shí)已經(jīng)得到一個(gè)存儲(chǔ)對(duì)象維護(hù)了普通類(lèi)對(duì)象的所有方法,則可以將封裝類(lèi)抽象設(shè)計(jì)為通用封裝類(lèi)。
綜上所述,設(shè)計(jì)一個(gè)通用封裝類(lèi),該類(lèi)繼承于ScriptableObject類(lèi),并且封裝類(lèi)對(duì)象需要擁有被封裝對(duì)象和與被封裝對(duì)象對(duì)應(yīng)的方法存儲(chǔ)對(duì)象的引用。
由于封裝類(lèi)繼承于ScriptableObject類(lèi),需要重寫(xiě)父類(lèi)get方法,其流程可以統(tǒng)一為:
(1)根據(jù)方法(或?qū)傩?名稱(chēng),通過(guò)存儲(chǔ)類(lèi)中的哈希表查詢相應(yīng)方法,若存在則執(zhí)行(2),否則執(zhí)行(3);
(2)返回查詢到的方法(或?qū)傩?,通過(guò)方法的invoke方法運(yùn)行得到的方法,其中invoke需要的obj參數(shù),為封裝類(lèi)所擁有的普通類(lèi)對(duì)象。之后執(zhí)行(4);
(3)調(diào)用父類(lèi)對(duì)象(ScriptableObject類(lèi)對(duì)象)get方法查詢相應(yīng)方法(或?qū)傩?,之后執(zhí)行(4);
(4)返回調(diào)用結(jié)果。
此外還必須重寫(xiě)父類(lèi)set方法,其流程與get方法類(lèi)似。最終封裝完成,封裝體系如圖4所示。
圖4 普通類(lèi)封裝體系
3.2.5 低侵入擴(kuò)展應(yīng)用
將任何普通類(lèi)對(duì)象封裝成為實(shí)現(xiàn)Scriptable接口的對(duì)象的方法,可以完成Rhino引擎對(duì)客戶端JavaS-cript內(nèi)置對(duì)象的擴(kuò)展。
(1)Dom對(duì)象擴(kuò)展。
根據(jù)圖3的結(jié)構(gòu),由網(wǎng)頁(yè)獲取的源代碼首先會(huì)使用解析工具NekoHtml生成Dom對(duì)象[11],該對(duì)象實(shí)現(xiàn)了W3C組織定義的各種DOM接口,然后使用本文的封裝方法,可以將Dom對(duì)象封裝為實(shí)現(xiàn)Scriptable接口的對(duì)象,再將封裝后的對(duì)象加入Rhino運(yùn)行域,Rhino引擎就可以對(duì)其進(jìn)行操作。在解析JavaScript腳本需要調(diào)用Document的各種方法時(shí),可以根據(jù)映射調(diào)用相應(yīng)Dom對(duì)象的各種方法。其他客戶端JavaScript內(nèi)置對(duì)象,如Window對(duì)象等也相同處理。
(2)Ajax擴(kuò)展。
實(shí)現(xiàn)Dom擴(kuò)展就可以解析一般頁(yè)面所包含的JavaScript腳本,但是對(duì)于一些使用了Ajax技術(shù)的網(wǎng)頁(yè),還需要為 Rhino 引擎提供 XMLHttpRequest[12]對(duì)象(IE為 Microsoft.XMLHTTP對(duì)象)。XMLHttpRequest對(duì)象也使用封裝類(lèi)進(jìn)行封裝,但是與Dom對(duì)象不同,XMLHttpRequest對(duì)象需要在JavaScript腳本中使用new關(guān)鍵字顯示創(chuàng)建后,再進(jìn)行初始化。故對(duì)XMLHttpRequest對(duì)象的擴(kuò)展,首先需要?jiǎng)?chuàng)建一個(gè)普通類(lèi),該類(lèi)實(shí)現(xiàn)XMLHttpRequest功能,然后創(chuàng)建XMLHttpRequest對(duì)象的構(gòu)建類(lèi),該類(lèi)實(shí)現(xiàn)Rhino引擎提供的 Function接口,并且在使用“new XMLHttpRequest()”腳本語(yǔ)句時(shí)進(jìn)行調(diào)用,返回封裝后的XMLHttpRequest對(duì)象。通過(guò)以上方法可以為Rhino引擎提供Ajax擴(kuò)展,并且在不使用Ajax的網(wǎng)頁(yè)時(shí)不會(huì)創(chuàng)建XMLHttpRequest對(duì)象,不會(huì)影響性能。
3.2.6 低侵入擴(kuò)展意義
低侵入為Rhino引擎添加擴(kuò)展的方法,實(shí)現(xiàn)擴(kuò)展類(lèi)和Rhino引擎本身的解耦和,使得Rhino引擎版本的升級(jí)和W3C標(biāo)準(zhǔn)接口的改變不會(huì)同時(shí)對(duì)已實(shí)現(xiàn)的擴(kuò)展產(chǎn)生影響。
通過(guò)對(duì)Rhino引擎和其擴(kuò)展的解耦和,進(jìn)一步提高了擴(kuò)展的可維護(hù)性和健壯性,也使得Rhino引擎對(duì)擴(kuò)展開(kāi)發(fā)人員變得透明,開(kāi)發(fā)人員不需要知道Rhino引擎的運(yùn)行原理,降低了擴(kuò)展開(kāi)發(fā)的難度。
通過(guò)本文的分析研究,得到了一個(gè)基于Rhino引擎的改進(jìn)解析器。該解析器為Rhino引擎提供客戶端JavaScript內(nèi)置對(duì)象的解析,并且可以實(shí)現(xiàn)對(duì)Ajax的支持。為驗(yàn)證改進(jìn)后Rhino引擎效果,使用Rhino引擎自帶的JavaScript測(cè)試帶代碼(位于Rhino引擎自帶 benchmarks/sunspider文件夾下)進(jìn)行標(biāo)準(zhǔn)JavaScript解析測(cè)試,使用特別編寫(xiě)的HTML測(cè)試代碼進(jìn)行Dom支持性測(cè)試,使用特別編寫(xiě)的含有Ajax調(diào)用的測(cè)試代碼進(jìn)行Ajax支持性測(cè)試,對(duì)比實(shí)驗(yàn)結(jié)果如表1所示。
表1 實(shí)驗(yàn)對(duì)比
其中,“標(biāo)準(zhǔn)JavaScript解析”實(shí)驗(yàn),主要測(cè)試解析引擎對(duì)ECMA-262標(biāo)準(zhǔn)的支持情況;“Dom支持性測(cè)試”實(shí)驗(yàn),主要測(cè)試在實(shí)際運(yùn)行環(huán)境中,解析引擎對(duì)JavaScript中含有對(duì)Dom操作的腳本支持情況;“Ajax支持性測(cè)試”實(shí)驗(yàn),主要測(cè)試在實(shí)際運(yùn)行環(huán)境中,解析引擎對(duì)JavaScript中含有Ajax調(diào)用的腳本支持情況。以上實(shí)驗(yàn)都是以通過(guò)測(cè)試數(shù)量進(jìn)行評(píng)優(yōu),通過(guò)越多表明解析引擎對(duì)腳本解析得越好。
通過(guò)實(shí)驗(yàn)對(duì)比可以看出,由于Rhino引擎對(duì)標(biāo)準(zhǔn)JavaScript有很好的支持,而本文的改進(jìn)型Rhino引擎并沒(méi)有對(duì)標(biāo)準(zhǔn)JavaScript的解析做任何修改,所以“標(biāo)準(zhǔn)JavaScript解析”實(shí)驗(yàn)2款解析引擎均完全滿足使用需求。但是,在“Dom支持性測(cè)試”實(shí)驗(yàn)和“Ajax支持性測(cè)試”實(shí)驗(yàn)中,原Rhino引擎并沒(méi)有相應(yīng)的擴(kuò)展類(lèi)和方法,在運(yùn)行測(cè)試時(shí)都以報(bào)出異常停止運(yùn)行而結(jié)束,而改進(jìn)型的Rhino引擎則完成腳本的解析并得到期望的結(jié)果。
實(shí)驗(yàn)結(jié)果表明,本文使用的改進(jìn)方法成功地為Rhino引擎添加了客戶端JavaScript內(nèi)置對(duì)象擴(kuò)展。改進(jìn)型Rhino引擎成功解析了包含JavaScript腳本的HTML頁(yè)面,提取出相應(yīng)信息,并且由于使用低侵入式設(shè)計(jì)結(jié)構(gòu),使得能夠很容易更換或者修改擴(kuò)展類(lèi)。由于改進(jìn)方法中增加了XMLHttpRequest對(duì)象的擴(kuò)展,使得改進(jìn)型Rhino引擎也可以解析使用Ajax技術(shù)的網(wǎng)頁(yè)。
本文通過(guò)深入研究Rhino引擎的運(yùn)行機(jī)制,提出一種使Rhino引擎可以解析客戶端JavaScript的低侵入式擴(kuò)展方法。使用此方法可以改進(jìn)Rhino引擎功能,可以使程序模擬瀏覽器執(zhí)行包括Ajax調(diào)用在內(nèi)的JavaScript腳本,可以較為完整地獲取網(wǎng)頁(yè)信息。
今后,筆者將在以下方面做進(jìn)一步研究:
(1)改進(jìn)低侵入式擴(kuò)展方式效率,盡量減少對(duì)象的創(chuàng)建;
(2)進(jìn)一步完善改進(jìn)型Rhino引擎對(duì)Ajax的支持,如完善腳本事件觸發(fā)模擬;
(3)整合Rhino引擎與網(wǎng)絡(luò)通訊模塊,完善瀏覽器的模擬。
[1]Sherman C,Price G.The invisible Web:Uncovering sources search engines can’t see[J].Library Trends,2003,52(2):282-298.
[2]金曉鷗,鐘寶燕,李翔.基于Rhino的JavaScript動(dòng)態(tài)頁(yè)面解析研究與實(shí)現(xiàn)[J].計(jì)算機(jī)技術(shù)與發(fā)展,2008,18(2):1-4,50.
[3]李蕊,魏更宇,王樅,等.Rhino解析引擎的分析與改進(jìn)[C]//全國(guó)通信安全學(xué)術(shù)會(huì)議論文集.2010:305-310.
[4]羅兵.支持Ajax的互聯(lián)網(wǎng)搜索引擎爬蟲(chóng)設(shè)計(jì)與實(shí)現(xiàn)[D].杭州:浙江大學(xué),2007.
[5]David Flanagan.JavaScript權(quán)威指南(第5版)[M].北京:機(jī)械工業(yè)出版社,2007:9-10.
[6]賴堃.Javascript引擎Rhino研究與J2ME實(shí)現(xiàn)[D].成都:電子科技大學(xué),2010.
[7]W3school.Html DOM 教 程[DB/OL].http://www.w3school.com.cn/htmldom/index.asp,2013-08-15.
[8]Rhino:JavaScript for Java[DB/OL].http://www.mozilla.org/Rhino/,2010-04-03.
[9]李剛.瘋狂 Java講義[M].北京:電子工業(yè)出版社,2011:863-874.
[10]David Flanagan.JavaScript權(quán)威指南(第4版)[M].張銘澤譯.北京:機(jī)械工業(yè)出版社,2003:141-144.
[11]開(kāi)源中國(guó)社區(qū).HTML文檔解析器NekoHTML[DB/OL].http://www.oschina.net/p/nekohtml/,2013-08-15.
[12]W3C.XMLHttpRequest[DB/OL].http://www.w3.org/TR/XMLHttpRequest/,2013-08-15.
[13]周立柱,林玲.聚焦爬蟲(chóng)技術(shù)研究綜述[J].計(jì)算機(jī)應(yīng)用,2005,25(9):1965-1969.
[14]楊麗萍,馬繼濤,張虹霞.網(wǎng)絡(luò)搜索引擎分類(lèi)與發(fā)展[J].情報(bào)學(xué)報(bào),2006,25:421-424.