分類: Uncategorized

  • 解決在 serverless 運作瀏覽器時 spawn ETXTBSY 的問題

    最近在嘗試用 RPA 來取代例行的行政工作。

    我用的架構是 Vercel + next.js,自動化網頁腳本 puppeteer-core 無頭瀏覽器 @sparticuz/chromium-min

    但實務上遇到一個問題是,如果一個請求內併發多個實例。就會發生spawn ETXTBSY (Text File Busy)的錯誤。

    單一個實例工作流程會是

    • 下載:下載瀏覽器的壓縮檔案
    • 解壓縮:解壓縮寫入到暫存目錄 /tmp/chromium
    • 執行:puppeteer 啟動瀏覽器

    @sparticuz/chromium 套件的 executablePath() 會檢查目錄底下是否已經有瀏覽器,若有的話就會直接進入到 3 執行的階段。

    當有多個實例並行時,第一個實例只執行到 2 的階段,正在寫入檔案中。由於一個 chromium-min 也有數百 MB,而寫入的過程是漸進式的,檔案會被先創建再持續寫入個幾秒鐘瀏覽器才算真的完成。

    同時第二實例發現當前目錄已經有瀏覽器了(但其實第一個實例的寫入尚未完成)就拿去執行,這時就會發生錯誤,因為檔案正處於被寫入的忙碌狀態。

    解決方案:

    不管有多少個實例只要其中一個開始下載,其他實例也就共用相同的下載動作(只有一次)。

    當下載動作完成時,所有的實例都會拿到相同的路徑。就不會發生其他實例去執行到還沒壓縮完的檔案。

    let cachedExecutablePath: string | null = null;
    let downloadPromise: Promise<string> | null = null;
    
    async function getChromiumPath(): Promise<string> {
      // Return cached path if available
      if (cachedExecutablePath) return cachedExecutablePath;
    
      // Prevent concurrent downloads by reusing the same promise
      if (!downloadPromise) {
        const chromium = (await import("@sparticuz/chromium-min")).default;
        downloadPromise = chromium
          .executablePath(
    "https://github.com/Sparticuz/chromium/releases/download/v129.0.0/chromium-v129.0.0-pack.tar"
          )
          .then((path) => {
            cachedExecutablePath = path;
            console.log("Chromium path resolved:", path);
            return path;
          })
          .catch((error) => {
            console.error("Failed to get Chromium path:", error);
            downloadPromise = null; // Reset on error to allow retry
            throw error;
          });
      }
    
      return downloadPromise;
    }
  • 極簡投資 應用程式隱私權政策

    本隱私權政策說明「極簡投資」App (下稱「本 App」或「我們」) 如何處理使用者資訊。本 App 致力於提供簡單、快速的股價通知服務,並嚴格保護您的隱私。

    1. 資料收集及不收集的個人資訊

    由於本 App 採取極簡主義設計,不提供使用者註冊、登入或收費功能:

    • 我們不收集個人身份識別資訊:我們不會收集您的姓名、電子郵件地址、電話號碼、性別或任何其他可直接識別您身份的資料。
    • 本地設定資訊:您的 App 內所有設定、股價追蹤清單以及通知參數,皆僅儲存於您的裝置本地,不會上傳到我們的伺服器。

    2. 關於推播通知 (Push Notifications)

    為實現股價波動通知的核心功能,我們需要處理以下資訊:

    • 裝置代碼 (Device Token):為了發送即時警報,我們必須使用 Apple (APNs) 或 Google (FCM) 提供的匿名裝置代碼來識別您的裝置。
    • 使用目的:此代碼的唯一目的是將您設定的股價警報即時推播到您的裝置上。
    • 資料儲存:此代碼僅用於推播目的,並安全地儲存於我們合作的推播服務供應商伺服器上。當您解除安裝本 App 時,此代碼將失效。

    3. 資料分享與資訊來源

    • 資料分享:我們不會向任何第三方出售、出租或交易您的任何資訊。所有資料揭露僅限於提供服務的必要範圍(例如:推播服務供應商)。
    • 股價資訊來源:本 App 提供的所有股價及金融資訊,均來自於 Google Finance 或其他公開的金融資料來源。我們不對第三方資料來源的準確性、完整性或隱私權政策負責。

    4. 服務提供者與數據分析

    我們可能會使用匿名、彙總的數據分析服務來追蹤 App 的性能和崩潰報告,以改進使用者體驗。這些數據無法用於識別任何個人使用者。

    5. 聯繫我們

    如果您對本隱私權政策有任何疑問或疑慮,請透過以下方式聯繫開發者:

  • ✅ 已解決。Enroll Apple Developer Program 付款問題

    要上架 App 到蘋果的應用程式商店之前,要先付費 Apple Developer Program,一年大約台幣三千多塊錢。

    但 Reddit 上許多人討論,都有遇到無法順利 Enroll 的狀況,付費後遲遲沒有扣款,申請流程就石沉大海。

    我自己遇到的狀況是信用卡更換了許多張,嘗試付款許多次,網頁上總顯示 “無法授權你的付款交易”

    解決方式

    1️⃣ 用 iPhone 下載 “App Developer” 的 App,從這 App 中去付費,而不是透過網頁。

    2️⃣ 如果你跟我一樣,App 中 Enroll 的按鈕是 disable,無法點擊。按鈕下方也有網址連結,但是我測試還是一樣會遇到無法付款的問題。

    這時要 https://developer.apple.com/contact/ 寫信給蘋果請官方協助解決。

    後續客服很快幫我處理掉,App 中 Enroll 的按鈕就能點擊,接續些身份資料的驗證(身分證 or 護照)與付費流程。

    3️⃣ 後續我就接著收到訂單與統一發票的通知信件。大約在三天後,不知為何又再次收到通知信,要求我提供身分證件,只好再進到指定的網址再上傳一次證件

    4️⃣ 證件上傳後,就順利通過申請囉。未來就能上架應用程式到蘋果的 app store 上。

    2025/10/24 我趁著連假空閒開始提交資料,當天就完成了步驟 1️⃣ 2️⃣,2025/10/27 當天上午再收到重新提供身份文件的通知。資料提交後當天下午就順利申請到了。總共用了 4 天的時間。

    官方是用這信箱回覆 [email protected]。整體回覆都還算時即時。若你也打算申請 Developer Program 希望這經驗可以幫得上忙!

    P.S. 這篇分享是用個人身分(individual)去申請。如果是公司(organization)身份,審核上可能會遇到更多問題,未來若有成功申請再與大家分享。