擷取AJAX動態產生的網頁內容:PhantomJS指令列工具 / Crawling AJAX Webpages with PhantomJS Command Line Utility
現在很多網頁內容都是以動態的方式產生,例如Facebook會在開啟網頁之後再來讀取網頁內容,就連「布丁布丁吃什麼?」也是在網頁開啟之後再來慢慢載入旁邊的小工具。這種使用AJAX技巧來調整畫面的網頁,雖然便於一般使用者用瀏覽器查看,但是卻會造成伺服器端用程式抓取網頁的困難。
還好,現在我們可以用Node.js寫成的虛擬瀏覽器PhantomJS來幫我們載入完整的網頁內容。為此我寫了一些搭配PhantomJS使用的命令列腳本,讓我們可以在Linux 32位元環境下以指令端擷取指定網址,並配合jQuery選取器抽取出需要的網頁元素,最後直接回傳顯示在螢幕上。
配置環境 / Environment
PhantomJS有Windows、Mac OS X、Linux 64位元、Linux 32位元、FreeBSD等不同的版本,可以跨平臺運作。不過本篇我是在Linux 32位元 Debian 7的環境下進行開發,所以不適用其他的環境。
要使用PhantomJS,系統還需要安裝一些相關的套件。詳情請看「How to install PhantomJS on Debian/Ubuntu」。
安裝與配置 / Installation
PhantomJS不能獨自運作,必須要搭配Node.js的腳本。而若要搭配jQuery選取器來擷取網頁元素,那又要有jQuery程式檔。因此我寫了一個可以Linux裡面執行的腳本phantomjs.sh,只要搭配網址、jQuery選取器等參數來執行它,這樣就可以將擷取結果輸出到螢幕上啦。
以下說明這個PhantomJS指令列工具怎麼安裝:
- PhantomJS與指令列腳本原始碼
https://github.com/pulipulichen/phantomjs - Zip壓縮檔:全部檔案下載
https://github.com/pulipulichen/phantomjs/archive/master.zip
先取得Zip壓縮檔,下載到任意的資料夾中,解壓縮它。我先把檔案下載到/tmp為例。配置完成之後的檔案結果如下圖:(請忽略.git資料夾)
接著我們要把部分檔案加入執行的權限,增加執行權限的指令如下:
#chmod +x phantomjs.sh
指令後面的「phantomjs.sh」就是檔案。要增加執行權限的檔案如下:
- phantomjs.sh
- demo.sh
- lib/phantomjs
好了,這樣PhantomJS指令列工具就配置完成啦。
用法 / Usage
接下來我們來看看要怎麼使用PhantomJS指令列工具。
範例網頁 / Demo AJAX Webpage
這裡有個用AJAX動態產生的網頁範例:http://pulipulichen.github.io/phantomjs/demo/demo.html
乍看之下這好像是顯示兩行「After Render!」。
但是從原始碼中可以看到,其實原來是兩行「Before Render…」。
如果是使用curl或PHP的file_get_content()等抓取網頁原始碼的工具,那就只能抓到「Before Render…」的內容。如果要抓取讀取完成「After Render!」,那就得用PhantomJS才行!
PhantomJS指令列工具用法 / PhantomJS Command Line Utility Usage
指令如下:
./phantomjs.sh "[URL]" "[SELECTOR]"
[URL]是指要抓取的網頁,例如「http://pulipulichen.github.io/phantomjs/demo/demo.html」。
[SELECTOR]則是jQuery的選取器,例如「div.content」。
若我們以上面的範例網頁為目標,想要抓取裡面含有「content」class屬性的div標籤的內容,那麼指令是這樣的:
./phantomjs.sh "http://pulipulichen.github.io/phantomjs/demo/demo.html" "div.content"
執行結果為:
<div class="content">After Render!</div><div class="content">After Render!</div>
這樣就完成抓取網頁的工作了。
這樣的指令列工具可以搭配任何程式語言運作,像是PHP的shell_exec(),很方便吧。
PhantomJS技巧 / Advanced Skill in PhantomJS
PhantomJS號稱下載、解壓縮、執行,就是這麼簡單。但實際上摸索之後,才發現根本就不是這回事。PhantomJS除了用來擷取網頁內容之外,還能夠為運作畫面截圖、執行自動化的腳本(類似Selenium IDE那樣,但不用真的開啟瀏覽器!)等等多種功能,其實非常地複雜。
PhantomJS必須要搭配Node.js腳本才能運作,而這個Node.js又跟一般的Node.js不一樣,是PhantomJS特別編譯的版本,操作方法請見它的API說明。
為了解決我想要的「擷取動態產生的網頁內容」跟「以jQuery擷取指定網頁元素」,這篇的Node.js腳本phantomjs-exec.js包含了許多PhantomJS的進階技巧:
- 等待網頁讀取完成再來擷取:phantomjs not waiting for “full” page load
- 在PhantomJS中使用jQuery:Use jQuery DOM selector syntax in PhantomJS?
- 若使用jQuery的話就無法讀取其他變數,所以要搭配eval來臨時建立包含變數的function程式碼:Pass arguments with page.evaluate
除此之外,我也在phantomjs-exec.js裡面加入了快取的功能。第二次執行抓取相同網頁的時候會從快取取得檔案。如果每次都想要開啟最新網頁的話,請修改phantomjs-exec.js的config.enable_cache = false;。
未來如果要做截圖或自動測試的話,應該也會以phantomjs-exec.js這個檔案繼續延伸吧。
小結 / In closing
其實這個PhantomJS指令列工具是為了Full Text RSS而寫得。現在許多RSS不僅不提供全文,就連網頁預設顯示的內容也只有摘要,改成讀取完成之後再用AJAX動態產生全文,像是「綠色工廠」或「硬是要學」。或著是許多網頁使用了延遲載入圖片lazyload的功能,導致RSS直接取得的程式碼沒辦法顯示圖片,像是「巴哈姆特GNN新聞」。每次瀏覽RSS的時候都還要跳到網頁才能看到全文,實在是太煩了,所以我就開始研究這篇的PhantomJS。
現在我的Full Text RSS已經內建了PhantomJS。但因為PhantomJS是仰賴系統的服務,所以我沒有直接把它跟Full Text RSS的原始碼擺在一起。取而代之的,我把它做成了OpenVZ虛擬範本,提供有需要的人直接使用吧:
就結果來說,很令我滿意。
雖然PhantomJS使用上比較複雜一些,但只要做好基礎的phantomjs-exec.js,未來各種應用就變得簡單許多。獲得了PhantomJS這個技術,就可以開啟更多可能性,大概就是這種感覺。
不知不覺的,Node.js越寫越多了啊。未來應該也會逐漸轉變成以Node.js為主要的程式語言吧。
因為現在Chrome有headless模式,所以PhantomJS停止維護了
回覆刪除https://www.solidot.org/story?sid=55672
下次要來研究怎麼用Chrome的headless擷取網頁內容了
http://blog.darkthread.net/post-2018-07-10-headless-chrome.aspx
刪除黑暗執行緒提供了如何使用Chrome的Headless來截取網頁資料的方法
非常值得參考,在這邊做個記錄
謝謝你的文章, 真的獲益良多, 我是搜尋GNN的全圖文rss到這裡的
回覆刪除搞了大半天, 波折重重, 終於在vSphere裡裝好CentOS+OpenVZ, 並成功執行你的full-text-rss, 可是使用的時候就出現這個錯誤... https://ibb.co/e1z6x8
我查過/phantomjs_cache/phantomjs_output.html裡是抓到東西的, 但/tmp/裡就甚麼檔案都沒有
我知道你不會再維護這個範本, 唯有期待你能早日研發好Headless Chrome版本, chrome-remote-interface和chromedriver好像就是關鍵的東西...
這邊有三個錯誤訊息:
刪除Warning: exec(): Unable to fork [/var/www/phantomjs-2.1.1-linux-i686/bin/phantomjs /var/www/phantomjs-2.1.1-linux-i686/bin/plantomjs-exec.js "https://gnn.gamer.com.tw/8/164718.html" "div.GN-lbox3B"] in /var/www/full-text-rss/libraries/conteat-extractor/ConteatExtractor.php on line 163
Warning: file_get_contents(/tmp/phantomjs_cache/phantomjs_output.html): failed to open stream: No such file or directory in /var/www/full-text-rss/libraries/content-extractor/ContentExtractor.php on line 167
Fatal error: Call to a member function save() on a non-object in /var/www/full-text-rss/libraries/content-extractor/ConteatExtractor.php on line 419
最重要的是第一個錯誤訊息,這表示執行phantomjs的時候發生了錯誤
請嘗試在你的Linux console執行錯誤訊息中的指令:
/var/www/phantomjs-2.1.1-linux-i686/bin/phantomjs /var/www/phantomjs-2.1.1-linux-i686/bin/phantomjs-exec.js "https://gnn.gamer.com.tw/8/164718.html" "div.GN-lbox3B"
並檢查執行時發生的錯誤訊息,再根據錯誤訊息來修正你的設定
我自己伺服器上執行起來倒是沒啥問題就是了
root@full-text-rss /# ls
刪除bin dev home media opt proc run selinux sys usr
boot etc lib mnt phantomjs_cache root sbin srv tmp var
root@full-text-rss /# /var/www/phantomjs-2.1.1-linux-i686/bin/phantomjs /var/www/phantomjs-2.1.1-linux-i686/bin/phantomjs-exec.js "https://gnn.gamer.com.tw/8/164718.html" "div.GN-lbox3B"
Load: https://gnn.gamer.com.tw/8/164718.html
沒有任何錯誤... Load: https://gnn.gamer.com.tw/8/164718.html 之後就停住了沒回應
不過OpenVZ裡的debug有點麻煩, 而我對PhantomJS也不熟悉, 現正研究nodejs的rss praser模組
我想你應該在/tmp目錄底下執行這個指令比較好
刪除或是在root帳號的家目錄 /root
而不是在系統的根目錄底下執行這個指令
不行...還是一樣 QQ
刪除cpu和記憶體應該都是足夠的
root@full-text-rss ~# lscpu
Architecture: i686
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 2
Vendor ID: GenuineIntel
CPU family: 6
Model: 60
Stepping: 3
CPU MHz: 3092.838
BogoMIPS: 6185.67
Virtualization: VT-x
Hypervisor vendor: VMware
Virtualization type: full
root@full-text-rss ~# free -m
total used free shared buffers cached
Mem: 2048 110 1937 0 0 42
-/+ buffers/cache: 68 1979
Swap: 2048 0 2048
算了吧..無論如何也得謝謝你的啟發, 我昨天研究了一下, 成功的用Puppeteer擷取到gnn的內文全圖文, 剩下的就看看塞回xml後, tt-rss那邊能否顯示到全圖文
好吧,也可能是OpenVZ沒辦法正常運作就是了
刪除請加油吧!
哇哈哈哈, 我成功了
刪除https://image.ibb.co/jCRsSJ/2018_07_02_013100.png
太厲害了,恭喜你!
刪除