Last Friday

You said

我寫了一個瀏覽器擴展功能,成功抓取了https://podcasts.apple.com/裡的podcast title,但是當我想進一步用fetch api時,卻出現了以下錯誤"TypeError: NetworkError when attempting to fetch resource."。

Copilot said

這個錯誤通常表明瀏覽器在嘗試取用資源時遇到了網絡或安全限制的問題。在瀏覽器擴展中使用 fetch API 時,幾個常見原因和解決辦法如下:

可能的原因

  1. 跨域(CORS)問題 瀏覽器會根據同源策略來限制跨域請求。如果你從擴展或內容腳本中直接發起請求,而目標頁面(例如 Apple Podcasts)並未允許來自該來源的跨域請求,就會產生錯誤。Apple 很可能沒有為非授權的跨域請求配置適當的 CORS 標頭。

  2. 權限設置不足 在瀏覽器擴展中,必須在 manifest.json 中明確聲明需要存取的網域。如果你沒有為 https://podcasts.apple.com/ 添加相應的權限,請求會因權限不足而失敗。

  3. 內容安全策略(CSP)限制 擴展或者目標頁面的 CSP 也可能會限制某些網絡請求。尤其是一些網站會對來自非預期來源的網絡請求進行封鎖。

  4. 環境問題(請求所在的腳本環境) 如果你在內容腳本中發起請求,由於內容腳本受目標頁面的 CSP 或環境影響,也可能導致網絡請求失敗。通常來說,跨域請求更適合放在背景腳本中進行。

解決方案

  1. 更新 manifest.json 添加權限

    確保擴展具備對 https://podcasts.apple.com/ 網域的存取權限。舉例如下:

    json
    {
      "manifest_version": 2,
      "name": "Podcast Fetcher",
      "version": "1.0",
      "permissions": [
        "https://podcasts.apple.com/"
      ],
      "background": {
        "scripts": ["background.js"],
        "persistent": false
      },
      "content_scripts": [
        {
          "matches": ["https://podcasts.apple.com/*"],
          "js": ["content.js"]
        }
      ]
    }
    
  2. 將請求移至背景腳本 背景腳本的環境通常較為寬鬆,能夠避免內容腳本可能遇到的 CSP 限制。有時候將 fetch 請求放在背景腳本中,並通過消息傳遞機制與內容腳本通信,可以解決跨域請求的問題。

    javascript
    // background.js
    fetch("https://podcasts.apple.com/...")
      .then(response => {
        if (!response.ok) {
          throw new Error("Network response was not ok");
        }
        return response.text();
      })
      .then(data => {
        console.log("Fetched data:", data);
        // 可以進一步處理或通過消息傳遞給內容腳本
      })
      .catch(error => {
        console.error("Fetch error:", error);
      });
    
  3. 調整 fetch 的配置選項 有時候可以嘗試修改 fetch 的配置,比如設置 mode"cors"(這通常是默認值),或者在極端情況下設置為 "no-cors"(但這會導致回傳的 response 變為不透明,無法讀取內容),例如:

    javascript
    fetch("https://podcasts.apple.com/...", { mode: "cors" })
      .then(response => response.text())
      .then(data => console.log(data))
      .catch(error => console.error("Fetch error:", error));
    
  4. 檢查 Network 面板的詳細信息 使用開發者工具的 Network 面板,觀察請求的詳細情況:請求是否被重定向、是否返回了 HTTP 錯誤代碼,或是否有 CORS 錯誤信息。這可以幫助你更具體地定位問題所在。

流程圖解釋

下面是一個簡單的 ASCII 流程圖,展示從擴展發起 fetch 請求到獲得響應的過程,以及可能遭遇錯誤的環節:

   +---------------------+
   |  擴展背景腳本       |
   | (發起 fetch 請求)   |
   +----------+----------+
              |
              | HTTP Request (可能受 CORS 或權限限制)
              V
   +---------------------+
   |  Apple Podcasts     |
   |  Server             |
   +----------+----------+
              |
              | HTTP Response 或 Error
              V
   +---------------------+
   |  背景腳本處理       |
   |  (成功或拋出錯誤)   |
   +---------------------+

其他注意事項

  • Apple 的額外保護措施 有些網站可能對請求做更多的驗證(例如 Cookie、User-Agent 等),如果你發現權限和 CORS 都已正確配置,請檢查是否需要添加特定的 headers。

  • 代理服務 如果上述方法都無法解決問題,可以考慮使用代理服務來繞過跨域限制,不過這需要自己搭建或使用第三方服務。

  • 錯誤捕獲與日誌 在調試網絡錯誤時,記得盡量捕獲詳細的錯誤信息,這有助於更精準地定位問題。將捕獲的錯誤信息記錄到開發者工具中,也能為日後解決類似問題提供參考。

這些方案應該能幫助你釐清原因並解決 TypeError: NetworkError when attempting to fetch resource 的問題。如果你有更詳細的 fetch 代碼或者遇到額外的錯誤信息,我們可以進一步深入討論。這裡還有一些額外的思考方向,例如了解 CORS 預檢請求(preflight)發生的過程,這可能有助於你理解服務端要求與客戶端限制之間的互動機制。希望對你有所幫助,讓你的擴展功能能更順利地運作!

You said

