在Python中執行javascript - 熊貓凶猛 - 博客園
在Python中執行javascript
在使用python抓取網頁的過程中,有的時候需要執行某些簡單的javascript,以獲得自己需要的內容,例如執行js里面的document.write或者document.getElementById等。自己解析js代碼顯然有點吃力不討好,因此最好能找到一些可以解析執行js的python庫。
google之可以找到三個候選者,分別是微軟的ScriptControl,v8的python移植PyV8,還有SpiderMonkey的Python移植Python-Spidermonkey。其中ScriptControl只能在windows上運行,需要win32com庫;PyV8能在windows和*nix上運行,但是需要裝PyV8庫;而SpiderMonkey是mozilla的js引擎在python上的移植,感覺已經不太活躍,因此沒用。
微軟的ScriptControl中對執行js最重要的方法就是addObject與eval,通過addObject,我們可以向js執行環境注入一個我們自定義的document對象,通過eval方法,我們可以執行一段js代碼。注入自定義對象需要使用win32com.server.util.wrap方法,將一個python對象包裝為COM對象,例如假設我們想注入一個只實現了write方法的document對象,代碼是這樣的:
import win32com.server.util, win32com.client class win32Doc: _public_methods_ = ['write'] def write(self, s): print s doc = win32Doc() jsengine = win32com.client.Dispatch('MSScriptControl.ScriptControl') jsengine.language = 'JavaScript' jsengine.allowUI = False jsengine.addObject('document', win32com.server.util.wrap(doc)) jsengine.eval('document.write("hello, world")')在windows里運行這段python代碼,最終就會打印出hello, world來。如果我們希望從python里讀取js通過document.write寫入的字符串並進行解析,只要給上面的win32Doc類添加對應的方法(例如read),就可以讀取並解析HTML代碼,並進行進一步處理了。
對PyV8來說,原理也是類似的,不過在具體機制上有所不同而已。在PyV8中需要在初始化的時候加入一個全局對象,其他的對象都是掛在全局對象之下的,例如document只是全局對象的一個屬性而已(實際上,document對象就是window對象的一個屬性么),當然,這個屬性對應的實際上是一個對象。需要注意的是,PyV8在處理字符串編碼的時候讓人很迷惑,在windows下它需要js的編碼為UTF8,而在Linux下只要求寬字符串,即python里的unicode,而在內部的字符串都是UTF8編碼的。至於為何如此,熊貓也騷擾過開發PyV8的flier,貌似是V8自己的feature。示例代碼是這樣的:
import PyV8 class v8Doc(PyV8.JSClass): def write(self, s): print s.decode('utf-8') class Global(PyV8.JSClass): def __init__(self): self.document = v8Doc() glob = Global() ctxt = PyV8.JSContext(glob) ctxt.enter() #or ctxt.eval(u'document.write("你好,中國")') for Linux ctxt.eval(u'document.write("你好,中國")'.encode('utf-8'))上面只是在python里模擬執行js的document.write的大體思路,如果還需要執行其他的js代碼對DOM樹進行操縱,那就一個個添加對應的方法好了。當然,這個添加也要保持一個限度,不然添加的方法太多,代碼會非常復雜,相當於自己已經開始實現一個DOM樹處理和解析的完全封裝了,如果是這樣,還不如使用自動化接口操縱瀏覽器,例如通過js/vbs操縱IE,或者在后台進行自動化批處理的話,使用一些headless browser軟件,例如phantomjs,這就留待以后再說了。