跳到主要內容

安裝 PythonWebkit

偶然看到 PythonWebkit 這個專案, 作者花了兩週左右的時間寫好 WebKit 的 python 介面, 於是操作 DOM 不再是 javascript 的專利, 透過 PythonWebkit 就能用 python 操作 DOM。作者自稱除一些小功能外, 這個專案已到可用的階段:

The current status is that the SVG 2D Canvas element is not available, and CSS Styles have to be modified through the style.setProperty function (rather than being accessible as python properties). Other than these gotchas, the Python Webkit bindings are in a useable state, providing full W3C compliant access to the full set of DOM functionality (except SVG Canvas) as provided by Webkit, and ordinarily only accessible via javascript.

不過相關文件超少, 花了些時間才裝成功, 也增加了一些編原始碼的功力。

背景介紹

WebKit歷史很有意思, 有興趣的人自行參照連結。在 Linux 上目前有兩種 port, 一個是用 GTK+ 實作的WebKitGTK+, 另一個是 QT 的實作版本。

PyWebKitGtk 是另外一個基於 WebKitGTK+ 的專案, 透過 WebKit 提供的 gobject binding 提供 python 介面, 但沒有提供直接操作 DOM 的功能。雖然有些補救方案, PythonWebkit 的作者覺得直接改出 python 介面比較省事, 效率也較好, 該頁有寫詳細的設計考量。所以, PythonWebkit 是以 WebKitGTK+ 為底, 修改部份內容直接提供 python 的 DOM API。

剛開始作者用 PyWebKitGtk 的 python module "webkit", 藉此省掉寫 python module 的步驟, 然後加上 patch 讓 webkit (python module) 能取出 DOM。也就是說, PythonWebkit 依賴 PyWebKitGtk; PyWebKitGtk 依賴 WebKitGTK+; WebKitGTK+WebKit 在 Linux 下的實作版本之一。

後來作者自己重寫一個 python module "pywebkitgtk", 就不用再裝 PyWebKitGtk 了。網路上找 PythonWebkit 的文件, 有部份會提到 PyWebKitGtk, 但現在已用不到它了。弄清楚這堆名稱差不多的專案, 花了我一些時間 ..., 搞清楚後也滿有成就感的。

上面是簡化的說法, 實際上 PythonWebkit 不是直接依賴 WebKitGTK+, 也可以改用基於 DirectFB 的 PyWebkitDFB。由於這還在實驗階段, 我就沒試了。

安裝方式

主要參考 PythonWebkit 的說明, 另外需要參照 WebKitGTK+ 的安裝說明, 了解它用到的套件。在 Ubuntu 下的安裝步驟如下:

1. 升級到 11.04, 11.04 的 glib 版本才夠新 (>= 2.27.4)

不然會出現

configure: error: You need the GLib dev tools in your path

2. 安裝編譯 WebKitGTK+ 需要的套件

注意: 最後三個指令不能用 aptitude, 只有用 apt-get 才能確保移除後仍保有需要的設定

$ apt-get install python-dev python-ply
$ apt-get build-essential
$ apt-get build-dep libwebkit-1.0-2
$ apt-get install libwebkit-1.0-2
$ apt-get remove libwebkit-1.0-2

在 Ubuntu 下自己編原始碼時, 似乎滿常用這招的, 透過原有的套件管理系統裝好相依的套件、取得需要的 header 等資源, 藉此減少手動操作部份。

3. 取得 PythonWebkit 原始碼, 這步會花很長的時間, 好像會到一小時吧

$ git clone git://git.savannah.gnu.org/pythonwebkit.git

4. 切換到 branch python_codegen

$ git checkout python_codegen

注意: 千萬不要加 "-b", 我不熟 git 參數, 誤加了 -b, 變成開一個新的 local branch 叫 python_codegen, 而不是取出遠端的 branch python_codegen。git 指令請參考《Git 版本控制系統(2) 開 branch 分支和操作遠端 repo.》

有取對 branch 的話, grep 整個目錄應該會找到 pywebkitgtk 相關檔案; 反之則否。pywebkitgtk 是 PythonWebkit 產生的 python module, 作用和 PyWebKitGtk 的 webkit 差不多, 不過多了取得 DOM 的方法 (web view 的 GetDomDocument())。

5. 開始編譯

$ mkdir build
$ cd build
$ ../autogen.sh
$ make