我先前可能沒將問題描述清楚,我再詳細說明一下。 (1)我用瀏覽器連上 https://podcasts.apple.com/tw/podcast/%E4%B8%8D%E8%89%AF%E5%A4%A7%E5%8F%94-naughty-uncles/id1701822049 (2)background.js如下 browser.browserAction.onClicked.addListener((tab) => { browser.tabs.executeScript(tab.id, { file: "main.js" }); }); (3) main.js如下,可以成功執行 (function () { const titleElement = document.querySelector('h1'); if (titleElement) { alert(titleElement.innerText); } else { alert('Title not found.'); } })(); (4)但是繼續在main.js加入fetch api時則會出現"TypeError: NetworkError when attempting to fetch resource."。 我的fetch url是https://cors-aywhere.herokuapp.com/https://itunes.apple.com/search?term=不良大叔 Naughty Uncles&country=US

Copilot said

下面有幾個方向可以檢查和調整,解決你在內容腳本中使用 fetch API 時出現網絡錯誤問題:

1. 檢查代理 URL 拼寫

你提供的 fetch URL 為

https://cors-aywhere.herokuapp.com/https://itunes.apple.com/search?term=不良大叔 Naughty Uncles&country=US

仔細觀察後可以發現: "cors-aywhere" 可能是打錯了,正確應該是 "cors-anywhere"(注意其中的 n 字母)。錯誤的 URL 會導致無法找到資源、解析失敗,從而產生類似的網絡錯誤。

2. 對 URL 參數進行正確編碼

URL 中包含空格和中文,如果直接寫入會破壞 URL 格式。你可以使用 JavaScript 提供的 encodeURIComponent() 函數來處理查詢字串。例如:

javascript
(function () {
  // 將查詢關鍵字做編碼
  const term = encodeURIComponent("不良大叔 Naughty Uncles");
  // 拼接正確的 URL(注意 cors-anywhere 的拼寫)
  const url = `https://cors-anywhere.herokuapp.com/https://itunes.apple.com/search?term=${term}&country=US`;
  
  fetch(url)
    .then(response => {
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }
      return response.json();
    })
    .then(data => {
      console.log("Fetched data:", data);
    })
    .catch(error => {
      console.error("Fetch error:", error);
    });
})();

這樣可以避免因 URL 格式錯誤而引起的問題。

3. 擴展權限與腳本執行環境

在瀏覽器擴展中,如果你希望從內容腳本發送跨域請求,除了能正確構造 URL 外,還需要注意以下幾點:

  • 權限設置: 確保你的 manifest.json 中包含對目標資源的權限。例如,如果你的請求最終目標是 https://itunes.apple.com/(或代理服務所在的域),建議添加相應的權限:

    json
    {
      "manifest_version": 2,
      "name": "Podcast Fetcher",
      "version": "1.0",
      "permissions": [
        "https://itunes.apple.com/",
        "https://cors-anywhere.herokuapp.com/"
      ],
      "background": {
        "scripts": ["background.js"],
        "persistent": false
      },
      "content_scripts": [
        {
          "matches": ["https://podcasts.apple.com/*"],
          "js": ["main.js"]
        }
      ]
    }
    
  • CSP 限制: 某些網頁自身有嚴格的內容安全政策(CSP),該政策可能會影響內容腳本中的外部請求。在這種情況下,可以考慮將 fetch 請求移到背景腳本中,利用背景腳本較寬鬆的權限通過消息傳遞返回結果給內容腳本。

    例如,在背景腳本中進行 fetch 請求:

    javascript
    // background.js
    browser.runtime.onMessage.addListener((request, sender, sendResponse) => {
      if (request.action === "fetchData") {
        const term = encodeURIComponent("不良大叔 Naughty Uncles");
        const url = `https://cors-anywhere.herokuapp.com/https://itunes.apple.com/search?term=${term}&country=US`;
        
        fetch(url)
          .then(response => response.json())
          .then(data => sendResponse({success: true, data: data}))
          .catch(error => sendResponse({success: false, error: error.message}));
        
        // 返回 true 表示異步回應
        return true;
      }
    });
    

    然後在內容腳本 中調用:

    javascript
    // main.js
    browser.runtime.sendMessage({ action: "fetchData" }, response => {
      if (response.success) {
        console.log("Fetched data:", response.data);
      } else {
        console.error("Fetch error:", response.error);
      }
    });
    

4. 驗證代理服務狀況

有時候使用的 CORS 代理也可能不穩定或存在其他限制,檢查下面幾點也很重要:

  • 使用 官方頁面,看看是否需要先點擊按鈕來開啟臨時訪問權限,否則請求可能被拒絕。

  • 如果代理服務存在限制(如流量或頻率限制),考慮使用其他 CORS 代理或自己搭建一個解決方案。

小結

你可以按照下列步驟逐一確認解決:

  1. 核對 URL 拼寫: 確保 https://cors-anywhere.herokuapp.com/ 拼寫正確。

  2. 編碼 URL 參數: 使用 encodeURIComponent() 處理包含空格和中文的查詢關鍵字。

  3. 檢查 manifest 權限:itunes.apple.com 和代理服務域名添加適當權限。

  4. 考慮腳本環境: 如果內容腳本受 CSP 限制,嘗試將 fetch 請求放在背景腳本中。

  5. 檢查代理服務: 確認代理服務狀態及是否需要開啟臨時授權。

這些措施應該能幫助你解決 "TypeError: NetworkError when attempting to fetch resource." 的問題。如果還有疑問或需要進一步調試的細節,我們可以繼續深入探討。