.

iT邦幫忙

1

試著動態抓取資料夾內容並在網頁上顯示(feat.ChatGPT)

api
  • 分享至 

  • xImage
  •  

先說結論:2025年1月26日,到現在只有靜態抓取成功,繼續努力。

一直有想自己從頭架個網站放自己的電繪作品(想當初也是推動我跨領域念資管所的一大原因),最近終於覺得好像有成功的可能性,故嘗試之。結果試了好幾天都沒辦法。

先在這記下過程以免重蹈覆轍,並希望和我一樣想從零串接前後端內容的人沒成功也別傷心,這裡有個人和你一起哭(喂
底下是花式失敗過程,歡迎指教qwqqq

之前補修大學部的課,和組員以firebase為後端建了一個場地預約系統;修碩士班的課則因為用了bigquery而接觸到了google console,發現「咦,有google drive api欸」——腦海浮現了一個念頭:我想做個展示作品集的網站,如果作品放在google drive裡某個資料夾,並運用神奇的方法讓網站讀取放進資料夾的圖片,這樣不是達成目標了嗎!

最初版本:手動上傳

為了增加信心,我先嘗試了以下html,讓手動上傳的圖片可以以正方形型態顯示(說到這個,IG的排版似乎變成長方形,果然只要不是自己的網站就要承受環境變化的風險…)。
測試是否可運作的方法是vscode裡的live server,不出所料,成功。

//最原始、上傳圖片的版本 
 
<!DOCTYPE html>
<html lang="zh-TW">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>圖片上傳展示</title>
  <style>
    body {
      font-family: Arial, sans-serif;
      margin: 0;
      padding: 20px;
      background-color: #f9f9f9;
    }
    #upload-btn {
      margin-bottom: 20px;
    }
    #gallery {
      display: grid;
      grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
      gap: 10px;
    }
    .image-item {
      position: relative;
      width: 100%;
      padding-top: 100%; /* 1:1 ratio */
      overflow: hidden;
      border: 1px solid #ddd;
      border-radius: 8px;
      background-color: #fff;
    }
    .image-item img {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      object-fit: cover;
    }
  </style>
</head>
<body>
  <h1>圖片上傳展示</h1>
  <input id="upload-btn" type="file" accept="image/*" multiple>
  <div id="gallery"></div>

  <script>
    const uploadBtn = document.getElementById('upload-btn');
    const gallery = document.getElementById('gallery');

    uploadBtn.addEventListener('change', (event) => {
      const files = event.target.files;
      Array.from(files).forEach(file => {
        const reader = new FileReader();
        reader.onload = function(e) {
          const imgUrl = e.target.result;
          const imageItem = document.createElement('div');
          imageItem.classList.add('image-item');
          imageItem.innerHTML = `<img src="${imgUrl}" alt="Uploaded Image">`;
          gallery.appendChild(imageItem);
        };
        reader.readAsDataURL(file);
      });
    });
  </script>
</body>
</html>

鬼打牆:Google Drive API來了

接下來進到了實在不想重新面對的時刻。F12的console裡的一堆錯誤訊息,實在頭很痛,這步我狂丟ChatGPT逼他解釋。這裡貼幾個給大家瞅瞅

錯誤 400:invalid_request
要求詳情: flowName=GeneralOAuthFlow

這通常是 OAuth 2.0 驗證或授權請求出問題了。
我一開始有在google console開project,並設定了 OAuth 2.0 的ID,也開了api key,但後來發現如果資料夾(以及裡面的檔案)設成公開,前者可以不用。

Unchecked runtime.lastError: The message port closed before a response was received.

據說這是個常見的錯誤訊息,特別是當開發Chrome擴充功能或使用瀏覽器api時會遇到。
奇怪的是,這裡我是使用有學校後綴的google帳號開啟api,用一般的google帳號開啟測試頁面、用學校後綴的測試就不會了。
後來發現,這個錯誤好像不會影響應用程式的主要功能,後來就先放著不管。

Requests from referer http://localhost are blocked

這裡我把測試頁面的網址(就是我的localhost和port號)新增在api key設定允許的HTTP引用網址(HTTP referrer)裡。

[Report Only] Refused to load the script 'https://www.gstatic.com/_/mss/boq-identity/_/js/k=boq-identity.IdpIFrameHttp.zh_TW.ERoUSIbVwj8.es5.O/am=BgM/d=1/rs=AOaEmlEePYgwwGOjUmaOblkA-zkOD6LYAA/m=base' because it violates the following Content Security Policy directive: "script-src 'unsafe-inline' 'unsafe-eval' blob: data:". Note that 'script-src-elem' was not explicitly set, so 'script-src' is used as a fallback.

上面似乎是說因為內嵌script違反了Content Security Policy (CSP) settings,這裡我試著把html和js拆開,但還是失敗,還提到說不能用iframe云云(當初有個錯誤訊息的解方是用iframe或img標籤的Google Drive url,這該死又甜美的矛盾)。

試了好幾天,總算顯示出圖的名稱,但圖片本圖就是錯誤方塊、而且會很快閃現一個正方形的輪廓又消失。