我的電腦是三年前買的, 在 VM 裡用雙 CPU 編的話 (配合 make -j 2), 大概四十多分鐘可編完。

執行 autogen.sh 時, 若發現缺了東西, 可用 aptitude search 加關鍵字, 或 apt-file search xxx.h 查看看放在那個套件, 通常是要裝一些 X-dev 的 package。印象中我好像有少了 pygtk。有問題時, 記得用 dpkg -l 和 WebKitGTK+ 的 Dependencies 對一下, 看有沒有少裝什麼。然後再重跑 autogen.sh。

6. 檢查結果

編好的東西放在 .libs/ 下, 不知是不是某種慣例。上網查 make install 的討論, 似乎沒有明確的方法可以找到會裝那些東西進系統。幸運的話, 可以用 checkinstall 裝成 deb 檔, 就能用 dpkg -L 查裝了什麼, 也能用 dpkg -r 移除。不過我用 checkinstall 的結果, 似乎比直接 make install 多裝了不少東西, 最後直接用 make install。

回到正題, 先切到 .libs 下看編完的東西是否可用。.libs/ 下應該要看到 libwebkit 的 so 檔, 還有 pywebkitgtk.so。接著在 .libs/ 目錄下執行 ipython, 依序打入:

import gtk
import pywebkitgtk
url = "http://www.gnu.org/software/pythonwebkit"
wv = pywebkitgtk.WebView(1024,768, url=url)
doc = wv.GetDomDocument()
ns = doc.getElementsByTagName('h2')
ns.length  # 9
ns.item(0).innerHTML  # 'Why is this important - why is it a "big deal"?'

注意, 上述程式要在 X Window 下執行。import gtk 後, 在產生 WebView 物件時會跳出視窗。若沒有 import gtk 的話, 不會有任何錯誤訊息, 但取得的 doc 裡面卻沒任何東西, ns.length 會是 0。

7. 安裝到系統

$ sudo make install

然後檢查 /usr/local/lib 是否有在 ldconfig 路徑中:

$ grep -R "/usr/local/lib" /etc/ld.so.conf.d/

若沒有的話, 在 /etc/ld.so.conf.d/ 下新增一個檔案:

$ sudo echo "/usr/local/lib" > /etc/ld.so.conf.d/webkit.conf

最後是更新 ldconfig 的 cache:

$ sudo ldconfig

不確定原本 /etc/ld.so.conf.d/ 下就有 /usr/local/lib 的情況是否要執行 ldconfig, 好像要執行才會 ok, 但看文件說, 一般 make install 時應該會自動執行這個動作。

之後就能在任何地方直接 import pywebkitgtk 啦。

用法

附上一個小例子: 取出 TechCrunch 文章有多少人按 Facebook 的 like:

import os

import gtk 
import pywebkitgtk

wv = pywebkitgtk.WebView(10, 10, url='http://techcrunch.com/2011/09/25/clinging-to-friction-some-thoughts-on-facebooks-f8/')
os.sleep(10)  # Wait Facebook plugin to be loaded.
dom = wv.GetDomDocument()
iframes = dom.getElementsByClassName('fb_ltr')
for i in range(iframes.length):
    e = iframes.item(i)
    ns = e.contentDocument.getElementsByClassName('connect_widget_button_count_count')
    if ns.length > 0:
        print ns.item(0).firstChild.nodeValue  # Count of "FB like"

TechCrunch 的推文數大多是用 AJAX 載入到 iframe 裡, 用普通的方法無法抓到內容。上面的作法相當粗糙, 就將就用啦, 這不是本篇的重點。

產生 wv 後會看到 console 出現一堆訊息, 大概是載入頁面後, 以及執行完後續 AJAX 時產生的。之後再來看看這些訊息是否有害。

PythonWebkit 沒有多提相關 API, 因為作者為了方便大家查文件, 讓 PythonWebkit 和原本的 DOM API 100% 相容 (目前只有 CSS 部份有一點不同), 所以, 直接學 DOM 就可以了。配合 ipython 直接操作, 還滿容易試的。

Trouble shooting

1. configure: error: You need the GLib dev tools in your path

參考 這篇, 要用新版 glib, Ubuntu 要到 11.04 才符合。我鐵齒的從 10.04 一路試, 直到 11.04 才真的 ok。不過 libsoup 用 2.24 也 OK, 不需要像 PythonWebkit 說要用最新版才行(2.29.90), 不確定之後會有什麼問題。

