<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:media="http://search.yahoo.com/mrss/">
  <channel>
    <title>Google Sheet API｜林鼎淵 Dean Lin</title>
    <link>https://deanlin.net/tags/google-sheet-api/</link>
    <image><url>https://deanlin.net/images/author_hu_aa256fc934e9a932.jpg</url><title>Google Sheet API｜林鼎淵 Dean Lin</title><link>https://deanlin.net/tags/google-sheet-api/</link><width>144</width><height>144</height></image>
    <description>林鼎淵（Dean Lin）的部落格：生成式 AI 企業內訓講師、外商資安公司軟體專家、7 本書作者。400 多篇文章用白話分享生成式 AI 實戰、Claude Code 與 Vibe Coding 教學、團隊合作與職場成長。</description>
    <generator>Hugo</generator>
    <language>zh-tw</language>
    <copyright>© 2020–2026 林鼎淵 Dean Lin</copyright>
    <lastBuildDate>Tue, 14 Jan 2025 22:45:48 +0800</lastBuildDate>
    <atom:link href="https://deanlin.net/tags/google-sheet-api/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>[n8n 教學]在 Google Sheet 自動建立 SEO 關鍵字矩陣</title>
      <link>https://deanlin.net/blog/202501/n8n-seo-keyword-matrix/</link>
      <pubDate>Tue, 14 Jan 2025 22:45:48 +0800</pubDate>
      <guid>https://deanlin.net/blog/202501/n8n-seo-keyword-matrix/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>AI 實戰</category>
      <description>「如何找到大量關鍵字」的其中一個方法，就是用 Google Search 取得「相關搜尋字詞」，然後將它整理成一個關鍵字矩陣。這篇文章用 n8n 把這個工作流整理成一個自動化流程。使用者只要填上自己的 Google Sheet URL，就能一鍵生成對應的 SEO 關鍵字矩陣！</description>
      <media:content url="https://deanlin.net/blog/202501/n8n-seo-keyword-matrix/images/n8n-seo-keyword-matrix-og_hu_7c47413100dfac27.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<p>最近上邱韜誠的 SEO 課程時，「如何找到大量關鍵字」的其中一個方法，就是用 Google Search 取得「相關搜尋字詞」，然後將它整理成一個關鍵字矩陣。</p>