變著法子盤問了ChatGPT,發現似乎跟 CORS(Cross-Origin Resource Sharing,跨來源資源共享)問題有關。
圖片url可能會受到CORS限制,尤其是在從本地或不同域名的網站加載圖片時。所以儘管我可以直接在瀏覽器中打開圖片,但用html載入圖片時,這些限制可能會導致無法顯示。

試著在html加了CORS標頭解決問題,但GPT說「如果只是單純從前端加載 Google Drive 的圖片,這通常很難繞過 CORS 限制」,我不知道有沒有道理,反正最後圖還是載入失敗。

最後的程式碼是這樣的
.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    

    <title>Google Drive Dynamic Image Gallery</title>
    <style>
        #image-gallery {
            display: flex;
            flex-wrap: wrap;
            gap: 10px;
        }
        img {
            width: 150px;
            height: 150px;
            object-fit: cover;
            border: 1px solid #ccc;
            border-radius: 5px;
        }
    </style>
</head>
<body>
    <h1>Dynamic Image Gallery</h1>
    <div id="image-gallery"></div>
    <img src="https://drive.google.com/uc?id=作品" alt="Test Image">

    <iframe src="https://drive.google.com/file/d/作品/view?usp=sharing" width="500" height="500"></iframe>
    <script>
        // 您的 Google Drive 資料夾 ID
        const folderId = "QAQ"; // 替換為您的資料夾 ID
        const apiKey = "QAQQ";     // 替換為您的 API Key
        const gallery = document.getElementById("image-gallery");



        async function fetchImagesFromDrive() {
            try {
                const response = await fetch(
                    `https://www.googleapis.com/drive/v3/files?q='${folderId}'+in+parents+and+mimeType+contains+'image/'&key=${apiKey}&fields=files(id,name,mimeType)`
                );

                if (!response.ok) {
                    throw new Error(`HTTP error! status: ${response.status}`);
                }

                const data = await response.json();

                if (!data.files || data.files.length === 0) {
                    console.error("No image files found in the folder.");
                    return;
                }

                data.files.forEach(file => {
                    const imgUrl = `https://drive.google.com/uc?id=${file.id}`;
                    const img = document.createElement("img");
                    img.src = imgUrl;
                    img.alt = file.name;

                    // 處理載入失敗
                    img.onerror = () => {
                        img.alt = "圖片載入失敗";
                        console.error(`Failed to load image: ${imgUrl}`);
                    };

                    const link = document.createElement("a");
                    link.href = `https://drive.google.com/file/d/${file.id}/view?usp=sharing`;
                    link.target = "_blank";
                    link.appendChild(img);

                    setTimeout(() => {
                        gallery.appendChild(link);
                    }, 500); // 延遲 500 毫秒

                    gallery.appendChild(link);
                });
            } catch (error) {
                console.error("Error fetching images:", error);
            }
        }     

        // 呼叫函式
        fetchImagesFromDrive();
    </script>
</body>
</html>

.js

// 初始化 Google API 客戶端
function initClient() {
    gapi.load('client', () => {
        gapi.client.init({
            apiKey: 'QAQ', // 替換為你的 API 金鑰
        }).then(() => {
            console.log("Google API client initialized.");
            listFiles();
        }).catch((error) => {
            console.error("Error initializing Google API client:", error);
        });
    });
}


function listFilesInFolder() {
    const folderId = 'QAQQ'; // 替換為你的資料夾 ID
    const folder = DriveApp.getFolderById(folderId);
    const files = folder.getFiles();
    
    while (files.hasNext()) {
      const file = files.next();
      Logger.log(`Name: ${file.getName()}, MIME Type: ${file.getMimeType()}`);
    }
}
  

// 列出指定資料夾中的檔案
function listFiles() {
    const folderId = 'QAQQ'; // 替換為你的公開資料夾 ID
    gapi.client.request({
        path: `https://www.googleapis.com/drive/v3/files`,
        method: 'GET',
        params: {
            q: `'${folderId}' in parents and mimeType contains 'image/'`,
            fields: 'files(id, name, mimeType, webViewLink)',
            key: 'QAQ', // 替換為你的 API 金鑰
        },
    }).then((response) => {
        const files = response.result.files;
        const gallery = document.getElementById('image-gallery');
        if (files && files.length > 0) {
            files.forEach((file) => {
                const img = document.createElement('img');
                img.src = `https://drive.google.com/uc?id=${file.id}`;
                img.alt = file.name;
                img.style.width = "150px";
                img.style.height = "150px";
                img.style.objectFit = "cover";

                const link = document.createElement('a');
                link.href = file.webViewLink;
                link.target = "_blank";
                link.appendChild(img);

                gallery.appendChild(link);
            });
        } else {
            gallery.innerHTML = '<p>No images found or permission denied.</p>';
        }
    }).catch((error) => {
        console.error("Error listing files: ", error);
    });
}

// 啟動 Google API 客戶端
gapi.load('client', initClient);

之後初步研沒辦法從google drive api討到便宜,決定先從讀取本機的圖片做起,這個除了因為路徑最前端多了一個「\」讓我抓狂了快半小時,倒是成功了。但想做到不輸入每張圖片的路徑、讓腳本自己抓取圖片,似乎還有很長一段路要走。

宮崎駿桑說過「越重要的事情就越麻煩」,現在只能先這樣安慰自己了(抱頭


.
圖片
  直播研討會

尚未有邦友留言

立即登入留言