[Chrome extension] 自動抓取 Google+ 相簿中的所有相片連結 New!
不久前曾經用 AutoIt 寫了一個「自動抓取 Google+ 相簿中的所有相片連結」的小程式,
只是遇到了一些問題無法解決,尤其是移動滑鼠去點到人臉時,
整個程式就會停擺,非常的糟糕~~
因此,一直在想一個解決的方法…
想了想,如果可以看到網頁的原始碼的話,
就可能可以把相片的網址直接抓出來,而不用用什麼滑鼠按右鍵的笨方法了~
之前其實就想學寫 Chrome 的 plugin,也就是擴充功能,
不過一直沒有去學,藉著這個機會就入門囉,
而且也發現,其實還蠻容易寫的,只需要會 html + javascript + css 就可以寫囉,
比我原本想的要容易的多,真不愧是 Google,把東西做的這麼方便~~
(我一直很喜歡 Google,這就不用說了 :P)
就來個簡單的教學吧,順便記錄一下這個 plugin 的寫作過程,免得連自己都忘了 😛
Step 1: 研讀 Google Chrome Extension how-to
是的,請到 Google Chrome Extension 的網站上,好好研究一下文件,
基本上只要看過 Tutorial: Getting Started,就會發現,
原來只要建立一個目錄,裡面有個 manifest.json 檔案,
就是一個基礎的 Chrome extension 架構了!
當然在目錄裡面,可以加入必要的 javascript, css, html 檔案,
因此就跟寫網頁差不多!
當然最好還是把上面的文件全部研讀過一遍,會比較有完整的概念~
Step 2: 思考程式架構
基本上我寫這 plugin 的時候,其實是照著範例一步一步做,
並沒有先思考程式的架構,而是後來才在想這件事…
不過以良好的程式習慣而言,應該還是要先想好架構再來入手會比較好~~
因此就先來想一下,這個程式應該要怎麼寫比較好吧!
這是我後來程式完成之後,再根據程式作出來的流程圖~
基本上的幾個重點是:
1. 每個 chrome plugin 都可以有一個背景頁面。
我們就在這頁面上使用 javascript 來檢查現在視窗內的網頁,是不是 Google+ 相簿。
如果是的話,才顯示出圖示給使用者點選,
避免使用者在非 Google+ 相簿的地方也點到這個 plugin…
同時,我們也會在網頁中注入一個 inject_page.js
(在 chrome extension 的說明文件裡是叫做 content script),
因為只有 content script 才會跟視窗內的網頁是處在同一個 DOM 空間內,
因此可以去處理視窗內網頁的內容。
2. 當使用者按下圖示時,會顯示 popup.html。
我們在 popup.html 裡面使用 javascript 送一個訊息給 inject_page.js,
當 inject_page.js 收到訊息後,就會去解析視窗內網頁的內容,
把裡面所有的相片連結都抓出來,再把資料回傳給 popup.html,
因此 popup.html 就可以把它顯示出來囉~
Step 3: 準備 manifest.json 檔案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
{ "name": "Extract Image URLs for Google+(TM) Gallery", "version": "1.1", "description": "Extract image URLs for Google+(TM) gallery", "icons": { "16": "icon16.png", "48": "icon48.png", "128": "icon128.png" }, "background_page": "background.html", "options_page": "options.html", "page_action": { "default_icon": "icon128.png", "default_popup": "popup.html" }, "permissions": [ "https://plus.google.com/*", "tabs" ] } |
manifest.json 裡面,主要定義的是這個 plugin 的一些屬性,
像是name, version, description 等等~
其實只要先準備好這個檔案,就可以開啟 chrome 的開發者模式,
來載入這一個 plugin 了~
可以直接在網址列上打 chrome://extensions/ 顯示目前已安裝的 plugin,
把開發人員模式打開之後,選擇「載入未封裝擴充功能」,
然後選擇包含 manifest.json 的那個目錄,就可以在自己的 chrome 上先行測試了!
當我看到 chrome 把我寫的粗淺 plugin 顯示出來的時候,
心裡真是十分的興奮呀,原來寫 plugin 這麼簡單?!
其實 manifest.json 只是個描述檔而已啦,離完成還很遠,
不過還是很高興!~~
這邊的一個重點是 background_page,這是每一個 plugin 會在背景執行的 html 檔案~
如同先前流程圖表示的,我們在這邊使用的 background_page 是 background.html 這個檔案~
page_action 代表的是這個 plugin 的圖示只有在特定情況下,才會顯示在網址列內…
至於什麼是特定情況?這就要看程式的目的了…
在下面提到 background.html 的時候,會說明如何顯示出 plugin 的圖示~
例如在這個程式中,只有在 Google+ 相簿頁面才會顯示圖示,如下圖:
當按下圖示時,瀏覽器就會執行 page_action 裡面的 default_popup 指向的網頁,
在此例中就是會去顯示 popup.html~
另外,如果想要圖示在每個頁面都顯示的話,就應該改用 browser_action,
這樣圖示就會永遠顯示在工具列的地方,像平常常見的 IE Tab Multi, Springpad 等等就是此類。
Step 4: 準備 popup.html
使用者按下圖示後,chrome 就會用一個像對話框的物件,將 popup.html 的內容顯示出來,
因此只要讓 popup.html 顯示出相片連結,就達到我們這個 plugin 的目的了!
下面是 popup.html 的原始碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
<html> <head> <link rel="stylesheet" type="text/css" href="style.css"> <script type="text/javascript" src="common.js"></script> <script> chrome.tabs.getSelected(null, function(tab) { // Display a waiting message document.getElementById("divResult").innerHTML = "Please wait a moment while loading all images..."; // Send request to active tab chrome.tabs.sendRequest( tab.id, { HTMLTemplate: jsGetOptionHTMLTemplate() }, function(result) { // Display output document.getElementById("divResult").innerHTML = "<textarea id='textResult'>" + result + "</textarea>"; } ); } ); </script> </head> <body> <div id="divResult"></div> </body> </html> |
首先先用 chrome.tabs.getSelected() 這個 API 取得目前的分頁 tab id,
在 chrome extension API 裡面,有許多是需要用到 tab id 的,
(不過後來發現在很多地方用 null 也是可以表示目前的 tab id)
取得 tab id 後,用 chrome.tabs.sendRequest() 這個 API,
可以送一個訊息到該分頁的背景 content script 裡面,在此例裡面就是 inject_page.js~
回傳回來的資料會放在 result 裡面,因此我們就可以直接將它顯示在一個 <div> 裡面~
不過,這樣還沒完喔~
背景的 background.html 和 content script 都還沒寫呢!
送過去的訊息還沒有人會接喔!
Step 5: 準備 background.html
background.html 是會一直在背景執行的網頁,
根據 Google 文件的說明,要注意不要讓它太耗效能!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
<html> <head> <script> function checkForValidUrl(tabId, changeInfo, tab) { // Only check when it is in complete state and within google+ albums... if (changeInfo.status == "complete") { if (tab.url.match(/plus.google.com/.+/photos$/) != null || tab.url.match(/plus.google.com/.+/photos/.+/albums/.+/) != null || tab.url.match(/plus.google.com/.+/photos/fromphone/) != null || tab.url.match(/plus.google.com/.+/photos/of/.+/) != null) { // Show the page action chrome.pageAction.show(tabId); // Inject inject.js into page chrome.tabs.executeScript(null, { file: "inject_page.js"} ); } } }; // Listen for any changes to the URL of any tab. chrome.tabs.onUpdated.addListener(checkForValidUrl); </script> </head> </html> |
首先用 chrome.tabs.onUpdated.addListener() 這個 API 註冊一個 callback,
只要分頁有什麼改變就通知我們,這樣我們就可以得知網址列的變化了~
當網址變化時,我們會用 checkForValidUrl() 這個自己寫的函式,
檢查網址是不是在 Google+ 相簿的網頁,
如果是的話,才用 chrome.pageAction.show() 這個 API 顯示圖示出來,
並且用 chrome.tabs.executeScript() 這個 API 將 inect_page.js 注入到網頁中~
只有被注入的 javascript 才有能力看到被注入網頁的內容喔!
不過,我用 Chrome 的開發者工具時,似乎看不到網頁有載入 inect_page.js…
不曉得是不是 chrome 直接把 js 的內容加到該網頁的 DOM 裡面,所以看不到…
Step 6: 準備 inject_page.js
這部分因為稍長一些,因此分做幾個部分講解。
// Check if the request listener is already added if (typeof chrome.extension.onRequest.myListenerAdded == "undefined") { chrome.extension.onRequest.myListenerAdded = true; // Add request listener chrome.extension.onRequest.addListener(function(request, sender, sendResponse) { g_nNumImages = -1; g_request = request; g_sendResponse = sendResponse; jsGetAllImages(); }); }
在程式的一開始,我們用 chrome.extension.onRequest.addListener() 這個 API,
註冊一個 callback,因此當有人送訊息到 plugin 的背景或 content script 的時候,我們可以收到訊息!
這邊我還額外加了一個 myListenerAdded 的變數,避免每跑一次程式就多加一次 listener…
其實理論上好像可以不用做這個檢查,只是我測試的結果,
每次我重新載入一個新的 Google+ 相簿網頁後,listener 好像就多了一個,
導致怪異的行為,因此只好先用這個方式解決…
當 popup.html 用 chrome.tabs.sendRequest() 送訊息過來的時候,
就會呼叫到 listener callback,進而呼叫自已定義的 jsGetAllImages() 函式~
function jsGetAllImages() { var sResult = ""; var objTargetDiv = null; // Get all divs var arrayDivs = document.getElementsByTagName("div"); if (arrayDivs != null) { for (var i = 0; i < arrayDivs.length; i++) { // Find the div that contains the images if (arrayDivs[i].className == "Sl") { objTargetDiv = arrayDivs[i]; break; } } } if (objTargetDiv == null) { objTargetDiv = document.getElementById("contentPane"); } if (objTargetDiv != null) { // Get all images within the div var arrayImages = objTargetDiv.getElementsByTagName("img"); if (arrayImages != null) { // If the number of images is not the same as previous test, scroll again if (g_nNumImages != arrayImages.length) { g_nNumImages = arrayImages.length; window.scrollBy(0, 10000); setTimeout("jsGetAllImages();", 500); return; } // Only goes to here when there are no more images... var regexImgURL = /(.+.googleusercontent.com/.+/.+/.+/.+/)(w.+)(/.+)/; for (var i = 0; i < arrayImages.length; i++) { // Ensure the image URL matches specific pattern if (arrayImages[i].src.match(regexImgURL) != null) { // Generate output sResult += g_request.HTMLTemplate.replace("%URL%", arrayImages[i].src.replace(regexImgURL, "$1w0$3")); } } } } g_sendResponse(sResult); }
上面這段程式就是從網頁中抓取出相片連結的程式…
這段基本上就幾乎沒有用到 chrome 的 API 了,大部分都是在解析網頁內容~
簡單的說,先取得正確包含相片的 <div>,然後取出裡面所有的 <img>…
不過有個問題,因為 Google+ 相簿裡面如果有太多的相片時,它不會一次全部顯示,
而是要等到使用者捲動到下面的時候,才會再顯示一部分相片出來…
因此,我們就在程式裡模擬這個動作,用 window.scrollBy() 先一口氣往下捲動,
然後等個半秒鐘,看看會不會 <img> 的個數就變多了?
如果有變多的話,表示有新的相片被載入了,
那就持續往下捲動的動作,直到 <img> 的個數不再變動為止~~
這可能不是很聰明的作法,不過至少目前是有解決問題啦~
最後,只要用 listener 傳進來的 sendResponse (是個函式),
將結果傳回去,就可以讓 popup.html 的發送者接收到了~
這樣,整個程式就完成了~~
下面就是程式執行的結果:
有興趣使用的人,可以到這裡去下載來試試看 🙂
//
//
One thought on “[Chrome extension] 自動抓取 Google+ 相簿中的所有相片連結 New!”
HI
這個相當棒
不過有辦法抓出原始的大圖檔嗎?
版主回覆:(04/03/2012 03:47:11 PM)
目前這個 plugin 抓的其實就是原始大圖的圖檔名稱~
其實做法很簡單,假設圖片網址是 https://…./s640/imagename.jpg,
那只要把 s640 換成 s0 就可以拿到大圖了~
這邊的 s 也有可能是 w…
目前的觀察是這樣子囉~