<p>但如果要查大量的關鍵字，就會浪費很多時間在複製貼上的重複任務上。</p>
<p>剛好最近在研究 n8n，就把這個工作流整理成一個自動化流程。使用者只要填上自己的 Google Sheet URL，就能一鍵生成對應的 SEO 關鍵字矩陣了！</p>
<p><img src="https://deanlin.net/blog/202501/n8n-seo-keyword-matrix/images/n8n-seo-keyword-matrix-01.webp" width="1400" height="789" alt="\[n8n 教學\]在 Google Sheet 自動建立 SEO 關鍵字矩陣（圖 1）" fetchpriority="high" decoding="async"></p>
<blockquote>
<p>n8n 有<a href="https://n8n.io/">雲端</a>與本地（local）的開源版本，基於資訊安全與使用限制，筆者強烈建立在本地（local）建立環境，<a href="https://deanlin.net/blog/202501/n8n-docker-local-setup/">詳細的操作步驟可以參考這篇文章</a>。</p>
</blockquote>
<hr>
<h2 id="-自動建立-seo-關鍵字矩陣工作流workflow簡介">自動建立 SEO 關鍵字矩陣工作流（Workflow）簡介<a class="h-anchor" href="#-%e8%87%aa%e5%8b%95%e5%bb%ba%e7%ab%8b-seo-%e9%97%9c%e9%8d%b5%e5%ad%97%e7%9f%a9%e9%99%a3%e5%b7%a5%e4%bd%9c%e6%b5%81workflow%e7%b0%a1%e4%bb%8b" aria-label="這個段落的連結"></a></h2>
<p><img src="https://deanlin.net/blog/202501/n8n-seo-keyword-matrix/images/n8n-seo-keyword-matrix-02.webp" width="1400" height="488" alt="自動建立 SEO 關鍵字矩陣工作流（Workflow）簡介（圖 2）" loading="lazy" decoding="async"></p>
<p>上圖是完整的工作流（Workflow），下面說明一下執行步驟 &amp; 設計邏輯:</p>
<p><strong>STEP 1</strong>: 填寫要讀取的 Google Sheet URL 與工作表名稱（set_google_sheet_info）。</p>
<p><strong>STEP 2</strong>: 讀取 Google Sheet 裡面的要查詢的關鍵字（get_keywords）。</p>
<p><strong>STEP 3</strong>: 第一層迴圈（loop_keywords_1st）會根據關鍵字來建立 Sheet（create_sheet）與查詢關鍵字（query_keywords_1st），並將資料格式化為可以使用的型態（format_query_result）給下一步驟。</p>
<p><strong>STEP 4</strong>: 第二層迴圈（loop_keywords_2nd）會根據上一層給予的關聯關鍵字，再做第二次的搜尋（query_keywords_2nd），同樣也要將資料格式化爲可以使用的型態（format_query_result2）。</p>
<p><strong>STEP 5</strong>: 得到 SEO 關鍵字後，要先把他們分成指定的群組（group_keys），並分割成適合寫入 Google Sheet 的格式（split_to_items）。</p>
<p><strong>STEP 6</strong>: 最後透過迴圈（loop_append_row_data）過濾掉無法寫入的值（remove_undefined_key），先等待 2.5 秒（wait_2.5s），再逐行寫入 Google Sheet（append_row_data），避免太頻繁呼叫 API。</p>
<p><strong>STEP 7</strong>: 完成這一切後，判斷是否為最後一次執行（skip_last_runtime），如果是最後一次就終止迴圈。</p>
<p><strong>備註1</strong>: reset_with_runtimes、reset_with_even_runtimes 這兩個節點（Node），是為了在 Done 的情境下 Reset Loop Over Items 重複執行時累計的資料。</p>
<p><strong>備註2</strong>: check_keyword_2nd_has_value、check_key_length 是為了處理空陣列的情境。</p>
<hr>
<h2 id="-如何使用這個-n8n-的工作流workflow">如何使用這個 n8n 的工作流（Workflow）<a class="h-anchor" href="#-%e5%a6%82%e4%bd%95%e4%bd%bf%e7%94%a8%e9%80%99%e5%80%8b-n8n-%e7%9a%84%e5%b7%a5%e4%bd%9c%e6%b5%81workflow" aria-label="這個段落的連結"></a></h2>
<p>如果你只是想要使用這個 Workflow，可以直接複製筆者放在 <a href="https://github.com/dean9703111/n8n-google-sheet-exmaple/blob/master/workflow/google_sheet_keyword.json">GitHub 上的範本</a>，貼到 n8n 的 Workflow（不要懷疑，貼上就對了），並根據下面的步驟完成設定就可以執行了。</p>
<p><strong>STEP 1</strong>: 建立一個 Google Sheet，在「工作表1」的第一欄填寫關鍵字（keyword），範例如下</p>
<p><img src="https://deanlin.net/blog/202501/n8n-seo-keyword-matrix/images/n8n-seo-keyword-matrix-03.webp" width="1123" height="585" alt="如何使用這個 n8n 的工作流（Workflow）（圖 3）" loading="lazy" decoding="async"></p>
<p><strong>STEP 2</strong>: 打開 n8n「set_google_sheet_info」的 Node，在「google_sheet_url」欄位上貼上自己的 Google Sheet URL</p>
<p><img src="https://deanlin.net/blog/202501/n8n-seo-keyword-matrix/images/n8n-seo-keyword-matrix-04.webp" width="1400" height="488" alt="如何使用這個 n8n 的工作流（Workflow）（圖 4）" loading="lazy" decoding="async"></p>
<p><img src="https://deanlin.net/blog/202501/n8n-seo-keyword-matrix/images/n8n-seo-keyword-matrix-05.webp" width="1293" height="967" alt="如何使用這個 n8n 的工作流（Workflow）（圖 5）" loading="lazy" decoding="async"></p>
<p><strong>STEP 3</strong>: 打開「get_keywords、create_sheet、append_row_data」的 Node，選擇 Google Sheets account 的 Credential（如果過去沒有建立過，可以參考後面文章的教學）。</p>
<p><img src="https://deanlin.net/blog/202501/n8n-seo-keyword-matrix/images/n8n-seo-keyword-matrix-06.webp" width="1400" height="488" alt="如何使用這個 n8n 的工作流（Workflow）（圖 6）" loading="lazy" decoding="async"></p>
<p><img src="https://deanlin.net/blog/202501/n8n-seo-keyword-matrix/images/n8n-seo-keyword-matrix-07.webp" width="1293" height="967" alt="如何使用這個 n8n 的工作流（Workflow）（圖 7）" loading="lazy" decoding="async"></p>
<p>完成上面的步驟後，點擊「Test workflow」就可以自動生成 Google SEO 關鍵字矩陣啦！從下圖可以看到</p>
<p><img src="https://deanlin.net/blog/202501/n8n-seo-keyword-matrix/images/n8n-seo-keyword-matrix-08.webp" width="1293" height="967" alt="如何使用這個 n8n 的工作流（Workflow）（圖 8）" loading="lazy" decoding="async"></p>
<p><img src="https://deanlin.net/blog/202501/n8n-seo-keyword-matrix/images/n8n-seo-keyword-matrix-09.webp" width="1400" height="732" alt="如何使用這個 n8n 的工作流（Workflow）（圖 9）" loading="lazy" decoding="async"></p>
<hr>
<h2 id="-建立-google-sheets-api-的-credential">建立 Google Sheets API 的 Credential<a class="h-anchor" href="#-%e5%bb%ba%e7%ab%8b-google-sheets-api-%e7%9a%84-credential" aria-label="這個段落的連結"></a></h2>
<p><strong>STEP 1</strong>: 打開任一 Google Sheet Node，點擊「Create New Credential」。</p>
<p><img src="https://deanlin.net/blog/202501/n8n-seo-keyword-matrix/images/n8n-seo-keyword-matrix-10.webp" width="1292" height="960" alt="建立 Google Sheets API 的 Credential（圖 10）" loading="lazy" decoding="async"></p>
<p><strong>STEP 2</strong>: 建立一個 <a href="https://cloud.google.com/">Google Cloud</a> 帳號，有 Gmail 就能直接使用，下面是登入後的畫面。</p>
<p><img src="https://deanlin.net/blog/202501/n8n-seo-keyword-matrix/images/n8n-seo-keyword-matrix-11.webp" width="1400" height="705" alt="建立 Google Sheets API 的 Credential（圖 11）" loading="lazy" decoding="async"></p>
<p><strong>STEP 3</strong>: 進入「<a href="https://console.cloud.google.com/">控制台（Google Cloud Console）</a>」，選擇「New Project」並在 Project name 輸入「n8n test」。</p>
<p><img src="https://deanlin.net/blog/202501/n8n-seo-keyword-matrix/images/n8n-seo-keyword-matrix-12.webp" width="1400" height="626" alt="建立 Google Sheets API 的 Credential（圖 12）" loading="lazy" decoding="async"></p>
<p><strong>STEP 4</strong>: 建立完成後會出現下面的頁面，因為我們要開啟 Google Sheet 的權限，所以點擊「API &amp; Services」。</p>
<p><img src="https://deanlin.net/blog/202501/n8n-seo-keyword-matrix/images/n8n-seo-keyword-matrix-13.webp" width="1400" height="663" alt="建立 Google Sheets API 的 Credential（圖 13）" loading="lazy" decoding="async"></p>
<p><strong>STEP 5</strong>: 然後點擊「+ ENABLE APIS AND SERVICES」。</p>
<p><img src="https://deanlin.net/blog/202501/n8n-seo-keyword-matrix/images/n8n-seo-keyword-matrix-14.webp" width="1400" height="494" alt="建立 Google Sheets API 的 Credential（圖 14）" loading="lazy" decoding="async"></p>
<p><strong>STEP 6</strong>: 在搜尋框輸入「google sheet api」。</p>
<p><img src="https://deanlin.net/blog/202501/n8n-seo-keyword-matrix/images/n8n-seo-keyword-matrix-15.webp" width="1292" height="756" alt="建立 Google Sheets API 的 Credential（圖 15）" loading="lazy" decoding="async"></p>
<p><strong>STEP 7</strong>: 點擊「ENABLE」來開啟權限。</p>
<p><img src="https://deanlin.net/blog/202501/n8n-seo-keyword-matrix/images/n8n-seo-keyword-matrix-16.webp" width="1292" height="601" alt="建立 Google Sheets API 的 Credential（圖 16）" loading="lazy" decoding="async"></p>
<p><strong>STEP 8</strong>: 開啟好權限後，點擊左側的「OAuth consent screen」建立 APP。</p>
<p><img src="https://deanlin.net/blog/202501/n8n-seo-keyword-matrix/images/n8n-seo-keyword-matrix-17.webp" width="1293" height="960" alt="建立 Google Sheets API 的 Credential（圖 17）" loading="lazy" decoding="async"></p>
<p><strong>STEP 9</strong>: 資訊填寫完成後，要點擊「PUBLISH APP」才能使用（你也可以加入 User，用內測的方式使用）。</p>
<p><img src="https://deanlin.net/blog/202501/n8n-seo-keyword-matrix/images/n8n-seo-keyword-matrix-18.webp" width="1293" height="967" alt="建立 Google Sheets API 的 Credential（圖 18）" loading="lazy" decoding="async"></p>
<p><strong>STEP 10</strong>: 點擊上方的「+ CREATE CREDENTAILS」，選擇「OAuth Client ID」。</p>
<p><img src="https://deanlin.net/blog/202501/n8n-seo-keyword-matrix/images/n8n-seo-keyword-matrix-19.webp" width="1293" height="601" alt="建立 Google Sheets API 的 Credential（圖 19）" loading="lazy" decoding="async"></p>
<p><strong>STEP 11</strong>: Application type 選擇「Web application」，Name 自行決定。</p>
<p><img src="https://deanlin.net/blog/202501/n8n-seo-keyword-matrix/images/n8n-seo-keyword-matrix-20.webp" width="1293" height="967" alt="建立 Google Sheets API 的 Credential（圖 20）" loading="lazy" decoding="async"></p>
<p><strong>STEP 12</strong>: 在「Authorized redirect URIs」填入 n8n 的網址，如果你是在 local 操作就填寫 <code><a href="http://localhost:5678/rest/oauth2-credential/callback">http://localhost:5678/rest/oauth2-credential/callback</a></code></p>
<p><img src="https://deanlin.net/blog/202501/n8n-seo-keyword-matrix/images/n8n-seo-keyword-matrix-21.webp" width="1293" height="967" alt="建立 Google Sheets API 的 Credential（圖 21）" loading="lazy" decoding="async"></p>
<p><strong>STEP 13</strong>: 按下 Create 後就會取得最關鍵的「Client ID、Client secret」，把他們複製下來。</p>
<p><img src="https://deanlin.net/blog/202501/n8n-seo-keyword-matrix/images/n8n-seo-keyword-matrix-22.webp" width="1293" height="967" alt="建立 Google Sheets API 的 Credential（圖 22）" loading="lazy" decoding="async"></p>
<p><strong>STEP 14</strong>: 畫面切回到 n8n 的 Google Sheet Node，填上「Client ID、Client secret」。</p>
<p><img src="https://deanlin.net/blog/202501/n8n-seo-keyword-matrix/images/n8n-seo-keyword-matrix-23.webp" width="1293" height="967" alt="建立 Google Sheets API 的 Credential（圖 23）" loading="lazy" decoding="async"></p>
<p><strong>STEP 15</strong>: 點擊「Sign in with Google」後，勇敢的點擊「繼續」與「前往」。</p>
<p><img src="https://deanlin.net/blog/202501/n8n-seo-keyword-matrix/images/n8n-seo-keyword-matrix-24.webp" width="1293" height="967" alt="建立 Google Sheets API 的 Credential（圖 24）" loading="lazy" decoding="async"></p>
<p><strong>STEP 16</strong>: 選擇允許 n8n 可以使用的範圍（這邊看個人需求，勾全選比較不會遇到權限的問題；但提醒大家這組 Secret 權限很大，千萬別外洩了）。</p>
<p><img src="https://deanlin.net/blog/202501/n8n-seo-keyword-matrix/images/n8n-seo-keyword-matrix-25.webp" width="1293" height="967" alt="建立 Google Sheets API 的 Credential（圖 25）" loading="lazy" decoding="async"></p>
<p><strong>STEP 17</strong>: 看到畫面出現「Got Connected」就代表你大功告成啦！！！</p>
<p><img src="https://deanlin.net/blog/202501/n8n-seo-keyword-matrix/images/n8n-seo-keyword-matrix-26.webp" width="1293" height="967" alt="建立 Google Sheets API 的 Credential（圖 26）" loading="lazy" decoding="async"></p>
<blockquote class="pullquote"><p>如果看完這篇文章，代表你是一個充滿意志力的人，給自己拍拍手的同時，也可以幫筆者的文章拍拍手喔（這系列真的寫到快崩潰 QQ）。</p>
</blockquote>
<p><strong>延伸閱讀</strong>:</p>
<ul>
<li><a href="https://deanlin.net/blog/202501/n8n-docker-local-setup/">[n8n 教學]用 Docker 在 Local 建立免費 &amp; 無限次數使用的自動化工具</a></li>
<li><a href="https://deanlin.net/blog/202501/n8n-core-nodes/">[n8n 教學]掌握重要節點 (Node) 使用情境：Set、Loop Over Items、If、Code、HTTP Request、Wait</a></li>
</ul>
<hr>
<a class="linkcard" href="https://deanlin.net/categories/ai/">
  <span class="lc-body">
    <span class="lc-title">AI 實戰</span>
    <span class="lc-desc">實測生成式 AI 工具、Vibe Coding 與自動化流程的第一手心得，把 AI 變成每天都用得上的即戰力。</span>
    <span class="lc-site"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M5 12h14M13 6l6 6-6 6"/></svg>林鼎淵 Dean Lin</span>
  </span>
