[Chrome extension] 自動抓取 Google+ 相簿中的所有相片連結 New!

[Chrome extension] 自動抓取 Google+ 相簿中的所有相片連結 New!

icon_webstore.png  

不久前曾經用 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 的時候,其實是照著範例一步一步做,

並沒有先思考程式的架構,而是後來才在想這件事…

不過以良好的程式習慣而言,應該還是要先想好架構再來入手會比較好~~

因此就先來想一下,這個程式應該要怎麼寫比較好吧!

 

chrome_extension_extract_image_url_for_google_plus_flow.png    

 

這是我後來程式完成之後,再根據程式作出來的流程圖~

基本上的幾個重點是: 

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_extensions_dev.png    

當我看到 chrome 把我寫的粗淺 plugin 顯示出來的時候,

心裡真是十分的興奮呀,原來寫 plugin 這麼簡單?!

其實 manifest.json 只是個描述檔而已啦,離完成還很遠,

不過還是很高興!~~

 

這邊的一個重點是 background_page,這是每一個 plugin 會在背景執行的 html 檔案~

如同先前流程圖表示的,我們在這邊使用的 background_page 是 background.html 這個檔案~

 

page_action 代表的是這個 plugin 的圖示只有在特定情況下,才會顯示在網址列內…

至於什麼是特定情況?這就要看程式的目的了…

在下面提到 background.html 的時候,會說明如何顯示出 plugin 的圖示~

例如在這個程式中,只有在 Google+ 相簿頁面才會顯示圖示,如下圖:

icon_in_url.png  

 

當按下圖示時,瀏覽器就會執行 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 的發送者接收到了~

這樣,整個程式就完成了~~

下面就是程式執行的結果:

chrome_extension_extract_image_url_final.png  

 

有興趣使用的人,可以到這裡去下載來試試看 🙂

 

 

//
//

(本頁面已被瀏覽過 63 次)

One thought on “[Chrome extension] 自動抓取 Google+ 相簿中的所有相片連結 New!

  1. HI
    這個相當棒
    不過有辦法抓出原始的大圖檔嗎?
    版主回覆:(04/03/2012 03:47:11 PM)
    目前這個 plugin 抓的其實就是原始大圖的圖檔名稱~
    其實做法很簡單,假設圖片網址是 https://…./s640/imagename.jpg,
    那只要把 s640 換成 s0 就可以拿到大圖了~
    這邊的 s 也有可能是 w…
    目前的觀察是這樣子囉~

發表迴響

你的電子郵件位址並不會被公開。