<?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>取得授權｜林鼎淵 Dean Lin</title>
    <link>https://deanlin.net/tags/%E5%8F%96%E5%BE%97%E6%8E%88%E6%AC%8A/</link>
    <image><url>https://deanlin.net/images/author_hu_aa256fc934e9a932.jpg</url><title>取得授權｜林鼎淵 Dean Lin</title><link>https://deanlin.net/tags/%E5%8F%96%E5%BE%97%E6%8E%88%E6%AC%8A/</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>Fri, 02 Oct 2020 09:14:06 +0800</lastBuildDate>
    <atom:link href="https://deanlin.net/tags/%E5%8F%96%E5%BE%97%E6%8E%88%E6%AC%8A/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>【Day17】Google Sheets - 免費儲存資料的好選擇，一起進入省錢起手式</title>
      <link>https://deanlin.net/blog/202010/fb-ig-crawler-day-17/</link>
      <pubDate>Fri, 02 Oct 2020 09:14:06 +0800</pubDate>
      <guid>https://deanlin.net/blog/202010/fb-ig-crawler-day-17/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>技術教學</category>
      <description>一般來說爬蟲的資料都是儲存到資料庫，但是我相信絕大多數的人不太可能隨時隨地打開資料庫觀看，而且存在資料庫除非你請網頁工程師幫你完成前端視覺化，不然直接看資料庫絕對會讓你懷疑人生XD</description>
      <media:content url="https://deanlin.net/blog/202010/fb-ig-crawler-day-17/images/cover_hu_4591ab322b33238b.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<blockquote>