</a>

<p><strong>想將 AI 導入自己的職場與生活嗎？歡迎<a href="https://www.books.com.tw/products/0011004216">購買筆者出版的新書</a></strong>，幫助您<strong>在 AI 的浪潮中站穩腳步，並在未來的職場競爭中佔據優勢！</strong></p>
<figure class="figure"><img src="https://deanlin.net/blog/202501/n8n-seo-keyword-matrix/images/n8n-seo-keyword-matrix-27.webp" width="1400" height="319" alt="林鼎淵著作《ChatGPT 與 AI 繪圖效率大師（新增 Copilot、Gamma、Runway、Suno）》" loading="lazy" decoding="async">
  <figcaption><a href="https://www.books.com.tw/products/0011004216">https://www.books.com.tw/products/0011004216</a></figcaption>
</figure>

]]></content:encoded>
    </item>
    <item>
      <title>【Day20】Google Sheets - 寫入爬蟲資料，跟 Copy &amp; Paste 的日子說掰掰</title>
      <link>https://deanlin.net/blog/202010/fb-ig-crawler-day-20/</link>
      <pubDate>Mon, 05 Oct 2020 09:01:38 +0800</pubDate>
      <guid>https://deanlin.net/blog/202010/fb-ig-crawler-day-20/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>技術教學</category>
      <description>1.1 分析 Google Sheets 上要呈現的資料、寫入時可能遇到的問題1.2 組合回傳的資訊：以 crawlerFB 為範例</description>
      <media:content url="https://deanlin.net/blog/202010/fb-ig-crawler-day-20/images/cover_hu_7524c32e28b30d4.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<blockquote>