2. 編譯時出現 "error: webkitmarshal.h: No such file or directory"

參照這篇, 執行

$ touch ./Source/WebKit/gtk/webkitmarshal.list

然後重跑 make。

3. 無法 import pywebkitgtk / 沒有編出 pywebkitgtk.so

記得在取得 repository 後要先執行 "git checkout pyhton_codegen" 才執行 ../autogen.sh, 千萬不要寫成 "git checkout -b pyhton_codegen"

4. import pywebkitgtk 時說找不到 libwebkitgtk

In [1]: import pywebkitgtk
---------------------------------------------------------------------------
ImportError                               Traceback (most recent call last)
/usr/local/lib/<ipython-input-1-8dab5af5d34b> in <module>()
----> 1 import pywebkitgtk

ImportError: libwebkitgtk-python-1.0.so.0: cannot open shared object file: No such file or directory

先用 ldd 檢查路徑是否確實有問題:

$ ldd /usr/local/lib/python2.7/dist-packages/pywebkitgtk.so | grep libwebkitgtk
	libwebkitgtk-python-1.0.so.0 => not found

出現 "not found" 表示 pywebkitgtk.so 找不到 libwebkitgtk-python-1.0.so.0。接著檢查 /etc/ld.so.conf.d/* 有沒有含 /usr/local/lib (當然, 要先確定你的 libwebkit-python-1.0.so 有裝到那)

將 /usr/local/lib 加到 ldconfig 的路徑後, 執行 sudo ldconfig

另外看到有人提到在編譯前這麼做:

$ export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig

不過我試了沒成功, 之後有空再來看 pkgconfig 是什麼。

其它參考資料和雜項心得

  • Program Library HOWTO: 動態函式庫的相關知識, 有提到 LD_LIBRARY_PATH 和 LD_DEBUG, 必要時可協助除錯。
  • 作者個人網站, 有他的 e-mail。原本想說再試不出來就寄信問看看。話說找這個也花了點時間, PythonWebkit 整篇都沒提到他是誰啊 ..., 在這封信才看到多一點情報。
  • 有問題時, 可以看看 commit log, 了解主要的修改為何。從這次的經驗, 發覺除系統安裝的知識外, 操作 VCS 的知識和讀 commit (changeset) 的能力也很重要。

留言

  1. 您好,最近一段時間我一直在安裝這個pythonwekbit,我一直都是按照 www.gnu.org/software/pythonwebkit 這個頁面的要求來做的,不過最終的目的是想用您之前提到的那個方法——“用 PyWebKitGtk 的 python module "webkit", 藉此省掉寫 python module 的步驟, 然後加上 patch 讓 webkit (python module) 能取出 DOM。”如果想對pywebkitgtk加上patch,應該是可以不用安裝pythonwebkit的吧。我現在是直接
    apt-get install libwebkitgtk-dev
    這之後就想安裝pywebkitgtk1.1.8,之後加上patch,請問這樣做是否可行?希望能夠得到您的幫助,my email:e.shijia@gmail.com

    回覆刪除

張貼留言

這個網誌中的熱門文章

virtualbox 使用 USB 裝置

2012-12-16 更新 現在 (4.x 版) 似乎無需做任何設定, 只要有裝 Oracle VM VirtualBox Extension Pack, 在 VirtualBox 視窗右下角按 USB 的圖示, 再點目標裝置, 即可加入或移除該裝置 同一時間只有 host 或 guest 可擁有該裝置, 所以從 guest OS 移除, 相當於接回 host OS 目前 VirtualBox 只支援 USB 2.0 的插槽, 若偵測不到時, 注意一下是否為這個問題 有時拔拔插插, VirtualBox 會進入奇怪的狀態, 接上去 guest OS 無法連接且跳出 device is busy 的錯誤訊息。試看看拔除該裝置, 重開 guest OS (續上則) 若重開 guest OS 無效, 並且 host OS 已移除該裝置, VirtualBox 的 USB 清單卻仍顯示 "captured", 試看看拔除該裝置, 重開 host OS原文網路上搜一下, 比較多是 Ubuntu 當 host 的解法, 我的情況是 Win7 當 host, Ubuntu 當 guest。 這兩篇說明很詳細《Learn How to Set Up USB and Networking Options in VirtualBox》《幻影千瞳的部落格: VirtualBox 使用筆記(二):使用 USB 裝置》 現在的版本圖形介面很好用了, 不用像第二篇說的那樣用指令操作。這裡記下我的操作步驟: 關掉 guest OS 在 VirtualBox 選單, 選擇 guest OS -> Settings -> USB -> Enable USB 2.0 會出現訊息框, 說明要安裝 Oracle VM VirtualBox Extension Pack。下載後安裝它 host OS 插入 USB 隨身碟 在 VirtualBox 選單, 選擇 guest OS -> Settings -> USB, 點右邊有綠色 "+" 的 USB 頭的圖示, 選擇該 USB 隨身碟, 加入它的 filter 從 host OS 移除 USB 隨身碟 開啟 guest OS 插入 USB 隨身碟, 於是 guest OS 會自動偵測…

(C/C++ ) 如何在 Linux 上使用自行編譯的第三方函式庫

以使用 LevelDB 為例。 抓好並編好相關檔案,編譯方式見第三方函式庫附的說明:$ ls include/ # header files leveldb/ $ ls out-shared/libleveldb.so* # shared library out-shared/libleveldb.so@ out-shared/libleveldb.so.1@ out-shared/libleveldb.so.1.20* 下面的例子用 clang++ 編譯,這裡用到的參數和 g++ 一樣。 問題一:找不到 header$ clang++ sample.cpp sample.cpp:5:10: fatal error: 'leveldb/db.h' file not found #include "leveldb/db.h" ^ 1 error generated. 解法:用 -I 指定 header 位置 問題二:找不到 shared library$ clang++ sample.cpp -I include/ /tmp/sample-2e7dd8.o: In function `main': sample.cpp:(.text+0x1e): undefined reference to `leveldb::Options::Options()' sample.cpp:(.text+0x6f): undefined reference to `leveldb::DB::Open(leveldb::Options const&, std::string const&, leveldb::DB**)' sample.cpp:(.text+0x10c): undefined reference to `leveldb::Status::ToString() const' sample.cpp:(.text+0x7d0): undefined reference to `leveldb::Status::ToString() const' clang: error: linker command failed with exit code 1 (u…

解決 undefined symbol / reference

C++ 新手上路, 有錯還請幫忙指正。 基本觀念相較於 script language 或 Java 來說, C/C++ 有完整的「編譯 -> 連結 -> 執行」三個階段, 各階段都可能發生 undefined symbol。在解決惱人的 undefined symbol 前, 得先明白整個編譯流程: 編譯 .c / .cpp 為 .o (object file) 時, 需要提供 header 檔 (用到 gcc 參數 -I)。事實上, 在編譯單一檔案時, gcc/g++ 根本不在意真正的 symbol 是否存在, 反正有宣告它就信了, 所以有引對 header 即可。這也是可分散編譯的原因 (如 distcc ), 程式之間在編譯成 .o 檔時, 並沒有相依性。 用 linker (ld 或 gold) 將 *.o 連結成 dynamic library 或執行檔時, 需要提供要連結的 library (用到 gcc 參數 -L 指定目錄位置, 用 -l 指定要連什麼函式庫)。不同於前一步, 此時 symbol 一定要在。 執行的時候, 會再動態開啟 shared library 讀出 symbol。換句話說, 前一個步驟只是檢查是否有。檢查通過也連結成 executable 或 shared library 後, 若執行時對應的檔案不見了, 仍會在執行期間找不到 symbol。若位置沒設好, 可能需要用 LIB_LIBRARY_PATH 指定動態函式的位置, 但不建議這麼做, 最好在執行 linker 時就指定好位置。原因見《Why LD_LIBRARY_PATH is bad》。明白這點後, 就看 undefined symbol 發生在那個階段, 若是編 object file 時發生, 就是沒和編譯器說 header 檔在那, 記得用 -I 告訴它。若在 linking 時發生, 就要同時設好 -L 和 -l。不過難就難在要去那找 undefined symbol 的出處。 解決問題的流程首先是判斷 symbol 是不是自己用到的原始碼裡, 可配合 id-utils 找看看 (我是用 gj, 比較方便一點)。或是看有沒有 man page, 有 man page 的話, 裡面會記錄用到的 header 和該怎麼下連結參數。若在專案裡找不到, …