<p>專案最基本的就是：<strong>符合客戶預算與實際使用需求</strong></p>
</blockquote>
<h2 id="為什麼選擇-google-sheets-作為資料載體">為什麼選擇 Google Sheets 作為資料載體?<a class="h-anchor" href="#%e7%82%ba%e4%bb%80%e9%ba%bc%e9%81%b8%e6%93%87-google-sheets-%e4%bd%9c%e7%82%ba%e8%b3%87%e6%96%99%e8%bc%89%e9%ab%94" aria-label="這個段落的連結"></a></h2>
<p>一般來說爬蟲的資料都是儲存到資料庫，但是我相信絕大多數的人不太可能隨時隨地打開資料庫觀看，而且存在資料庫除非你請網頁工程師幫你完成前端視覺化，不然直接看資料庫絕對會讓你懷疑人生XD</p>
<p>經過多方思慮我最後採用 Google Sheets 的服務，主要理由：</p>
<ol>
<li>只要有網路我就能看</li>
<li>絕大多數人都有使用 Google Sheets 的經驗</li>
<li>使用者能輕易的將 Google Sheets 轉化成圖表</li>
<li>你不需要一台 Server 專門來存這些資料</li>
<li><strong><code>免費</code></strong></li>
</ol>
<p>儘管 Google Sheets 並不完美，但他真的很適合用在這個專案上面</p>
<hr>
<h2 id="今日目標">今日目標<a class="h-anchor" href="#%e4%bb%8a%e6%97%a5%e7%9b%ae%e6%a8%99" aria-label="這個段落的連結"></a></h2>
<ol>
<li>取得 Google Sheets 憑證(credentials)</li>
<li>在專案加入官方提供的範例程式做測試</li>
<li>將 Google Sheets 的憑證、授權檔加入 .gitignore</li>
</ol>
<hr>
<h2 class="h-nomark" id="1-取得-google-sheets-憑證credentials">1. 取得 Google Sheets 憑證(credentials)<a class="h-anchor" href="#1-%e5%8f%96%e5%be%97-google-sheets-%e6%86%91%e8%ad%89credentials" aria-label="這個段落的連結"></a></h2>
<p>如果把 Google Sheets 想像成一個藏寶庫，憑證(credentials)就像是一把鑰匙，只有擁有這把鑰匙的人才有打開門的資格；不然這個藏寶庫誰都能任意進出豈不是很危險？</p>
<h3 id="憑證credentials取得的步驟">憑證(credentials)取得的步驟<a class="h-anchor" href="#%e6%86%91%e8%ad%89credentials%e5%8f%96%e5%be%97%e7%9a%84%e6%ad%a5%e9%a9%9f" aria-label="這個段落的連結"></a></h3>
<ol>
<li>請先確認<code>已經安裝過 Node.js &amp; npm</code>(跟著教學走的人都安裝過)，<code>有 Goolge 帳號</code></li>
<li>進入 Google Sheet Node.js 申請憑證(credentials)的<a href="https://developers.google.com/sheets/api/quickstart/nodejs">網頁</a>
<ol>
<li>
<p>按下紅框處按鈕 <strong>Enable theGoogle Sheets API</strong></p>
<p><img src="https://deanlin.net/blog/202010/fb-ig-crawler-day-17/images/fb-ig-crawler-day-17-01.webp" width="1024" height="254" alt="憑證(credentials)取得的步驟（圖 1）" fetchpriority="high" decoding="async"></p>
</li>
<li>
<p>輸入專案的名稱，建議<strong>填寫符合專案功能的命名</strong></p>
<p><img src="https://deanlin.net/blog/202010/fb-ig-crawler-day-17/images/fb-ig-crawler-day-17-02.webp" width="1024" height="297" alt="憑證(credentials)取得的步驟（圖 2）" loading="lazy" decoding="async"></p>
</li>
<li>
<p>本轉案選擇 <strong>Desktop app</strong> 即可</p>
<p><img src="https://deanlin.net/blog/202010/fb-ig-crawler-day-17/images/fb-ig-crawler-day-17-03.webp" width="1024" height="528" alt="憑證(credentials)取得的步驟（圖 3）" loading="lazy" decoding="async"></p>
</li>
<li>
<p>千萬<mark>不要忘記下載這個憑證</mark>，下面的Client ID、Client Secret不需特別複製，都存在憑證檔裡面了</p>
<p><img src="https://deanlin.net/blog/202010/fb-ig-crawler-day-17/images/fb-ig-crawler-day-17-04.webp" width="1024" height="528" alt="憑證(credentials)取得的步驟（圖 4）" loading="lazy" decoding="async"></p>
</li>
</ol>
</li>
<li>在專案的<code>tools 資料夾下新增一個 google_sheets 資料夾</code>，專門存放 Google Sheets 相關檔案</li>
<li>將<code>下載好的憑證(credentials.json)放進去 google_sheets 資料夾</code>，這樣的目錄節結構會方便我們日後管理</li>
</ol>
<hr>
<h2 class="h-nomark" id="2-在專案加入官方提供的範例程式做測試">2. 在專案加入官方提供的範例程式做測試<a class="h-anchor" href="#2-%e5%9c%a8%e5%b0%88%e6%a1%88%e5%8a%a0%e5%85%a5%e5%ae%98%e6%96%b9%e6%8f%90%e4%be%9b%e7%9a%84%e7%af%84%e4%be%8b%e7%a8%8b%e5%bc%8f%e5%81%9a%e6%b8%ac%e8%a9%a6" aria-label="這個段落的連結"></a></h2>
<p>在詳細的閱讀官方文件之前，我會先<mark>用官方的範例程式確認他能否在我的專案順利運作</mark>，如果遇到太多環境衝突才能儘早尋求替代方案</p>
<ol>
<li>
<p>下載 <code>googleapis</code> 套件</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 add googleapis@39
</span></span></code></pre></div></li>
<li>
<p>在 <code>tools/google_sheets 資料夾中新增 index.js</code> 檔案，並複製 Google 範例程式碼貼上</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">const</span> fs <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#39;fs&#39;</span>);
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> readline <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#39;readline&#39;</span>);
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> { google } <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#39;googleapis&#39;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">// If modifying these scopes, delete token.json.
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> SCOPES <span style="color:#ff7b72;font-weight:bold">=</span> [<span style="color:#a5d6ff">&#39;https://www.googleapis.com/auth/spreadsheets.readonly&#39;</span>];
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">// The file token.json stores the user&#39;s access and refresh tokens, and is
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">// created automatically when the authorization flow completes for the first
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">// time.
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> TOKEN_PATH <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">&#39;tools/google_sheets/token.json&#39;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">// Load client secrets from a local file.
</span></span></span><span style="display:flex;"><span>fs.readFile(<span style="color:#a5d6ff">&#39;credentials.json&#39;</span>, (err, content) =&gt; {
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">if</span> (err) <span style="color:#ff7b72">return</span> console.log(<span style="color:#a5d6ff">&#39;Error loading client secret file:&#39;</span>, err);
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">// Authorize a client with credentials, then call the Google Sheets API.
</span></span></span><span style="display:flex;"><span>authorize(JSON.parse(content), listMajors);
</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">/**
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">* Create an OAuth2 client with the given credentials, and then execute the
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">* given callback function.
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">* @param {Object} credentials The authorization client credentials.
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">* @param {function} callback The callback to call with the authorized client.
</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">function</span> authorize (credentials, callback) {
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> { client_secret, client_id, redirect_uris } <span style="color:#ff7b72;font-weight:bold">=</span> credentials.installed;
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> oAuth2Client <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">new</span> google.auth.OAuth2(
</span></span><span style="display:flex;"><span>    client_id, client_secret, redirect_uris[<span style="color:#a5d6ff">0</span>]);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">// Check if we have previously stored a token.
</span></span></span><span style="display:flex;"><span>fs.readFile(TOKEN_PATH, (err, token) =&gt; {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">if</span> (err) <span style="color:#ff7b72">return</span> getNewToken(oAuth2Client, callback);
</span></span><span style="display:flex;"><span>    oAuth2Client.setCredentials(JSON.parse(token));
</span></span><span style="display:flex;"><span>    callback(oAuth2Client);
</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">/**
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">* Get and store new token after prompting for user authorization, and then
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">* execute the given callback with the authorized OAuth2 client.
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">* @param {google.auth.OAuth2} oAuth2Client The OAuth2 client to get token for.
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">* @param {getEventsCallback} callback The callback for the authorized client.
</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">function</span> getNewToken (oAuth2Client, callback) {
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> authUrl <span style="color:#ff7b72;font-weight:bold">=</span> oAuth2Client.generateAuthUrl({
</span></span><span style="display:flex;"><span>    access_type<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#39;offline&#39;</span>,
</span></span><span style="display:flex;"><span>    scope<span style="color:#ff7b72;font-weight:bold">:</span> SCOPES,
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>console.log(<span style="color:#a5d6ff">&#39;Authorize this app by visiting this url:&#39;</span>, authUrl);
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> rl <span style="color:#ff7b72;font-weight:bold">=</span> readline.createInterface({
</span></span><span style="display:flex;"><span>    input<span style="color:#ff7b72;font-weight:bold">:</span> process.stdin,
</span></span><span style="display:flex;"><span>    output<span style="color:#ff7b72;font-weight:bold">:</span> process.stdout,
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>rl.question(<span style="color:#a5d6ff">&#39;Enter the code from that page here: &#39;</span>, (code) =&gt; {
</span></span><span style="display:flex;"><span>    rl.close();
</span></span><span style="display:flex;"><span>    oAuth2Client.getToken(code, (err, token) =&gt; {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">if</span> (err) <span style="color:#ff7b72">return</span> console.error(<span style="color:#a5d6ff">&#39;Error while trying to retrieve access token&#39;</span>, err);
</span></span><span style="display:flex;"><span>    oAuth2Client.setCredentials(token);
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">// Store the token to disk for later program executions
</span></span></span><span style="display:flex;"><span>    fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) =&gt; {
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">if</span> (err) <span style="color:#ff7b72">return</span> console.error(err);
</span></span><span style="display:flex;"><span>        console.log(<span style="color:#a5d6ff">&#39;Token stored to&#39;</span>, TOKEN_PATH);
</span></span><span style="display:flex;"><span>    });
</span></span><span style="display:flex;"><span>    callback(oAuth2Client);
</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></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">/**
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">* Prints the names and majors of students in a sample spreadsheet:
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">* @see https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/edit
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">* @param {google.auth.OAuth2} auth The authenticated Google OAuth client.
</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">function</span> listMajors (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>sheets.spreadsheets.values.get({
</span></span><span style="display:flex;"><span>    spreadsheetId<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#39;1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms&#39;</span>,
</span></span><span style="display:flex;"><span>    range<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#39;Class Data!A2:E&#39;</span>,
</span></span><span style="display:flex;"><span>}, (err, res) =&gt; {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">if</span> (err) <span style="color:#ff7b72">return</span> console.log(<span style="color:#a5d6ff">&#39;The API returned an error: &#39;</span> <span style="color:#ff7b72;font-weight:bold">+</span> err);
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">const</span> rows <span style="color:#ff7b72;font-weight:bold">=</span> res.data.values;
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">if</span> (rows.length) {
</span></span><span style="display:flex;"><span>    console.log(<span style="color:#a5d6ff">&#39;Name, Major:&#39;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">// Print columns A and E, which correspond to indices 0 and 4.
</span></span></span><span style="display:flex;"><span>    rows.map((row) =&gt; {
</span></span><span style="display:flex;"><span>        console.log(<span style="color:#a5d6ff">`</span><span style="color:#a5d6ff">${</span>row[<span style="color:#a5d6ff">0</span>]<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff">, </span><span style="color:#a5d6ff">${</span>row[<span style="color:#a5d6ff">4</span>]<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">else</span> {
</span></span><span style="display:flex;"><span>    console.log(<span style="color:#a5d6ff">&#39;No data found.&#39;</span>);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div></li>
<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>node tools/google_sheets/index.js
</span></span></code></pre></div><ol>
<li>
<p>你應該會先遇到找不到 credentials.json 的錯誤</p>
<p><img src="https://deanlin.net/blog/202010/fb-ig-crawler-day-17/images/fb-ig-crawler-day-17-05.webp" width="1024" height="125" alt="2. 在專案加入官方提供的範例程式做測試（圖 5）" loading="lazy" decoding="async"></p>
<p>請將範例程式中<code>讀取 'credentials.json' 路徑修改成專案的路徑 'tools/google_sheets/credentials.json'</code></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:#8b949e;font-style:italic">//fs.readFile(&#39;credentials.json&#39;, (err, content) =&gt; {
</span></span></span><span style="display:flex;"><span>fs.readFile(<span style="color:#a5d6ff">&#39;tools/google_sheets/credentials.json&#39;</span>, (err, content) =&gt; {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">if</span> (err) <span style="color:#ff7b72">return</span> console.log(<span style="color:#a5d6ff">&#39;Error loading client secret file:&#39;</span>, err);
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">// Authorize a client with credentials, then call the Google Sheets API.
</span></span></span><span style="display:flex;"><span>    authorize(JSON.parse(content), listMajors);
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div></li>
<li>
<p>再執行一次 <code>node tools/google_sheets/index.js</code> 時下面會有連結請你打開</p>
<p><img src="https://deanlin.net/blog/202010/fb-ig-crawler-day-17/images/fb-ig-crawler-day-17-06.webp" width="1024" height="57" alt="2. 在專案加入官方提供的範例程式做測試（圖 6）" loading="lazy" decoding="async"></p>
</li>
<li>
<p>打開後的網頁會要你選擇登入的 Google 帳號，看到下面的網頁時別害怕，點擊 <code>進階</code> 然後再點擊 <code>前往「Quickstart」(不安全)</code></p>
<p><img src="https://deanlin.net/blog/202010/fb-ig-crawler-day-17/images/fb-ig-crawler-day-17-07.webp" width="1024" height="651" alt="2. 在專案加入官方提供的範例程式做測試（圖 7）" loading="lazy" decoding="async"></p>
</li>
<li>
<p>接著所有的授權都必須按 <code>允許</code> ，最後你會看到一組授權碼，把他複製下來貼回終端機(Terminal)就完成惹</p>
<p><img src="https://deanlin.net/blog/202010/fb-ig-crawler-day-17/images/fb-ig-crawler-day-17-08.webp" width="1024" height="503" alt="2. 在專案加入官方提供的範例程式做測試（圖 8）" loading="lazy" decoding="async"></p>
</li>
<li>
<p>如果終端機(Terminal)有輸出類似下面的資料時就代表你成功了</p>
<pre tabindex="0"><code>Name, Major:
Alexandra, English
Andrew, Math
Anna, English
Becky, Art
Benjamin, English
Carl, Art
Carrie, English
Dorothy, Math
...
</code></pre></li>
<li>
<p>聰明的你會發現 <code>專案根目錄下多了一個 token.json</code> 的檔案，如果說 credentials.json 是打開藏寶庫的鑰匙，token.json 就像是能夠把裡面寶藏搬進搬出的權杖，而這個權杖是有期限的(expiry_date)，期限過了就需要再申請</p>
<ol>
<li>
<p>為了方便管理我們把 <code>token.json 搬移到 tools/google_sheets</code> 的資料夾下</p>
</li>
<li>
<p><code>修改範例程式中讀取 token.json 的路徑</code></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:#8b949e;font-style:italic">//const TOKEN_PATH = &#39;token.json&#39;;
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> TOKEN_PATH <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">&#39;tools/google_sheets/token.json&#39;</span>;
</span></span></code></pre></div></li>
</ol>
</li>
</ol>
</li>
</ol>
<hr>
<h2 class="h-nomark" id="3-將-google-sheets-的憑證授權檔加入-gitignore">3. 將 Google Sheets 的憑證、授權檔加入 .gitignore<a class="h-anchor" href="#3-%e5%b0%87-google-sheets-%e7%9a%84%e6%86%91%e8%ad%89%e6%8e%88%e6%ac%8a%e6%aa%94%e5%8a%a0%e5%85%a5-gitignore" aria-label="這個段落的連結"></a></h2>
<p>我想應該不會有人想把藏寶庫的鑰匙跟權杖公開讓大家使用吧？如果你不小心把 <code>credentials.json、token.json</code> 兩個檔案加入版控就等於把你 Google Sheets 的權限送給別人了，所以你要調整 .gitignore 如下：</p>
<pre tabindex="0"><code>node_modules
.env
chromedriver.exe
debug.log
credentials.json
token.json
</code></pre><blockquote>
<p>不熟悉的朋友可以參考 <a href="https://deanlin.net/blog/202009/fb-ig-crawler-day-07/">【Day7】gitignore-請勿上傳敏感、主程式以外的資料</a> 的文章</p>
</blockquote>
<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/day17">這裡</a>找到喔</li>
<li>我也貼心地把昨天的把昨天的程式碼打包成<a href="https://github.com/dean9703111/ithelp_30days/raw/master/sampleCode/day16_sample_code.zip">壓縮檔</a>，你可以用裡面乾淨的環境來實作今天Google Sheets的起手式喔
<ul>
<li>請記得在終端機下指令 <strong>yarn</strong> 才會把之前的套件安裝</li>
<li>要在tools/google_sheets資料夾放上自己的憑證，今天的程式才能執行喔</li>
</ul>
</li>
</ul>
<hr>
<blockquote>
<p><img src="https://deanlin.net/blog/202010/fb-ig-crawler-day-17/images/fb-ig-crawler-day-17-09.webp" width="1024" height="233" alt="ℹ️ 專案原始碼（圖 9）" 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>