<p>爬蟲是一個技術，他將網頁的數據收集下來</p>
<p>Google Sheets 是一個容器，他可以儲存資料並將資料以不同面向做展示</p>
<p><mark>爬蟲 Ｘ Google Sheets ＝ 你要學習的技術整合</mark></p>
</blockquote>
<h2 id="今日目標">今日目標<a class="h-anchor" href="#%e4%bb%8a%e6%97%a5%e7%9b%ae%e6%a8%99" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="1-改寫crawlerigcrawler這兩隻函式讓他們依照我們想要格式回傳爬蟲資料">1. 改寫<code>crawlerIG、crawler</code>這兩隻函式，讓他們依照我們想要格式回傳爬蟲資料<a class="h-anchor" href="#1-%e6%94%b9%e5%af%abcrawlerigcrawler%e9%80%99%e5%85%a9%e9%9a%bb%e5%87%bd%e5%bc%8f%e8%ae%93%e4%bb%96%e5%80%91%e4%be%9d%e7%85%a7%e6%88%91%e5%80%91%e6%83%b3%e8%a6%81%e6%a0%bc%e5%bc%8f%e5%9b%9e%e5%82%b3%e7%88%ac%e8%9f%b2%e8%b3%87%e6%96%99" aria-label="這個段落的連結"></a></h3>
<p>1.1 分析 Google Sheets 上要呈現的資料、寫入時可能遇到的問題</p>
<p>1.2 組合回傳的資訊：以 <code>crawlerFB</code> 為範例</p>
<h3 class="h-nomark" id="2-以主程式當橋樑將爬蟲資料傳遞到updategooglesheets">2. 以主程式當橋樑，將爬蟲資料傳遞到<code>updateGoogleSheets</code><a class="h-anchor" href="#2-%e4%bb%a5%e4%b8%bb%e7%a8%8b%e5%bc%8f%e7%95%b6%e6%a9%8b%e6%a8%91%e5%b0%87%e7%88%ac%e8%9f%b2%e8%b3%87%e6%96%99%e5%82%b3%e9%81%9e%e5%88%b0updategooglesheets" aria-label="這個段落的連結"></a></h3>
<p>2.1 用<code>主程式</code>傳遞爬蟲資料</p>
<p>2.2 改寫 <code>updateGoogleSheets</code> 函式來接收並處理爬蟲資料</p>
<h3 class="h-nomark" id="3-將-fbig粉專爬蟲資料寫入各自的-sheet">3. 將 FB、IG粉專爬蟲資料寫入各自的 Sheet<a class="h-anchor" href="#3-%e5%b0%87-fbig%e7%b2%89%e5%b0%88%e7%88%ac%e8%9f%b2%e8%b3%87%e6%96%99%e5%af%ab%e5%85%a5%e5%90%84%e8%87%aa%e7%9a%84-sheet" aria-label="這個段落的連結"></a></h3>
<p>3.1 分析寫入 Google Sheets 的步驟：<code>writeSheet</code></p>
<p>3.2 在官方文件尋找 <strong>寫入Sheet</strong> 的範例</p>
<p>3.3 第一欄寫入 title(粉專名稱)：<code>writeTitle</code></p>
<p>3.4 取得 Sheet 最後一欄的空白欄位：<code>getLastCol</code></p>
<p>3.5 將 trace(追蹤人數)資訊寫入最後一欄：<code>writeTrace</code></p>
<hr>
<h2 class="h-nomark" id="1-改寫crawlerigcrawler這兩隻函式讓他們依照我們想要格式回傳爬蟲資料-1">1. 改寫<code>crawlerIG、crawler</code>這兩隻函式，讓他們依照我們想要格式回傳爬蟲資料<a class="h-anchor" href="#1-%e6%94%b9%e5%af%abcrawlerigcrawler%e9%80%99%e5%85%a9%e9%9a%bb%e5%87%bd%e5%bc%8f%e8%ae%93%e4%bb%96%e5%80%91%e4%be%9d%e7%85%a7%e6%88%91%e5%80%91%e6%83%b3%e8%a6%81%e6%a0%bc%e5%bc%8f%e5%9b%9e%e5%82%b3%e7%88%ac%e8%9f%b2%e8%b3%87%e6%96%99-1" aria-label="這個段落的連結"></a></h2>
<blockquote>
<p>過去我們只將 FB粉專、IG粉專的資訊用 console.log 輸出，現在我們要把這些資料有結構的存入 json 物件方便後續使用</p>
</blockquote>
<h3 class="h-nomark" id="11-分析-google-sheets-上要呈現的資料寫入時可能遇到的問題">1.1 分析 Google Sheets 上要呈現的資料、寫入時可能遇到的問題<a class="h-anchor" href="#11-%e5%88%86%e6%9e%90-google-sheets-%e4%b8%8a%e8%a6%81%e5%91%88%e7%8f%be%e7%9a%84%e8%b3%87%e6%96%99%e5%af%ab%e5%85%a5%e6%99%82%e5%8f%af%e8%83%bd%e9%81%87%e5%88%b0%e7%9a%84%e5%95%8f%e9%a1%8c" aria-label="這個段落的連結"></a></h3>
<ul>
<li><strong>Google Sheets 呈現的資料</strong>：
<ol>
<li>粉專名稱</li>
<li>追蹤人數</li>
<li>追蹤日期</li>
</ol>
</li>
<li><strong>可能會遇到的問題</strong>：
<ol>
<li>重複名稱的粉專
<ul>
<li>解決方案：粉專名稱有可能重名，但是<mark>粉專網址是唯一值</mark>，所以使用<code>粉專名稱+粉專網址作為key</code>就能解決這個問題，同時也方便使用者可以點擊連結直接前往粉專</li>
</ul>
</li>
</ol>
</li>
<li><strong>總結</strong>：
<ol>
<li>因為追蹤的 FB、IG粉專數量很多，所以 <strong>crawlerFB、crawlerIG</strong> 這兩個函式回傳的最外層用陣列(array)包起來</li>
<li>根據 Google Sheets 所需的資料，每個粉專的物件(object)需要有以下內容：
<ol>
<li>粉專名稱(title)</li>
<li>粉專網址(url)</li>
<li>追蹤人數(trace)</li>
</ol>
</li>
</ol>
</li>
</ul>
<hr>
<h3 class="h-nomark" id="12-組合回傳的資訊以-crawlerfb-為範例">1.2 組合回傳的資訊：以 <code>crawlerFB</code> 為範例<a class="h-anchor" href="#12-%e7%b5%84%e5%90%88%e5%9b%9e%e5%82%b3%e7%9a%84%e8%b3%87%e8%a8%8a%e4%bb%a5-crawlerfb-%e7%82%ba%e7%af%84%e4%be%8b" aria-label="這個段落的連結"></a></h3>
<ul>
<li>
<p>在 try-catch 的後面加上 finally，在 finally 階段才把粉專物件存入 result_array 是因為<mark>無論這個粉專爬蟲成功或是失敗我們都要記錄他的數據</mark></p>
<blockquote>
<p>finally 代表在 try-catch 結束後會執行的任務，忘記的可以回到<a href="https://deanlin.net/blog/202009/fb-ig-crawler-day-14/">Day14 try-catch-finally 基礎語法說明</a>複習喔</p>
</blockquote>
</li>
<li>
<p>在 FB粉專爬蟲完成後，回傳儲存所有粉專資訊的 result_array 讓主程式做資料串接</p>
</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#ff7b72">async</span> <span style="color:#ff7b72">function</span> crawlerFB (driver) {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">const</span> isLogin <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> loginFacebook(driver)
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">if</span> (isLogin) {
</span></span><span style="display:flex;"><span>        console.log(<span style="color:#a5d6ff">`FB開始爬蟲`</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">let</span> result_array <span style="color:#ff7b72;font-weight:bold">=</span> []
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">for</span> (fanpage <span style="color:#ff7b72">of</span> fanpage_array) {
</span></span><span style="display:flex;"><span>            <span style="color:#ff7b72">let</span> trace <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#79c0ff">null</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff7b72">try</span> {
</span></span><span style="display:flex;"><span>                <span style="color:#ff7b72">const</span> isGoFansPage <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> goFansPage(driver, fanpage.url)
</span></span><span style="display:flex;"><span>                <span style="color:#ff7b72">if</span> (isGoFansPage) {
</span></span><span style="display:flex;"><span>                    <span style="color:#ff7b72">await</span> driver.sleep((Math.floor(Math.random() <span style="color:#ff7b72;font-weight:bold">*</span> <span style="color:#a5d6ff">4</span>) <span style="color:#ff7b72;font-weight:bold">+</span> <span style="color:#a5d6ff">3</span>) <span style="color:#ff7b72;font-weight:bold">*</span> <span style="color:#a5d6ff">1000</span>)
</span></span><span style="display:flex;"><span>                    trace <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> getTrace(driver, By, until)
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>                <span style="color:#ff7b72">if</span> (trace <span style="color:#ff7b72;font-weight:bold">===</span> <span style="color:#79c0ff">null</span>) {
</span></span><span style="display:flex;"><span>                    console.log(<span style="color:#a5d6ff">`</span><span style="color:#a5d6ff">${</span>fanpage.title<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff">無法抓取追蹤人數`</span>)
</span></span><span style="display:flex;"><span>                } <span style="color:#ff7b72">else</span> {
</span></span><span style="display:flex;"><span>                    console.log(<span style="color:#a5d6ff">`</span><span style="color:#a5d6ff">${</span>fanpage.title<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff">追蹤人數：</span><span style="color:#a5d6ff">${</span>trace<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff">`</span>)
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>            } <span style="color:#ff7b72">catch</span> (e) {
</span></span><span style="display:flex;"><span>                console.error(e);
</span></span><span style="display:flex;"><span>                <span style="color:#ff7b72">continue</span>;
</span></span><span style="display:flex;"><span>            } <span style="color:#ff7b72">finally</span> {<span style="color:#8b949e;font-style:italic">// 將粉專的資訊塞入物件
</span></span></span><span style="display:flex;"><span>                result_array.push({
</span></span><span style="display:flex;"><span>                    url<span style="color:#ff7b72;font-weight:bold">:</span> fanpage.url,
</span></span><span style="display:flex;"><span>                    title<span style="color:#ff7b72;font-weight:bold">:</span> fanpage.title,
</span></span><span style="display:flex;"><span>                    trace<span style="color:#ff7b72;font-weight:bold">:</span> trace
</span></span><span style="display:flex;"><span>                })
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#8b949e;font-style:italic">// 回傳FB粉專爬蟲資料
</span></span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">return</span> result_array
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><hr>
<h2 class="h-nomark" id="2-以主程式當橋樑將爬蟲資料傳遞到updategooglesheets-1">2. 以主程式當橋樑，將爬蟲資料傳遞到<code>updateGoogleSheets</code><a class="h-anchor" href="#2-%e4%bb%a5%e4%b8%bb%e7%a8%8b%e5%bc%8f%e7%95%b6%e6%a9%8b%e6%a8%91%e5%b0%87%e7%88%ac%e8%9f%b2%e8%b3%87%e6%96%99%e5%82%b3%e9%81%9e%e5%88%b0updategooglesheets-1" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="21-用主程式傳遞爬蟲資料">2.1 用<code>主程式</code>傳遞爬蟲資料<a class="h-anchor" href="#21-%e7%94%a8%e4%b8%bb%e7%a8%8b%e5%bc%8f%e5%82%b3%e9%81%9e%e7%88%ac%e8%9f%b2%e8%b3%87%e6%96%99" aria-label="這個段落的連結"></a></h3>
<p>主程式將 crawlerIG、crawlerFB 回傳的爬蟲資訊提供給 updateGoogleSheets 當參數</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#ff7b72">async</span> <span style="color:#ff7b72">function</span> crawler () {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">const</span> driver <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> initDrive();
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">if</span> (<span style="color:#ff7b72;font-weight:bold">!</span>driver) {
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">return</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">//取得回傳的爬蟲資料
</span></span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">const</span> ig_result_array <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> crawlerIG(driver)
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">const</span> fb_result_array <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> crawlerFB(driver)
</span></span><span style="display:flex;"><span>    driver.quit();
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">//將回傳的爬蟲資料傳遞給Google Sheets處理
</span></span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">await</span> updateGoogleSheets(ig_result_array, fb_result_array)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><hr>
<h3 class="h-nomark" id="22-改寫-updategooglesheets-函式來接收並處理爬蟲資料">2.2 改寫 <code>updateGoogleSheets</code> 函式來接收並處理爬蟲資料<a class="h-anchor" href="#22-%e6%94%b9%e5%af%ab-updategooglesheets-%e5%87%bd%e5%bc%8f%e4%be%86%e6%8e%a5%e6%94%b6%e4%b8%a6%e8%99%95%e7%90%86%e7%88%ac%e8%9f%b2%e8%b3%87%e6%96%99" aria-label="這個段落的連結"></a></h3>
<ul>
<li>接受爬蟲回傳的資料：ig_result_array, fb_result_array</li>
<li>新增函式 <code>writeSheet</code> 將收到的爬蟲資料寫入對應的 Sheet</li>
<li>最後印出 Google Sheets 的網址方便查看</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#ff7b72">async</span> <span style="color:#ff7b72">function</span> updateGoogleSheets (ig_result_array, fb_result_array) {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">try</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">const</span> auth <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> getAuth()
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">let</span> sheets <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> getFBIGSheet(auth)
</span></span><span style="display:flex;"><span>        console.log(<span style="color:#a5d6ff">&#39;FB、IG Sheet資訊:&#39;</span>)
</span></span><span style="display:flex;"><span>        console.log(sheets)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#8b949e;font-style:italic">// 將爬蟲資料寫入各自的Sheet
</span></span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">await</span> writeSheet(<span style="color:#a5d6ff">&#39;FB粉專&#39;</span>, fb_result_array, auth)
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">await</span> writeSheet(<span style="color:#a5d6ff">&#39;IG帳號&#39;</span>, ig_result_array, auth)
</span></span><span style="display:flex;"><span>        console.log(<span style="color:#a5d6ff">`成功更新Google Sheets：https://docs.google.com/spreadsheets/d/</span><span style="color:#a5d6ff">${</span>process.env.SPREADSHEET_ID<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff">`</span>);
</span></span><span style="display:flex;"><span>    } <span style="color:#ff7b72">catch</span> (err) {
</span></span><span style="display:flex;"><span>        console.error(<span style="color:#a5d6ff">&#39;更新Google Sheets失敗&#39;</span>);
</span></span><span style="display:flex;"><span>        console.error(err);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><hr>
<h2 class="h-nomark" id="3-將fbig粉專爬蟲資料寫入各自的sheet">3. 將FB、IG粉專爬蟲資料寫入各自的Sheet<a class="h-anchor" href="#3-%e5%b0%87fbig%e7%b2%89%e5%b0%88%e7%88%ac%e8%9f%b2%e8%b3%87%e6%96%99%e5%af%ab%e5%85%a5%e5%90%84%e8%87%aa%e7%9a%84sheet" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="31-分析寫入-google-sheets-的步驟writesheet">3.1 分析寫入 Google Sheets 的步驟：<code>writeSheet</code><a class="h-anchor" href="#31-%e5%88%86%e6%9e%90%e5%af%ab%e5%85%a5-google-sheets-%e7%9a%84%e6%ad%a5%e9%a9%9fwritesheet" aria-label="這個段落的連結"></a></h3>
<p><strong>目標</strong>：希望寫入的 Google Sheets 長得像下圖：</p>
<p><img src="https://deanlin.net/blog/202010/fb-ig-crawler-day-20/images/fb-ig-crawler-day-20-01.webp" width="1024" height="351" alt="3.1 分析寫入 Google Sheets 的步驟：writeSheet（圖 1）" fetchpriority="high" decoding="async"></p>
<p><strong>我們先將想要完成的功能羅列出來，再一個個去實現：</strong></p>
<ol>
<li>第一欄為 <code>粉專名稱+粉專網址</code> 組合的 <strong>HYPERLINK</strong>，每次執行爬蟲時我們會用 <code>writeTitle</code> 這個函式來更新第一欄的資料</li>
<li>因為希望新的爬蟲紀錄會寫入到下一欄，所以我們寫一個函式 <code>getLastCol</code> 來取得 Sheet 最後一欄的空白欄位</li>
<li>取得目標寫入欄位後，我們用函式 <code>writeTrace</code> 把爬蟲下來的追蹤人數寫入就完成嚕</li>
</ol>
<p><strong>實作</strong>：將上述要完成的功能各自撰寫成函式後，由 <code>writeSheet</code> 這個函式來管理寫入 Google Sheets 的步驟</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#ff7b72">async</span> <span style="color:#ff7b72">function</span> writeSheet (title, result_array, auth) {
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">// title可以直接超連結到粉專
</span></span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">let</span> title_array <span style="color:#ff7b72;font-weight:bold">=</span> result_array.map(fanpage =&gt; [<span style="color:#a5d6ff">`=HYPERLINK(&#34;</span><span style="color:#a5d6ff">${</span>fanpage.url<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff">&#34;,&#34;</span><span style="color:#a5d6ff">${</span>fanpage.title<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff">&#34;)`</span>]);
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">// 開頭填入填上FB粉專/IG帳號
</span></span></span><span style="display:flex;"><span>    title_array.unshift([title])<span style="color:#8b949e;font-style:italic">//unshift是指插入陣列開頭
</span></span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">// 在第一欄寫入title(粉專名稱)
</span></span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">await</span> writeTitle(title, title_array, auth)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">// 取得目前最後一欄
</span></span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">let</span> lastCol <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> getLastCol(title, auth)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">let</span> trace_array <span style="color:#ff7b72;font-weight:bold">=</span> result_array.map(fanpage =&gt; [fanpage.trace]);
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">// 抓取當天日期
</span></span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">const</span> datetime <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">new</span> Date()
</span></span><span style="display:flex;"><span>    trace_array.unshift([dateFormat(datetime, <span style="color:#a5d6ff">&#34;GMT:yyyy/mm/dd&#34;</span>)])
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">// 再寫入trace(追蹤人數)
</span></span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">await</span> writeTrace(title, trace_array, lastCol, auth)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><hr>
<h3 class="h-nomark" id="32-在官方文件尋找-寫入sheet-的範例">3.2 在官方文件尋找 <code>寫入Sheet</code> 的範例<a class="h-anchor" href="#32-%e5%9c%a8%e5%ae%98%e6%96%b9%e6%96%87%e4%bb%b6%e5%b0%8b%e6%89%be-%e5%af%ab%e5%85%a5sheet-%e7%9a%84%e7%af%84%e4%be%8b" aria-label="這個段落的連結"></a></h3>
<ol>
<li>
<p>有了<mark>昨天的經驗</mark>我們可以很容易找到今天所需的資源，在範例首頁的大標題<a href="https://developers.google.com/sheets/api/samples/writing">Basic Writing</a>就符合今天的需求</p>
<p><img src="https://deanlin.net/blog/202010/fb-ig-crawler-day-20/images/fb-ig-crawler-day-20-02.webp" width="1024" height="600" alt="3.2 在官方文件尋找 寫入Sheet 的範例（圖 2）" loading="lazy" decoding="async"></p>
</li>
<li>
<p>在閱讀 <strong>Basic Writing</strong> 描述時有兩個標題吸引我的注意</p>
<ul>
<li>
<p><a href="https://developers.google.com/sheets/api/samples/writing#append_values">Append Values</a>：在閱讀標題時我原本覺得是最好的選擇，但發現他只能插入列(row)，與我們插入欄(col)的需求不符</p>
</li>
<li>
<p><a href="https://developers.google.com/sheets/api/samples/writing#write_a_single_range">Write a single range</a>：在開頭便提供了一個連結，並說<mark>這個方法能寫入指定 Sheet 範圍內的資料</mark></p>
<p><img src="https://deanlin.net/blog/202010/fb-ig-crawler-day-20/images/fb-ig-crawler-day-20-03.webp" width="1024" height="345" alt="3.2 在官方文件尋找 寫入Sheet 的範例（圖 3）" loading="lazy" decoding="async"></p>
</li>
</ul>
</li>
<li>
<p>接著你可以用<code>Try this API</code>來確認 <a href="https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/update">Method: spreadsheets.values.update</a> 是否能正確更新 Sheet 的內容，相關操作方式<a href="https://deanlin.net/blog/202010/fb-ig-crawler-day-19/">昨天的文章</a>有示範，這裡就不再贅述了，下面我把官方範例翻譯了一下，讓大家更清楚使用的方法</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#ff7b72">let</span> title <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">&#39;你的sheet title&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">//Google Sheets能吃的array格式範例
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">let</span> array <span style="color:#ff7b72;font-weight:bold">=</span> [[<span style="color:#a5d6ff">&#39;test1&#39;</span>],[<span style="color:#a5d6ff">&#39;test2&#39;</span>],[<span style="color:#a5d6ff">&#39;test3&#39;</span>],[<span style="color:#a5d6ff">&#39;test4&#39;</span>]]
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">async</span> <span style="color:#ff7b72">function</span> writeSheet (title, array, auth) {<span style="color:#8b949e;font-style:italic">//auth為憑證通過後取得
</span></span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">const</span> sheets <span style="color:#ff7b72;font-weight:bold">=</span> google.sheets({ version<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#39;v4&#39;</span>, auth });
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">const</span> request <span style="color:#ff7b72;font-weight:bold">=</span> {
</span></span><span style="display:flex;"><span>    spreadsheetId<span style="color:#ff7b72;font-weight:bold">:</span> process.env.SPREADSHEET_ID,
</span></span><span style="display:flex;"><span>    valueInputOption<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#34;USER_ENTERED&#34;</span>,<span style="color:#8b949e;font-style:italic">//寫入格式的分類有：INPUT_VALUE_OPTION_UNSPECIFIED|RAW|USER_ENTERED
</span></span></span><span style="display:flex;"><span>    range<span style="color:#ff7b72;font-weight:bold">:</span> [
</span></span><span style="display:flex;"><span>      <span style="color:#a5d6ff">`&#39;</span><span style="color:#a5d6ff">${</span>title<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff">&#39;!A:A`</span><span style="color:#8b949e;font-style:italic">//title是sheet的標題，A:A是能寫入的範圍
</span></span></span><span style="display:flex;"><span>    ],
</span></span><span style="display:flex;"><span>    resource<span style="color:#ff7b72;font-weight:bold">:</span> {
</span></span><span style="display:flex;"><span>      values<span style="color:#ff7b72;font-weight:bold">:</span> array
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">try</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">await</span> sheets.spreadsheets.values.update(request);<span style="color:#8b949e;font-style:italic">//執行後即完成Google Sheets更新
</span></span></span><span style="display:flex;"><span>    console.log(<span style="color:#a5d6ff">`updated </span><span style="color:#a5d6ff">${</span>title<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff"> title`</span>);
</span></span><span style="display:flex;"><span>  } <span style="color:#ff7b72">catch</span> (err) {
</span></span><span style="display:flex;"><span>    console.error(err);
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div></li>
</ol>
<hr>
<h3 class="h-nomark" id="33-第一欄寫入-title粉專名稱writetitle">3.3 第一欄寫入 title(粉專名稱)：<code>writeTitle</code><a class="h-anchor" href="#33-%e7%ac%ac%e4%b8%80%e6%ac%84%e5%af%ab%e5%85%a5-title%e7%b2%89%e5%b0%88%e5%90%8d%e7%a8%b1writetitle" aria-label="這個段落的連結"></a></h3>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#ff7b72">async</span> <span style="color:#ff7b72">function</span> writeTitle (title, title_array, auth) {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">const</span> sheets <span style="color:#ff7b72;font-weight:bold">=</span> google.sheets({ version<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#39;v4&#39;</span>, auth });
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">const</span> request <span style="color:#ff7b72;font-weight:bold">=</span> {
</span></span><span style="display:flex;"><span>        spreadsheetId<span style="color:#ff7b72;font-weight:bold">:</span> process.env.SPREADSHEET_ID,
</span></span><span style="display:flex;"><span>        valueInputOption<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#34;USER_ENTERED&#34;</span>,
</span></span><span style="display:flex;"><span>        range<span style="color:#ff7b72;font-weight:bold">:</span> [
</span></span><span style="display:flex;"><span>        <span style="color:#a5d6ff">`&#39;</span><span style="color:#a5d6ff">${</span>title<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff">&#39;!A:A`</span> <span style="color:#8b949e;font-style:italic">// 這是寫入第一欄的意思
</span></span></span><span style="display:flex;"><span>        ],
</span></span><span style="display:flex;"><span>        resource<span style="color:#ff7b72;font-weight:bold">:</span> {
</span></span><span style="display:flex;"><span>        values<span style="color:#ff7b72;font-weight:bold">:</span> title_array
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">try</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">await</span> sheets.spreadsheets.values.update(request);
</span></span><span style="display:flex;"><span>        console.log(<span style="color:#a5d6ff">`updated </span><span style="color:#a5d6ff">${</span>title<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff"> title`</span>);
</span></span><span style="display:flex;"><span>    } <span style="color:#ff7b72">catch</span> (err) {
</span></span><span style="display:flex;"><span>        console.error(err);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><hr>
<h3 class="h-nomark" id="34-取得-sheet-最後一欄的空白欄位getlastcol">3.4 取得 Sheet 最後一欄的空白欄位：<code>getLastCol</code><a class="h-anchor" href="#34-%e5%8f%96%e5%be%97-sheet-%e6%9c%80%e5%be%8c%e4%b8%80%e6%ac%84%e7%9a%84%e7%a9%ba%e7%99%bd%e6%ac%84%e4%bd%8dgetlastcol" aria-label="這個段落的連結"></a></h3>
<ul>
<li>在這個函式中我們取得 Sheet 的第一列來做分析，詳細的文件請參考<a href="https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/batchGet">官方說明</a></li>
<li>同時我們也需要用函式 <code>toColumnName</code> 把取得的欄位名稱轉換為英文，這樣 Google Sheets 才知道要寫入的欄位</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#ff7b72">async</span> <span style="color:#ff7b72">function</span> getLastCol (title, auth) {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">const</span> sheets <span style="color:#ff7b72;font-weight:bold">=</span> google.sheets({ version<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#39;v4&#39;</span>, auth });
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">const</span> request <span style="color:#ff7b72;font-weight:bold">=</span> {
</span></span><span style="display:flex;"><span>        spreadsheetId<span style="color:#ff7b72;font-weight:bold">:</span> process.env.SPREADSHEET_ID,
</span></span><span style="display:flex;"><span>        ranges<span style="color:#ff7b72;font-weight:bold">:</span> [
</span></span><span style="display:flex;"><span>        <span style="color:#a5d6ff">`&#39;</span><span style="color:#a5d6ff">${</span>title<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff">&#39;!A1:ZZ1`</span>
</span></span><span style="display:flex;"><span>        ],
</span></span><span style="display:flex;"><span>        majorDimension<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#34;COLUMNS&#34;</span>,
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">try</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">let</span> values <span style="color:#ff7b72;font-weight:bold">=</span> (<span style="color:#ff7b72">await</span> sheets.spreadsheets.values.batchGet(request)).data.valueRanges[<span style="color:#a5d6ff">0</span>].values;
</span></span><span style="display:flex;"><span>        <span style="color:#8b949e;font-style:italic">// console.log(title + &#34; StartCol: &#34; + toColumnName(values.length + 1))
</span></span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">return</span> toColumnName(values.length <span style="color:#ff7b72;font-weight:bold">+</span> <span style="color:#a5d6ff">1</span>)
</span></span><span style="display:flex;"><span>    } <span style="color:#ff7b72">catch</span> (err) {
</span></span><span style="display:flex;"><span>        console.error(err);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">function</span> toColumnName (num) {<span style="color:#8b949e;font-style:italic">//Google Sheets無法辨認數字欄位，需轉為英文才能使用
</span></span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">for</span> (<span style="color:#ff7b72">var</span> ret <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">&#39;&#39;</span>, a <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">1</span>, b <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">26</span>; (num <span style="color:#ff7b72;font-weight:bold">-=</span> a) <span style="color:#ff7b72;font-weight:bold">&gt;=</span> <span style="color:#a5d6ff">0</span>; a <span style="color:#ff7b72;font-weight:bold">=</span> b, b <span style="color:#ff7b72;font-weight:bold">*=</span> <span style="color:#a5d6ff">26</span>) {
</span></span><span style="display:flex;"><span>        ret <span style="color:#ff7b72;font-weight:bold">=</span> String.fromCharCode(parseInt((num <span style="color:#ff7b72;font-weight:bold">%</span> b) <span style="color:#ff7b72;font-weight:bold">/</span> a) <span style="color:#ff7b72;font-weight:bold">+</span> <span style="color:#a5d6ff">65</span>) <span style="color:#ff7b72;font-weight:bold">+</span> ret;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">return</span> ret;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><hr>
<h3 class="h-nomark" id="35-將-trace追蹤人數資訊寫入最後一欄writetrace">3.5 將 trace(追蹤人數)資訊寫入最後一欄：<code>writeTrace</code><a class="h-anchor" href="#35-%e5%b0%87-trace%e8%bf%bd%e8%b9%a4%e4%ba%ba%e6%95%b8%e8%b3%87%e8%a8%8a%e5%af%ab%e5%85%a5%e6%9c%80%e5%be%8c%e4%b8%80%e6%ac%84writetrace" aria-label="這個段落的連結"></a></h3>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#ff7b72">async</span> <span style="color:#ff7b72">function</span> writeTrace (title, trace_array, lastCol, auth) {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">const</span> sheets <span style="color:#ff7b72;font-weight:bold">=</span> google.sheets({ version<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#39;v4&#39;</span>, auth });
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">const</span> request <span style="color:#ff7b72;font-weight:bold">=</span> {
</span></span><span style="display:flex;"><span>        spreadsheetId<span style="color:#ff7b72;font-weight:bold">:</span> process.env.SPREADSHEET_ID,
</span></span><span style="display:flex;"><span>        valueInputOption<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#34;USER_ENTERED&#34;</span>,
</span></span><span style="display:flex;"><span>        range<span style="color:#ff7b72;font-weight:bold">:</span> [
</span></span><span style="display:flex;"><span>        <span style="color:#a5d6ff">`&#39;</span><span style="color:#a5d6ff">${</span>title<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff">&#39;!</span><span style="color:#a5d6ff">${</span>lastCol<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff">:</span><span style="color:#a5d6ff">${</span>lastCol<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff">`</span><span style="color:#8b949e;font-style:italic">//將追蹤人數填入最後一欄
</span></span></span><span style="display:flex;"><span>        ],
</span></span><span style="display:flex;"><span>        resource<span style="color:#ff7b72;font-weight:bold">:</span> {
</span></span><span style="display:flex;"><span>        values<span style="color:#ff7b72;font-weight:bold">:</span> trace_array
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">try</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">await</span> sheets.spreadsheets.values.update(request);
</span></span><span style="display:flex;"><span>        console.log(<span style="color:#a5d6ff">`updated </span><span style="color:#a5d6ff">${</span>title<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff"> trace`</span>);
</span></span><span style="display:flex;"><span>    } <span style="color:#ff7b72">catch</span> (err) {
</span></span><span style="display:flex;"><span>        console.error(err);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><hr>
<h3 id="執行程式">執行程式<a class="h-anchor" href="#%e5%9f%b7%e8%a1%8c%e7%a8%8b%e5%bc%8f" aria-label="這個段落的連結"></a></h3>
<ol>
<li>
<p>在專案資料夾的終端機(Terminal)執行指令</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>yarn start
</span></span></code></pre></div></li>
<li>
<p>確認執行過程中爬蟲資料抓取正常且更新 Google sheets 時沒有錯誤</p>
<p><img src="https://deanlin.net/blog/202010/fb-ig-crawler-day-20/images/fb-ig-crawler-day-20-04.webp" width="1024" height="563" alt="執行程式（圖 4）" loading="lazy" decoding="async"></p>
</li>
<li>
<p>點進去連結看看線上的 Google Sheets 是不是也被成功寫入惹～</p>
<p><img src="https://deanlin.net/blog/202010/fb-ig-crawler-day-20/images/fb-ig-crawler-day-20-05.webp" width="1024" height="349" alt="執行程式（圖 5）" loading="lazy" decoding="async"></p>
</li>
</ol>
<p>目前為止將爬蟲寫入 Google Sheets 的動作已經完成了，大家可以思考一下還有什麼東西是我們忽略的呢？有什麼狀況會造成錯誤？希望大家在下方提供自己的想法喔～</p>
<hr>
<h3 id="ℹ-專案原始碼">ℹ️ 專案原始碼<a class="h-anchor" href="#%e2%84%b9-%e5%b0%88%e6%a1%88%e5%8e%9f%e5%a7%8b%e7%a2%bc" aria-label="這個段落的連結"></a></h3>
<ul>
<li>今天的完整程式碼可以在<a href="https://github.com/dean9703111/ithelp_30days/tree/master/day20">這裡</a>找到喔</li>
<li>我也貼心地把昨天的把昨天的程式碼打包成<a href="https://github.com/dean9703111/ithelp_30days/raw/master/sampleCode/day19_sample_code.zip">壓縮檔</a>，你可以用裡面乾淨的環境來實作今天Google Sheets的爬蟲資料寫入喔
<ul>
<li>請記得在終端機下指令 <strong>yarn</strong> 才會把之前的套件安裝</li>
<li>windows需下載與你chrome版本相同的<a href="http://chromedriver.storage.googleapis.com/index.html">chrome driver</a>放在專案根目錄</li>
<li>要在tools/google_sheets資料夾放上自己的憑證，申請流程請參考<a href="https://deanlin.net/blog/202010/fb-ig-crawler-day-17/">Day17</a></li>
<li>調整fanspages資料夾內目標爬蟲的粉專網址</li>
<li>調整.env檔
<ul>
<li>填上FB登入資訊</li>
<li>填上FB版本(classic/new)</li>
<li>填上IG登入資訊</li>
<li>填上SPREADSHEET_ID</li>
</ul>
</li>
</ul>
</li>
</ul>
<hr>
<blockquote>
<p><img src="https://deanlin.net/blog/202010/fb-ig-crawler-day-20/images/fb-ig-crawler-day-20-06.webp" width="1024" height="233" alt="ℹ️ 專案原始碼（圖 6）" loading="lazy" decoding="async"></p>
<p><strong>在許多人的幫助下，本系列文章已出版成書，並添加了新的篇章與細節補充：</strong></p>
<ul>
<li>加入更多實務經驗，用完整的開發流程讓讀者了解專案每個階段要注意的事項</li>
<li>將爬蟲的步驟與技巧做更詳細的說明，讓讀者可以輕鬆入門</li>
<li>調整專案架構
<ul>
<li>優化爬蟲程式，以更廣的視角來擷取網頁資訊</li>
<li>增加資料驗證、錯誤通知等功能，讓爬蟲執行遇到問題時可以第一時間通知使用者</li>
<li>排程部分改用 node-schedule &amp; pm2 的組合，讓讀者可以輕鬆管理專案程序並獲得更精確的 log 資訊</li>
</ul>
</li>
</ul>
<p><strong>有興趣的朋友可以到天瓏書局選購，感謝大家的支持。</strong></p>
<p><strong>購書連結</strong>：<a href="https://www.tenlong.com.tw/products/9789864348008">https://www.tenlong.com.tw/products/9789864348008</a></p>
</blockquote>
]]></content:encoded>
    </item>
  </channel>
</rss>
