<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>API on IT 空間</title><link>https://blog.jiatool.com/tags/API/</link><description>Recent content in API on IT 空間</description><generator>Hugo -- gohugo.io</generator><language>zh</language><managingEditor>jia@jiatool.com (Jia)</managingEditor><webMaster>jia@jiatool.com (Jia)</webMaster><copyright>&amp;copy;{year}, Jia All Rights Reserved</copyright><lastBuildDate>Sat, 24 Jan 2026 20:45:00 +0800</lastBuildDate><atom:link href="https://blog.jiatool.com/tags/API/index.xml" rel="self" type="application/rss+xml"/><item><title>LiteLLM 串接需要 API Key 身分驗證的 Ollama API</title><link>https://blog.jiatool.com/posts/litellm_ollama_api_key/</link><pubDate>Sat, 24 Jan 2026 20:45:00 +0800</pubDate><author>jia@jiatool.com (Jia)</author><atom:modified>Sat, 24 Jan 2026 20:45:00 +0800</atom:modified><guid>https://blog.jiatool.com/posts/litellm_ollama_api_key/</guid><description>前言 上一篇我們為 Ollama 加了身份驗證，以提升安全性。 不過因為我有在使用 LiteLLM Proxy Server，需要為 LiteLLM 連 Ollama 時加上 API Key，但遇到了一些問題，最終改使用 Ollama</description><content:encoded>&lt;h2 id="前言">前言&lt;/h2>
&lt;p>上一篇我們為 Ollama 加了身份驗證，以提升安全性。&lt;/p>
&lt;p>不過因為我有在使用 LiteLLM Proxy Server，需要為 LiteLLM 連 Ollama 時加上 API Key，但遇到了一些問題，最終改使用 &lt;a href="https://ollama.com/blog/openai-compatibility" target="_blank" rel="noopener">
Ollama 的 OpenAI 相容格式
&lt;/a> 去串接成功~👍&lt;/p>
&lt;p>* 延伸閱讀：&lt;a href="https://blog.jiatool.com/posts/ollama_api_key_setup" target="_blank" rel="noopener">
透過 Nginx 為 Ollama 加上 API Key 身分驗證，提升安全性
&lt;/a>&lt;/p>
&lt;br/>
&lt;p>也藉此記錄下來，供需要的讀者參考~&lt;/p>
&lt;br/>
&lt;p>LiteLLM AI Gateway (LLM Proxy) 可以讓使用者透過統一介面呼叫不同的 API，並且可追蹤支出、Log 紀錄、為每個虛擬 API Key/使用者設定預算、負載平衡&amp;hellip;&amp;hellip;等等功能。&lt;br />
如果是像公司、團體內，要提供不同 LLM API 給其他人使用，LiteLLM 或許是個不錯的工具。&lt;/p>
&lt;br/>
&lt;div class="alert alert-info" role="alert" data-dir="ltr">[小廣告] 我製作了一款可愛的「&lt;a href="https://blog.jiatool.com/posts/penguin_wizard_sticker">企鵝魔法師&lt;/a>」貼圖~&lt;br />
歡迎下載：&lt;a href="https://line.me/S/sticker/31703222">LINE 貼圖&lt;/a>、&lt;a href="https://t.me/addstickers/penguin_wizard">Telegram 貼圖 (免費)&lt;/a>&lt;/p>
&lt;img src="https://res.cloudinary.com/jiablog/penguin_wizard_sticker/show.png" caption="企鵝魔法師" width="600px" position="center">&lt;/div>
&lt;br/>
&lt;h2 id="設定-litellm-config">設定 LiteLLM Config&lt;/h2>
&lt;p>需要為 LiteLLM 連 Ollama 時加上 API Key。&lt;/p>
&lt;p>* 以下是在 &lt;code>config.yaml&lt;/code> 內設定。&lt;/p>
&lt;br/>
&lt;p>原本我想說簡單，在 config.yaml 裡加上 &lt;code>api_key&lt;/code> 欄位就行，但試了幾次發現 api_key 不會被帶上 (從 LiteLLM 的 source code 查也確實在連 Ollama 時不會帶上 api_key)。&lt;/p>
&lt;p>後來我想到 &lt;a href="%28https://ollama.com/blog/openai-compatibility%29" target="_blank" rel="noopener">
Ollama 也支援 OpenAI API 相容格式
&lt;/a>，或許 LiteLLM 改使用 OpenAI 格式去串接我的 Ollama 可行。&lt;br />
經過實測，確實 OK~&lt;/p>
&lt;pre>&lt;code>model_list:
- model_name: gpt-oss:20b
litellm_params:
model: openai/gpt-oss:20b
api_base: &amp;quot;http://abc.com:11434/v1&amp;quot;
api_key: &amp;quot;sk-123456&amp;quot;
&lt;/code>&lt;/pre>&lt;br/>
&lt;p>也可以將 api_base 和 api_key 設定移到環境變數，並透過以下方式帶入：&lt;/p>
&lt;pre>&lt;code>model_list:
- model_name: gpt-oss:20b
litellm_params:
model: openai/gpt-oss:20b
api_base: &amp;quot;os.environ/OLLAMA_API_BASE&amp;quot;
api_key: &amp;quot;os.environ/OLLAMA_API_KEY&amp;quot;
&lt;/code>&lt;/pre>&lt;p>環境變數像我使用 K8S 就如下設定：&lt;/p>
&lt;pre>&lt;code>env:
- name: OLLAMA_API_BASE
value: 'http://abc.com:11434/v1'
- name: OLLAMA_API_KEY
value: sk-123456
&lt;/code>&lt;/pre>&lt;br/>
&lt;!--adsense-->
&lt;br/>
&lt;h2 id="結語">結語&lt;/h2>
&lt;p>有遇到同樣問題的讀者，可以參考我的作法~&lt;/p>
&lt;br/>
&lt;p>對生成式 AI 感興趣的讀者，記得追蹤 FB 粉專『&lt;a href="https://www.facebook.com/jiatool" target="_blank" rel="noopener">
IT空間
&lt;/a>』，以免錯過最新的發文通知呦~🔔&lt;/p>
&lt;br/>
&lt;br/>
&lt;hr />
&lt;p>參考：&lt;br />
&lt;a href="https://github.com/BerriAI/litellm" target="_blank" rel="noopener">
LiteLLM | GitHub
&lt;/a>&lt;br />
&lt;a href="https://docs.litellm.ai/docs/simple_proxy" target="_blank" rel="noopener">
LiteLLM 官方文件
&lt;/a>&lt;/p>
&lt;br/>
&lt;blockquote>
&lt;p>成功路上並不擁擠，因為堅持的人不多。&lt;/p>
&lt;/blockquote></content:encoded><dc:creator>Jia</dc:creator><media:content url="https://blog.jiatool.comimages/cover/litellm_ollama_api_key.jpg" medium="image"><media:title type="html">featured image</media:title></media:content><media:content url="https://blog.jiatool.comimages/posts/litellm_ollama_api_key_meta.jpg" medium="image"><media:title type="html">meta image</media:title></media:content><category>LiteLLM</category><category>Ollama</category><category>API</category><category>AI</category><category>人工智慧</category><category>LLM</category><category>分享</category></item><item><title>GitHub Models 讓你免費玩 GPT、Llama、Phi，還提供 API 串接</title><link>https://blog.jiatool.com/posts/github_models/</link><pubDate>Sat, 11 Jan 2025 21:15:00 +0800</pubDate><author>jia@jiatool.com (Jia)</author><atom:modified>Sat, 12 Apr 2025 14:00:00 +0800</atom:modified><guid>https://blog.jiatool.com/posts/github_models/</guid><description>2025-04-12 更新 GitHub Token 權限設定 前言 我前陣子才發現，原來 GitHub 上面也有可以免費使用的 LLM，像是 GPT-4o、Llama-3.3、Phi-3.5&amp;hellip</description><content:encoded>&lt;div class="alert alert-info" role="alert" data-dir="ltr">2025-04-12 更新 GitHub Token 權限設定&lt;/div>
&lt;h2 id="前言">前言&lt;/h2>
&lt;p>我前陣子才發現，原來 GitHub 上面也有可以免費使用的 LLM，像是 GPT-4o、Llama-3.3、Phi-3.5&amp;hellip;等等，甚至還提供 API 可串接程式！&lt;/p>
&lt;p>雖然 GitHub Models 主要是讓我們在開發生成式 AI 應用程式測試用，算是試用性質，所以有 速率 &amp;amp; Token 數量限制，但我覺得用作個人專案還蠻不錯的，每日請求上限也不算太少 (50~150 次，依模型而定)，有需求的網友可以試試~&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/github_models/github_models.jpg" alt="GitHub Models" data-caption="GitHub Models" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='650px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:650px;height:;"/>
&lt;figcaption style="text-align: center;">
GitHub Models
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;p>如果你發現還不能使用，可加入候補名單：&lt;a href="https://github.com/marketplace/models/waitlist">https://github.com/marketplace/models/waitlist&lt;/a>&lt;/p>
&lt;br/>
&lt;!--adsense-->
&lt;br/>
&lt;h2 id="速率限制">速率限制&lt;/h2>
&lt;p>在開始使用之前，首先來看看它速率限制到底是多少。&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://docs.github.com/en/github-models/prototyping-with-ai-models#rate-limits" target="_blank" rel="noopener">
Rate limits | GitHub Docs
&lt;/a>&lt;/li>
&lt;/ul>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/github_models/rate_limits.jpg" alt="GitHub Models 使用速率限制" data-caption="GitHub Models 使用速率限制" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='700px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:700px;height:;"/>
&lt;figcaption style="text-align: center;">
GitHub Models 使用速率限制
&lt;/figcaption>
&lt;/figure>
&lt;p>GitHub Models 是依照模型分成 Low、High、Embedding 等級去做限制，&lt;br />
在 模型介紹 和 Playground 頁面都有寫此模型是採用哪個限制等級 (&amp;quot;Rate limit tier&amp;quot;)。&lt;/p>
&lt;p>* 表格底下雖然還有 Azure OpenAI o1-preview 和 Azure OpenAI o1-mini，但我好像沒辦法使用。&lt;/p>
&lt;br/>
&lt;p>例如 GPT-4o 是 &amp;quot;High&amp;quot;，那它的限制就是：&lt;/p>
&lt;ul>
&lt;li>每分鐘請求數：10 次&lt;/li>
&lt;li>每天的請求數：50 次&lt;/li>
&lt;li>每個請求的 Tokens：輸入 8000, 輸出 4000&lt;/li>
&lt;li>並發請求：2 個&lt;/li>
&lt;/ul>
&lt;p>* 當然假如你是 Copilot Business 或 Copilot Enterprise，那可使用次數就會更多。&lt;/p>
&lt;br/>
&lt;h2 id="模型清單">模型清單&lt;/h2>
&lt;p>GitHub Models 有提供哪些模型讓我們試用呢？&lt;/p>
&lt;p>這邊有完整支援的模型清單：&lt;a href="https://github.com/marketplace?type=models">https://github.com/marketplace?type=models&lt;/a>&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/github_models/models_list.jpg" alt="GitHub Models 支援模型清單" data-caption="GitHub Models 支援模型清單" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='900px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:900px;height:;"/>
&lt;figcaption style="text-align: center;">
GitHub Models 支援模型清單
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;p>例如聊天的模型有：&lt;/p>
&lt;ul>
&lt;li>OpenAI GPT-4o&lt;/li>
&lt;li>OpenAI GPT-4o mini&lt;/li>
&lt;li>DeepSeek-V3-0324&lt;/li>
&lt;li>DeepSeek-R1&lt;/li>
&lt;li>Llama 4 Maverick 17B 128E Instruct FP8&lt;/li>
&lt;li>Llama-3.3-70B-Instruct&lt;/li>
&lt;li>Llama-3.2-90B-Vision-Instruct&lt;/li>
&lt;li>Phi-4&lt;/li>
&lt;li>Phi-3.5-MoE instruct&lt;/li>
&lt;li>Phi-3.5-vision instruct&lt;/li>
&lt;li>Mistral Large&lt;/li>
&lt;li>Mistral Small 3.1&lt;/li>
&lt;li>Codestral 25.01&lt;/li>
&lt;li>Cohere Command R+&lt;/li>
&lt;li>AI21 Jamba 1.5&lt;/li>
&lt;li>JAIS 30b Chat&lt;/li>
&lt;li>&amp;hellip;(更多)&lt;/li>
&lt;/ul>
&lt;p>還有 Embedding 嵌入模型：&lt;/p>
&lt;ul>
&lt;li>OpenAI Text Embedding 3&lt;/li>
&lt;li>Cohere Embed v3 Multilingual&lt;/li>
&lt;li>&amp;hellip;(更多)&lt;/li>
&lt;/ul>
&lt;br/>
&lt;p>點選任一個模型後，會進入模型介紹頁面。&lt;/p>
&lt;p>會有模型的相關說明介紹、測試評估分數、License 等等，右邊區塊還有像是 簡介、Context (模型&amp;quot;本身&amp;quot; 輸入、輸出 tokens 限制)、訓練資料日期、速率限制等級 (Rate limit tier)、提供者、支援語言 等等資訊。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/github_models/model_readme.jpg" alt="模型介紹頁面" data-caption="模型介紹頁面" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='1000px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:1000px;height:;"/>
&lt;figcaption style="text-align: center;">
模型介紹頁面
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;h2 id="playground">Playground&lt;/h2>
&lt;p>從剛剛 &amp;quot;模型介紹頁面&amp;quot; 點擊右上角「Playground」，或在 &lt;a href="https://github.com/marketplace/models" target="_blank" rel="noopener">
GitHub Marketplace
&lt;/a> 左上角選擇一個模型。&lt;br />
即可進入 Playground 頁面跟它聊天。&lt;/p>
&lt;p>* 例如 &lt;a href="https://github.com/marketplace/models/azure-openai/gpt-4o/playground" target="_blank" rel="noopener">
GPT-4o 的 Playground
&lt;/a>&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/github_models/model_playground.jpg" alt="模型 Playground 頁面" data-caption="模型 Playground 頁面" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='900px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:900px;height:;"/>
&lt;figcaption style="text-align: center;">
模型 Playground 頁面
&lt;/figcaption>
&lt;/figure>
&lt;p>先看右側區塊可以切換 &amp;quot;Parameters&amp;quot; 和 &amp;quot;Details&amp;quot;：&lt;/p>
&lt;ul>
&lt;li>Parameters：設定、調整模型參數 (System prompt、Response format、Max Tokens、Temperature&amp;hellip;&amp;hellip;)。&lt;/li>
&lt;li>Details：模型的相關說明 (就跟剛剛的模型介紹頁面差不多)。&lt;/li>
&lt;/ul>
&lt;br/>
&lt;p>再來看左側區塊分為 &amp;quot;Chat&amp;quot;、&amp;quot;Code&amp;quot;、&amp;quot;Raw&amp;quot;：&lt;/p>
&lt;ul>
&lt;li>Chat：如同使用 ChatGPT、Gemini 一樣，可以與模型做多輪的聊天。&lt;/li>
&lt;li>Code：展示 API 如何使用，有不同程式語言的範例，下一節 &lt;a href="#api" target="_blank" rel="noopener">
API
&lt;/a> 還會介紹。&lt;/li>
&lt;li>Raw：你與模型對話紀錄的原始資料。&lt;/li>
&lt;/ul>
&lt;br/>
&lt;p>另外，&lt;br />
在頁面左上方還可以看到「Compare」按鈕，用於比較兩種不同模型的回答，在你輸入問題(prompt)後，它會同時送給兩個模型，讓你方便比較兩種模型哪個回覆比較好。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/github_models/compare_model.jpg" alt="比較兩種不同模型的回答" data-caption="比較兩種不同模型的回答" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='900px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:900px;height:;"/>
&lt;figcaption style="text-align: center;">
比較兩種不同模型的回答
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;p>如果你想將目前調整好的參數、聊天記錄儲存起來 (甚至分享)，可以使用右上角的「Preset」功能。&lt;/p>
&lt;br/>
&lt;h2 id="api">API&lt;/h2>
&lt;p>如同文章標題提到的，除了在網頁使用 Playground 介面，GitHub Models 還提供 API 供我們串接自己的程式做測試。&lt;/p>
&lt;br/>
&lt;h3 id="創建-github-token">創建 GitHub Token&lt;/h3>
&lt;p>在開始使用 API 之前，我們要先去 GitHub 建立 Token，用作身份驗證。&lt;/p>
&lt;p>* 關於 GitHub 的 Token 介紹，可以參考這篇官方文件：&lt;a href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens" target="_blank" rel="noopener">
Managing your personal access tokens
&lt;/a>&lt;/p>
&lt;br/>
&lt;p>Settings &amp;gt; 左側最下方 Developer settings &amp;gt; Personal access tokens &amp;gt; &lt;a href="https://github.com/settings/personal-access-tokens" target="_blank" rel="noopener">
Fine-grained tokens
&lt;/a>&lt;/p>
&lt;p>點選 Generate new token。&lt;/p>
&lt;br/>
&lt;p>Expiration 過期時間可以改成 &amp;quot;No expiration&amp;quot; (無期限)。&lt;/p>
&lt;p>Repository access 欄位維持 &amp;quot;Public Repositories&amp;quot; 即可。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/github_models/expiration_repository.jpg" alt="Expiration、Repository access 欄位設定" data-caption="Expiration、Repository access 欄位設定" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='750px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:750px;height:;"/>
&lt;figcaption style="text-align: center;">
Expiration、Repository access 欄位設定
&lt;/figcaption>
&lt;/figure>
&lt;p>&lt;strong>重點&lt;/strong>：&lt;br />
Permissions &amp;gt; Account permissions &amp;gt; Models 要改為 &amp;quot;Read-only&amp;quot;，這樣才有 GitHub Models 的權限。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/github_models/models_readonly.jpg" alt="Permissions &amp;gt; Account permissions &amp;gt; Models 權限" data-caption="Permissions &amp;gt; Account permissions &amp;gt; Models 權限" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='800px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:800px;height:;"/>
&lt;figcaption style="text-align: center;">
Permissions &amp;gt; Account permissions &amp;gt; Models 權限
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;p>填寫完欄位後，最下方點擊 Generate token 按鈕。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/github_models/generate_token_0.jpg" alt="創建新的 Token" data-caption="創建新的 Token" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='750px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:750px;height:;"/>
&lt;figcaption style="text-align: center;">
創建新的 Token
&lt;/figcaption>
&lt;/figure>
&lt;p>將 token 複製並保存好，之後忘記就只能再重新產生了。&lt;/p>
&lt;p>Fine-grained personal access token 會長的類似這樣：&lt;code>github_pat_11AHxxxxxxxxxxxxxxxxxxxxxxxxxCp7pLSr3a&lt;/code>&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/github_models/generate_token_2.jpg" alt="將 Token 複製存起來，之後就沒辦法再看到了" data-caption="將 Token 複製存起來，之後就沒辦法再看到了" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='800px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:800px;height:;"/>
&lt;figcaption style="text-align: center;">
將 Token 複製存起來，之後就沒辦法再看到了
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;h3 id="使用範例">使用範例&lt;/h3>
&lt;p>回到剛剛 Playground 頁面的 &amp;quot;Code&amp;quot; 分頁 (像是 &lt;a href="https://github.com/marketplace/models/azure-openai/gpt-4o/playground/code" target="_blank" rel="noopener">
GPT-4o 的 Playground
&lt;/a>)。&lt;/p>
&lt;p>右上角可以下拉清單選擇不同的程式語言、SDK (OpenAI SDK 或 Azure AI Inference SDK)。&lt;/p>
&lt;br/>
&lt;p>切到 REST 語言，可以看到實際發送請求的 URL、Headers、Body 等等，API 格式是跟 OpenAI API 一樣的，所以如果你使用的套件、框架、軟體有支援 OpenAI API 格式，那就可以直接切換來用，或者以後 GitHub Models 測試完要換到穩定的付費 Azure OpenAI、OpenAI 也很簡單。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/github_models/example_code.jpg" alt="官方範例程式碼" data-caption="官方範例程式碼" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='900px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:900px;height:;"/>
&lt;figcaption style="text-align: center;">
官方範例程式碼
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;br/>
&lt;p>這邊我使用 Postman 來示範如何發送請求。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/github_models/postman_1.jpg" alt="API URL、Headers 的設定" data-caption="API URL、Headers 的設定" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='900px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:900px;height:;"/>
&lt;figcaption style="text-align: center;">
API URL、Headers 的設定
&lt;/figcaption>
&lt;/figure>
&lt;p>Request URL: &lt;code>https://models.inference.ai.azure.com/chat/completions&lt;/code>&lt;br />
Request Method: &lt;code>POST&lt;/code>&lt;br />
Request Headers:&lt;/p>
&lt;pre>&lt;code>Content-Type: application/json
Authorization: Bearer github_pat_11AHxxxxxxxxxxxxxxxxxxxxxxxxxCp7pLSr3a
&lt;/code>&lt;/pre>&lt;p>* Authorization 內 Bearer 後面那串就是剛剛創建的 personal access token。&lt;/p>
&lt;br/>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/github_models/postman_2.jpg" alt="API Body 的設定" data-caption="API Body 的設定" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='900px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:900px;height:;"/>
&lt;figcaption style="text-align: center;">
API Body 的設定
&lt;/figcaption>
&lt;/figure>
&lt;p>Body (JSON 格式)：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;messages&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;role&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;system&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;content&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>
&lt;span class="p">},&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;role&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;user&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;content&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;你是誰？&amp;#34;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">],&lt;/span>
&lt;span class="nt">&amp;#34;model&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Llama-3.3-70B-Instruct&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;temperature&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mf">0.8&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;max_tokens&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">2048&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;top_p&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mf">0.1&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>* 只有 messages 和 model 欄位是必要的&lt;/p>
&lt;br/>
&lt;p>回傳資料範例：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;choices&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;finish_reason&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;stop&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;index&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;message&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;content&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;我是一個人工智慧語言模型，稱為LLaMA。LLaMA是Meta開發的一種人工智慧模型，旨在處理和生成類似人類的語言。&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;role&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;assistant&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;tool_calls&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">null&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">],&lt;/span>
&lt;span class="nt">&amp;#34;created&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">1734225172&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;id&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;cmpl-6c7bdcda-1211-4f5a-b9fc-2f2526465445&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;model&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Llama-3.3-70B-Instruct&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;object&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;chat.completion&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;usage&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;completion_tokens&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">47&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;prompt_tokens&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">39&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;total_tokens&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">86&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>* 不過我在猜它是不是還會塞入其他 prompt，因為 usage &amp;gt; prompt_tokens 看起來明顯大於我下的 prompt token 數量。知道的網友可以留言幫我解惑~🙏&lt;/p>
&lt;br/>
&lt;!--adsense-->
&lt;br/>
&lt;h2 id="結語">結語&lt;/h2>
&lt;p>如果你剛好在開發 LLM 應用專案，或使用量不多，推薦可以嘗試 GitHub Models。&lt;/p>
&lt;br/>
&lt;p>如果對於 生成式 AI 有興趣的讀者，記得追蹤『&lt;a href="https://www.facebook.com/jiatool" target="_blank" rel="noopener">
IT空間
&lt;/a>』FB 粉專，才不會錯過最新的發文通知呦~🔔&lt;/p>
&lt;br/>
&lt;br/>
&lt;hr />
&lt;p>參考：&lt;br />
&lt;a href="https://github.com/marketplace/models" target="_blank" rel="noopener">
GitHub Marketplace Models
&lt;/a>&lt;br />
&lt;a href="https://docs.github.com/en/github-models/prototyping-with-ai-models" target="_blank" rel="noopener">
GitHub Models 說明文件
&lt;/a>&lt;/p>
&lt;br/>
&lt;blockquote>
&lt;p>我不是最好的那個，但我想成為最努力的那個。&lt;/p>
&lt;p align="right">—— 李洋 (台灣羽球國手)&lt;/p>
&lt;/blockquote></content:encoded><dc:creator>Jia</dc:creator><media:content url="https://blog.jiatool.comimages/cover/github_models.jpg" medium="image"><media:title type="html">featured image</media:title></media:content><media:content url="https://blog.jiatool.comimages/posts/github_models_meta.jpg" medium="image"><media:title type="html">meta image</media:title></media:content><category>GitHub</category><category>API</category><category>LLM</category><category>生成式AI</category><category>AI</category><category>人工智慧</category><category>分享</category></item><item><title>[Metabase 系列] Metabase API 使用教學，輕鬆串接自己的系統</title><link>https://blog.jiatool.com/posts/metabase_api/</link><pubDate>Fri, 18 Oct 2024 15:10:00 +0800</pubDate><author>jia@jiatool.com (Jia)</author><atom:modified>Fri, 18 Oct 2024 15:10:00 +0800</atom:modified><guid>https://blog.jiatool.com/posts/metabase_api/</guid><description>前言 前幾篇我們學會了安裝 Metabase、建立提問和圖表、建立 Dashboard，那你有沒有想過能否把 Metabase 串接到自己的系統內呢？例如可以做一些</description><content:encoded>&lt;h2 id="前言">前言&lt;/h2>
&lt;p>前幾篇我們學會了安裝 Metabase、建立提問和圖表、建立 Dashboard，那你有沒有想過能否把 Metabase 串接到自己的系統內呢？例如可以做一些自動化執行任務，或需要大量批次處理。&lt;/p>
&lt;p>其實 Metabase 本身就有提供很完善的 API。應該說，Metabase 本身連接前端和後端，就是使用這組 API，所以幾乎你在 Metabase 執行的所有操作，都有對應的 API 端點可以使用。&lt;/p>
&lt;br/>
&lt;p>今天我們來學習一下如何使用 Metabase API。&lt;/p>
&lt;br/>
&lt;p>Metabase 系列教學文章：&lt;/p>
&lt;ol>
&lt;li>&lt;a href="https://blog.jiatool.com/posts/metabase_install" target="_blank" rel="noopener">
Metabase 簡介與安裝教學，BI 工具推薦
&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://blog.jiatool.com/posts/metabase_chart" target="_blank" rel="noopener">
建立提問 (Question) 與各式圖表 (Chart) 教學
&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://blog.jiatool.com/posts/metabase_dashboard" target="_blank" rel="noopener">
建立 Dashboard (資訊看板、儀表板) 教學
&lt;/a>&lt;/li>
&lt;li>Metabase API 使用教學，輕鬆串接自己的系統《本篇》&lt;/li>
&lt;/ol>
&lt;br/>
&lt;p>* 本文使用 Metabase 版本為：v0.50.28&lt;/p>
&lt;br/>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/metabase_install/background.jpg" alt="圖片來源：Metabase 官網" data-caption="圖片來源：Metabase 官網" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='800px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:800px;height:;"/>
&lt;figcaption style="text-align: center;">
圖片來源：Metabase 官網
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;!--adsense-->
&lt;br/>
&lt;h2 id="metabase-api">Metabase API&lt;/h2>
&lt;p>Metabase 本身連接前端和後端，就是使用這組 API，所以幾乎我們在 Metabase 可以執行的所有操作，都有對應的 API 端點。&lt;br />
例如新增資料庫、建立提問、建立 Dashboard、人員權限設定、管理員設定、搜尋功能&amp;hellip;&lt;/p>
&lt;p>* 這邊有官方的 &lt;a href="https://www.metabase.com/learn/metabase-basics/administration/administration-and-operation/metabase-api" target="_blank" rel="noopener">
API 教學
&lt;/a> 和 &lt;a href="https://www.metabase.com/docs/latest/api-documentation" target="_blank" rel="noopener">
API documentation
&lt;/a> 可以參考。&lt;/p>
&lt;br/>
&lt;p>但該如何知道，我們想要的功能該用哪個 API 端點，以及需要帶上哪些參數呢？&lt;/p>
&lt;p>有三種方法可以來尋找：&lt;/p>
&lt;ol>
&lt;li>從 &lt;a href="https://www.metabase.com/docs/latest/api-documentation" target="_blank" rel="noopener">
Metabase API documentation
&lt;/a>&lt;/li>
&lt;li>在 API live docs 進行測試&lt;/li>
&lt;li>使用瀏覽器的開發人員工具&lt;/li>
&lt;/ol>
&lt;br/>
&lt;p>Metabase API documentation 就是官方整理的文件，可以快速查詢。&lt;/p>
&lt;p>但有些看出不出參數要如何帶入，以及它回傳資料的內容與格式，所以我自己比較常用 &amp;quot;開發人員工具&amp;quot; 的方式來查詢。&lt;br />
(API live docs 好像是&lt;a href="https://github.com/metabase/metabase/pull/40162" target="_blank" rel="noopener">
最近才加入
&lt;/a>的功能)&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/metabase_api/metabase_api_doc.jpg" alt="Metabase API documentation" data-caption="Metabase API documentation" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='900px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:900px;height:;"/>
&lt;figcaption style="text-align: center;">
Metabase API documentation
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;p>API live docs 是可以在你自己運行的 Metabase 上，查看透過 RapiDoc 提供的即時 OpenAPI 文件。只要前往 &lt;code>/api/docs&lt;/code> 路徑 (例如 &lt;code>http://localhost:3000/api/docs&lt;/code>)。&lt;/p>
&lt;p>左邊清單可以方便找到 API 端點，點擊右方的 &amp;quot;TRY&amp;quot; 會發送請求，並且會顯示回傳的結果，挺方便的。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/metabase_api/api_live_docs.jpg" alt="API live docs" data-caption="API live docs" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='900px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:900px;height:;"/>
&lt;figcaption style="text-align: center;">
API live docs
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;p>而瀏覽器的開發人員工具，就跟我們在寫網站或網路爬蟲時，使用的方式一樣。&lt;/p>
&lt;p>開啟瀏覽器的 &lt;strong>開發人員工具&lt;/strong> (F12 或 Ctrl + Shift + i)，切換到 &amp;quot;Network&amp;quot; &amp;gt; &amp;quot;Fetch/XHR&amp;quot; 分頁。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/metabase_api/browser_devtools.jpg" alt="瀏覽器 的 開發人員工具" data-caption="瀏覽器 的 開發人員工具" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='900px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:900px;height:;"/>
&lt;figcaption style="text-align: center;">
瀏覽器 的 開發人員工具
&lt;/figcaption>
&lt;/figure>
&lt;ul>
&lt;li>&amp;quot;Headers&amp;quot; 頁面可以查看請求的網址與方法&lt;/li>
&lt;li>&amp;quot;Payload&amp;quot; 頁面可以查看請求的 Body&lt;/li>
&lt;li>&amp;quot;Preview&amp;quot; 頁面可以查看請求的回傳結果&lt;/li>
&lt;/ul>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/metabase_api/request.jpg" alt="瀏覽器 的 開發人員工具" data-caption="瀏覽器 的 開發人員工具" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='900px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:900px;height:;"/>
&lt;figcaption style="text-align: center;">
瀏覽器 的 開發人員工具
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;h3 id="api-金鑰-api-key">API 金鑰 (API key)&lt;/h3>
&lt;p>剛剛我們再查看有哪些 API 端點時，因為是在登入的瀏覽器內操作，所以它有自動帶上我們的身分認證，但之後要整合進我們的程式或專案，那就需要使用 API 金鑰做身分認證。&lt;/p>
&lt;br/>
&lt;p>在 Metabase 裡右上角齒輪 &amp;gt; 管理員設定 &amp;gt; 設定 &amp;gt; 授權認證 &amp;gt; API 金鑰。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/metabase_api/admin_setting.jpg" alt="右上角齒輪 &amp;gt; 管理員設定" data-caption="右上角齒輪 &amp;gt; 管理員設定" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='400px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:400px;height:;"/>
&lt;figcaption style="text-align: center;">
右上角齒輪 &amp;gt; 管理員設定
&lt;/figcaption>
&lt;/figure>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/metabase_api/setting_api_key.jpg" alt="管理員設定內 設定 &amp;gt; 授權認證 &amp;gt; API 金鑰" data-caption="管理員設定內 設定 &amp;gt; 授權認證 &amp;gt; API 金鑰" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='750px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:750px;height:;"/>
&lt;figcaption style="text-align: center;">
管理員設定內 設定 &amp;gt; 授權認證 &amp;gt; API 金鑰
&lt;/figcaption>
&lt;/figure>
&lt;p>點擊「建立 API 金鑰」。&lt;/p>
&lt;p>金鑰名稱自己取一個，純粹為了區分用。&lt;br />
接下來要選擇一個群組，該 API 金鑰會擁有與此群組相同的權限。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/metabase_api/create_api_key.jpg" alt="建立 API 金鑰" data-caption="建立 API 金鑰" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='500px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:500px;height:;"/>
&lt;figcaption style="text-align: center;">
建立 API 金鑰
&lt;/figcaption>
&lt;/figure>
&lt;p>建立後，它會顯示完整的 API 金鑰，這時候要將其複製並保存起來。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/metabase_api/copy_api_key.jpg" alt="複製 API 金鑰" data-caption="複製 API 金鑰" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='500px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:500px;height:;"/>
&lt;figcaption style="text-align: center;">
複製 API 金鑰
&lt;/figcaption>
&lt;/figure>
&lt;p>之後只能編輯 API 金鑰名稱跟改群組，金鑰內容忘記就只能再產生新的了。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/metabase_api/edit_api_key.jpg" alt="編輯 API 金鑰" data-caption="編輯 API 金鑰" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='850px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:850px;height:;"/>
&lt;figcaption style="text-align: center;">
編輯 API 金鑰
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;p>在使用 API 時，只要將此 API 金鑰帶入 Headers 內即可：&lt;/p>
&lt;pre>&lt;code>x-api-key: YOUR_API_KEY
&lt;/code>&lt;/pre>&lt;p>關於其他說明，可以參考 &lt;a href="https://www.metabase.com/docs/latest/people-and-groups/api-keys" target="_blank" rel="noopener">
官方文件 API keys
&lt;/a>。&lt;/p>
&lt;br/>
&lt;h4 id="session-token">Session Token&lt;/h4>
&lt;p>除了使用 API key 驗證以外，其實還可以使用 session token 來驗證，這個就等同輸入帳號密碼的方式。&lt;/p>
&lt;p>(在以前的版本，還沒有 API key 的時候，就只能透過 session token 驗證，不過它過一段時間後會過期，需要再重新取得，現在新版都建議直接使用 API key 即可)&lt;/p>
&lt;p>* 帳號 是 email 格式，例如 &lt;code>person@metabase.com&lt;/code>&lt;/p>
&lt;pre>&lt;code class="language-curl" data-lang="curl">curl -X POST \
-H &amp;quot;Content-Type: application/json&amp;quot; \
-d '{&amp;quot;username&amp;quot;: &amp;quot;帳號&amp;quot;, &amp;quot;password&amp;quot;: &amp;quot;密碼&amp;quot;}' \
http://localhost:3000/api/session
&lt;/code>&lt;/pre>&lt;p>它會回傳像是 &lt;code>{ &amp;quot;id&amp;quot;: &amp;quot;38f4939c-ad7f-4cbe-ae54-30946daf8593&amp;quot; }&lt;/code>。&lt;/p>
&lt;p>之後在使用 API 時，將此 id 帶入 Headers 內：&lt;/p>
&lt;pre>&lt;code>X-Metabase-Session: 38f4939c-ad7f-4cbe-ae54-30946daf8593
&lt;/code>&lt;/pre>&lt;br/>
&lt;p>* 預設情況，session token 有效期為 14 天。可以透過設定環境變數 &lt;code>MB_SESSION_AGE&lt;/code> （值以分鐘為單位）來修改持續時間。&lt;br />
* 最好將 session token 保存起來重複使用，直到過期再重新取得。因為安全因素，使用帳密取得 ession token 有速率限制。&lt;/p>
&lt;br/>
&lt;h3 id="api-如何使用">API 如何使用&lt;/h3>
&lt;p>在整合進我們的程式或專案之前，可以先使用 HTTP Request、測試 API 的工具，例如 Postman 來先測試，方便我們理解該如何使用、回傳資料長怎樣。&lt;/p>
&lt;p>以下就以最有名的 Postman 工具來示範。&lt;/p>
&lt;br/>
&lt;br/>
&lt;p>我們就以取得「全部群組的清單」來當第一個例子。&lt;br />
* 官方 API 文件：&lt;a href="https://www.metabase.com/docs/latest/api/permissions#get-apipermissionsgroup" target="_blank" rel="noopener">
GET /api/permissions/group
&lt;/a>&lt;/p>
&lt;pre>&lt;code>Request URL：http://localhost:3000/api/permissions/group
Request Method：GET
Headers：
x-api-key:mb_GEeE7gBEcObtBFvanzyeTMIBzvCUmhEvjYv729OCkIA=
(請換成自己剛剛建立的 API 金鑰)
&lt;/code>&lt;/pre>&lt;p>Send 送出請求後，可以收到回覆，這就是我們目前的全部群組：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="p">[&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;id&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Administrators&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;entity_id&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;member_count&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">2&lt;/span>
&lt;span class="p">},&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;id&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;All Users&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;entity_id&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;member_count&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">4&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">]&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/metabase_api/api_permissions_group1.jpg" alt="Postman 取得全部群組的清單" data-caption="Postman 取得全部群組的清單" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='900px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:900px;height:;"/>
&lt;figcaption style="text-align: center;">
Postman 取得全部群組的清單
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;br/>
&lt;p>還記得剛剛建立 API 金鑰時，有指定一個群組嗎？&lt;br />
我們是選擇「Administrators」，代表它有管理者的權限。那假如我們再創建一個「All Users」群組的 API 金鑰，測試同一個 API 呢？&lt;/p>
&lt;p>可以看到它回覆 &amp;quot;您沒有權限那麼做&amp;quot;，HTTP Status Code 是 403 Forbidden，代表客戶端沒有訪問該資源的權限，這就是權限管控。&lt;/p>
&lt;p>就跟一般使用者登入 Metabase 後，他也沒有權限進入 &amp;quot;管理員設定&amp;quot; 一樣。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/metabase_api/api_permissions_group2.jpg" alt="Postman 取得全部群組的清單 (沒權限)" data-caption="Postman 取得全部群組的清單 (沒權限)" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='900px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:900px;height:;"/>
&lt;figcaption style="text-align: center;">
Postman 取得全部群組的清單 (沒權限)
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;br/>
&lt;p>再來試一個。&lt;/p>
&lt;p>我想查看一個「提問 (Questions，也就是圖表) 的資訊」，該如何操作？&lt;/p>
&lt;p>* 在 API 中，提問被稱為卡片(card)&lt;br />
* 官方 API 文件：&lt;a href="https://www.metabase.com/docs/latest/api/card#get-apicardid" target="_blank" rel="noopener">
GET /api/card/:id
&lt;/a>&lt;/p>
&lt;br/>
&lt;p>例如網址是在 &lt;code>http://localhost:3000/question/27-products&lt;/code> 的圖表。&lt;/p>
&lt;p>card id 就是在網址後面的數字，以上範例的話就是 &lt;code>27&lt;/code>。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/metabase_api/question_url.jpg" alt="提問 (Questions，也就是圖表) 的網址" data-caption="提問 (Questions，也就是圖表) 的網址" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='650px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:650px;height:;"/>
&lt;figcaption style="text-align: center;">
提問 (Questions，也就是圖表) 的網址
&lt;/figcaption>
&lt;/figure>
&lt;pre>&lt;code>Request URL：http://localhost:3000/api/card/27
Request Method：GET
Headers：
x-api-key:mb_j6OtF0o24wnnJb3A/wteFfuilW4lKyKHtx9Cl6cQKBE=
(請換成自己剛剛建立的 API 金鑰)
&lt;/code>&lt;/pre>&lt;p>Send 送出請求後，可以收到回覆，就是有關這個提問(圖表)的資訊：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;id&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">27&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Products 依類別分組&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;description&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;archived&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;view_count&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;question&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;query_type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;query&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;display&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;bar&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;database_id&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;can_write&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;average_query_time&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mf">38.0000000000000000&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;enable_embedding&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;last_query_start&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;2024-10-12T13:07:51.402753Z&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;last_used_at&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;2024-10-12T13:07:51.422213Z&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;updated_at&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;2024-10-12T13:31:43.873511Z&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;created_at&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;2024-10-12T13:07:21.456419Z&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;creator_id&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="err">...以下省略&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/metabase_api/api_card_info.jpg" alt="Postman 取得提問 (圖表) 的資訊" data-caption="Postman 取得提問 (圖表) 的資訊" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='900px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:900px;height:;"/>
&lt;figcaption style="text-align: center;">
Postman 取得提問 (圖表) 的資訊
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;br/>
&lt;p>以上兩個範例都是 GET 資料，來看看假如是想要 POST 的請求方式呢？&lt;/p>
&lt;p>例如想取得「提問 (Questions，圖表) 的查詢資料」，也就是網頁底下切換成原始資料的部分。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/metabase_api/question_rawdata.jpg" alt="提問 (圖表) 的查詢資料" data-caption="提問 (圖表) 的查詢資料" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='650px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:650px;height:;"/>
&lt;figcaption style="text-align: center;">
提問 (圖表) 的查詢資料
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;p>* 官方 API 文件：&lt;a href="https://www.metabase.com/docs/latest/api/card#post-apicardcard-idquery" target="_blank" rel="noopener">
POST /api/card/:card-id/query
&lt;/a>&lt;/p>
&lt;pre>&lt;code>Request URL：http://localhost:3000/api/card/27/query
Request Method：POST
Headers：
x-api-key:mb_j6OtF0o24wnnJb3A/wteFfuilW4lKyKHtx9Cl6cQKBE=
(請換成自己剛剛建立的 API 金鑰)
Body:
{
&amp;quot;ignore_cache&amp;quot;: true
}
&lt;/code>&lt;/pre>&lt;p>* 這個 API 端點的 Body 非必要，可以省略&lt;/p>
&lt;p>Send 送出請求後，可以收到回覆，是這個提問(圖表)的查詢資料：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;data&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;rows&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;span class="p">[&lt;/span>
&lt;span class="s2">&amp;#34;Doohickey&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="mi">42&lt;/span>
&lt;span class="p">],&lt;/span>
&lt;span class="p">[&lt;/span>
&lt;span class="s2">&amp;#34;Gadget&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="mi">53&lt;/span>
&lt;span class="p">],&lt;/span>
&lt;span class="p">[&lt;/span>
&lt;span class="s2">&amp;#34;Gizmo&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="mi">51&lt;/span>
&lt;span class="p">],&lt;/span>
&lt;span class="p">[&lt;/span>
&lt;span class="s2">&amp;#34;Widget&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="mi">54&lt;/span>
&lt;span class="p">]&lt;/span>
&lt;span class="p">],&lt;/span>
&lt;span class="err">...以下省略&lt;/span>
&lt;span class="p">},&lt;/span>
&lt;span class="nt">&amp;#34;cached&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;database_id&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;started_at&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;2024-10-12T13:42:49.532062Z&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;status&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;completed&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;context&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;question&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;row_count&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">4&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;running_time&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">25&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="err">...以下省略&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/metabase_api/api_card_query.jpg" alt="Postman 取得提問 (圖表) 的查詢資料" data-caption="Postman 取得提問 (圖表) 的查詢資料" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='900px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:900px;height:;"/>
&lt;figcaption style="text-align: center;">
Postman 取得提問 (圖表) 的查詢資料
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;br/>
&lt;p>其他更多說明與 API 端點，可以參考 &lt;a href="https://www.metabase.com/docs/latest/api-documentation" target="_blank" rel="noopener">
Metabase 官方 API documentation
&lt;/a>。&lt;/p>
&lt;br/>
&lt;h3 id="備註">備註&lt;/h3>
&lt;p>雖然他們有說，API 介面盡量不會做更改，但 新功能推出、舊 bug 修復 可能多多少少 API 還是有調整，因此在升級 Metabase 版本之前，可以先參考官方整理的文章：&lt;a href="https://www.metabase.com/docs/latest/developers-guide/api-changelog" target="_blank" rel="noopener">
API 介面的重大更改
&lt;/a>&lt;/p>
&lt;br/>
&lt;!--adsense-->
&lt;br/>
&lt;h2 id="結語">結語&lt;/h2>
&lt;p>在以上了解 Metabase API 的使用之後，我們就可以把 Metabase 串接到自己的系統內，做更深度的整合，或做些自動化的應用了~&lt;/p>
&lt;p>而且從 API 回傳資料，也可以發現一些沒有顯示在網頁畫面上的資訊 XD&lt;/p>
&lt;br/>
&lt;p>如果對於 Metabase 有興趣的讀者，記得『&lt;a href="https://www.facebook.com/jiatool" target="_blank" rel="noopener">
IT空間
&lt;/a>』FB 粉專要追蹤起來，才不會錯過最新的發文通知哦~🔔&lt;/p>
&lt;br/>
&lt;br/>
&lt;hr />
&lt;p>參考：&lt;br />
&lt;a href="https://www.metabase.com/" target="_blank" rel="noopener">
Metabase 官方網站
&lt;/a>&lt;br />
&lt;a href="https://www.metabase.com/learn/metabase-basics/administration/administration-and-operation/metabase-api" target="_blank" rel="noopener">
Metabase API 官方教學
&lt;/a>&lt;br />
&lt;a href="https://www.metabase.com/docs/latest/api-documentation" target="_blank" rel="noopener">
Metabase 官方 API documentation
&lt;/a>&lt;br />
&lt;a href="https://github.com/metabase/metabase" target="_blank" rel="noopener">
Metabase 官方 GitHub
&lt;/a>&lt;br />
&lt;a href="https://www.metabase.com/docs/latest/developers-guide/api-changelog" target="_blank" rel="noopener">
Metabase API 介面的重大更改
&lt;/a>&lt;/p>
&lt;br/>
&lt;blockquote>
&lt;p>有那個時間絕望的話，還不如去吃好吃的美食，然後好好睡一覺。&lt;/p>
&lt;p align="right">—— 《法醫女王》(日本電視劇)&lt;/p>
&lt;/blockquote></content:encoded><dc:creator>Jia</dc:creator><media:content url="https://blog.jiatool.comimages/cover/metabase_api.jpg" medium="image"><media:title type="html">featured image</media:title></media:content><media:content url="https://blog.jiatool.comimages/posts/metabase_api_meta.jpg" medium="image"><media:title type="html">meta image</media:title></media:content><category>BI工具</category><category>API</category><category>分享</category><category>Metabase教學</category></item><item><title>Bright Data 手把手教學 — 輕鬆取得 Google、Yahoo 搜尋引擎結果 API</title><link>https://blog.jiatool.com/posts/brightdata_serp_api/</link><pubDate>Sat, 23 Dec 2023 21:00:00 +0800</pubDate><author>jia@jiatool.com (Jia)</author><atom:modified>Sat, 23 Dec 2023 21:00:00 +0800</atom:modified><guid>https://blog.jiatool.com/posts/brightdata_serp_api/</guid><description>此篇為商業合作文章 前言 Bright Data 跟我之前介紹過的 DataForSEO 、Aves API 一樣，都有提供抓取搜尋引擎結果的服務，因此照理說穩定度應該會比較好，不過 Bright Data 是專注於</description><content:encoded>&lt;div class="alert alert-info" role="alert" data-dir="ltr">此篇為商業合作文章&lt;/div>
&lt;h2 id="前言">前言&lt;/h2>
&lt;p>&lt;a href="https://brightdata.com/" target="_blank" rel="noopener">
Bright Data
&lt;/a> 跟我之前介紹過的 &lt;a href="https://blog.jiatool.com/posts/dataforseo" target="_blank" rel="noopener">
DataForSEO
&lt;/a>、&lt;a href="https://blog.jiatool.com/posts/aves_api" target="_blank" rel="noopener">
Aves API
&lt;/a> 一樣，都有提供抓取搜尋引擎結果的服務，因此照理說穩定度應該會比較好，不過 Bright Data 是專注於商務客戶，所以要提醒目前個人是無法使用的 (不支持個人用途，如打遊戲之類的)。&lt;/p>
&lt;p>Bright Data 的 SERP API 目前支援 Google、Bing、DuckDuckGo、Yandex、Yahoo、百度 和 Naver 搜尋引擎，你可以用來做關鍵字追蹤、比較價格、市場研究、偵測版權侵權、廣告情報等等用途。&lt;/p>
&lt;br/>
&lt;p>底下我會以最多人使用的「Google 搜尋 API」來當範例，一起從註冊、設定、測試到最後程式碼帶大家認識。&lt;/p>
&lt;br/>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/brightdata_serp_api/brightdata_home.jpg" alt="Bright Data 官網" data-caption="Bright Data 官網" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='600px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:600px;height:;"/>
&lt;figcaption style="text-align: center;">
Bright Data 官網
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;br/>
&lt;h2 id="註冊-bright-data">註冊 Bright Data&lt;/h2>
&lt;p>首先，需要有一組 Bright Data 帳號。&lt;/p>
&lt;div class="notices info" data-title="註冊 bright data">
&lt;p>如果願意透過此連結註冊，我可以獲得一絲絲分潤，就當支持我寫作吧~🫡&lt;/p>
&lt;p>註冊 Bright Data 帳號：&lt;a href="https://get.brightdata.com/fabjye">https://get.brightdata.com/fabjye&lt;/a>&lt;/p>
&lt;p>官方還提供我的讀者優惠~&lt;br />
透過上述連結註冊的新用戶，首次儲值 25 美元再&lt;strong>送 25 美元&lt;/strong>，直接省一半!!&lt;/p>
&lt;/div>
&lt;br/>
&lt;p>需要填寫姓名、工作用 Email、公司規模，就像文章一開始說的，它是面向商務的服務。&lt;br />
(不支持個人用途)&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/brightdata_serp_api/signup.jpg" alt="註冊 Bright Data" data-caption="註冊 Bright Data" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='400px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:400px;height:;"/>
&lt;figcaption style="text-align: center;">
註冊 Bright Data
&lt;/figcaption>
&lt;/figure>
&lt;p>註冊後，在添加付款方式時，將收到 5 美元的額度可以讓你試用。或者可以聯絡你的客戶經理，會贈送一些額度可以讓你 &lt;a href="https://help.brightdata.com/hc/en-us/articles/4425821434769" target="_blank" rel="noopener">
免費試用
&lt;/a>。&lt;/p>
&lt;br/>
&lt;h2 id="serp-api">SERP API&lt;/h2>
&lt;p>&lt;a href="https://get.brightdata.com/fabjye" target="_blank" rel="noopener">
SERP API
&lt;/a> 目前支援以下幾種搜尋，可以看出來涵蓋蠻多的：&lt;/p>
&lt;ul>
&lt;li>Google Search&lt;/li>
&lt;li>Google Images&lt;/li>
&lt;li>Google Videos&lt;/li>
&lt;li>Google Maps&lt;/li>
&lt;li>Google Trends&lt;/li>
&lt;li>Google Reviews&lt;/li>
&lt;li>Google News&lt;/li>
&lt;li>Google Jobs&lt;/li>
&lt;li>Google Shopping&lt;/li>
&lt;li>Google Hotels&lt;/li>
&lt;li>Bing Search&lt;/li>
&lt;li>DuckDuckGo Search&lt;/li>
&lt;li>Yandex Search&lt;/li>
&lt;li>Yahoo Search&lt;/li>
&lt;li>百度 Search&lt;/li>
&lt;li>Naver Search&lt;/li>
&lt;/ul>
&lt;br/>
&lt;h3 id="serp-api-價格">SERP API 價格&lt;/h3>
&lt;p>SERP API 價格官方有詳細說明在 &lt;a href="https://brightdata.com/pricing/serp" target="_blank" rel="noopener">
這個網頁
&lt;/a>，&lt;/p>
&lt;p>依照方案分為四種：PAY AS YOU GO、GROWTH、BUSINESS、ENTERPRISE。&lt;/p>
&lt;p>CPM 指的是每一千次請求 API 的價格。&lt;br />
以方案 GROWTH 為例，$2.30/CPM 就是每一千個請求要 2.3 美金，等同於每個請求 0.0023 美金 (約 0.072 新台幣)。&lt;/p>
&lt;p>GROWTH 和 BUSINESS 方案會有每月最低消費金額 (分別為 500 和 1000 美金)，但它們使用 API 的價格就會再便宜一些些。&lt;/p>
&lt;p>* 以上價格為美金💵&lt;br />
* 針對費用、週期計算有疑慮的讀者，建議可以直接諮詢客戶經理。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/brightdata_serp_api/serp_api_pricing.jpg" alt="SERP API 價格" data-caption="SERP API 價格" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='700px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:700px;height:;"/>
&lt;figcaption style="text-align: center;">
SERP API 價格
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;h3 id="建立新的-serp-api-zone">建立新的 SERP API Zone&lt;/h3>
&lt;p>第一次使用可以先參考 &lt;a href="https://help.brightdata.com/hc/en-us/articles/16063653689489" target="_blank" rel="noopener">
這篇官方文章
&lt;/a> 去設定，或直接跟著我底下的教學來實作。&lt;/p>
&lt;br/>
&lt;p>進到 &amp;quot;&lt;a href="https://brightdata.com/cp/zones" target="_blank" rel="noopener">
My Proxies
&lt;/a>&amp;quot; 頁面，找到 「SERP API」點 &amp;quot;Get started&amp;quot; 來新增一個 Solution。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/brightdata_serp_api/get_started.jpg" alt="My Proxies &amp;gt; SERP API &amp;gt; Get started" data-caption="My Proxies &amp;gt; SERP API &amp;gt; Get started" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='600px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:600px;height:;"/>
&lt;figcaption style="text-align: center;">
My Proxies &amp;gt; SERP API &amp;gt; Get started
&lt;/figcaption>
&lt;/figure>
&lt;p>接下來，設定一個 Solution 名稱 (在 Bright Data 內也等同 zone 名稱)，注意這個設定後就無法更改 (就只能把它刪除，再建一個新的)，其餘設定都可以之後再更改。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/brightdata_serp_api/add_new_proxy.jpg" alt="設定一個 Solution" data-caption="設定一個 Solution" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='500px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:500px;height:;"/>
&lt;figcaption style="text-align: center;">
設定一個 Solution
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;p>SERP API Zone 上方可以看到有三個頁籤，可以自己隨意點點看看：&lt;/p>
&lt;ul>
&lt;li>Access parameters (存取參數)&lt;/li>
&lt;li>Configuration (配置)&lt;/li>
&lt;li>Statistics (統計數據)&lt;/li>
&lt;/ul>
&lt;p>* 有幾個資訊後續 call API 時會用到，我也在這邊先標出來：&amp;quot;host&amp;quot;、&amp;quot;account id&amp;quot;、&amp;quot;password&amp;quot;、&amp;quot;zone&amp;quot;。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/brightdata_serp_api/api_setting.jpg" alt="SERP API Zone 設定" data-caption="SERP API Zone 設定" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='900px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:900px;height:;"/>
&lt;figcaption style="text-align: center;">
SERP API Zone 設定
&lt;/figcaption>
&lt;/figure>
&lt;p>* Access parameters (存取參數)分頁底下有個 Limit 欄位，可以設定請求量限制，當到達限制後要關閉還是通知你，可以避免不小心意外耗費太多額度。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/brightdata_serp_api/api_limit.jpg" alt="可限制 API 使用，避免超額" data-caption="可限制 API 使用，避免超額" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='500px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:500px;height:;"/>
&lt;figcaption style="text-align: center;">
可限制 API 使用，避免超額
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;h3 id="測試與範例頁面">測試與範例頁面&lt;/h3>
&lt;p>Bright Data 也同樣提供 SERP API 相關測試與參數介紹頁面，在開始撰寫程式前，可以先來這邊透過網頁 UI 的方式嘗試、熟悉各參數與如何使用。&lt;/p>
&lt;p>* 官方介紹中有看到支援 Google、Bing、DuckDuckGo、Yandex、Yahoo、百度 和 Naver 搜尋，不過在這個頁面缺少 百度、Yahoo、Naver，經過我向官方詢問，使用方式與參數都是一樣的，所以如果需要，稍微改一下應該就能使用了。&lt;/p>
&lt;br/>
&lt;h4 id="playground---測試頁面">Playground - 測試頁面&lt;/h4>
&lt;p>在 &lt;a href="https://brightdata.com/cp/zones/serp_playground" target="_blank" rel="noopener">
SERP API Playground
&lt;/a> 測試頁面，透過 UI 介面選擇瀏覽器、設定關鍵字與參數 (下圖橘框處)，並實際查看它查詢後回傳的資料 (網頁與 JSON)，&lt;/p>
&lt;p>而且使用 Playground 是 &amp;quot;免費&amp;quot; 的！！👍&lt;br />
你可以先在這邊先用不同關鍵字、參數去測試，沒問題後再整合進程式裡面。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/brightdata_serp_api/playground.jpg" alt="SERP API Playground 測試頁面" data-caption="SERP API Playground 測試頁面" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='900px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:900px;height:;"/>
&lt;figcaption style="text-align: center;">
SERP API Playground 測試頁面
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;p>下方有個 &amp;quot;Generate API code&amp;quot; 按鈕 (上圖藍框處)，可以查看假如依照你目前的參數設定，該如何下查詢指令(或程式碼)。&lt;br />
要注意你 proxy 設定 &amp;quot;同步&amp;quot; 或 &amp;quot;非同步&amp;quot; 請求 (下一個章節會說明)，這邊的範例也會跟著改變。&lt;/p>
&lt;p>但有點可惜的是，&amp;quot;非同步請求&amp;quot; 的程式碼範例只有 Shell 跟 Node.js，而&amp;quot;同步請求&amp;quot; 雖然有 Python 範例，但不是使用我習慣的 Requests 套件，因此我花了一些時間研究與嘗試，後面章節我有將程式碼列出來供大家參考，&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/brightdata_serp_api/generate_api_code.jpg" alt="Generate API code 範例程式" data-caption="Generate API code 範例程式" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='900px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:900px;height:;"/>
&lt;figcaption style="text-align: center;">
Generate API code 範例程式
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;br/>
&lt;p>* 如果你還沒註冊就等不及的話，可以到 SERP API 介紹網頁中的 &lt;a href="https://brightdata.com/products/serp-api" target="_blank" rel="noopener">
SERP API Live Demo
&lt;/a> 先嘗試，不過就只有讓你測試 Google 搜尋就是了。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/brightdata_serp_api/serp_api_live_demo.jpg" alt="SERP API Live Demo" data-caption="SERP API Live Demo" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='650px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:650px;height:;"/>
&lt;figcaption style="text-align: center;">
SERP API Live Demo
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;h4 id="api-guide---參數介紹與指令">API Guide - 參數介紹與指令&lt;/h4>
&lt;p>這個 &lt;a href="https://brightdata.com/cp/serp_api/api/google/search" target="_blank" rel="noopener">
API Guide
&lt;/a> 頁面是介紹 SERP API 支援那些參數，以及該如何下這些參數。對於參數不了解的話，也可以先到這邊來看看。&lt;/p>
&lt;p>同樣這頁也會因為你 proxy 設定 &amp;quot;同步&amp;quot; 或 &amp;quot;非同步&amp;quot; 請求，而有所對應改變。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/brightdata_serp_api/api_guide.jpg" alt="SERP API Guide - 參數介紹與指令" data-caption="SERP API Guide - 參數介紹與指令" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='900px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:900px;height:;"/>
&lt;figcaption style="text-align: center;">
SERP API Guide - 參數介紹與指令
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;h3 id="發送請求">發送請求&lt;/h3>
&lt;p>它的 SERP API 也有分成「同步請求」、「非同步請求」兩種請求方法。&lt;/p>
&lt;p>預設是使用同步請求，使用上最簡單，只需要發送一個請求即可。但對於有大量請求需求的使用者，還是建議使用非同步請求，因為它有比較好的穩定性。&lt;/p>
&lt;br/>
&lt;p>如果想切換 同步/非同步 請求，則在 SERP API Zone &amp;gt; Configuration 的最下面，有個「Asynchronous requests」開關&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/brightdata_serp_api/async_requests_setting.jpg" alt="SERP API 切換 同步/非同步 請求" data-caption="SERP API 切換 同步/非同步 請求" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='550px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:550px;height:;"/>
&lt;figcaption style="text-align: center;">
SERP API 切換 同步/非同步 請求
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;p>官方範例是使用我不熟悉的套件，但我比較習慣使用 requests 套件，因此底下範例程式是我自己改寫的~✍️&lt;/p>
&lt;br/>
&lt;h4 id="同步請求">同步請求&lt;/h4>
&lt;p>只要發送一個 GET 請求即可取得結果。&lt;/p>
&lt;p>它是透過 proxy 將請求轉給他們，proxy 設定為：&lt;br />
&lt;code>http://brd-customer-{ACCOUNT_ID}-zone-{ZONE}:{PASSWORD}@{HOST}&lt;/code>&lt;br />
完整樣子會像是：&lt;br />
&lt;code>http://brd-customer-oooooooo-zone-serp_api:xxxxxxxx@brd.superproxy.io:22225&lt;/code>&lt;/p>
&lt;br/>
&lt;p>另外，出於資訊安全的考量，進行 HTTPS 連線時 Python 會檢查伺服器的 SSL 憑證是否有效，因此還需要安裝 SSL 憑證，否則會出現像下圖那樣的錯誤，可以參考官方文章：&lt;a href="https://help.brightdata.com/hc/en-us/articles/4413322250001" target="_blank" rel="noopener">
如何安裝 SSL 憑證
&lt;/a>&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/brightdata_serp_api/disabling_ssl_verification.png" alt="遇到 SSL 憑證問題" data-caption="遇到 SSL 憑證問題" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='900px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:900px;height:;"/>
&lt;figcaption style="text-align: center;">
遇到 SSL 憑證問題
&lt;/figcaption>
&lt;/figure>
&lt;p>那假如只是要測試，我們可以 &amp;ldquo;忽略憑證&amp;rdquo; (&lt;code>verify=False&lt;/code>，參考 &lt;a href="https://stackoverflow.com/questions/51925384" target="_blank" rel="noopener">
這篇文章
&lt;/a>)，如同下方範例程式馬。&lt;/p>
&lt;br/>
&lt;p>參數說明可以 &lt;a href="https://help.brightdata.com/hc/en-us/articles/16063645923473-API" target="_blank" rel="noopener">
參考這篇
&lt;/a>，或者如果已經登入，可以在後台的 &lt;a href="https://brightdata.com/cp/serp_api/api/google/search" target="_blank" rel="noopener">
API Guide 頁面
&lt;/a> 去查看各種查詢與參數該如何下。&lt;/p>
&lt;br/>
&lt;br/>
&lt;p>Python 程式碼範例：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="n">url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;https://www.google.com.tw/search&amp;#39;&lt;/span>
&lt;span class="n">params&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="s2">&amp;#34;q&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Google Gemini&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 搜尋關鍵字&lt;/span>
&lt;span class="s2">&amp;#34;gl&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;tw&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s2">&amp;#34;num&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;100&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s2">&amp;#34;brd_json&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;1&amp;#34;&lt;/span> &lt;span class="c1"># 回傳 JSON 格式，預設是 html&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="n">proxy_url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="s2">&amp;#34;http://brd-customer-{ACCOUNT_ID}-zone-{ZONE}:{PASSWORD}@{HOST}&amp;#34;&lt;/span>
&lt;span class="n">proxies&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s1">&amp;#39;http&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">proxy_url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;https&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">proxy_url&lt;/span>&lt;span class="p">}&lt;/span>
&lt;span class="c1"># 使用 verify=False 忽略 SSL 憑證&lt;/span>
&lt;span class="c1"># 為了安全，建議還是安裝 SSL 憑證：https://help.brightdata.com/hc/en-us/articles/4413322250001&lt;/span>
&lt;span class="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">requests&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">params&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">params&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">proxies&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">proxies&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">verify&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="bp">False&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="k">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">json&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;br/>
&lt;p>* 各個狀 SERP API 錯誤訊息意思如&lt;a href="https://help.brightdata.com/hc/en-us/articles/4413230015505" target="_blank" rel="noopener">
這個官方清單
&lt;/a>。&lt;/p>
&lt;br/>
&lt;h4 id="非同步請求">非同步請求&lt;/h4>
&lt;p>在說明用法之前，我們要先去產生一組 API Token (&lt;a href="https://help.brightdata.com/hc/en-us/articles/4413411462417" target="_blank" rel="noopener">
官方說明文章
&lt;/a>)。&lt;/p>
&lt;p>到後台的 &lt;a href="https://brightdata.com/cp/setting/users" target="_blank" rel="noopener">
Account Settings &amp;gt; User access
&lt;/a> 頁面，點擊最下方 API tokens 的右邊 &amp;quot;Add token&amp;quot; 按鈕。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/brightdata_serp_api/account_add_token.jpg" alt="Account Setting &amp;gt; Add token" data-caption="Account Setting &amp;gt; Add token" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='900px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:900px;height:;"/>
&lt;figcaption style="text-align: center;">
Account Setting &amp;gt; Add token
&lt;/figcaption>
&lt;/figure>
&lt;p>並指定要給哪一位 User 使用，Permission 就選擇 &amp;quot;User&amp;quot; 即可，期限可以指定日期或勾選下方的&amp;quot;Unlimited (無期限)&amp;quot;。&lt;/p>
&lt;p>* 因為這關係到權限，可能會需要去信箱收驗證信。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/brightdata_serp_api/add_api_token.jpg" alt="Add API token" data-caption="Add API token" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='500px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:500px;height:;"/>
&lt;figcaption style="text-align: center;">
Add API token
&lt;/figcaption>
&lt;/figure>
&lt;p>它產生的 API Token 要複製起來，不然之後忘記就要重新產生了。&lt;/p>
&lt;br/>
&lt;br/>
&lt;p>再來到你的 SERP API Zone 中 Configuration 分頁，最下方將「Asynchronous requests」開關打開。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/brightdata_serp_api/async_requests_setting.jpg" alt="SERP API 切換 同步/非同步 請求" data-caption="SERP API 切換 同步/非同步 請求" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='550px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:550px;height:;"/>
&lt;figcaption style="text-align: center;">
SERP API 切換 同步/非同步 請求
&lt;/figcaption>
&lt;/figure>
&lt;p>這邊的 &amp;quot;Web Hook URL&amp;quot; 和 &amp;quot;Web Hook Request Method&amp;quot; 你可以設定，讓任務完成後會主動來呼叫你，或者像我下面將要說明的 — 由我們自己去取得結果，那就不需要填這兩個欄位了。&lt;/p>
&lt;p>記得要 Save。&lt;/p>
&lt;br/>
&lt;br/>
&lt;p>好了，終於要來講 &amp;quot;非同步請求&amp;quot; 該怎麼使用了。&lt;/p>
&lt;p>它與 &amp;quot;同步請求&amp;quot; 使用方式有所不同，&amp;quot;非同步請求&amp;quot; 使用上分為兩個請求：&lt;/p>
&lt;ol>
&lt;li>設定任務&lt;/li>
&lt;li>取得任務的查詢結果&lt;/li>
&lt;/ol>
&lt;br/>
&lt;p>先使用 POST 請求「設定任務」，指定查詢參數、回傳格式。&lt;br />
如果成功的話，會在收到回覆的 Header 看到一個 &amp;quot;&lt;code>x-response-id&lt;/code>&amp;quot;，這個代表 Response ID，也就是下一步要使用的。&lt;/p>
&lt;p>第二步，送出 GET 請求「取得任務的查詢結果」，帶上第一步取得的 &lt;code>x-response-id&lt;/code>。&lt;br />
如果查詢任務已完成，會收到 &lt;code>Status Code = 200&lt;/code>，Body 內容即是查詢結果資料；如果查詢任務還沒完成，收到 &lt;code>Status Code = 404&lt;/code> (Not Found)，那就要等個幾秒再試一次。&lt;/p>
&lt;p>可以直接參考下方範例程式碼，或參考 &lt;a href="https://help.brightdata.com/hc/en-us/articles/18191740593169" target="_blank" rel="noopener">
這篇官方說明
&lt;/a>。&lt;/p>
&lt;p>* 當然只有 &amp;quot;設定任務&amp;quot; 請求會收費 (如果成功的話)。&lt;br />
* 查詢結果最多會存在伺服器 24 小時，這段時間你還是可以使用 Response ID 去取得查詢結果。&lt;/p>
&lt;br/>
&lt;br/>
&lt;p>Python 程式碼範例：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="c1"># 記得要先去後台開啟 Asynchronous requests&lt;/span>
&lt;span class="n">url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="s1">&amp;#39;https://api.brightdata.com/serp/req?customer={ACCOUNT_ID}&amp;amp;zone={ZONE}&amp;#39;&lt;/span>
&lt;span class="n">headers&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="s1">&amp;#39;Content-Type&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s1">&amp;#39;application/json&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s1">&amp;#39;Authorization&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="s1">&amp;#39;Bearer {API_TOKEN}&amp;#39;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="n">data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="s2">&amp;#34;country&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;tw&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s2">&amp;#34;query&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="s2">&amp;#34;q&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Google Gemini&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 搜尋關鍵字&lt;/span>
&lt;span class="s2">&amp;#34;gl&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;tw&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s2">&amp;#34;num&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;100&amp;#34;&lt;/span>
&lt;span class="p">},&lt;/span>
&lt;span class="s2">&amp;#34;brd_json&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;json&amp;#34;&lt;/span> &lt;span class="c1"># 回傳 JSON 格式，預設是 html&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">requests&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">post&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">headers&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">headers&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">json&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="n">response_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">headers&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;x-response-id&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="k">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">f&lt;/span>&lt;span class="s2">&amp;#34;response id: {response_id}&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sleep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">10&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 等待它完成查詢&lt;/span>
&lt;span class="n">url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="s1">&amp;#39;https://api.brightdata.com/serp/get_result?customer={ACCOUNT_ID}&amp;amp;zone={ZONE}&amp;amp;response_id={response_id}&amp;#39;&lt;/span>
&lt;span class="n">headers&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s1">&amp;#39;Authorization&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="s1">&amp;#39;Bearer {API_TOKEN}&amp;#39;&lt;/span>&lt;span class="p">}&lt;/span>
&lt;span class="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">requests&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">headers&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">headers&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="k">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">json&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;br/>
&lt;p>* 各個狀 SERP API 錯誤訊息意思如&lt;a href="https://help.brightdata.com/hc/en-us/articles/4413230015505" target="_blank" rel="noopener">
這個官方清單
&lt;/a>。&lt;/p>
&lt;br/>
&lt;h3 id="查詢結果說明">查詢結果說明&lt;/h3>
&lt;p>SERP API 回傳的查詢結果我以 JSON 格式來說明，以下直接貼出範例結果給大家參考，大部分欄位應該很容易就看出來意思。&lt;/p>
&lt;p>其他說明可以直接參考官方說明：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://help.brightdata.com/hc/en-us/articles/14597847891217" target="_blank" rel="noopener">
SERP API 輸出 JSON 欄位說明
&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://help.brightdata.com/hc/en-us/articles/8699149516177" target="_blank" rel="noopener">
SERP API 的回應 JSON 中的排名欄位是什麼？
&lt;/a>&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;span class="lnt">44
&lt;/span>&lt;span class="lnt">45
&lt;/span>&lt;span class="lnt">46
&lt;/span>&lt;span class="lnt">47
&lt;/span>&lt;span class="lnt">48
&lt;/span>&lt;span class="lnt">49
&lt;/span>&lt;span class="lnt">50
&lt;/span>&lt;span class="lnt">51
&lt;/span>&lt;span class="lnt">52
&lt;/span>&lt;span class="lnt">53
&lt;/span>&lt;span class="lnt">54
&lt;/span>&lt;span class="lnt">55
&lt;/span>&lt;span class="lnt">56
&lt;/span>&lt;span class="lnt">57
&lt;/span>&lt;span class="lnt">58
&lt;/span>&lt;span class="lnt">59
&lt;/span>&lt;span class="lnt">60
&lt;/span>&lt;span class="lnt">61
&lt;/span>&lt;span class="lnt">62
&lt;/span>&lt;span class="lnt">63
&lt;/span>&lt;span class="lnt">64
&lt;/span>&lt;span class="lnt">65
&lt;/span>&lt;span class="lnt">66
&lt;/span>&lt;span class="lnt">67
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;general&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;search_engine&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;google&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;results_cnt&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">2030000000&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;search_time&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mf">0.31&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;language&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;zh-TW&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;mobile&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;basic_view&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;search_type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;text&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;page_title&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Google Pixel - Google 搜尋&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;timestamp&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;2023-12-03T14:27:44.026Z&amp;#34;&lt;/span>
&lt;span class="p">},&lt;/span>
&lt;span class="nt">&amp;#34;input&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;original_url&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;https://www.google.com.tw/search?q=Google+Pixel&amp;amp;gl=tw&amp;amp;lang=zh-TW&amp;amp;location=Kaohsiung+City%2CTaiwan&amp;amp;brd_json=1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;request_id&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;xxxxxxxx&amp;#34;&lt;/span>
&lt;span class="p">},&lt;/span>
&lt;span class="nt">&amp;#34;organic&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;link&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;https://store.google.com/tw/category/phones?hl=zh-TW&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;display_link&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;https://store.google.com › category › phones&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;title&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Google Pixel 手機- Google 商店&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;description&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Google 5G smartphones feature the latest technology so you always have that new phone feeling. Find out which Pixel is right for you.&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;rank&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;global_rank&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">1&lt;/span>
&lt;span class="p">},&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;link&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;https://store.google.com/tw/?hl=zh-TW&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;display_link&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;https://store.google.com › ...&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;title&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Google 商店官網，專售Google 製造的裝置和配件&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;description&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;全新Pixel 手機內建Google AI 技術，搭載最令人驚艷的Pixel 相機。 ... 支援大多數搭載Android 9.0 以上版本的手機，須有Google 帳戶、Google Pixel Watch 應用程式和網路連 ...&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;rank&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;global_rank&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">2&lt;/span>
&lt;span class="p">},&lt;/span>
&lt;span class="err">......&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;link&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;https://guidebooks.google.com/pixel?hl=zh-Hant&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;display_link&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;https://guidebooks.google.com › pixel&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;title&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;瞭解「Google Pixel」&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;description&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;設定新的Pixel 手機. 瞭解如何從你的舊Android 手機或iPhone® 轉移資料。 接下來，你可以學習如何自訂各項設定、取得日常事務協助以及 因應緊急狀況等等。&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;rank&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">10&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;global_rank&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">10&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">],&lt;/span>
&lt;span class="nt">&amp;#34;pagination&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;current_page&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;next_page_link&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;https://www.google.com.tw/search?q=Google+Pixel&amp;amp;sca_esv=587474982&amp;amp;gl=tw&amp;amp;hl=zh-TW&amp;amp;ei=XZBsZxxxxxxxx4HIDg&amp;amp;start=10&amp;amp;sa=N&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;next_page_start&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">10&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;next_page&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">2&lt;/span>
&lt;span class="p">},&lt;/span>
&lt;span class="nt">&amp;#34;related&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;list_group&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;link&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;https://www.google.com.tw/search?sca_esv=587474982&amp;amp;gl=tw&amp;amp;hl=zh-TW&amp;amp;q=google+pixel%E8%A9%95%E5%83%B9&amp;amp;sa=X&amp;amp;ved=2ahUKEwiS9uLuvPOCAxXYBogKHdNVAOkQ1QJ6BAgjEAE&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;text&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;google pixel評價&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;rank&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;global_rank&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">11&lt;/span>
&lt;span class="p">},&lt;/span>
&lt;span class="err">......&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;list_group&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;link&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;https://www.google.com.tw/search?sca_esv=587474982&amp;amp;gl=tw&amp;amp;hl=zh-TW&amp;amp;q=google%E6%89%8B%E6%A9%9Fpixel+7&amp;amp;sa=X&amp;amp;ved=2ahUKEwiS9uLuvPOCAxXYBogKHdNVAOkQ1QJ6BAgeEAE&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;text&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;google手機pixel 7&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;rank&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">8&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;global_rank&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">18&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">]&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;br/>
&lt;h3 id="完整-python-範例">完整 Python 範例&lt;/h3>
&lt;p>首先要確認有安裝 Requests 套件：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-Shell" data-lang="Shell">pip install requests
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>完整 Python 程式碼：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt"> 10
&lt;/span>&lt;span class="lnt"> 11
&lt;/span>&lt;span class="lnt"> 12
&lt;/span>&lt;span class="lnt"> 13
&lt;/span>&lt;span class="lnt"> 14
&lt;/span>&lt;span class="lnt"> 15
&lt;/span>&lt;span class="lnt"> 16
&lt;/span>&lt;span class="lnt"> 17
&lt;/span>&lt;span class="lnt"> 18
&lt;/span>&lt;span class="lnt"> 19
&lt;/span>&lt;span class="lnt"> 20
&lt;/span>&lt;span class="lnt"> 21
&lt;/span>&lt;span class="lnt"> 22
&lt;/span>&lt;span class="lnt"> 23
&lt;/span>&lt;span class="lnt"> 24
&lt;/span>&lt;span class="lnt"> 25
&lt;/span>&lt;span class="lnt"> 26
&lt;/span>&lt;span class="lnt"> 27
&lt;/span>&lt;span class="lnt"> 28
&lt;/span>&lt;span class="lnt"> 29
&lt;/span>&lt;span class="lnt"> 30
&lt;/span>&lt;span class="lnt"> 31
&lt;/span>&lt;span class="lnt"> 32
&lt;/span>&lt;span class="lnt"> 33
&lt;/span>&lt;span class="lnt"> 34
&lt;/span>&lt;span class="lnt"> 35
&lt;/span>&lt;span class="lnt"> 36
&lt;/span>&lt;span class="lnt"> 37
&lt;/span>&lt;span class="lnt"> 38
&lt;/span>&lt;span class="lnt"> 39
&lt;/span>&lt;span class="lnt"> 40
&lt;/span>&lt;span class="lnt"> 41
&lt;/span>&lt;span class="lnt"> 42
&lt;/span>&lt;span class="lnt"> 43
&lt;/span>&lt;span class="lnt"> 44
&lt;/span>&lt;span class="lnt"> 45
&lt;/span>&lt;span class="lnt"> 46
&lt;/span>&lt;span class="lnt"> 47
&lt;/span>&lt;span class="lnt"> 48
&lt;/span>&lt;span class="lnt"> 49
&lt;/span>&lt;span class="lnt"> 50
&lt;/span>&lt;span class="lnt"> 51
&lt;/span>&lt;span class="lnt"> 52
&lt;/span>&lt;span class="lnt"> 53
&lt;/span>&lt;span class="lnt"> 54
&lt;/span>&lt;span class="lnt"> 55
&lt;/span>&lt;span class="lnt"> 56
&lt;/span>&lt;span class="lnt"> 57
&lt;/span>&lt;span class="lnt"> 58
&lt;/span>&lt;span class="lnt"> 59
&lt;/span>&lt;span class="lnt"> 60
&lt;/span>&lt;span class="lnt"> 61
&lt;/span>&lt;span class="lnt"> 62
&lt;/span>&lt;span class="lnt"> 63
&lt;/span>&lt;span class="lnt"> 64
&lt;/span>&lt;span class="lnt"> 65
&lt;/span>&lt;span class="lnt"> 66
&lt;/span>&lt;span class="lnt"> 67
&lt;/span>&lt;span class="lnt"> 68
&lt;/span>&lt;span class="lnt"> 69
&lt;/span>&lt;span class="lnt"> 70
&lt;/span>&lt;span class="lnt"> 71
&lt;/span>&lt;span class="lnt"> 72
&lt;/span>&lt;span class="lnt"> 73
&lt;/span>&lt;span class="lnt"> 74
&lt;/span>&lt;span class="lnt"> 75
&lt;/span>&lt;span class="lnt"> 76
&lt;/span>&lt;span class="lnt"> 77
&lt;/span>&lt;span class="lnt"> 78
&lt;/span>&lt;span class="lnt"> 79
&lt;/span>&lt;span class="lnt"> 80
&lt;/span>&lt;span class="lnt"> 81
&lt;/span>&lt;span class="lnt"> 82
&lt;/span>&lt;span class="lnt"> 83
&lt;/span>&lt;span class="lnt"> 84
&lt;/span>&lt;span class="lnt"> 85
&lt;/span>&lt;span class="lnt"> 86
&lt;/span>&lt;span class="lnt"> 87
&lt;/span>&lt;span class="lnt"> 88
&lt;/span>&lt;span class="lnt"> 89
&lt;/span>&lt;span class="lnt"> 90
&lt;/span>&lt;span class="lnt"> 91
&lt;/span>&lt;span class="lnt"> 92
&lt;/span>&lt;span class="lnt"> 93
&lt;/span>&lt;span class="lnt"> 94
&lt;/span>&lt;span class="lnt"> 95
&lt;/span>&lt;span class="lnt"> 96
&lt;/span>&lt;span class="lnt"> 97
&lt;/span>&lt;span class="lnt"> 98
&lt;/span>&lt;span class="lnt"> 99
&lt;/span>&lt;span class="lnt">100
&lt;/span>&lt;span class="lnt">101
&lt;/span>&lt;span class="lnt">102
&lt;/span>&lt;span class="lnt">103
&lt;/span>&lt;span class="lnt">104
&lt;/span>&lt;span class="lnt">105
&lt;/span>&lt;span class="lnt">106
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="kn">import&lt;/span> &lt;span class="nn">time&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">json&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">requests&lt;/span>
&lt;span class="n">ZONE&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;lt;your_zone&amp;gt;&amp;#34;&lt;/span>
&lt;span class="n">ACCOUNT_ID&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;lt;your_account_id&amp;gt;&amp;#34;&lt;/span>
&lt;span class="n">PASSWORD&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;lt;your_password&amp;gt;&amp;#34;&lt;/span>
&lt;span class="n">HOST&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;lt;your_host&amp;gt;&amp;#34;&lt;/span>
&lt;span class="n">API_TOKEN&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;lt;your_api_token&amp;gt;&amp;#34;&lt;/span>
&lt;span class="k">class&lt;/span> &lt;span class="nc">Brightdata&lt;/span>&lt;span class="p">():&lt;/span>
&lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">zone&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">account_id&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">password&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">host&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">api_token&lt;/span>&lt;span class="p">):&lt;/span>
&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">zone&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">zone&lt;/span>
&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">account_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">account_id&lt;/span>
&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">password&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">password&lt;/span>
&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">host&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">host&lt;/span>
&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">api_token&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">api_token&lt;/span>
&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">username&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="s2">&amp;#34;brd-customer-{self.account_id}-zone-{self.zone}&amp;#34;&lt;/span>
&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">proxy_url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="s2">&amp;#34;http://{self.username}:{self.password}@{self.host}&amp;#34;&lt;/span>
&lt;span class="k">def&lt;/span> &lt;span class="nf">sync_get_search_result&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">keyword&lt;/span>&lt;span class="p">):&lt;/span>
&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34; SERP API 同步請求 - 取得搜尋結果&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;span class="n">proxies&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="s1">&amp;#39;http&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">proxy_url&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s1">&amp;#39;https&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">proxy_url&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="n">url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;https://www.google.com.tw/search&amp;#39;&lt;/span>
&lt;span class="n">params&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="s2">&amp;#34;q&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">keyword&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 搜尋關鍵字&lt;/span>
&lt;span class="s2">&amp;#34;gl&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;tw&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s2">&amp;#34;lang&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;zh-TW&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s2">&amp;#34;location&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Kaohsiung City,Taiwan&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s2">&amp;#34;num&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;100&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s2">&amp;#34;brd_json&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;1&amp;#34;&lt;/span> &lt;span class="c1"># 回傳 JSON 格式，預設是 html&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="c1"># 使用 verify=False 忽略 SSL 憑證&lt;/span>
&lt;span class="c1"># 為了安全，建議還是安裝 SSL 憑證：https://help.brightdata.com/hc/en-us/articles/4413322250001&lt;/span>
&lt;span class="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">requests&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">params&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">params&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">proxies&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">proxies&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">verify&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="bp">False&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">status_code&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="mi">200&lt;/span>&lt;span class="p">:&lt;/span>
&lt;span class="k">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">f&lt;/span>&lt;span class="s2">&amp;#34;Error: {response.status_code} - {response.reason}&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">json&lt;/span>&lt;span class="p">()&lt;/span>
&lt;span class="k">def&lt;/span> &lt;span class="nf">async_set_search_job&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">keyword&lt;/span>&lt;span class="p">):&lt;/span>
&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34; SERP API 非同步請求 - 設定搜尋任務
&lt;/span>&lt;span class="s2">
&lt;/span>&lt;span class="s2"> * 記得要先去後台開啟 Asynchronous requests
&lt;/span>&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;span class="n">url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="s1">&amp;#39;https://api.brightdata.com/serp/req?customer={self.account_id}&amp;amp;zone={self.zone}&amp;#39;&lt;/span>
&lt;span class="n">headers&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="s1">&amp;#39;Content-Type&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s1">&amp;#39;application/json&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s1">&amp;#39;Authorization&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="s1">&amp;#39;Bearer {self.api_token}&amp;#39;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="n">data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="s2">&amp;#34;country&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;tw&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s2">&amp;#34;query&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="s2">&amp;#34;q&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">keyword&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 搜尋關鍵字&lt;/span>
&lt;span class="s2">&amp;#34;gl&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;tw&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s2">&amp;#34;lang&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;zh-TW&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s2">&amp;#34;location&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Kaohsiung City,Taiwan&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s2">&amp;#34;num&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;100&amp;#34;&lt;/span>
&lt;span class="p">},&lt;/span>
&lt;span class="s2">&amp;#34;brd_json&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;json&amp;#34;&lt;/span> &lt;span class="c1"># 回傳 JSON 格式，預設是 html&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">requests&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">post&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">headers&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">headers&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">json&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="n">response_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">status_code&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">200&lt;/span>&lt;span class="p">:&lt;/span>
&lt;span class="n">response_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">headers&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;x-response-id&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="k">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">f&lt;/span>&lt;span class="s2">&amp;#34;response id: {response_id}&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="k">else&lt;/span>&lt;span class="p">:&lt;/span>
&lt;span class="k">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">f&lt;/span>&lt;span class="s2">&amp;#34;Error: {response.status_code} - {response.reason}&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="n">response_id&lt;/span>
&lt;span class="k">def&lt;/span> &lt;span class="nf">async_get_search_result&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">response_id&lt;/span>&lt;span class="p">):&lt;/span>
&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34; SERP API 非同步請求 - 取得搜尋結果
&lt;/span>&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;span class="n">url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="s1">&amp;#39;https://api.brightdata.com/serp/get_result?customer={self.account_id}&amp;amp;zone={self.zone}&amp;amp;response_id={response_id}&amp;#39;&lt;/span>
&lt;span class="n">headers&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s1">&amp;#39;Authorization&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="s1">&amp;#39;Bearer {self.api_token}&amp;#39;&lt;/span>&lt;span class="p">}&lt;/span>
&lt;span class="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">requests&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">headers&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">headers&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">status_code&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="mi">200&lt;/span>&lt;span class="p">:&lt;/span>
&lt;span class="k">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">f&lt;/span>&lt;span class="s2">&amp;#34;Error: {response.status_code} - {response.reason}&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">json&lt;/span>&lt;span class="p">()&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="vm">__name__&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s1">&amp;#39;__main__&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;span class="n">brightdata&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Brightdata&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ZONE&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ACCOUNT_ID&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">PASSWORD&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">HOST&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">API_TOKEN&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="c1"># SERP API 同步請求&lt;/span>
&lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">brightdata&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sync_get_search_result&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Google Pixel&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="k">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">dumps&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">))&lt;/span>
&lt;span class="c1"># SERP API 非同步請求&lt;/span>
&lt;span class="c1"># response_id = brightdata.async_set_search_job(&amp;#34;Google Pixel&amp;#34;)&lt;/span>
&lt;span class="c1"># time.sleep(10) # 等待它完成查詢&lt;/span>
&lt;span class="c1"># result = brightdata.async_get_search_result(response_id)&lt;/span>
&lt;span class="c1"># print(json.dumps(result))&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;br/>
&lt;br/>
&lt;p>* 官方還有更多的說明文件，有需要都可以進去看看：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://help.brightdata.com/hc/en-us/sections/16062473940113" target="_blank" rel="noopener">
PROXIES &amp;amp; SCRAPING INFRA &amp;gt; SERP API
&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://help.brightdata.com/hc/en-us/sections/4413189032977" target="_blank" rel="noopener">
API DOCUMENTATION &amp;gt; SERP-API
&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://help.brightdata.com/hc/en-us/articles/16593685131153" target="_blank" rel="noopener">
SERP-API 常見 Q&amp;amp;A
&lt;/a>&lt;/li>
&lt;/ul>
&lt;br/>
&lt;br/>
&lt;div class="notices info" data-title="註冊 bright data">
&lt;p>如果願意透過此連結註冊，我可以獲得一絲絲分潤，就當支持我寫作吧~🫡&lt;/p>
&lt;p>註冊 Bright Data 帳號：&lt;a href="https://get.brightdata.com/fabjye">https://get.brightdata.com/fabjye&lt;/a>&lt;/p>
&lt;p>官方還提供我的讀者優惠~&lt;br />
透過上述連結註冊的新用戶，首次儲值 25 美元再&lt;strong>送 25 美元&lt;/strong>，直接省一半!!&lt;/p>
&lt;/div>
&lt;br/>
&lt;br/>
&lt;h2 id="結語">結語&lt;/h2>
&lt;p>如果你有爬取(抓取)搜尋結果資料的需求，我覺得花點小錢還是蠻值得的，總比自己在那邊 搞爬蟲、驗證碼(CAPTCHA)、換 IP、Proxy、被Ban帳號 好太多了&amp;hellip; (&amp;lt;- 過來人😭😭😭&lt;/p>
&lt;p>Bright Data 除了這次介紹的 SERP API 工具外，他還有其他很多的服務，像是 Proxy、Scraping Browser API、Web Scraper IDE、Web Unlocker 等等，如果有這方面的需求，也可以跟他們詢問 (反正問又不用錢XD)。&lt;/p>
&lt;br/>
&lt;br/>
&lt;hr />
&lt;p>參考：&lt;br />
&lt;a href="https://brightdata.com/" target="_blank" rel="noopener">
BrightData 官方網站
&lt;/a>&lt;br />
&lt;a href="https://help.brightdata.com/hc/en-us/sections/16062473940113" target="_blank" rel="noopener">
BrightData SERP-API 官方文檔1
&lt;/a>&lt;br />
&lt;a href="https://help.brightdata.com/hc/en-us/sections/4413189032977" target="_blank" rel="noopener">
BrightData SERP-API 官方文檔2
&lt;/a>&lt;br />
&lt;a href="https://help.brightdata.com/hc/en-us/articles/16593685131153" target="_blank" rel="noopener">
BrightData SERP-API 常見 Q&amp;amp;A
&lt;/a>&lt;br />
&lt;a href="https://brightdata.com/cp/zones" target="_blank" rel="noopener">
BrightData 使用者後台
&lt;/a>&lt;/p>
&lt;br/>
&lt;blockquote>
&lt;p>只做能力範圍的事，就永遠無法進步。&lt;/p>
&lt;/blockquote></content:encoded><dc:creator>Jia</dc:creator><media:content url="https://blog.jiatool.comimages/cover/brightdata_serp_api.jpg" medium="image"><media:title type="html">featured image</media:title></media:content><media:content url="https://blog.jiatool.comimages/posts/brightdata_serp_api_meta.jpg" medium="image"><media:title type="html">meta image</media:title></media:content><category>Google搜尋</category><category>Yahoo搜尋</category><category>SEO</category><category>API</category><category>Python</category><category>分享</category></item><item><title>如何使用 Google 的 Gemini 模型 API？(基礎教學，附上 Python 範例程式)</title><link>https://blog.jiatool.com/posts/gemini_api/</link><pubDate>Sun, 17 Dec 2023 21:45:00 +0800</pubDate><author>jia@jiatool.com (Jia)</author><atom:modified>Wed, 28 Feb 2024 15:35:00 +0800</atom:modified><guid>https://blog.jiatool.com/posts/gemini_api/</guid><description>前言 在上個禮拜(12/6)推出了 Google DeepMind 開發的 Gemini (雙子星) ，是第一個在 MMLU (大規模多任務語言理解) 方面超越人類專家的模型，要與 OpenAI 的 GPT-4 來抗衡。 我之前</description><content:encoded>&lt;h2 id="前言">前言&lt;/h2>
&lt;p>在上個禮拜(12/6)推出了 Google DeepMind 開發的 &lt;a href="https://deepmind.google/technologies/gemini/" target="_blank" rel="noopener">
Gemini (雙子星)
&lt;/a>，是第一個在 MMLU (大規模多任務語言理解) 方面超越人類專家的模型，要與 OpenAI 的 GPT-4 來抗衡。&lt;/p>
&lt;p>我之前寫過一篇 &lt;a href="https://blog.jiatool.com/posts/chatgpt_api" target="_blank" rel="noopener">
如何使用 OpenAI ChatGPT API
&lt;/a>，而在前幾天(12/13) Google 也開放了 &lt;a href="https://developers.googleblog.com/2023/12/build-with-gemini-pro.html" target="_blank" rel="noopener">
Gemini Pro 版本的 API
&lt;/a>，可以透過「Google AI Studio 中的 Gemini API」或「Google Cloud 的 Vertex AI 平臺」來存取。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/gemini_api/welcome_gemini.jpg" alt="Google DeepMind 開發的 Gemini 多模態模型" data-caption="Google DeepMind 開發的 Gemini 多模態模型" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='600px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:600px;height:;"/>
&lt;figcaption style="text-align: center;">
Google DeepMind 開發的 Gemini 多模態模型
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;div class="alert alert-info" role="alert" data-dir="ltr">右邊有目錄，可直接跳至你想看的章節 →&lt;/div>
&lt;br/>
&lt;!--adsense-->
&lt;h2 id="gemini-簡介">Gemini 簡介&lt;/h2>
&lt;p>Gemini 是一個原生多模態的 LLM (大型語言模型)，從訓練時就餵進去文字、影像、音訊等等多種形態的資料，使用 Google 自行開發的 TPU 晶片訓練而成，是第一個在 MMLU (大規模多任務語言理解) 方面超越人類專家的模型。&lt;/p>
&lt;p>* 官方 Gemini 簡介文章：&lt;a href="https://blog.google/technology/ai/google-gemini-ai">https://blog.google/technology/ai/google-gemini-ai&lt;/a>&lt;/p>
&lt;br/>
&lt;p>而官方有釋出一部試用 Gemini Ultra 的展示影片，我看完真的覺得很驚訝，在網路上也掀起了一陣熱烈討論。&lt;/p>
&lt;p>* 有 CC 中文字幕&lt;/p>
&lt;iframe width="672" height="378" src="https://www.youtube.com/embed/UIZAiXYceBI?si=6VOjykF9uKkO_mt6" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen>&lt;/iframe>
&lt;p>* 有文章版可以看：&lt;a href="https://developers.googleblog.com/2023/12/how-its-made-gemini-multimodal-prompting.html" target="_blank" rel="noopener">
How it's Made: Interacting with Gemini through multimodal prompting
&lt;/a>&lt;/p>
&lt;p>* Gemini 的訓練資料是到 2023 年初，在此之後的它可能就不知道了。&lt;/p>
&lt;br/>
&lt;h3 id="gemini-模型三種尺寸">Gemini 模型三種尺寸&lt;/h3>
&lt;p>Gemini 依照尺寸分成三種版本：&lt;/p>
&lt;ul>
&lt;li>Gemini Ultra：最強大，適用高度複雜的任務&lt;/li>
&lt;li>Gemini Pro：最通用&lt;/li>
&lt;li>Gemini Nano：可於行動裝置上運作&lt;/li>
&lt;/ul>
&lt;p>目前 Google 的 Bard 背後已經換成了 Gemini Pro (好像只有英文版)，Gemini Nano 也應用於 Google Pixel 8 上。&lt;br />
而 Gemini Ultra 應該明年初會推出。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/gemini_api/gemini_sizes.jpg" alt="Gemini 分成三種尺寸" data-caption="Gemini 分成三種尺寸" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='650px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:650px;height:;"/>
&lt;figcaption style="text-align: center;">
Gemini 分成三種尺寸
&lt;/figcaption>
&lt;/figure>
&lt;p>* Gemini Ultra 對比 OpenAI GPT-4；Gemini Pro 對比 OpenAI GPT-3.5。&lt;br />
* 目前只開放 Gemini Pro 版本的 API。&lt;/p>
&lt;br/>
&lt;h3 id="api-價格">API 價格&lt;/h3>
&lt;p>以下價格都是 12/16 查詢的金額。&lt;/p>
&lt;br/>
&lt;p>在明年初全面上市之前，可以 &amp;quot;免費&amp;quot; 使用相同速率限制、相同模型來嘗試，不確定之後還會不會有免費方案(但降低使用速率)。&lt;br />
* 免費方案的輸入輸出資料會被拿去訓練，需要注意。&lt;/p>
&lt;p>Gemini Pro 的 API 限制每分鐘最多 60 個請求(以個人使用絕對夠用)，預計明年初之後收費如下：&lt;/p>
&lt;ul>
&lt;li>Price (input)&lt;br />
$0.00025 / 1K characters&lt;br />
$0.0025 / image&lt;/li>
&lt;li>Price (output)&lt;br />
$0.0005 / 1K characters&lt;/li>
&lt;/ul>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/gemini_api/gemini_pro_price.jpg" alt="Gemini Pro 版本的 API 價格" data-caption="Gemini Pro 版本的 API 價格" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='600px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:600px;height:;"/>
&lt;figcaption style="text-align: center;">
Gemini Pro 版本的 API 價格
&lt;/figcaption>
&lt;/figure>
&lt;p>而 OpenAI GPT 的 API 收費如下 (以 GPT-3.5 Turbo 為例)：&lt;/p>
&lt;ul>
&lt;li>Price (input)&lt;br />
$0.001 / 1K tokens&lt;/li>
&lt;li>Price (output)&lt;br />
$0.002 / 1K tokens&lt;/li>
&lt;/ul>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/gemini_api/openai_gpt_price.jpg" alt="OpenAI GPT 的 API 價格" data-caption="OpenAI GPT 的 API 價格" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='750px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:750px;height:;"/>
&lt;figcaption style="text-align: center;">
OpenAI GPT 的 API 價格
&lt;/figcaption>
&lt;/figure>
&lt;p>* 以上價格皆為美金，撰寫文章當下約 1 美金 = 31.3 新台幣。&lt;/p>
&lt;br/>
&lt;p>比較一下，可以看到 Google 的 Gemini 相較來說更划算，便宜了 4 倍，而且注意看他們的計算方式也不同，一個是用 character、一個是用 token，我們來看看不同的計算方式差多少。&lt;/p>
&lt;br/>
&lt;p>底下我自己實際用它們官網計算 token 的工具，來比較兩者的差距。&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://makersuite.google.com/" target="_blank" rel="noopener">
Google AI Studio
&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://platform.openai.com/tokenizer" target="_blank" rel="noopener">
OpenAI Tokenizer
&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>先測試一段英文，兩者算出來的 token 是差不多的 (分別為 24 跟 25)：&lt;/p>
&lt;blockquote>
&lt;p>Gemini is built from the ground up for multimodality — reasoning seamlessly across image, video, audio, and code.&lt;/p>
&lt;/blockquote>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/gemini_api/gemini_count_tokens_english.jpg" alt="Google Gemini token 計算 - 英文" data-caption="Google Gemini token 計算 - 英文" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='850px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:850px;height:;"/>
&lt;figcaption style="text-align: center;">
Google Gemini token 計算 - 英文
&lt;/figcaption>
&lt;/figure>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/gemini_api/gpt_count_tokens_english.jpg" alt="OpenAI GPT-3.5 token 計算 - 英文" data-caption="OpenAI GPT-3.5 token 計算 - 英文" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='650px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:650px;height:;"/>
&lt;figcaption style="text-align: center;">
OpenAI GPT-3.5 token 計算 - 英文
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;p>但是，如果是中文，因為計算方式的差異，整整差了兩倍！！ (分別為 25 跟 50)&lt;/p>
&lt;blockquote>
&lt;p>Gemini 是一個原生多模態的大型語言模型，在大規模多任務語言理解方面甚至超越人類專家。&lt;/p>
&lt;/blockquote>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/gemini_api/gemini_count_tokens_chinese.jpg" alt="Google Gemini token 計算 - 中文" data-caption="Google Gemini token 計算 - 中文" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='850px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:850px;height:;"/>
&lt;figcaption style="text-align: center;">
Google Gemini token 計算 - 中文
&lt;/figcaption>
&lt;/figure>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/gemini_api/gpt_count_tokens_chinese.jpg" alt="OpenAI GPT-3.5 token 計算 - 中文" data-caption="OpenAI GPT-3.5 token 計算 - 中文" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='650px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:650px;height:;"/>
&lt;figcaption style="text-align: center;">
OpenAI GPT-3.5 token 計算 - 中文
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;h2 id="gemini-pro-api">Gemini Pro API&lt;/h2>
&lt;h3 id="創建-api-key">創建 API key&lt;/h3>
&lt;p>進到 &lt;a href="https://ai.google.dev/?hl=zh-tw" target="_blank" rel="noopener">
Google AI for Developers
&lt;/a> 的網站，可以查看 Google AI 模型的介紹、價格、說明文件與範例。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/gemini_api/build_with_gemini1.jpg" alt="建置 Gemini" data-caption="建置 Gemini" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='650px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:650px;height:;"/>
&lt;figcaption style="text-align: center;">
建置 Gemini
&lt;/figcaption>
&lt;/figure>
&lt;p>點擊 &amp;quot;Get API key in Google AI Studio&amp;quot; 前往 &lt;a href="https://makersuite.google.com/" target="_blank" rel="noopener">
Google AI Studio
&lt;/a> 來在網頁上測試 LLM AI 模型(類似 Playground 頁面的用途) 與取得 API key，&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/gemini_api/build_with_gemini2.jpg" alt="建置 Gemini" data-caption="建置 Gemini" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='600px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:600px;height:;"/>
&lt;figcaption style="text-align: center;">
建置 Gemini
&lt;/figcaption>
&lt;/figure>
&lt;blockquote>
&lt;p>Google AI Studio 是以瀏覽器為基礎的 IDE，可使用生成式模型進行原型設計。Google AI Studio 可讓您快速試用模型並嘗試各種提示建構符合需求的項目後，您可以從 Gemini API 提供支援的程式語言，並將其匯出為程式碼。&lt;/p>
&lt;/blockquote>
&lt;p>關於 Google AI Studio 的使用我就不多介紹了。提醒如果想輸入圖片，右邊的 Model 記得要切換成 Gemini Pro Vision，才有支援圖像。&lt;/p>
&lt;p>* &lt;a href="https://ai.google.dev/tutorials/ai-studio_quickstart?hl=zh-tw" target="_blank" rel="noopener">
Google AI Studio 官方教學文章
&lt;/a>&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/gemini_api/google_ai_studio.jpg" alt="Google AI Studio" data-caption="Google AI Studio" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='800px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:800px;height:;"/>
&lt;figcaption style="text-align: center;">
Google AI Studio
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;p>點擊左邊的 &amp;quot;Get API key&amp;quot; &amp;gt; &amp;quot;Create API key in new project&amp;quot; 來自動產生一個 Google Cloud 專案並創建一個 API key。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/gemini_api/google_cloud_keys.jpg" alt="創建 Google Cloud API key" data-caption="創建 Google Cloud API key" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='700px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:700px;height:;"/>
&lt;figcaption style="text-align: center;">
創建 Google Cloud API key
&lt;/figcaption>
&lt;/figure>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/gemini_api/google_cloud_key_generated.jpg" alt="創建 Google Cloud API key" data-caption="創建 Google Cloud API key" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='700px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:700px;height:;"/>
&lt;figcaption style="text-align: center;">
創建 Google Cloud API key
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;h3 id="gemini-模型種類說明">Gemini 模型種類說明&lt;/h3>
&lt;p>&lt;a href="https://ai.google.dev/models/gemini?hl=zh-tw" target="_blank" rel="noopener">
Gemini models 種類說明
&lt;/a> 列出目前可使用的 Gemini 模型資訊，包含 &amp;quot;模型說明&amp;quot;、&amp;quot;模型更新時間&amp;quot;、&amp;quot;輸入輸出類型&amp;quot;、&amp;quot;Token限制&amp;quot;、&amp;quot;頻率限制&amp;quot;&lt;/p>
&lt;p>Gemini Pro 用在文字輸入，而 Gemini Pro Vision 可以文字加影像輸入。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/gemini_api/api_models.png" alt="Gemini 可使用的模型種類" data-caption="Gemini 可使用的模型種類" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='800px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:800px;height:;"/>
&lt;figcaption style="text-align: center;">
Gemini 可使用的模型種類
&lt;/figcaption>
&lt;/figure>
&lt;h3 id="api-版本說明">API 版本說明&lt;/h3>
&lt;p>目前 Gemini API 有 &lt;a href="https://ai.google.dev/docs/api_versions?hl=zh-tw" target="_blank" rel="noopener">
v1 和 v1beta 版本
&lt;/a>：&lt;/p>
&lt;ul>
&lt;li>v1：API 的穩定版。在主要版本的生命週期內，穩定版本的功能都能完整支援。如有任何破壞性變更，系統會建立 API 的下一個主要版本，並在合理的時間內淘汰現有版本。&lt;/li>
&lt;li>v1beta：包含可能處於開發階段的搶先體驗功能，且需要快速更新及破壞性變更。請勿使用此版本於正式版應用程式。&lt;/li>
&lt;/ul>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/gemini_api/api_versions_explained.png" alt="API 版本比較 (v1 與 v1beta)" data-caption="API 版本比較 (v1 與 v1beta)" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='750px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:750px;height:;"/>
&lt;figcaption style="text-align: center;">
API 版本比較 (v1 與 v1beta)
&lt;/figcaption>
&lt;/figure>
&lt;h3 id="傳入參數">傳入參數&lt;/h3>
&lt;p>我們使用的 &lt;a href="https://ai.google.dev/api/rest/v1/models/generateContent?hl=zh-tw" target="_blank" rel="noopener">
generateContent 方法
&lt;/a> 其 Request 相關資訊如下：&lt;/p>
&lt;pre>&lt;code>POST https://generativelanguage.googleapis.com/v1/models/{gemini-pro or gemini-pro-vision}:generateContent?key={API_KEY}
&lt;/code>&lt;/pre>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;contents&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;parts&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;text&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;prompt...&amp;#34;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">],&lt;/span>
&lt;span class="nt">&amp;#34;role&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;user or model&amp;#34;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">],&lt;/span>
&lt;span class="nt">&amp;#34;safetySettings&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;category&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;&amp;lt;enum (HarmCategory)&amp;gt;&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;threshold&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;&amp;lt;enum (HarmBlockThreshold)&amp;gt;&amp;#34;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">],&lt;/span>
&lt;span class="nt">&amp;#34;generationConfig&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;temperature&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;&amp;lt;number&amp;gt;&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;topP&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;&amp;lt;number&amp;gt;&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;topK&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;&amp;lt;number&amp;gt;&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;candidateCount&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;&amp;lt;integer&amp;gt;&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;maxOutputTokens&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;&amp;lt;integer&amp;gt;&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;stopSequences&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;&amp;lt;string&amp;gt;&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;code>contents&lt;/code> 放 prompt 提示 (&lt;code>text&lt;/code>) 與角色 (&lt;code>role&lt;/code>，可以是 &lt;code>user&lt;/code> 或 &lt;code>model&lt;/code>，不填則預設 &lt;code>user&lt;/code>)。&lt;/p>
&lt;br/>
&lt;p>&lt;code>safetySettings&lt;/code> 是 OpenAI GPT 沒有的參數，用於封鎖不安全的回覆內容。&lt;/p>
&lt;p>&lt;code>safetySettings&lt;/code> &amp;gt; &lt;code>category&lt;/code> 類別，可以使用以下數值：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>數值&lt;/th>
&lt;th>代表意思&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>HARM_CATEGORY_HARASSMENT&lt;/td>
&lt;td>騷擾內容。&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>HARM_CATEGORY_HATE_SPEECH&lt;/td>
&lt;td>仇恨言論和內容。&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>HARM_CATEGORY_SEXUALLY_EXPLICIT&lt;/td>
&lt;td>情色露骨內容。&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>HARM_CATEGORY_DANGEROUS_CONTENT&lt;/td>
&lt;td>危險內容。&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>* 其他還有 HARM_CATEGORY_UNSPECIFIED、HARM_CATEGORY_DEROGATORY、HARM_CATEGORY_TOXICITY、HARM_CATEGORY_VIOLENCE、HARM_CATEGORY_SEXUAL、HARM_CATEGORY_MEDICAL、HARM_CATEGORY_DANGEROUS，不過那是給 PaLM 2（舊版）模型使用的，Gemini 模型不支援。&lt;/p>
&lt;p>&lt;code>safetySettings&lt;/code> &amp;gt; &lt;code>threshold&lt;/code> 封鎖門檻，可以使用以下數值：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>數值&lt;/th>
&lt;th>代表意思&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>HARM_BLOCK_THRESHOLD_UNSPECIFIED&lt;/td>
&lt;td>未指定門檻。&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>BLOCK_LOW_AND_ABOVE&lt;/td>
&lt;td>允許含有「NEGLIGIBLE」的內容&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>BLOCK_MEDIUM_AND_ABOVE&lt;/td>
&lt;td>允許含有「NEGLIGIBLE」、「LOW」的內容。&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>BLOCK_ONLY_HIGH&lt;/td>
&lt;td>允許含有「NEGLIGIBLE」、「LOW」、「MEDIUM」的內容。&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>BLOCK_NONE&lt;/td>
&lt;td>允許所有內容。&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;br/>
&lt;p>&lt;code>generationConfig&lt;/code> 用於設定模型生成和輸出的設定參數，其中幾個比較會用到的是：&lt;/p>
&lt;ul>
&lt;li>&lt;code>temperature&lt;/code>：輸出內容的隨機性。越接近 1.0，產生的回應會豐富、多元、更有創意；反之越接近 0.0，則會產生較有確定性、可能性較高的回覆。&lt;/li>
&lt;li>&lt;code>maxOutputTokens&lt;/code>：最大輸出回應 Token 數量。Gemini Pro 模型預設 2048；Gemini Pro Vision 模型預設 4096。&lt;/li>
&lt;li>&lt;code>candidateCount&lt;/code>：要傳回的回應數量。預設 1，可設定 1~8，但目前好像限制只能用 1。&lt;/li>
&lt;/ul>
&lt;br/>
&lt;br/>
&lt;p>用法可以直接看下一章節的 Python 範例程式碼。其他更詳細的說明，請參考以下官方文件：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/gemini" target="_blank" rel="noopener">
Gemini API 說明文件
&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://ai.google.dev/api/rest/v1/models/generateContent?hl=zh-tw" target="_blank" rel="noopener">
Gemini API generateContent 參考資料
&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://ai.google.dev/docs/concepts?hl=zh-tw#model_parameters" target="_blank" rel="noopener">
LLM 概念指南 &amp;gt; 模型參數
&lt;/a>&lt;/li>
&lt;/ul>
&lt;br/>
&lt;h3 id="回覆內容">回覆內容&lt;/h3>
&lt;p>API 範例回覆內容如下：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;span class="lnt">44
&lt;/span>&lt;span class="lnt">45
&lt;/span>&lt;span class="lnt">46
&lt;/span>&lt;span class="lnt">47
&lt;/span>&lt;span class="lnt">48
&lt;/span>&lt;span class="lnt">49
&lt;/span>&lt;span class="lnt">50
&lt;/span>&lt;span class="lnt">51
&lt;/span>&lt;span class="lnt">52
&lt;/span>&lt;span class="lnt">53
&lt;/span>&lt;span class="lnt">54
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;candidates&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;content&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;parts&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;text&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;我知道王建民。王建民，1980年3月31日出生於台灣台中市，是一位前台灣棒球選手，司職投手。他曾效力於中華職棒的興農牛隊，美國職棒的紐約洋基隊、華盛頓國民隊和芝加哥白襪隊，以及中國棒球聯賽的北京猛虎隊。\n\n王建民是台灣史上第一位大聯盟先發勝投破百的投手，也是第一位入選大聯盟全明星賽的台灣選手。他在2006年締造19勝6敗、 防禦率3.63的優異成績，並在季後賽拿下3勝0敗的戰績，幫助洋基隊奪得世界大賽冠軍。王建民也因此成為台灣的棒球英雄，並獲得「台灣之光」的稱號。\n\n然而，王建民在2008年季初因傷缺陣，並在2009年進行了韌帶移植手術。此後，他的成績大幅下滑，並在2012年離開了大聯盟。王建民於2013年回歸中華職棒，效力於義大犀牛隊。2016年，他宣布正式退休。\n\n王建民的職業生涯戰績為127勝72敗， 防禦率3.92，三振數1718次。他是台灣棒球史上最成功的投手之一，也是台灣人民的驕傲。&amp;#34;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">],&lt;/span>
&lt;span class="nt">&amp;#34;role&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;model&amp;#34;&lt;/span>
&lt;span class="p">},&lt;/span>
&lt;span class="nt">&amp;#34;finishReason&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;STOP&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;index&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;safetyRatings&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;category&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;HARM_CATEGORY_SEXUALLY_EXPLICIT&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;probability&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;NEGLIGIBLE&amp;#34;&lt;/span>
&lt;span class="p">},&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;category&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;HARM_CATEGORY_HATE_SPEECH&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;probability&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;NEGLIGIBLE&amp;#34;&lt;/span>
&lt;span class="p">},&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;category&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;HARM_CATEGORY_HARASSMENT&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;probability&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;NEGLIGIBLE&amp;#34;&lt;/span>
&lt;span class="p">},&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;category&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;HARM_CATEGORY_DANGEROUS_CONTENT&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;probability&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;NEGLIGIBLE&amp;#34;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">]&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">],&lt;/span>
&lt;span class="nt">&amp;#34;promptFeedback&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;safetyRatings&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;category&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;HARM_CATEGORY_SEXUALLY_EXPLICIT&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;probability&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;NEGLIGIBLE&amp;#34;&lt;/span>
&lt;span class="p">},&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;category&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;HARM_CATEGORY_HATE_SPEECH&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;probability&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;NEGLIGIBLE&amp;#34;&lt;/span>
&lt;span class="p">},&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;category&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;HARM_CATEGORY_HARASSMENT&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;probability&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;NEGLIGIBLE&amp;#34;&lt;/span>
&lt;span class="p">},&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;category&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;HARM_CATEGORY_DANGEROUS_CONTENT&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;probability&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;NEGLIGIBLE&amp;#34;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">]&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;code>candidates&lt;/code> 就是回應候選內容，目前好像只會有一個，因為輸入的 &lt;code>generationConfig&lt;/code> &amp;gt; &lt;code>candidateCount&lt;/code> 它也只讓我設定 1。&lt;/p>
&lt;ul>
&lt;li>&lt;code>content&lt;/code>：生成回應內容，格式跟輸入的 &lt;code>contents&lt;/code> 一樣。&lt;/li>
&lt;li>&lt;code>finishReason&lt;/code>：模型停止產生 token 的原因。&lt;/li>
&lt;/ul>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>數值&lt;/th>
&lt;th>代表意思&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>FINISH_REASON_UNSPECIFIED&lt;/td>
&lt;td>預設值。這個值未使用。&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>STOP&lt;/td>
&lt;td>模型的自然停止或提供的停止序列。&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>MAX_TOKENS&lt;/td>
&lt;td>已達到請求中指定的 token 數量上限。&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>SAFETY&lt;/td>
&lt;td>內容因安全原因而被標記。&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>RECITATION&lt;/td>
&lt;td>內容因遭檢舉為引用原因而被標記。&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>OTHER&lt;/td>
&lt;td>未知原因。&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;ul>
&lt;li>&lt;code>index&lt;/code>：此候選內容在候選清單中的索引 (目前只有一則)。&lt;/li>
&lt;li>&lt;code>safetyRatings&lt;/code>：安全性評級清單。顯示此回覆內容在各項安全性類別的等級(可能性)。&lt;/li>
&lt;/ul>
&lt;br/>
&lt;p>我自己在測試時，有時會如下回應，不知道是不是剛推出，所以還不太穩。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;candidates&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[{&lt;/span>&lt;span class="nt">&amp;#34;finishReason&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;OTHER&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;index&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">}],&lt;/span>
&lt;span class="nt">&amp;#34;promptFeedback&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;safetyRatings&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;span class="p">{&lt;/span>&lt;span class="nt">&amp;#34;category&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;HARM_CATEGORY_SEXUALLY_EXPLICIT&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;probability&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;NEGLIGIBLE&amp;#34;&lt;/span>&lt;span class="p">},&lt;/span>
&lt;span class="p">{&lt;/span>&lt;span class="nt">&amp;#34;category&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;HARM_CATEGORY_HATE_SPEECH&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;probability&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;NEGLIGIBLE&amp;#34;&lt;/span>&lt;span class="p">},&lt;/span>
&lt;span class="p">{&lt;/span>&lt;span class="nt">&amp;#34;category&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;HARM_CATEGORY_HARASSMENT&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;probability&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;NEGLIGIBLE&amp;#34;&lt;/span>&lt;span class="p">},&lt;/span>
&lt;span class="p">{&lt;/span>&lt;span class="nt">&amp;#34;category&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;HARM_CATEGORY_DANGEROUS_CONTENT&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;probability&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;NEGLIGIBLE&amp;#34;&lt;/span>&lt;span class="p">}&lt;/span>
&lt;span class="p">]&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;br/>
&lt;p>其他更詳細的說明，請參考以下官方文件：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://ai.google.dev/api/rest/v1/GenerateContentResponse?hl=zh-tw" target="_blank" rel="noopener">
Gemini API GenerateContentResponse 參考資料
&lt;/a>&lt;/li>
&lt;/ul>
&lt;br/>
&lt;h3 id="python-範例程式碼">Python 範例程式碼&lt;/h3>
&lt;p>在 &lt;a href="https://ai.google.dev/tutorials/python_quickstart" target="_blank" rel="noopener">
官網的範例
&lt;/a> 是使用他們創建的 &lt;a href="https://pypi.org/project/google-generativeai/" target="_blank" rel="noopener">
google-generativeai
&lt;/a> 套件。&lt;br />
不過這邊我想改用我們熟悉的 Requests 套件來嘗試、示範。&lt;/p>
&lt;br/>
&lt;p>首先要確認有安裝 Requests 套件：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-Shell" data-lang="Shell">pip install requests
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>那我們開始吧~🏃&lt;/p>
&lt;br/>
&lt;h4 id="單個純文字">單個純文字&lt;/h4>
&lt;p>純粹問它一段話：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="c1"># 單個純文字&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">json&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">requests&lt;/span>
&lt;span class="n">url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="s1">&amp;#39;https://generativelanguage.googleapis.com/v1/models/gemini-pro:generateContent?key={API_KEY}&amp;#39;&lt;/span>
&lt;span class="n">headers&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s1">&amp;#39;Content-Type&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s1">&amp;#39;application/json&amp;#39;&lt;/span>&lt;span class="p">}&lt;/span>
&lt;span class="n">data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="s2">&amp;#34;contents&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="s2">&amp;#34;parts&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[{&lt;/span>&lt;span class="s2">&amp;#34;text&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;你知道王建民嗎？&amp;#34;&lt;/span>&lt;span class="p">}]&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">]&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">requests&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">post&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">headers&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">headers&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">json&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="k">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">f&lt;/span>&lt;span class="s2">&amp;#34;response status_code: {response.status_code}&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="k">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">dumps&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">json&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="n">indent&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">4&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ensure_ascii&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="bp">False&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;br/>
&lt;h4 id="單個純文字--參數">單個純文字 + 參數&lt;/h4>
&lt;p>問它一段話，並且加上一些參數設定：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="c1"># 單個純文字 + 參數&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">json&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">requests&lt;/span>
&lt;span class="n">url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="s1">&amp;#39;https://generativelanguage.googleapis.com/v1/models/gemini-pro:generateContent?key={API_KEY}&amp;#39;&lt;/span>
&lt;span class="n">headers&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s1">&amp;#39;Content-Type&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s1">&amp;#39;application/json&amp;#39;&lt;/span>&lt;span class="p">}&lt;/span>
&lt;span class="n">data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="s2">&amp;#34;contents&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="s2">&amp;#34;parts&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[{&lt;/span>&lt;span class="s2">&amp;#34;text&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;你知道王建民嗎？&amp;#34;&lt;/span>&lt;span class="p">}]&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">],&lt;/span>
&lt;span class="s2">&amp;#34;safetySettings&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="s2">&amp;#34;category&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;HARM_CATEGORY_DANGEROUS_CONTENT&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s2">&amp;#34;threshold&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;BLOCK_NONE&amp;#34;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">],&lt;/span>
&lt;span class="s2">&amp;#34;generationConfig&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="s2">&amp;#34;temperature&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mf">1.0&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s2">&amp;#34;maxOutputTokens&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">30&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s2">&amp;#34;topP&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mf">0.8&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s2">&amp;#34;topK&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">10&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">requests&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">post&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">headers&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">headers&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">json&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="k">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">f&lt;/span>&lt;span class="s2">&amp;#34;response status_code: {response.status_code}&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="k">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">dumps&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">json&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="n">indent&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">4&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ensure_ascii&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="bp">False&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;br/>
&lt;h4 id="多輪純文字對話聊天">多輪純文字對話（聊天）&lt;/h4>
&lt;p>像在 Bard 或 ChatGPT 上一樣，可以多輪對話，它會記得之前的內容：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="c1"># 多輪對話（聊天）&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">json&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">requests&lt;/span>
&lt;span class="n">url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="s1">&amp;#39;https://generativelanguage.googleapis.com/v1/models/gemini-pro:generateContent?key={API_KEY}&amp;#39;&lt;/span>
&lt;span class="n">headers&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s1">&amp;#39;Content-Type&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s1">&amp;#39;application/json&amp;#39;&lt;/span>&lt;span class="p">}&lt;/span>
&lt;span class="n">data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="s2">&amp;#34;contents&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="s2">&amp;#34;role&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;user&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s2">&amp;#34;parts&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[{&lt;/span>&lt;span class="s2">&amp;#34;text&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;你知道王建民嗎？&amp;#34;&lt;/span>&lt;span class="p">}]&lt;/span>
&lt;span class="p">},&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="s2">&amp;#34;role&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;model&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s2">&amp;#34;parts&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[{&lt;/span>&lt;span class="s2">&amp;#34;text&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;我知道王建民。王建民，1980年3月31日出生於台灣台中市，是一位前台灣棒球選手，司職投手。他曾效力於中華職棒的興農牛隊，美國職棒的紐約洋基隊、華盛頓國民隊和芝加哥白襪隊，以及中國棒球聯賽的北京猛虎隊。&lt;/span>&lt;span class="se">\n\n&lt;/span>&lt;span class="s2">王建民是台灣史上第一位大聯盟先發勝投破百的投手，也是第一位入選大聯盟全明星賽的台灣選手。他在2006年締造19勝6敗、 防禦率3.63的優異成績，並在季後賽拿下3勝0敗的戰績，幫助洋基隊奪得世界大賽冠軍。王建民也因此成為台灣的棒球英雄，並獲得「台灣之光」的稱號。&lt;/span>&lt;span class="se">\n\n&lt;/span>&lt;span class="s2">然而，王建民在2008年季初因傷缺陣，並在2009年進行了韌帶移植手術。此後，他的成績大幅下滑，並在2012年離開了大聯盟。王建民於2013年回歸中華職棒，效力於義大犀牛隊。2016年，他宣布正式退休。&lt;/span>&lt;span class="se">\n\n&lt;/span>&lt;span class="s2">王建民的職業生涯戰績為127勝72敗， 防禦率3.92，三振數1718次。他是台灣棒球史上最成功的投手之一，也是台灣人民的驕傲。&amp;#34;&lt;/span>&lt;span class="p">}]&lt;/span>
&lt;span class="p">},&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="s2">&amp;#34;role&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;user&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s2">&amp;#34;parts&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[{&lt;/span>&lt;span class="s2">&amp;#34;text&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;他現在在哪裡？&amp;#34;&lt;/span>&lt;span class="p">}]&lt;/span>
&lt;span class="p">},&lt;/span>
&lt;span class="p">]&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">requests&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">post&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">headers&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">headers&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">json&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="k">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">f&lt;/span>&lt;span class="s2">&amp;#34;response status_code: {response.status_code}&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="k">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">dumps&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">json&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="n">indent&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">4&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ensure_ascii&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="bp">False&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;br/>
&lt;h4 id="單個文字和圖片">單個文字和圖片&lt;/h4>
&lt;p>如果需要 AI 可以看圖片，需要改用 Gemini Pro Vision 模型 (支援文字和圖片輸入)，並且圖片要轉換為 Base64 編碼的字串，&lt;/p>
&lt;p>圖片的 &lt;code>mime_type&lt;/code> 參數目前支援「&lt;code>image/png&lt;/code>」、「&lt;code>image/jpeg&lt;/code>」、「&lt;code>image/heic&lt;/code>」、「&lt;code>image/heif&lt;/code>」、「&lt;code>image/webp&lt;/code>」幾種格式。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="c1"># 單個文字和圖片&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">json&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">requests&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">base64&lt;/span>
&lt;span class="c1"># 讀取圖片檔案，並轉換成 Base64 編碼的字串&lt;/span>
&lt;span class="k">with&lt;/span> &lt;span class="nb">open&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;gemini_test_image.jpg&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;rb&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">image_file&lt;/span>&lt;span class="p">:&lt;/span>
&lt;span class="n">image_base64_string&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">base64&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">b64encode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">image_file&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">read&lt;/span>&lt;span class="p">())&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">decode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;utf-8&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="c1"># print(image_base64_string)&lt;/span>
&lt;span class="n">url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="s1">&amp;#39;https://generativelanguage.googleapis.com/v1/models/gemini-pro-vision:generateContent?key={API_KEY}&amp;#39;&lt;/span>
&lt;span class="n">headers&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s1">&amp;#39;Content-Type&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s1">&amp;#39;application/json&amp;#39;&lt;/span>&lt;span class="p">}&lt;/span>
&lt;span class="n">data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="s2">&amp;#34;contents&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="s2">&amp;#34;parts&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;text&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;詳細說明你在這張圖片中看到什麼？&amp;#34;&lt;/span>&lt;span class="p">},&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="s2">&amp;#34;inline_data&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="s2">&amp;#34;mime_type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;image/jpeg&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s2">&amp;#34;data&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">image_base64_string&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">]&lt;/span>
&lt;span class="p">},&lt;/span>
&lt;span class="p">]&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">requests&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">post&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">headers&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">headers&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">json&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="k">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">f&lt;/span>&lt;span class="s2">&amp;#34;response status_code: {response.status_code}&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="k">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">dumps&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">json&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="n">indent&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">4&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ensure_ascii&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="bp">False&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;br/>
&lt;!--adsense-->
&lt;h2 id="結語">結語&lt;/h2>
&lt;p>v1 Beta 版本還有更多功能，像是 &lt;a href="https://ai.google.dev/docs/function_calling" target="_blank" rel="noopener">
函數呼叫(Function calling)
&lt;/a>、&lt;a href="https://ai.google.dev/docs/semantic_retriever" target="_blank" rel="noopener">
語意檢索器(Semantic Retriever、RAG)
&lt;/a>，雖然還在測試中，不建議用於正式版應用，但有興趣的還是可以去玩玩看🤖。&lt;/p>
&lt;p>至少在今年底前使用 Google Gemini API 都是「免費」使用，你想要拿來練習、做專案、做 Side Project 都可以使盡玩(?)，但要注意不要上傳任何敏感資料，因為目前方案所有的輸入輸出都可能會被拿去當訓練資料。&lt;/p>
&lt;br/>
&lt;br/>
&lt;hr />
&lt;p>參考：&lt;br />
&lt;a href="https://deepmind.google/technologies/gemini/" target="_blank" rel="noopener">
Google Gemini 官方網站
&lt;/a>&lt;br />
&lt;a href="https://ai.google.dev/docs?hl=zh-tw" target="_blank" rel="noopener">
Google Gemini API 說明文件
&lt;/a>&lt;br />
&lt;a href="https://makersuite.google.com/" target="_blank" rel="noopener">
Google AI Studio
&lt;/a>&lt;br />
&lt;a href="https://developers.googleblog.com/" target="_blank" rel="noopener">
Google for Developers Blog
&lt;/a>&lt;/p>
&lt;br/>
&lt;blockquote>
&lt;p>The sky&amp;rsquo;s the limit&lt;br />
一切皆有可能&lt;/p>
&lt;/blockquote></content:encoded><dc:creator>Jia</dc:creator><media:content url="https://blog.jiatool.comimages/cover/gemini_api.jpg" medium="image"><media:title type="html">featured image</media:title></media:content><media:content url="https://blog.jiatool.comimages/posts/gemini_api_meta.jpg" medium="image"><media:title type="html">meta image</media:title></media:content><category>Gemini</category><category>LLM</category><category>AI</category><category>人工智慧</category><category>API</category><category>Python</category><category>Google</category><category>分享</category></item><item><title>DataForSEO 教學 — Google、Yahoo 搜尋結果 SERP API</title><link>https://blog.jiatool.com/posts/dataforseo/</link><pubDate>Sat, 18 Nov 2023 21:50:00 +0800</pubDate><author>jia@jiatool.com (Jia)</author><atom:modified>Sat, 18 Nov 2023 21:50:00 +0800</atom:modified><guid>https://blog.jiatool.com/posts/dataforseo/</guid><description>(先澄清，以下非業配，純粹依個人使用過程描述。) 前言 在很久之前，我有寫一篇關於第三方 Google 搜尋結果的 API (Aves API) 。而最近又在因緣際會下，被迫(?)接觸</description><content:encoded>&lt;p>(先澄清，以下非業配，純粹依個人使用過程描述。)&lt;/p>
&lt;h2 id="前言">前言&lt;/h2>
&lt;p>在很久之前，我有寫一篇關於第三方 &lt;a href="https://blog.jiatool.com/posts/aves_api" target="_blank" rel="noopener">
Google 搜尋結果的 API (Aves API)
&lt;/a>。而最近又在因緣際會下，被迫(?)接觸了另一個類似的服務平台 — DataForSEO。&lt;/p>
&lt;p>&lt;a href="https://dataforseo.com/" target="_blank" rel="noopener">
DataForSEO
&lt;/a> 提供了更多的服務，包括 Google Search、Google Maps、Google News、Google Images、YouTube、Yahoo Search、Bing Search、百度 Search、Google Play 和 Apple App，還有一些其他商業應用的服務。可以看的出來還蠻多樣的，如果有這方面的需求或許可以參考。&lt;/p>
&lt;p>這次就改以「Yahoo 搜尋 API」當範例，來跟著我一起嘗試這個 API 服務吧，這篇也算把我自己的使用經驗記錄下來。&lt;/p>
&lt;br/>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/dataforseo/dataforseo_home.jpg" alt="DataForSEO 官網" data-caption="DataForSEO 官網" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='600px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:600px;height:;"/>
&lt;figcaption style="text-align: center;">
DataForSEO 官網
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;!--adsense-->
&lt;h2 id="serp-api">SERP API&lt;/h2>
&lt;p>官方的 &lt;a href="https://dataforseo.com/apis/serp-api" target="_blank" rel="noopener">
SERP API 頁面
&lt;/a> 有一些簡單的介紹，SERP API 目前支援以下幾種搜尋：&lt;/p>
&lt;ul>
&lt;li>Google SERP&lt;/li>
&lt;li>Google 圖片&lt;/li>
&lt;li>Google 新聞&lt;/li>
&lt;li>Google 地圖&lt;/li>
&lt;li>Yahoo SERP&lt;/li>
&lt;li>Bing SERP&lt;/li>
&lt;li>百度 SERP&lt;/li>
&lt;li>YouTube SERP&lt;/li>
&lt;/ul>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/dataforseo/serp_api.jpg" alt="SERP API 支援的搜尋引擎" data-caption="SERP API 支援的搜尋引擎" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='500px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:500px;height:;"/>
&lt;figcaption style="text-align: center;">
SERP API 支援的搜尋引擎
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;h3 id="搜尋測試頁面">搜尋測試頁面&lt;/h3>
&lt;p>在 DataForSEO 官網上有提供一個 &lt;a href="https://dataforseo.com/apis/serp-api#playground" target="_blank" rel="noopener">
Google SERP 搜尋測試頁面
&lt;/a>，可以實際輸入關鍵字，並查看它回傳的資料，只可惜好像沒有提供台灣的選項。&lt;/p>
&lt;p>但它在使用者後台(需註冊)也有提供一個 API Explorer (就是 Playground) 的 GUI 介面，會完整許多，各種支援的搜尋引擎都可以選，地區也有台灣(並細分到縣市)，也有許多參數可以設定，但這邊就會直接花到錢的 (新帳戶有送 1 美元的額度可供試用)。&lt;/p>
&lt;p>關於使用者後台頁面，我文章後續章節會再介紹。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/dataforseo/playground.jpg" alt="Google SERP 搜尋測試頁面" data-caption="Google SERP 搜尋測試頁面" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='700px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:700px;height:;"/>
&lt;figcaption style="text-align: center;">
Google SERP 搜尋測試頁面
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;h3 id="serp-api-價格">SERP API 價格&lt;/h3>
&lt;p>搜尋 API 價格官方有詳細說明在&lt;a href="https://dataforseo.com/pricing/serp/serp-api" target="_blank" rel="noopener">
這個網頁
&lt;/a>，依照你使用的模式它分為三種價格：STANDARD QUEUE、PRIORITY QUEUE、LIVE MODE。&lt;/p>
&lt;ul>
&lt;li>STANDARD QUEUE：每一次查詢 $0.0006，平均等 5 分鐘。&lt;/li>
&lt;li>PRIORITY QUEUE：每一次查詢 $0.0012，平均等 1 分鐘。&lt;/li>
&lt;li>LIVE MODE：每一次查詢 $0.002，平均等 6 秒。&lt;/li>
&lt;/ul>
&lt;p>* 以上價格為美金&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/dataforseo/serp_pricing.jpg" alt="SERP API 價格" data-caption="SERP API 價格" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='600px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:600px;height:;"/>
&lt;figcaption style="text-align: center;">
SERP API 價格
&lt;/figcaption>
&lt;/figure>
&lt;p>LIVE MODE 使用上最簡單，只要單一請求就會回傳結果，速度也最快，但價格當然也最貴。&lt;br />
STANDARD QUEUE 和 PRIORITY QUEUE 則是要先發送查詢任務請求，再來定時去問問它查好了沒，最後再取得查詢結果，要分成三段，在程式上寫起來比較麻煩，但它比較便宜(窮人的悲哀QQ)，而 STANDARD QUEUE 和 PRIORITY QUEUE 兩者看起來只差在等待時間及價格。&lt;/p>
&lt;br/>
&lt;p>以下文章我會以 STANDARD QUEUE 和 PRIORITY QUEUE 模式來示範。&lt;/p>
&lt;br/>
&lt;h3 id="api-使用">API 使用&lt;/h3>
&lt;p>SERP API 使用上分為三個請求：&lt;/p>
&lt;ol>
&lt;li>&lt;a href="https://docs.dataforseo.com/v3/serp/yahoo/organic/task_post/" target="_blank" rel="noopener">
Task POST
&lt;/a> (&lt;code>task_post&lt;/code>)：設定任務&lt;/li>
&lt;li>&lt;a href="https://docs.dataforseo.com/v3/serp/yahoo/organic/tasks_ready/" target="_blank" rel="noopener">
Tasks Ready
&lt;/a> (&lt;code>tasks_ready&lt;/code>)：取得已完成任務&lt;/li>
&lt;li>&lt;a href="https://docs.dataforseo.com/v3/serp/yahoo/organic/task_get/regular/" target="_blank" rel="noopener">
Task GET
&lt;/a> (&lt;code>task_get&lt;/code>)：取得任務的結果&lt;/li>
&lt;/ol>
&lt;p>* STANDARD QUEUE、PRIORITY QUEUE 模式的區分是在送出 task_post 請求時，帶上 &lt;code>priority&lt;/code> 參數來指定，&lt;code>1&lt;/code> (預設)代表 STANDARD、&lt;code>2&lt;/code> 代表 PRIORITY。&lt;/p>
&lt;br/>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/dataforseo/serp_api_flow.jpg" alt="SERP API 流程" data-caption="SERP API 流程" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='650px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:650px;height:;"/>
&lt;figcaption style="text-align: center;">
SERP API 流程
&lt;/figcaption>
&lt;/figure>
&lt;ol>
&lt;li>
&lt;p>首先使用 &amp;quot;Task POST&amp;quot; 設定查詢任務，指定關鍵字以及其餘參數，讓它開始去抓我們想要的搜尋結果，這邊最多可以一次給 100 組關鍵字，不過稍微注意有限制每分鐘最多只能發送 2000 個 API 請求。&lt;/p>
&lt;p>* 你也可以指定 &lt;code>pingback_url&lt;/code>，當任務完成時它會主動通知你；&lt;br />
* 或指定 &lt;code>postback_url&lt;/code>，當任務完成時它會主動將結果傳送給你。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>再來使用 &amp;quot;Tasks Ready&amp;quot; 查詢它執行完哪些任務，可以每 10 秒檢查一次，或一分鐘檢查一次，它只有限制每分鐘最多進行 20 次 API 請求。另外，已經被收集的任務(我猜是指已使用 &lt;code>task_get&lt;/code> 取得的任務)和放超過三天未收集的任務會被移除不會在裡面。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>最後，使用 &amp;quot;Task GET&amp;quot; 帶上任務 ID 取得任務的查詢結果，它有分三種 Regular、Advanced、HTML，Regular 就是以 JSON 格式整理好的結果；Advanced 跟 Regular 一樣，只是好像還有多一些特定資訊，我也不是很清楚；HTML 就是回傳搜尋結果的 HTML 格式。&lt;/p>
&lt;p>* 而且任務結果在 30 天內都會保留，用 ID 都可以取得結果。&lt;br />
* 關於搜尋結果不同類別(type)各自代表甚麼，在這邊有官方的圖文說明：&lt;a href="https://dataforseo.com/serp-features">https://dataforseo.com/serp-features&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>* 以上三種請求，只有 &amp;quot;Task POST&amp;quot; 會收費，其餘兩個並不會產生費用。&lt;/p>
&lt;br/>
&lt;br/>
&lt;p>送出的請求 Header 皆需要帶上憑證，確認是哪個帳號發出的，其格式如下：&lt;/p>
&lt;pre>&lt;code>Authorization: Basic ${credentials}
&lt;/code>&lt;/pre>&lt;p>其中 &lt;code>credentials&lt;/code> 是將你的 &amp;quot;API login&amp;quot; 與 &amp;quot;API password&amp;quot; 組的字串 &lt;code>{login}:{password}&lt;/code> 經過 Base64 編碼的結果。&lt;/p>
&lt;p>例如帳號是 &lt;code>jia@gmail.com&lt;/code>、密碼是 &lt;code>abc123&lt;/code>，組成 &lt;code>jia@gmail.com:abc123&lt;/code>，再經過 Base64 變成 &lt;code>amlhQGdtYWlsLmNvbTphYmMxMjM=&lt;/code>。&lt;/p>
&lt;p>* &amp;quot;API login&amp;quot; 與 &amp;quot;API password&amp;quot; 可以在 &lt;a href="https://app.dataforseo.com/api-dashboard" target="_blank" rel="noopener">
使用者後台 API Dashboard
&lt;/a> 網頁找到。&lt;/p>
&lt;br/>
&lt;br/>
&lt;p>對了，&lt;br />
它們 API 還有提供 📦 沙盒模式(Sandbox)，讓我們可以在撰寫程式時做測試，不會花費到費用，但當然回傳的資料就是它們的範例資料，但可以讓我們確認 API 回傳的資料格式、測試程式有沒有問題。&lt;/p>
&lt;p>官方文件：&lt;a href="https://docs.dataforseo.com/v3/appendix/sandbox/">https://docs.dataforseo.com/v3/appendix/sandbox/&lt;/a>&lt;/p>
&lt;br/>
&lt;br/>
&lt;p>* 官方文件有更完整的說明：&lt;a href="https://docs.dataforseo.com/v3/serp/yahoo/overview/">https://docs.dataforseo.com/v3/serp/yahoo/overview/&lt;/a>&lt;/p>
&lt;p>* 其他常見的 Q&amp;amp;A：&lt;a href="https://dataforseo.com/help-center/category/serp-api">https://dataforseo.com/help-center/category/serp-api&lt;/a>&lt;/p>
&lt;br/>
&lt;h3 id="python-範例">Python 範例&lt;/h3>
&lt;p>首先要確認有安裝 Requests 套件：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-Shell" data-lang="Shell">pip install requests
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>完整 Python 程式碼：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;span class="lnt">44
&lt;/span>&lt;span class="lnt">45
&lt;/span>&lt;span class="lnt">46
&lt;/span>&lt;span class="lnt">47
&lt;/span>&lt;span class="lnt">48
&lt;/span>&lt;span class="lnt">49
&lt;/span>&lt;span class="lnt">50
&lt;/span>&lt;span class="lnt">51
&lt;/span>&lt;span class="lnt">52
&lt;/span>&lt;span class="lnt">53
&lt;/span>&lt;span class="lnt">54
&lt;/span>&lt;span class="lnt">55
&lt;/span>&lt;span class="lnt">56
&lt;/span>&lt;span class="lnt">57
&lt;/span>&lt;span class="lnt">58
&lt;/span>&lt;span class="lnt">59
&lt;/span>&lt;span class="lnt">60
&lt;/span>&lt;span class="lnt">61
&lt;/span>&lt;span class="lnt">62
&lt;/span>&lt;span class="lnt">63
&lt;/span>&lt;span class="lnt">64
&lt;/span>&lt;span class="lnt">65
&lt;/span>&lt;span class="lnt">66
&lt;/span>&lt;span class="lnt">67
&lt;/span>&lt;span class="lnt">68
&lt;/span>&lt;span class="lnt">69
&lt;/span>&lt;span class="lnt">70
&lt;/span>&lt;span class="lnt">71
&lt;/span>&lt;span class="lnt">72
&lt;/span>&lt;span class="lnt">73
&lt;/span>&lt;span class="lnt">74
&lt;/span>&lt;span class="lnt">75
&lt;/span>&lt;span class="lnt">76
&lt;/span>&lt;span class="lnt">77
&lt;/span>&lt;span class="lnt">78
&lt;/span>&lt;span class="lnt">79
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="kn">import&lt;/span> &lt;span class="nn">json&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">requests&lt;/span>
&lt;span class="k">class&lt;/span> &lt;span class="nc">DataforseoSerp&lt;/span>&lt;span class="p">():&lt;/span>
&lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">credentials&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">open_sandbox&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="bp">False&lt;/span>&lt;span class="p">):&lt;/span>
&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">base_url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;https://api.dataforseo.com/v3/serp&amp;#39;&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="n">open_sandbox&lt;/span>&lt;span class="p">:&lt;/span>
&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">base_url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;https://sandbox.dataforseo.com/v3/serp&amp;#39;&lt;/span> &lt;span class="c1"># 沙盒模式&lt;/span>
&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">search_engine&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;yahoo&amp;#39;&lt;/span>
&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">headers&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="s1">&amp;#39;Authorization&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="s1">&amp;#39;Basic ${credentials}&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s1">&amp;#39;Content-Type&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s1">&amp;#39;application/json&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">location_code&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">1012825&lt;/span> &lt;span class="c1"># New Taipei City,Taiwan&lt;/span>
&lt;span class="c1"># https://docs.dataforseo.com/v3/serp/yahoo/locations/&lt;/span>
&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">language_code&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;zh-TW&amp;#39;&lt;/span>
&lt;span class="c1"># https://docs.dataforseo.com/v3/serp/yahoo/languages/&lt;/span>
&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">device&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;desktop&amp;#34;&lt;/span> &lt;span class="c1"># mobile&lt;/span>
&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">os&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;windows&amp;#34;&lt;/span> &lt;span class="c1"># macos android ios&lt;/span>
&lt;span class="k">def&lt;/span> &lt;span class="nf">task_post&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">keywords&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">depth&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">100&lt;/span>&lt;span class="p">):&lt;/span>
&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;span class="s2"> 設定查詢任務，keywords 最多一次可以 100 個，每分鐘最多可以發送 2000 個 API 呼叫
&lt;/span>&lt;span class="s2"> https://docs.dataforseo.com/v3/serp/yahoo/organic/task_post/
&lt;/span>&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">keywords&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="bp">None&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="ow">or&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">keywords&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">):&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="bp">None&lt;/span>
&lt;span class="n">url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="s1">&amp;#39;{self.base_url}/{self.search_engine}/organic/task_post&amp;#39;&lt;/span>
&lt;span class="n">data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;span class="k">for&lt;/span> &lt;span class="n">keyword&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">keywords&lt;/span>&lt;span class="p">:&lt;/span>
&lt;span class="n">data&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">({&lt;/span>
&lt;span class="s2">&amp;#34;keyword&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">keyword&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s2">&amp;#34;location_code&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">location_code&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s2">&amp;#34;language_code&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">language_code&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s2">&amp;#34;device&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">device&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s2">&amp;#34;os&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">os&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s2">&amp;#34;depth&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">depth&lt;/span>
&lt;span class="p">})&lt;/span>
&lt;span class="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">requests&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">post&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">headers&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">headers&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">json&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">json&lt;/span>&lt;span class="p">()&lt;/span>
&lt;span class="k">def&lt;/span> &lt;span class="nf">tasks_ready&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">):&lt;/span>
&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;span class="s2"> 取得已完成的查詢任務
&lt;/span>&lt;span class="s2"> https://docs.dataforseo.com/v3/serp/yahoo/organic/tasks_ready/
&lt;/span>&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;span class="n">url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="s1">&amp;#39;{self.base_url}/{self.search_engine}/organic/tasks_ready&amp;#39;&lt;/span>
&lt;span class="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">requests&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">headers&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">headers&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">json&lt;/span>&lt;span class="p">()&lt;/span>
&lt;span class="k">def&lt;/span> &lt;span class="nf">task_get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">task_id&lt;/span>&lt;span class="p">):&lt;/span>
&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;span class="s2"> 取得查詢任務的結果
&lt;/span>&lt;span class="s2"> https://docs.dataforseo.com/v3/serp/yahoo/organic/task_get/regular/
&lt;/span>&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="n">task_id&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="bp">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="bp">None&lt;/span>
&lt;span class="n">url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="s1">&amp;#39;{self.base_url}/{self.search_engine}/organic/task_get/regular/{task_id}&amp;#39;&lt;/span>
&lt;span class="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">requests&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">headers&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">headers&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">json&lt;/span>&lt;span class="p">()&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="vm">__name__&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;__main__&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;span class="n">dataforseo_serp&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">DataforseoSerp&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;xxxxxxxxxxxxxxxxxxxxxxxxxxx&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">open_sandbox&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="bp">True&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">dataforseo_serp&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">task_post&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="s2">&amp;#34;飲料&amp;#34;&lt;/span>&lt;span class="p">])&lt;/span>
&lt;span class="k">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">dumps&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">))&lt;/span>
&lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">dataforseo_serp&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">tasks_ready&lt;/span>&lt;span class="p">()&lt;/span>
&lt;span class="k">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">dumps&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">))&lt;/span>
&lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">dataforseo_serp&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">task_get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;11171539-6790-0066-2000-fa82270464a2&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="k">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">dumps&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">))&lt;/span>
&lt;span class="k">with&lt;/span> &lt;span class="nb">open&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;dataforseo_serp_result.json&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;w&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="p">:&lt;/span>
&lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">dump&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>當然比較好的做法，是還要判斷它回傳結果的 &lt;code>status_code&lt;/code> 欄位和 &lt;code>tasks&lt;/code> 底下的 &lt;code>status_code&lt;/code> 欄位，確保任務執行 OK 才去取資料，如果有錯誤，程式內要做相對應處理。&lt;/p>
&lt;p>這邊只是為了示範，我就沒有寫出來了。&lt;/p>
&lt;p>* 各個狀態代碼意思如&lt;a href="https://docs.dataforseo.com/v3/appendix/errors/" target="_blank" rel="noopener">
這個官方清單
&lt;/a>。&lt;/p>
&lt;br/>
&lt;h2 id="使用者後台">使用者後台&lt;/h2>
&lt;p>當註冊後登入，即可進入 &lt;a href="https://app.dataforseo.com/api-dashboard" target="_blank" rel="noopener">
使用者後台頁面
&lt;/a> 去查看更多相關資訊。&lt;/p>
&lt;p>其中有幾個重要的頁面：&lt;/p>
&lt;ul>
&lt;li>API Dashboard (儀錶板)&lt;/li>
&lt;li>API Usage (使用紀錄)&lt;/li>
&lt;li>API Errors (錯誤紀錄)&lt;/li>
&lt;li>API Explorer (API Playground)&lt;/li>
&lt;li>API Settings (設定 API 限制)&lt;/li>
&lt;/ul>
&lt;h3 id="api-dashboard-儀錶板">API Dashboard (儀錶板)&lt;/h3>
&lt;p>有顯示你的 &amp;quot;API login&amp;quot; 與 &amp;quot;API password&amp;quot;，以及圖表化顯示每天的花費。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/dataforseo/api_dashboard.jpg" alt="API Dashboard (儀錶板) 頁面" data-caption="API Dashboard (儀錶板) 頁面" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='850px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:850px;height:;"/>
&lt;figcaption style="text-align: center;">
API Dashboard (儀錶板) 頁面
&lt;/figcaption>
&lt;/figure>
&lt;h3 id="api-usage-使用紀錄">API Usage (使用紀錄)&lt;/h3>
&lt;p>就是你 API 的呼叫紀錄，它有將不同種類的 API 區分開來，方便我們查找。&lt;br />
有紀錄任務 ID、執行時間、執行狀態、花費、送出的參數，甚至可以從這邊看到查詢結果(等同於 &amp;quot;Task GET&amp;quot; 請求)。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/dataforseo/api_usage.jpg" alt="API Usage (使用紀錄) 頁面" data-caption="API Usage (使用紀錄) 頁面" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='850px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:850px;height:;"/>
&lt;figcaption style="text-align: center;">
API Usage (使用紀錄) 頁面
&lt;/figcaption>
&lt;/figure>
&lt;h3 id="api-errors-錯誤紀錄">API Errors (錯誤紀錄)&lt;/h3>
&lt;p>發生錯誤的 API 請求會在這頁列出來。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/dataforseo/api_errors.jpg" alt="API Errors (錯誤紀錄) 頁面" data-caption="API Errors (錯誤紀錄) 頁面" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='850px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:850px;height:;"/>
&lt;figcaption style="text-align: center;">
API Errors (錯誤紀錄) 頁面
&lt;/figcaption>
&lt;/figure>
&lt;h3 id="api-explorer-api-playground">API Explorer (API Playground)&lt;/h3>
&lt;p>以圖形化的操作頁面，讓你實際測試 API 的結果是否符合你的預期，有輸入框讓你設定 API 不同參數，像是用哪個搜尋引擎、地區(有細分到縣市)、語言、電腦版或手機版、作業系統、關鍵字。&lt;/p>
&lt;p>但要稍微注意，這邊是會直接花到錢的 (新帳戶有送 1 美元的額度可供試用)。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/dataforseo/api_explorer.jpg" alt="API Explorer (API Playground) 頁面" data-caption="API Explorer (API Playground) 頁面" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='850px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:850px;height:;"/>
&lt;figcaption style="text-align: center;">
API Explorer (API Playground) 頁面
&lt;/figcaption>
&lt;/figure>
&lt;h3 id="api-settings-設定-api-限制">API Settings (設定 API 限制)&lt;/h3>
&lt;p>能設定可以呼叫 API 的 IP 白名單、限制每日最大花費額度(總共額度，或也可以細到 by 不同 API 去限制)。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/dataforseo/api_settings.jpg" alt="API Settings (設定 API 限制) 頁面" data-caption="API Settings (設定 API 限制) 頁面" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='850px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:850px;height:;"/>
&lt;figcaption style="text-align: center;">
API Settings (設定 API 限制) 頁面
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;h2 id="注意事項">注意事項&lt;/h2>
&lt;p>我在使用 Yahoo 搜尋 API 時發現，我明明設定要 100 筆結果(&lt;code>depth=100&lt;/code>)，結果回傳只有 70 筆、甚至 40 幾筆而已的，我也有透過 API 回傳的 &lt;code>check_url&lt;/code> 網址去確認，經過詢問與確認，官方是這樣答覆我：&lt;/p>
&lt;blockquote>
&lt;p>Today, you contacted our Support Team and provided us with ID of your task sent to the Yahoo Serp API that returned fewer results than expected.&lt;br />
Our developers have carefully checked the situation and explained that such a search engine has some limitations. To avoid timeouts, we have a restriction that allows us to crawl up to 6 pages of the ordinary SERP (the one you can see in a browser). Taking into account that each page contains 7-8 results. In total, the API does not return more than 50 results.&lt;br />
If we remove such a restriction globally, this API will return 500 errors, and since we will have to send numerous requests to this search engine, it may harm it due to high traffic coming from us. I hope it makes sense.&lt;br />
Hope for your understanding!&lt;/p>
&lt;/blockquote>
&lt;p>也就是可能他們的 Yahoo 搜尋 API 很可能沒辦法抓到我們指定的筆數，除非你只需要第一頁，或前三頁之類的，不然這也是很困擾，要特別注意。&lt;/p>
&lt;br/>
&lt;!--adsense-->
&lt;h2 id="結語">結語&lt;/h2>
&lt;p>相比上次使用的 Aves API 來說，DataForSEO 真的多了非常多的服務，而且價格貌似也比較便宜。官方同樣有線上客服，有相關需求都可以直接詢問，回應速度還蠻快的。&lt;/p>
&lt;p>當然我目前只是短暫的嘗試，還不確定它長久、大量地使用下來穩定性如何，這部分有需求的人可以自行比較看看。&lt;/p>
&lt;br/>
&lt;br/>
&lt;hr />
&lt;p>參考：&lt;br />
&lt;a href="https://dataforseo.com/" target="_blank" rel="noopener">
DataForSEO 官方網站
&lt;/a>&lt;br />
&lt;a href="https://docs.dataforseo.com/v3/" target="_blank" rel="noopener">
DataForSEO 官方文檔
&lt;/a>&lt;br />
&lt;a href="https://app.dataforseo.com/api-dashboard" target="_blank" rel="noopener">
DataForSEO Dashboard 後台
&lt;/a>&lt;/p>
&lt;br/>
&lt;blockquote>
&lt;p>「失敗很正常，如果沒有經歷失敗，表示你還不夠創新。」&lt;br />
Failure is an option here. If things are not failing, you are not innovating enough.&lt;/p>
&lt;p align="right">—— Elon Musk 伊隆·馬斯克 (著名企業家)&lt;/p>
&lt;/blockquote></content:encoded><dc:creator>Jia</dc:creator><media:content url="https://blog.jiatool.comimages/cover/dataforseo.jpg" medium="image"><media:title type="html">featured image</media:title></media:content><media:content url="https://blog.jiatool.comimages/posts/dataforseo_meta.jpg" medium="image"><media:title type="html">meta image</media:title></media:content><category>Google搜尋</category><category>Yahoo搜尋</category><category>SEO</category><category>API</category><category>Python</category><category>分享</category></item><item><title>OpenAI ChatGPT API 如何使用？(附上 Python 範例程式)</title><link>https://blog.jiatool.com/posts/chatgpt_api/</link><pubDate>Sat, 18 Mar 2023 21:10:00 +0800</pubDate><author>jia@jiatool.com (Jia)</author><atom:modified>Sun, 19 Mar 2023 14:45:00 +0800</atom:modified><guid>https://blog.jiatool.com/posts/chatgpt_api/</guid><description>前言 ChatGPT 是由 OpenAI 所開發的一個基於 GPT-3.5 架構的大型語言模型，自從去年底發表到現在依然話題不斷、人氣超高。而在三月初，OpenAI 公開了 ChatGPT 的 API，也就</description><content:encoded>&lt;h2 id="前言">前言&lt;/h2>
&lt;p>ChatGPT 是由 OpenAI 所開發的一個基於 GPT-3.5 架構的大型語言模型，自從去年底發表到現在依然話題不斷、人氣超高。而在三月初，OpenAI 公開了 ChatGPT 的 API，也就是 gpt-3.5-turbo 模型的 API，讓我們不再被限制只能透過官方網頁使用，並且提供更多可調整的參數選項。&lt;/p>
&lt;p>這篇文章就是要一起來了解 ChatGPT API，並實際使用 Python 串接 API (當然有 Python 範例程式碼)，帶著大家快速上手。&lt;/p>
&lt;br/>
&lt;p>這兩份官方文件建議可以看看：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://platform.openai.com/docs/guides/chat" target="_blank" rel="noopener">
OpenAI 官方說明文件
&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://platform.openai.com/docs/api-reference/chat" target="_blank" rel="noopener">
OpenAI 官方 API 參考
&lt;/a>&lt;/li>
&lt;/ul>
&lt;br/>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/chatgpt_api/gtpapi.jpg" alt="OpenAI 公開 ChatGPT API" data-caption="OpenAI 公開 ChatGPT API" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='700px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:700px;height:;"/>
&lt;figcaption style="text-align: center;">
OpenAI 公開 ChatGPT API
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;!--adsense-->
&lt;br/>
&lt;h2 id="api-key-申請">API key 申請&lt;/h2>
&lt;p>進到帳號的 &lt;a href="https://platform.openai.com/account/api-keys" target="_blank" rel="noopener">
API key
&lt;/a> 頁面，登入帳號後，點擊 &amp;ldquo;Create new secret key&amp;rdquo; 來產生 API key，這時候就要把 key 複製並保存下來了，如果忘記的話，再產生一次即可。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/chatgpt_api/apikey.jpg" alt="API key 申請" data-caption="API key 申請" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='900px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:900px;height:;"/>
&lt;figcaption style="text-align: center;">
API key 申請
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;h2 id="使用量與計費方式">使用量與計費方式&lt;/h2>
&lt;p>而帳號實際的總使用量可以到 &lt;a href="https://platform.openai.com/account/usage" target="_blank" rel="noopener">
帳號 Usage
&lt;/a> 頁面查看。&lt;/p>
&lt;p>目前每個帳號會贈送 18 美元的額度讓你試用 (如果你是用同一組手機去開多個帳號，那就不一定了)，並且有使用期限要留意，不要白白浪費了~&lt;br />
如果免費額度用完過到期，就應該要綁信用卡才能使用了。&lt;/p>
&lt;p>每種模型的計費方式可參考 &lt;a href="https://openai.com/pricing" target="_blank" rel="noopener">
官網 Pricing
&lt;/a> 頁面說明。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/chatgpt_api/usage.jpg" alt="帳號實際的總使用量" data-caption="帳號實際的總使用量" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='900px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:900px;height:;"/>
&lt;figcaption style="text-align: center;">
帳號實際的總使用量
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;h2 id="範例程式碼">範例程式碼&lt;/h2>
&lt;p>馬上給各位看看範例程式碼。&lt;/p>
&lt;p>這邊分別使用使用兩種套件來示範，我們之前常用的 requests，與官方提供的 openai 套件。&lt;/p>
&lt;p>Model 使用 ChatGPT 的 &lt;code>gpt-3.5-turbo&lt;/code>，如果之後 GPT-4 的也開放後可以使用 &lt;code>gpt-4&lt;/code>。&lt;br />
目前 OpenAI API 有提供&lt;a href="https://platform.openai.com/docs/models" target="_blank" rel="noopener">
這些 Model
&lt;/a>，裡面有對每一種 Model 做詳細說明。&lt;/p>
&lt;br/>
&lt;h3 id="使用-requests-套件">使用 requests 套件&lt;/h3>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-Python" data-lang="Python">&lt;span class="kn">import&lt;/span> &lt;span class="nn">requests&lt;/span>
&lt;span class="n">API_KEY&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;&amp;lt;YOUR_API_KEY&amp;gt;&amp;#39;&lt;/span>
&lt;span class="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">requests&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">post&lt;/span>&lt;span class="p">(&lt;/span>
&lt;span class="s1">&amp;#39;https://api.openai.com/v1/chat/completions&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="n">headers&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>
&lt;span class="s1">&amp;#39;Content-Type&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s1">&amp;#39;application/json&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s1">&amp;#39;Authorization&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="s1">&amp;#39;Bearer {API_KEY}&amp;#39;&lt;/span>
&lt;span class="p">},&lt;/span>
&lt;span class="n">json&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>
&lt;span class="s1">&amp;#39;model&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s1">&amp;#39;gpt-3.5-turbo&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s1">&amp;#39;messages&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[{&lt;/span>&lt;span class="s2">&amp;#34;role&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;user&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;content&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;說句話吧&amp;#34;&lt;/span>&lt;span class="p">}],&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">)&lt;/span>
&lt;span class="k">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">json&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;br/>
&lt;h3 id="使用官方-openai-套件">使用官方 openai 套件&lt;/h3>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-Python" data-lang="Python">&lt;span class="kn">import&lt;/span> &lt;span class="nn">openai&lt;/span>
&lt;span class="n">openai&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">api_key&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;&amp;lt;YOUR_API_KEY&amp;gt;&amp;#39;&lt;/span>
&lt;span class="n">completion&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">openai&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ChatCompletion&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">create&lt;/span>&lt;span class="p">(&lt;/span>
&lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;gpt-3.5-turbo&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="n">messages&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>
&lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;role&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;user&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;content&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;說句話吧&amp;#34;&lt;/span>&lt;span class="p">}&lt;/span>
&lt;span class="p">]&lt;/span>
&lt;span class="p">)&lt;/span>
&lt;span class="k">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">completion&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;br/>
&lt;h2 id="參數">參數&lt;/h2>
&lt;p>輸入參數除了上方範例中的 &lt;code>model&lt;/code> 和 &lt;code>messages&lt;/code>，還有以下這些。&lt;/p>
&lt;p>此表格是依照官方 &lt;a href="https://platform.openai.com/docs/api-reference/chat/create" target="_blank" rel="noopener">
API Reference
&lt;/a> 所整理出來的。&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>參數名稱&lt;/th>
&lt;th>資料型態&lt;/th>
&lt;th>必填/選填&lt;/th>
&lt;th>預設值&lt;/th>
&lt;th>說明&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>model&lt;/code>&lt;/td>
&lt;td>string&lt;/td>
&lt;td>必填&lt;/td>
&lt;td>-&lt;/td>
&lt;td>要使用的 Model ID。 (&lt;a href="https://platform.openai.com/docs/models/model-endpoint-compatibility" target="_blank" rel="noopener">
可使用的model
&lt;/a>)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>messages&lt;/code>&lt;/td>
&lt;td>array&lt;/td>
&lt;td>必填&lt;/td>
&lt;td>-&lt;/td>
&lt;td>以對話格式生成對話的訊息。 (&lt;a href="https://platform.openai.com/docs/guides/chat/introduction" target="_blank" rel="noopener">
格式參考
&lt;/a>，或以下說明)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>temperature&lt;/code>&lt;/td>
&lt;td>number&lt;/td>
&lt;td>選填&lt;/td>
&lt;td>1&lt;/td>
&lt;td>介於 0 和 2 之間。較高的值(如 0.8)將使輸出更加隨機，而較低的值(如 0.2)將使輸出更加集中和確定。&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>top_p&lt;/code>&lt;/td>
&lt;td>number&lt;/td>
&lt;td>選填&lt;/td>
&lt;td>1&lt;/td>
&lt;td>一種替代&lt;code>temperature&lt;/code>的方法(nucleus sampling)。Model 考慮具有 top_p 概率質量的標記的結果。所以 0.1 意味著只考慮構成前 10% 概率質量的標記。&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>n&lt;/code>&lt;/td>
&lt;td>integer&lt;/td>
&lt;td>選填&lt;/td>
&lt;td>1&lt;/td>
&lt;td>輸出幾種回覆結果。 (參考以下說明)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>stream&lt;/code>&lt;/td>
&lt;td>boolean&lt;/td>
&lt;td>選填&lt;/td>
&lt;td>false&lt;/td>
&lt;td>開啟 stream 方式傳送，就像 ChatGPT 網頁版那樣會一個一個字跑出來。(&lt;a href="https://github.com/openai/openai-cookbook/blob/main/examples/How_to_stream_completions.ipynb" target="_blank" rel="noopener">
官方範例
&lt;/a>)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>stop&lt;/code>&lt;/td>
&lt;td>string or array&lt;/td>
&lt;td>選填&lt;/td>
&lt;td>null&lt;/td>
&lt;td>指定字串，如果回覆有出現這些字串將會停止輸出。&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>max_tokens&lt;/code>&lt;/td>
&lt;td>integer&lt;/td>
&lt;td>選填&lt;/td>
&lt;td>inf&lt;/td>
&lt;td>聊天完成時生成的最大令牌數。如果太小它可能回覆到一半就會斷掉，但每種 Model 都有各自的最大值。&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>presence_penalty&lt;/code>&lt;/td>
&lt;td>number&lt;/td>
&lt;td>選填&lt;/td>
&lt;td>0&lt;/td>
&lt;td>-2.0 和 2.0 之間的數字。正值會根據到目前為止是否出現在文本中來懲罰新標記，從而增加 Model 談論新主題的可能性。&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>frequency_penalty&lt;/code>&lt;/td>
&lt;td>number&lt;/td>
&lt;td>選填&lt;/td>
&lt;td>0&lt;/td>
&lt;td>-2.0 和 2.0 之間的數字。正值會根據新標記在文本中的現有頻率對其進行懲罰，從而降低 Model 逐字重複同一行的可能性。&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>logit_bias&lt;/code>&lt;/td>
&lt;td>map&lt;/td>
&lt;td>選填&lt;/td>
&lt;td>null&lt;/td>
&lt;td>修改指定標記出現在完成中的可能性。&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>user&lt;/code>&lt;/td>
&lt;td>string&lt;/td>
&lt;td>選填&lt;/td>
&lt;td>-&lt;/td>
&lt;td>代表你的用戶的ID，幫助 OpenAI 監控和檢測濫用行為。 (&lt;a href="https://platform.openai.com/docs/guides/safety-best-practices/end-user-ids" target="_blank" rel="noopener">
更多說明
&lt;/a>)&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>* 官方建議不要同時更改 temperature 和 top_p，可以參考&lt;a href="https://platform.openai.com/docs/api-reference/parameter-details" target="_blank" rel="noopener">
這邊的說明
&lt;/a>。&lt;/p>
&lt;p>* 表格內有幾個說明你可能看不懂，因為我也不太懂 XP&lt;/p>
&lt;br/>
&lt;p>&lt;code>messages&lt;/code> 是個陣列的格式，放著你們之間的對話。&lt;br />
裡面的 &lt;code>role&lt;/code> 欄位可以放三種身分：&amp;ldquo;system&amp;rdquo;、&amp;ldquo;user&amp;rdquo;、&amp;ldquo;assistant&amp;rdquo;。&lt;/p>
&lt;p>對話可以先有一則 &amp;ldquo;system&amp;rdquo;，對 Model 先做出指示，例如 &amp;ldquo;你是一隻貓&amp;rdquo; (？，不過官方說 gpt-3.5-turbo 對 &amp;ldquo;system&amp;rdquo; 消息的關注度不高，因此重要的說明建議還是放在 &amp;ldquo;user&amp;rdquo; 的消息比較好。&lt;br />
&amp;ldquo;user&amp;rdquo; 是我們使用者的發問；&amp;ldquo;assistant&amp;rdquo; 則是 Model 的回話。&lt;/p>
&lt;p>如果你想要讓對話有上下文關係(要讓 Model 記得之前講過的話)，要把全部對話紀錄都在傳給它，然後這些都算進 token(使用量) 裡&amp;hellip;，像是底下這樣。&lt;/p>
&lt;p>第一次問它：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-Json" data-lang="Json">&lt;span class="s2">&amp;#34;messages&amp;#34;&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;span class="p">{&lt;/span>&lt;span class="nt">&amp;#34;role&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;user&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;content&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;王建民是誰&amp;#34;&lt;/span>&lt;span class="p">}&lt;/span>
&lt;span class="p">]&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>它回覆：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-Json" data-lang="Json">&lt;span class="s2">&amp;#34;usage&amp;#34;&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;prompt_tokens&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">14&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;completion_tokens&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">217&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;total_tokens&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">231&lt;/span>
&lt;span class="p">}&lt;/span>&lt;span class="err">,&lt;/span>
&lt;span class="s2">&amp;#34;choices&amp;#34;&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;message&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;role&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;assistant&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;content&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;\n\n王建民是一位台灣籍棒球運動員，曾效力於美國職棒大聯盟紐約洋基隊、匹茲堡海盜隊、倫敦青年隊、堪薩斯城皇家隊等隊伍，擅長投球，被譽為是台灣最優秀的投手之一。他在職業生涯期間獲得多項獎項，包括台灣大賽MVP、亞洲聯盟MVP、美國聯盟先發投手等。他在2015年正式宣布退役，現在是一位棒球分析師和教練。&amp;#34;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">]&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>想再繼續深入詢問，第二次問它，就要把剛剛我問的跟他回的都放進去：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-Json" data-lang="Json">&lt;span class="err">&amp;#39;messages&amp;#39;:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;span class="p">{&lt;/span>&lt;span class="nt">&amp;#34;role&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;user&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;content&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;王建民是誰&amp;#34;&lt;/span>&lt;span class="p">},&lt;/span>
&lt;span class="p">{&lt;/span>&lt;span class="nt">&amp;#34;role&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;assistant&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;content&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;\n\n王建民是一位台灣籍棒球運動員，曾效力於美國職棒大聯盟紐約洋基隊、匹茲堡海盜隊、倫敦青年隊、堪薩斯城皇家隊等隊伍，擅長投球，被譽為是台灣最優秀的投手之一。他在職業生涯期間獲得多項獎項，包括台灣大賽MVP、亞洲聯盟MVP、美國聯盟先發投手等。他在2015年正式宣布退役，現在是一位棒球分析師和教練。&amp;#34;&lt;/span>&lt;span class="p">},&lt;/span>
&lt;span class="p">{&lt;/span>&lt;span class="nt">&amp;#34;role&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;user&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;content&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;他在美國大聯盟最多一年曾經拿過幾勝&amp;#34;&lt;/span>&lt;span class="p">},&lt;/span>
&lt;span class="p">]&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>它回覆：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-Json" data-lang="Json">&lt;span class="s2">&amp;#34;usage&amp;#34;&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;prompt_tokens&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">268&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;completion_tokens&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">63&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;total_tokens&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">331&lt;/span>
&lt;span class="p">}&lt;/span>&lt;span class="err">,&lt;/span>
&lt;span class="s2">&amp;#34;choices&amp;#34;&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;message&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;role&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;assistant&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;content&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;王建民在美國大聯盟生涯中最多一年的勝場數為19勝，是在2008年效力於倫敦青年隊時所取得的成績。&amp;#34;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">]&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>可以感受到 total_tokens 的使用量了嗎？&lt;br />
如果想讓它記得以前講過的話，每次請求所消耗的 token 是要繼續往上疊的，也是蠻恐怖的 XD&lt;/p>
&lt;p>以上內容可以參考&lt;a href="https://platform.openai.com/docs/guides/chat/introduction" target="_blank" rel="noopener">
官方說明
&lt;/a>。&lt;/p>
&lt;br/>
&lt;p>參數 &lt;code>n&lt;/code> 是代表你想要它給出幾種回覆，例如 &lt;code>n=3&lt;/code> 會產生如下三則訊息回覆：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-Json" data-lang="Json">&lt;span class="s2">&amp;#34;choices&amp;#34;&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;message&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;role&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;assistant&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;content&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;\n\n你好，有什麼我可以幫助您的？&amp;#34;&lt;/span>
&lt;span class="p">},&lt;/span>
&lt;span class="nt">&amp;#34;finish_reason&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;stop&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;index&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">0&lt;/span>
&lt;span class="p">},&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;message&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;role&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;assistant&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;content&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;\n\n您好，有什麼我能幫助您的嗎？&amp;#34;&lt;/span>
&lt;span class="p">},&lt;/span>
&lt;span class="nt">&amp;#34;finish_reason&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;stop&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;index&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">1&lt;/span>
&lt;span class="p">},&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;message&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;role&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;assistant&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;content&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;\n\n您好，有什麼我可以為您做的嗎？&amp;#34;&lt;/span>
&lt;span class="p">},&lt;/span>
&lt;span class="nt">&amp;#34;finish_reason&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;stop&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;index&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">2&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">]&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;br/>
&lt;h2 id="回覆內容">回覆內容&lt;/h2>
&lt;p>依照上方的範例程式，他回傳的格式與內容會類似這樣：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-Json" data-lang="Json">&lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;id&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;chatcmpl-6v4faabcd9gXfUerJvBf123Co&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;object&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;chat.completion&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;created&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">1679060660&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;model&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;gpt-3.5-turbo-0301&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;usage&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;prompt_tokens&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">14&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;completion_tokens&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">24&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;total_tokens&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">38&lt;/span>
&lt;span class="p">},&lt;/span>
&lt;span class="nt">&amp;#34;choices&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;message&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;role&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;assistant&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;content&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;\n\n你好，有什麼我可以為你效勞的嗎？&amp;#34;&lt;/span>
&lt;span class="p">},&lt;/span>
&lt;span class="nt">&amp;#34;finish_reason&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;stop&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;index&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">0&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">]&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;code>usage&lt;/code> 欄位顯示你本次消耗的 token 數量。&lt;/p>
&lt;ul>
&lt;li>&lt;code>prompt_tokens&lt;/code>：你問他(輸入)所消耗的 token。&lt;/li>
&lt;li>&lt;code>completion_tokens&lt;/code>：它回覆(輸出)所消耗的 token。&lt;/li>
&lt;li>&lt;code>total_tokens&lt;/code>：本次請求總共消耗多少 token，也就是 &lt;code>prompt_tokens&lt;/code> 加 &lt;code>completion_tokens&lt;/code>。&lt;/li>
&lt;/ul>
&lt;p>如果想知道一句話代表幾個 token，可以使用官方提供的 Python 套件 — tiktoken：&lt;a href="https://github.com/openai/tiktoken">https://github.com/openai/tiktoken&lt;/a>&lt;br />
也有計算 token 相關的&lt;a href="https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb" target="_blank" rel="noopener">
使用說明
&lt;/a>。&lt;/p>
&lt;p>本來想說可以使用官方的 &lt;a href="https://platform.openai.com/tokenizer" target="_blank" rel="noopener">
Tokenizer 網頁
&lt;/a>來計算，但它是 GPT-3 Model 的，官方有說轉換 token 的計算方式可能因不同 Model 而異，因此我實際使用其實跟 gpt-3.5-turbo 出來的結果有落差。&lt;/p>
&lt;p>帳號實際的總使用量可以到 &lt;a href="https://platform.openai.com/account/usage" target="_blank" rel="noopener">
帳號 Usage
&lt;/a> 頁面查看。&lt;/p>
&lt;p>&lt;em>* 你會發現中文消耗的 token 比英文還多很多😭&lt;/em>&lt;/p>
&lt;br/>
&lt;p>&lt;code>choices&lt;/code> 內就是主要我們想知道的部分 — ChatGPT 的回覆。&lt;/p>
&lt;ul>
&lt;li>&lt;code>message&lt;/code> &amp;gt; &lt;code>content&lt;/code>：回覆的內容。&lt;/li>
&lt;li>&lt;code>index&lt;/code>：代表第幾種回覆。(如果輸入參數有設定 &lt;code>n&lt;/code> 的話)&lt;/li>
&lt;li>&lt;code>finish_reason&lt;/code>：代表此次回覆結束的原因(狀態)，可能會有以下四種值：
&lt;ul>
&lt;li>&lt;code>stop&lt;/code>：完整的輸出。&lt;/li>
&lt;li>&lt;code>length&lt;/code>：由於 max_tokens 參數或 token 限制，導致輸出不完整。&lt;/li>
&lt;li>&lt;code>content_filter&lt;/code>：由於內容過濾器中的標誌而省略了內容。&lt;/li>
&lt;li>&lt;code>null&lt;/code>：API 響應仍在進行中或未完成。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;br/>
&lt;h2 id="playground-遊樂場">Playground 遊樂場&lt;/h2>
&lt;p>OpenAI 還有提供 Playground 遊樂場，可以在上面測試不同的模型、調整不同的參數，觀察其結果，方便我們去快速了解。&lt;/p>
&lt;p>Playground 遊樂場：&lt;a href="https://platform.openai.com/playground">https://platform.openai.com/playground&lt;/a>&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/chatgpt_api/playground.jpg" alt="Playground 遊樂場" data-caption="Playground 遊樂場" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='850px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:850px;height:;"/>
&lt;figcaption style="text-align: center;">
Playground 遊樂場
&lt;/figcaption>
&lt;/figure>
&lt;p>對了，使用 Playground 也是會消耗你自己的 token，這點要稍微注意一下，不要以為是 ChatGPT 網頁而玩過頭了🤣&lt;/p>
&lt;br/>
&lt;h2 id="其他說明">其他說明&lt;/h2>
&lt;p>串接 OpenAI API 發出請求可能會收到錯誤，而完整詳細的錯誤代碼說明與進一步的解決辦法，可以參考這邊官方的文章：&lt;a href="https://platform.openai.com/docs/guides/error-codes">https://platform.openai.com/docs/guides/error-codes&lt;/a>&lt;/p>
&lt;p>OpenAI API 在使用上還有一些速率限制，如果使用會比較大量的朋友可以過去了解一下：&lt;a href="https://platform.openai.com/docs/guides/rate-limits/overview">https://platform.openai.com/docs/guides/rate-limits/overview&lt;/a>&lt;/p>
&lt;br/>
&lt;!--adsense-->
&lt;br/>
&lt;h2 id="結語">結語&lt;/h2>
&lt;p>使用 OpenAI API 上非常簡單，只是有些小地方要注意一下。&lt;br />
在看完以上介紹，趕快實際動手做，看看有沒有什麼 idea 可以進一步放大 ChatGPT 的用途~&lt;/p>
&lt;br/>
&lt;p>歡迎追蹤『&lt;a href="https://www.facebook.com/jiatool" target="_blank" rel="noopener">
IT空間
&lt;/a>』FB 粉專，取得最新發文通知🔔&lt;/p>
&lt;br/>
&lt;br/>
&lt;hr />
&lt;p>參考：&lt;br />
&lt;a href="https://platform.openai.com/docs/guides/chat" target="_blank" rel="noopener">
OpenAI 官方說明文件
&lt;/a>&lt;br />
&lt;a href="https://platform.openai.com/docs/api-reference/chat" target="_blank" rel="noopener">
OpenAI 官方 API 參考
&lt;/a>&lt;br />
&lt;a href="https://platform.openai.com/account/usage" target="_blank" rel="noopener">
OpenAI 帳號後台
&lt;/a>&lt;br />
&lt;a href="https://platform.openai.com/docs/models" target="_blank" rel="noopener">
OpenAI 各種 Model 說明
&lt;/a>&lt;br />
&lt;a href="https://openai.com/pricing" target="_blank" rel="noopener">
OpenAI 各種 Model 價格
&lt;/a>&lt;br />
&lt;a href="https://platform.openai.com/playground" target="_blank" rel="noopener">
OpenAI Playground 遊樂場
&lt;/a>&lt;br />
&lt;a href="https://platform.openai.com/docs/guides/error-codes" target="_blank" rel="noopener">
OpenAI API 錯誤代碼
&lt;/a>&lt;br />
&lt;a href="https://platform.openai.com/docs/guides/rate-limits/overview" target="_blank" rel="noopener">
OpenAI API 速率限制
&lt;/a>&lt;/p>
&lt;br/>
&lt;blockquote>
&lt;p>別讓沒有夢想的人摧毀你的夢想。&lt;/p>
&lt;/blockquote></content:encoded><dc:creator>Jia</dc:creator><media:content url="https://blog.jiatool.comimages/cover/chatgpt_api.jpg" medium="image"><media:title type="html">featured image</media:title></media:content><media:content url="https://blog.jiatool.comimages/posts/chatgpt_api_meta.jpg" medium="image"><media:title type="html">meta image</media:title></media:content><category>OpenAI</category><category>LLM</category><category>AI</category><category>人工智慧</category><category>API</category><category>Python</category><category>分享</category></item><item><title>「TDX 運輸資料流通服務平台」Google Apps Script 範例，PTX 平台的升級版~</title><link>https://blog.jiatool.com/posts/tdx_google_apps_script/</link><pubDate>Fri, 30 Dec 2022 21:00:00 +0800</pubDate><author>jia@jiatool.com (Jia)</author><atom:modified>Fri, 30 Dec 2022 21:00:00 +0800</atom:modified><guid>https://blog.jiatool.com/posts/tdx_google_apps_script/</guid><description>前言 先預祝 2023 元旦快樂~🎉🎉🎉 恭喜各位又老一歲了(? 之前介紹的「PTX 公共運輸資訊平台 」已經確定在今年 2022/12/01 正式落日，所以後來我有寫一篇將取代 PTX 平</description><content:encoded>&lt;h2 id="前言">前言&lt;/h2>
&lt;p>先預祝 2023 元旦快樂~🎉🎉🎉 恭喜各位又老一歲了(?&lt;/p>
&lt;br/>
&lt;p>之前介紹的「&lt;a href="https://blog.jiatool.com/posts/ptx_intro" target="_blank" rel="noopener">
PTX 公共運輸資訊平台
&lt;/a>」已經確定在今年 2022/12/01 正式落日，所以後來我有寫一篇將取代 PTX 平台的「TDX 運輸資訊整合流通服務平台」介紹，並示範如何使用 Python 來串接。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/tdx_python/ptx_close2.jpg" alt="PTX 平台關閉" data-caption="PTX 平台關閉" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='850px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:850px;height:;"/>
&lt;figcaption style="text-align: center;">
PTX 平台關閉
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;p>因為剛好有網友詢問，所以本篇也會使用 Google Apps Script 來取得 TDX 的交通資料，兩者在 API 認證授權機制差異不小。&lt;br />
關於 TDX 平台的介紹、如何註冊並取得API金鑰等資訊&lt;a href="https://blog.jiatool.com/posts/ptx_intro" target="_blank" rel="noopener">
之前這篇文章
&lt;/a>都講解過了，這篇我們就直接進入 Google Apps Script 程式主題。&lt;/p>
&lt;br/>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/tdx_python/tdx_home.jpg" alt="TDX 運輸資訊整合流通服務平台" data-caption="TDX 運輸資訊整合流通服務平台" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='700px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:700px;height:;"/>
&lt;figcaption style="text-align: center;">
TDX 運輸資訊整合流通服務平台
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;!--adsense-->
&lt;br/>
&lt;h2 id="取得api金鑰">取得API金鑰&lt;/h2>
&lt;blockquote>
&lt;ol>
&lt;li>TDX 平臺現階段開放每位會員最多建立三把API金鑰，每把API金鑰由一組 Client Id 和 Client Secret 所組成。&lt;/li>
&lt;li>每個呼叫來源端 IP 呼叫次數限制為 50 次/秒。&lt;/li>
&lt;li>TDX 平臺 API 採 OIDC Client Credential 機制進行身分驗證，程式介接範例可參考範例程式碼。&lt;/li>
&lt;/ol>
&lt;/blockquote>
&lt;p>登入 TDX 平台後，前往 會員中心 &amp;gt; (左邊) &amp;gt; 資料服務 &amp;gt; API金鑰 頁面，點擊 &amp;quot;編輯&amp;quot; 即可查看 &amp;quot;Client Id&amp;quot; 和 &amp;quot;Client Secret&amp;quot;，將這兩組字串記錄下來，call API 時需要帶上。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/tdx_python/tdx_api_key.png" alt="API金鑰 頁面" data-caption="API金鑰 頁面" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='800px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:800px;height:;"/>
&lt;figcaption style="text-align: center;">
API金鑰 頁面
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;p>有關 API 金鑰服務使用流程說明，請參考&lt;a href="https://tdx.transportdata.tw/about/service" target="_blank" rel="noopener">
官方說明
&lt;/a>。&lt;/p>
&lt;br/>
&lt;h2 id="api-認證授權機制">API 認證授權機制&lt;/h2>
&lt;p>詳細步驟說明如下:&lt;/p>
&lt;ol>
&lt;li>取得 Access Token&lt;/li>
&lt;/ol>
&lt;p>依照以下格式發出請求：&lt;/p>
&lt;pre>&lt;code>Request URL: https://tdx.transportdata.tw/auth/realms/TDXConnect/protocol/openid-connect/token
Request Method: POST
Request Headers:
content-type: application/x-www-form-urlencoded
data:
grant_type: client_credentials
client_id: &amp;lt;your_client_id&amp;gt;
client_secret: &amp;lt;your_client_secret&amp;gt;
&lt;/code>&lt;/pre>&lt;p>data 參數說明如下:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Key&lt;/th>
&lt;th>Value&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>grant_type&lt;/code>&lt;/td>
&lt;td>固定使用 &lt;code>client_credentials&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>client_id&lt;/code>&lt;/td>
&lt;td>您的 Client Id，從 TDX 會員中心取得&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>client_secret&lt;/code>&lt;/td>
&lt;td>您的 Client Secret，從 TDX 會員中心取得&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>回傳資料是 JSON 格式，其中包含 &lt;code>access_token&lt;/code> 參數，就是我們要的 Access Token。&lt;/p>
&lt;br/>
&lt;p>對應的 Google Apps Script code：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-JavaScript" data-lang="JavaScript">&lt;span class="kd">function&lt;/span> &lt;span class="nx">GetAuthorizationToken&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="kd">var&lt;/span> &lt;span class="nx">token_url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;https://tdx.transportdata.tw/auth/realms/TDXConnect/protocol/openid-connect/token&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="kd">var&lt;/span> &lt;span class="nx">options&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="s2">&amp;#34;method&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;post&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s2">&amp;#34;headers&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="s2">&amp;#34;content-type&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;application/x-www-form-urlencoded&amp;#34;&lt;/span>
&lt;span class="p">},&lt;/span>
&lt;span class="s2">&amp;#34;payload&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="s2">&amp;#34;grant_type&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;client_credentials&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s2">&amp;#34;client_id&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;&amp;lt;YOUR CLIENT ID&amp;gt;&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s2">&amp;#34;client_secret&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;&amp;lt;YOUR CLIENT SECRET&amp;gt;&amp;#39;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">};&lt;/span>
&lt;span class="kd">var&lt;/span> &lt;span class="nx">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">UrlFetchApp&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">token_url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">options&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="nx">outData&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">JSON&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">parse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">response&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">getContentText&lt;/span>&lt;span class="p">());&lt;/span>
&lt;span class="c1">// console.log(outData);
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="nx">outData&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;access_token&amp;#39;&lt;/span>&lt;span class="p">];&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;br/>
&lt;ol start="2">
&lt;li>呼叫 TDX API 服務，取得數據資料&lt;/li>
&lt;/ol>
&lt;pre>&lt;code>Request URL: TDX_API_URI
Request Method: GET
Request Headers:
authorization: Bearer &amp;lt;Access_Token&amp;gt;
&lt;/code>&lt;/pre>&lt;p>* 若 Access Token 時間超過有效期限(第一步驟收到回應中的 expires_in 參數)，則再重新透過第一步驟取得即可。&lt;/p>
&lt;br/>
&lt;p>對應的 Google Apps Script code：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-JavaScript" data-lang="JavaScript">&lt;span class="kd">function&lt;/span> &lt;span class="nx">doGet&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">e&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="kd">var&lt;/span> &lt;span class="nx">url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;https://tdx.transportdata.tw/api/basic/v2/Rail/TRA/LiveBoard/Station/1000?$filter=Direction eq 1&amp;amp;$format=JSON&amp;#39;&lt;/span>
&lt;span class="kd">var&lt;/span> &lt;span class="nx">options&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="s2">&amp;#34;method&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;get&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s2">&amp;#34;headers&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="s2">&amp;#34;authorization&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;Bearer &amp;#34;&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nx">GetAuthorizationToken&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">}&lt;/span>
&lt;span class="p">};&lt;/span>
&lt;span class="kd">var&lt;/span> &lt;span class="nx">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">UrlFetchApp&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">options&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="c1">// console.log(response.getResponseCode());
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="kd">var&lt;/span> &lt;span class="nx">outData&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">JSON&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">parse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">response&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">getContentText&lt;/span>&lt;span class="p">());&lt;/span>
&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">outData&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="nx">ContentService&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">createTextOutput&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">JSON&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">stringify&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">outData&lt;/span>&lt;span class="p">)).&lt;/span>&lt;span class="nx">setMimeType&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ContentService&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">MimeType&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">JSON&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;br/>
&lt;p>* 官方的說明文件： &lt;a href="https://github.com/tdxmotc/SampleCode">https://github.com/tdxmotc/SampleCode&lt;/a>&lt;/p>
&lt;br/>
&lt;h2 id="完整-google-apps-script-程式範例">完整 Google Apps Script 程式範例&lt;/h2>
&lt;p>以同樣條件當範例查詢：台鐵&amp;quot;台北&amp;quot;車站即時的列車到離站看板資訊，並且只要&amp;quot;逆行&amp;quot;的列車&lt;/p>
&lt;p>查詢的 URL 會長得像這樣：&lt;br />
&lt;code>https://tdx.transportdata.tw/api/basic/v2/Rail/TRA/LiveBoard/Station/1000?$filter=Direction eq 1&amp;amp;$format=JSON&lt;/code>&lt;/p>
&lt;br/>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-JavaScript" data-lang="JavaScript">&lt;span class="kd">var&lt;/span> &lt;span class="nx">CLIENT_ID&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;&amp;lt;YOUR CLIENT ID&amp;gt;&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="kd">var&lt;/span> &lt;span class="nx">CLIENT_SECRET&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;&amp;lt;YOUR CLIENT SECRET&amp;gt;&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="kd">function&lt;/span> &lt;span class="nx">doGet&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">e&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="kd">var&lt;/span> &lt;span class="nx">url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;https://tdx.transportdata.tw/api/basic/v2/Rail/TRA/LiveBoard/Station/1000?$filter=Direction eq 1&amp;amp;$format=JSON&amp;#39;&lt;/span>
&lt;span class="kd">var&lt;/span> &lt;span class="nx">options&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="s2">&amp;#34;method&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;get&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s2">&amp;#34;headers&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="s2">&amp;#34;authorization&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;Bearer &amp;#34;&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nx">GetAuthorizationToken&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">}&lt;/span>
&lt;span class="p">};&lt;/span>
&lt;span class="kd">var&lt;/span> &lt;span class="nx">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">UrlFetchApp&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">options&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="c1">// console.log(response.getResponseCode());
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="kd">var&lt;/span> &lt;span class="nx">outData&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">JSON&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">parse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">response&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">getContentText&lt;/span>&lt;span class="p">());&lt;/span>
&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">outData&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="nx">ContentService&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">createTextOutput&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">JSON&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">stringify&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">outData&lt;/span>&lt;span class="p">)).&lt;/span>&lt;span class="nx">setMimeType&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ContentService&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">MimeType&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">JSON&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="kd">function&lt;/span> &lt;span class="nx">GetAuthorizationToken&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="kd">var&lt;/span> &lt;span class="nx">token_url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;https://tdx.transportdata.tw/auth/realms/TDXConnect/protocol/openid-connect/token&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="kd">var&lt;/span> &lt;span class="nx">options&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="s2">&amp;#34;method&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;post&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s2">&amp;#34;headers&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="s2">&amp;#34;content-type&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;application/x-www-form-urlencoded&amp;#34;&lt;/span>
&lt;span class="p">},&lt;/span>
&lt;span class="s2">&amp;#34;payload&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="s2">&amp;#34;grant_type&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;client_credentials&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s2">&amp;#34;client_id&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">CLIENT_ID&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s2">&amp;#34;client_secret&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">CLIENT_SECRET&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">};&lt;/span>
&lt;span class="kd">var&lt;/span> &lt;span class="nx">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">UrlFetchApp&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">token_url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">options&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="nx">outData&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">JSON&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">parse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">response&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">getContentText&lt;/span>&lt;span class="p">());&lt;/span>
&lt;span class="c1">// console.log(outData);
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="nx">outData&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;access_token&amp;#39;&lt;/span>&lt;span class="p">];&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>透過上方的 &amp;quot;執行&amp;quot; 確認沒問題後，可參考&lt;a href="https://blog.jiatool.com/posts/ptx_google_apps_script" target="_blank" rel="noopener">
這篇的教學
&lt;/a>將程式部署起來。&lt;/p>
&lt;br/>
&lt;h2 id="其他程式範例">其他程式範例&lt;/h2>
&lt;p>除了參考我上面的 Google Apps Script 範例，與我上次整理的 &lt;a href="https://blog.jiatool.com/posts/tdx_python" target="_blank" rel="noopener">
Python 教學
&lt;/a>，TDX 官方有提供 C#、JavaScript、Java、PHP、R 程式語言的範例程式碼，需要的可前往參考 (&lt;a href="https://github.com/tdxmotc/SampleCode" target="_blank" rel="noopener">
GitHub
&lt;/a>)。&lt;/p>
&lt;br/>
&lt;!--adsense-->
&lt;h2 id="結語">結語&lt;/h2>
&lt;p>如果你想做的應用需要「公共運輸」相關資料，那應該就是串接 TDX 平台，資料齊全、方便很多~&lt;/p>
&lt;br/>
&lt;p>歡迎追蹤『&lt;a href="https://www.facebook.com/jiatool" target="_blank" rel="noopener">
IT空間
&lt;/a>』FB 粉專，取得最新發文通知🔔&lt;/p>
&lt;br/>
&lt;br/>
&lt;hr />
&lt;p>參考：&lt;br />
&lt;a href="https://tdx.transportdata.tw/" target="_blank" rel="noopener">
TDX 運輸資料流通服務平台 | 官網
&lt;/a>&lt;br />
&lt;a href="https://tdx.transportdata.tw/api-service/swagger" target="_blank" rel="noopener">
TDX API 說明 | Swagger 文件工具
&lt;/a>&lt;br />
&lt;a href="https://github.com/tdxmotc/SampleCode" target="_blank" rel="noopener">
TDX 官方介接說明與範例程式碼 | GitHub
&lt;/a>&lt;br />
&lt;a href="https://tdx.transportdata.tw/about/faq" target="_blank" rel="noopener">
TDX 平台常見問題 | 官網
&lt;/a>&lt;/p>
&lt;br/>
&lt;blockquote>
&lt;p>與其埋怨暗路，不如自己點燈。&lt;/p>
&lt;/blockquote></content:encoded><dc:creator>Jia</dc:creator><media:content url="https://blog.jiatool.comimages/cover/tdx_google_apps_script.jpg" medium="image"><media:title type="html">featured image</media:title></media:content><media:content url="https://blog.jiatool.comimages/posts/tdx_google_apps_script_meta.jpg" medium="image"><media:title type="html">meta image</media:title></media:content><category>TDX</category><category>API</category><category>交通</category><category>公共運輸</category><category>GoogleAppsScript</category><category>分享</category></item><item><title>「TDX 運輸資料流通服務平台」含 Python 範例程式，PTX 平台的升級版~</title><link>https://blog.jiatool.com/posts/tdx_python/</link><pubDate>Sun, 19 Jun 2022 21:25:00 +0800</pubDate><author>jia@jiatool.com (Jia)</author><atom:modified>Sat, 31 Dec 2022 21:00:00 +0800</atom:modified><guid>https://blog.jiatool.com/posts/tdx_python/</guid><description>前言 在之前文章介紹 PTX 公共運輸整合資訊流通服務平台 ，它整合多項公共運輸的資料服務 API，供我們串接取得相關數據。 「PTX 公共運輸資訊平台」AP</description><content:encoded>&lt;h2 id="前言">前言&lt;/h2>
&lt;p>在之前文章介紹 &lt;a href="https://ptx.transportdata.tw/PTX/" target="_blank" rel="noopener">
PTX 公共運輸整合資訊流通服務平台
&lt;/a>，它整合多項公共運輸的資料服務 API，供我們串接取得相關數據。&lt;/p>
&lt;ol>
&lt;li>&lt;a href="https://blog.jiatool.com/posts/ptx_intro" target="_blank" rel="noopener">
「PTX 公共運輸資訊平台」API 介紹 (含 Odata 說明)
&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://blog.jiatool.com/posts/ptx_python" target="_blank" rel="noopener">
「PTX 公共運輸資訊平台」Python 範例
&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://blog.jiatool.com/posts/ptx_google_apps_script" target="_blank" rel="noopener">
「PTX 公共運輸資訊平台」Google Apps Script 範例
&lt;/a>&lt;/li>
&lt;/ol>
&lt;br/>
&lt;p>但經網友提醒，才發現 PTX 竟然只服務到今年(2022年)底，之後會改以「TDX 運輸資訊整合流通服務平台」代替，因此本篇文章將帶大家快速了解一下 TDX 平台，以及實際使用 Python 來串接，取得相關數據資料。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/tdx_python/ptx_close.jpg" alt="PTX 平台關閉" data-caption="PTX 平台關閉" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='600px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:600px;height:;"/>
&lt;figcaption style="text-align: center;">
PTX 平台關閉
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;p>後來我有寫一篇 &lt;a href="%28/posts/tdx_google_apps_script%29" target="_blank" rel="noopener">
Google Apps Script 範例程式
&lt;/a> 可以參考哦~&lt;/p>
&lt;br/>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/tdx_python/tdx_home.jpg" alt="TDX 運輸資訊整合流通服務平台" data-caption="TDX 運輸資訊整合流通服務平台" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='700px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:700px;height:;"/>
&lt;figcaption style="text-align: center;">
TDX 運輸資訊整合流通服務平台
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;!--adsense-->
&lt;h2 id="tdx-平台說明">TDX 平台說明&lt;/h2>
&lt;p>按照官網的說法，TDX 整合交通部運輸數據五大平台(PTX、Traffic、GIS-T、TICP、LINK)，並使用全新的認證授權方式管控資源存取權限，提供單一服務查詢與介接入口。短時間內五大平台與 TDX 平台將持續並行運作，但未來會以 TDX 為主要的數據流通平台。&lt;br />
(參考：&lt;a href="https://tdx.transportdata.tw/about/faq" target="_blank" rel="noopener">
TDX 平台常見問題
&lt;/a>)&lt;/p>
&lt;figure >
&lt;img data-src="https://tdx.transportdata.tw/backend/images/Uploads/4c57b4tdxapi2.PNG" alt="TDX 整合五大平台 (圖片來源：TDX官網)" data-caption="TDX 整合五大平台 (圖片來源：TDX官網)" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='750px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:750px;height:;"/>
&lt;figcaption style="text-align: center;">
TDX 整合五大平台 (圖片來源：TDX官網)
&lt;/figcaption>
&lt;/figure>
&lt;p>實際使用其實「TDX 運輸資訊整合流通服務平台」服務及概念是跟「PTX 公共運輸整合資訊流通服務平台」差不多的，只是相較於 PTX 平台，TDX 平台又多了許多種類的資料，像是 &amp;quot;即時路況&amp;quot;、&amp;quot;停車資訊&amp;quot;、&amp;quot;GIS圖資&amp;quot;、&amp;quot;道路編碼&amp;quot; 等等也把它整合進來。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/tdx_python/tdx_service_scope.png" alt="TDX 平台服務範疇" data-caption="TDX 平台服務範疇" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='900px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:900px;height:;"/>
&lt;figcaption style="text-align: center;">
TDX 平台服務範疇
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;p>TDX 平台也有個&lt;a href="https://tdx.transportdata.tw/data-service/basic" target="_blank" rel="noopener">
詳細各項服務查詢頁面
&lt;/a>，可依照 &amp;quot;服務類型&amp;quot;、&amp;quot;資料主題&amp;quot;、&amp;quot;領域類型&amp;quot;、&amp;quot;資料類型&amp;quot; 來做篩選。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/tdx_python/tdx_service.jpg" alt="TDX 各項服務查詢" data-caption="TDX 各項服務查詢" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='900px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:900px;height:;"/>
&lt;figcaption style="text-align: center;">
TDX 各項服務查詢
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;h2 id="註冊會員--取得api金鑰">註冊會員 &amp;amp; 取得API金鑰&lt;/h2>
&lt;p>同樣為了系統資源使用之公平性與資通訊安全考量，要先註冊會員來取得 API 金鑰(&lt;a href="https://tdx.transportdata.tw/register" target="_blank" rel="noopener">
TDX 註冊頁面
&lt;/a>)，但不知道是不是因為平台還在試營運階段的關係，審核完成並沒有寄 Email 通知，是我主動登入平台才發覺已審核通過&amp;hellip;&amp;hellip;&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/tdx_python/signup_account.png" alt="註冊 TDX 平台會員" data-caption="註冊 TDX 平台會員" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='850px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:850px;height:;"/>
&lt;figcaption style="text-align: center;">
註冊 TDX 平台會員
&lt;/figcaption>
&lt;/figure>
&lt;p>登入 TDX 平台後，前往 會員中心 &amp;gt; (左邊) &amp;gt; 資料服務 &amp;gt; API金鑰 頁面，對 預設的API Key (或建立新的金鑰) 點擊 &amp;quot;編輯&amp;quot;，即可查看 &amp;quot;Client Id&amp;quot; 和 &amp;quot;Client Secret&amp;quot;，這兩組字串先記錄下來，之後 call API 時需要帶上。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/tdx_python/tdx_api_key.png" alt="API金鑰 頁面" data-caption="API金鑰 頁面" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='800px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:800px;height:;"/>
&lt;figcaption style="text-align: center;">
API金鑰 頁面
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;p>API 呼叫次數限制：&lt;/p>
&lt;ul>
&lt;li>使用 API 金鑰呼叫，每個呼叫來源端 IP 呼叫次數限制為 50 次/秒 (無每日上限)。&lt;/li>
&lt;li>不使用 API 金鑰呼叫，則僅能透過瀏覽器呼叫 API，且每個呼叫來源端 IP 的上限為每日 50 次。&lt;/li>
&lt;/ul>
&lt;br/>
&lt;p>有關 API 金鑰服務使用流程說明，請參考&lt;a href="https://tdx.transportdata.tw/about/service" target="_blank" rel="noopener">
官方說明
&lt;/a>。&lt;/p>
&lt;br/>
&lt;h2 id="swagger-工具">Swagger 工具&lt;/h2>
&lt;p>從上方導航欄 開發指引 &amp;gt; API說明，前往 TDX 平台的 &lt;a href="https://tdx.transportdata.tw/api-service/swagger" target="_blank" rel="noopener">
Swagger 工具
&lt;/a>，能查看有提供哪些 API，與各自的請求參數與回傳欄位說明。&lt;br />
詳細使用說明可以查看之前我寫的 &lt;a href="https://blog.jiatool.com/posts/ptx_intro" target="_blank" rel="noopener">
PTX 介紹 (含 Odata 說明)
&lt;/a> 文章。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/tdx_python/tdx_api_swagger.jpg" alt="TDX 平台 Swagger 工具" data-caption="TDX 平台 Swagger 工具" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='900px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:900px;height:;"/>
&lt;figcaption style="text-align: center;">
TDX 平台 Swagger 工具
&lt;/figcaption>
&lt;/figure>
&lt;p>右方有 Authorize 按鈕，填入 &amp;quot;Client Id&amp;quot; 與 &amp;quot;Client Secret&amp;quot; 驗證，即可使用你的 API 金鑰來呼叫。不使用 API 金鑰呼叫，有每日 50 次的上限。&lt;/p>
&lt;br/>
&lt;h2 id="api-認證授權機制">API 認證授權機制&lt;/h2>
&lt;p>API 認證授權機制與 PTX 平台有蠻大的差異，簡單來說每次請求前要先身份認證取得 Access Token，才能使用 Access Token 來取得 TDX API 服務的數據資料。&lt;br />
(或使用尚未過期的 Access Token，有效期限預設為 1 天)&lt;/p>
&lt;br/>
&lt;p>詳細步驟說明如下:&lt;/p>
&lt;ol>
&lt;li>取得 Access Token&lt;/li>
&lt;/ol>
&lt;p>依照以下格式發出請求：&lt;/p>
&lt;pre>&lt;code>Request URL: https://tdx.transportdata.tw/auth/realms/TDXConnect/protocol/openid-connect/token
Request Method: POST
Request Headers:
content-type: application/x-www-form-urlencoded
data:
grant_type: client_credentials
client_id: &amp;lt;your_client_id&amp;gt;
client_secret: &amp;lt;your_client_secret&amp;gt;
&lt;/code>&lt;/pre>&lt;p>data 參數說明如下:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Key&lt;/th>
&lt;th>Value&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>grant_type&lt;/code>&lt;/td>
&lt;td>固定使用 &lt;code>client_credentials&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>client_id&lt;/code>&lt;/td>
&lt;td>您的 Client Id，從 TDX 會員中心取得&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>client_secret&lt;/code>&lt;/td>
&lt;td>您的 Client Secret，從 TDX 會員中心取得&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>回傳資料是 JSON 格式，其中包含 &lt;code>access_token&lt;/code> 參數，就是我們要的 Access Token。&lt;/p>
&lt;br/>
&lt;ol start="2">
&lt;li>呼叫 TDX API 服務，取得數據資料&lt;/li>
&lt;/ol>
&lt;pre>&lt;code>Request URL: TDX_API_URI
Request Method: GET
Request Headers:
authorization: Bearer &amp;lt;Access_Token&amp;gt;
&lt;/code>&lt;/pre>&lt;p>* 若 Access Token 時間超過有效期限(第一步驟收到回應中的 expires_in 參數)，則再重新透過第一步驟取得即可。&lt;/p>
&lt;br/>
&lt;p>* 官方的說明文件： &lt;a href="https://github.com/tdxmotc/SampleCode">https://github.com/tdxmotc/SampleCode&lt;/a>&lt;/p>
&lt;br/>
&lt;h2 id="完整-python-程式範例">完整 Python 程式範例&lt;/h2>
&lt;p>因為 TDX 平台的 API 一樣使用 OData (Open Data Protocol) 標準介面，所以這邊我就不再寫一次了，還不太懂的可前往 &lt;a href="https://blog.jiatool.com/posts/ptx_intro" target="_blank" rel="noopener">
「PTX 平台」API 介紹 (含 Odata 說明)
&lt;/a> 了解。&lt;/p>
&lt;p>同樣以下方條件當範例查詢：&lt;/p>
&lt;p>台鐵&amp;quot;台北&amp;quot;車站即時的列車到離站看板資訊，並且只要&amp;quot;逆行&amp;quot;的列車&lt;/p>
&lt;br/>
&lt;p>查詢的 URL 會長得像這樣：&lt;br />
&lt;code>https://tdx.transportdata.tw/api/basic/v2/Rail/TRA/LiveBoard/Station/1000?$filter=Direction eq 1&amp;amp;$format=JSON&lt;/code>&lt;/p>
&lt;p>會發現與 PTX 平台根本一樣，只差在網址前方的部分。&lt;/p>
&lt;br/>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-Python" data-lang="Python">&lt;span class="kn">import&lt;/span> &lt;span class="nn">requests&lt;/span>
&lt;span class="n">client_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;&amp;lt;your_client_id&amp;gt;&amp;#39;&lt;/span>
&lt;span class="n">client_secret&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;&amp;lt;your_client_secret&amp;gt;&amp;#39;&lt;/span>
&lt;span class="k">class&lt;/span> &lt;span class="nc">TDX&lt;/span>&lt;span class="p">():&lt;/span>
&lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">client_id&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">client_secret&lt;/span>&lt;span class="p">):&lt;/span>
&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">client_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">client_id&lt;/span>
&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">client_secret&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">client_secret&lt;/span>
&lt;span class="k">def&lt;/span> &lt;span class="nf">get_token&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">):&lt;/span>
&lt;span class="n">token_url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;https://tdx.transportdata.tw/auth/realms/TDXConnect/protocol/openid-connect/token&amp;#39;&lt;/span>
&lt;span class="n">headers&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s1">&amp;#39;content-type&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s1">&amp;#39;application/x-www-form-urlencoded&amp;#39;&lt;/span>&lt;span class="p">}&lt;/span>
&lt;span class="n">data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="s1">&amp;#39;grant_type&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s1">&amp;#39;client_credentials&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s1">&amp;#39;client_id&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">client_id&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s1">&amp;#39;client_secret&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">client_secret&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">requests&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">post&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">token_url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">headers&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">headers&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">data&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="c1"># print(response.status_code)&lt;/span>
&lt;span class="c1"># print(response.json())&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">json&lt;/span>&lt;span class="p">()[&lt;/span>&lt;span class="s1">&amp;#39;access_token&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;span class="k">def&lt;/span> &lt;span class="nf">get_response&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">url&lt;/span>&lt;span class="p">):&lt;/span>
&lt;span class="n">headers&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s1">&amp;#39;authorization&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="s1">&amp;#39;Bearer {self.get_token()}&amp;#39;&lt;/span>&lt;span class="p">}&lt;/span>
&lt;span class="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">requests&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">headers&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">headers&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">json&lt;/span>&lt;span class="p">()&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="vm">__name__&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s1">&amp;#39;__main__&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;span class="n">tdx&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">TDX&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">client_id&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">client_secret&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="c1"># url = &amp;#39;https://tdx.transportdata.tw/api/basic/v2/Rail/TRA/LiveBoard/Station/1000?$filter=Direction eq 1&amp;amp;$format=JSON&amp;#39;&lt;/span>
&lt;span class="n">base_url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;https://tdx.transportdata.tw/api&amp;#34;&lt;/span>
&lt;span class="c1"># 取得指定[車站]列車即時到離站電子看板(動態前後30分鐘的車次)&lt;/span>
&lt;span class="n">endpoint&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;/basic/v2/Rail/TRA/LiveBoard/Station/1000&amp;#34;&lt;/span>
&lt;span class="nb">filter&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;Direction eq 1&amp;#34;&lt;/span> &lt;span class="c1"># 順逆行: [0:&amp;#39;順行&amp;#39;, 1:&amp;#39;逆行&amp;#39;]&lt;/span>
&lt;span class="n">url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="s2">&amp;#34;{base_url}{endpoint}?$filter={filter}&amp;amp;$format=JSON&amp;#34;&lt;/span>
&lt;span class="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">tdx&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get_response&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="k">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">response&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>如此應該就能順利取得資料 🎉🎉🎉&lt;/p>
&lt;br/>
&lt;h2 id="其他程式範例">其他程式範例&lt;/h2>
&lt;p>除了參考我上面的 Python 範例，TDX 官方有提供 JavaScript 與 C# 程式語言的範例程式碼，需要的可前往參考 (&lt;a href="https://github.com/tdxmotc/SampleCode" target="_blank" rel="noopener">
GitHub
&lt;/a>)。&lt;/p>
&lt;br/>
&lt;p>我有寫一篇 &lt;a href="%28/posts/tdx_google_apps_script%29" target="_blank" rel="noopener">
Google Apps Script 範例程式
&lt;/a> 可以參考。&lt;/p>
&lt;p>也感謝熱心網友 meebox，提供 &amp;quot;適用在嵌入式系統的 MicroPython 環境、token 有效期判斷&amp;quot; 的版本：&lt;br />
&lt;a href="https://github.com/codemee/tdx">https://github.com/codemee/tdx&lt;/a>&lt;/p>
&lt;br/>
&lt;!--adsense-->
&lt;h2 id="結語">結語&lt;/h2>
&lt;p>這次快速(?)帶大家了解「TDX 運輸資料流通服務平台」，看起來就是 PTX 平台的升級版，加入更多不同服務的資料。&lt;br />
如果你之前已經用 PTX 做些作品出來，不妨試著將其改串接至 TDX，畢竟可能明年 PTX 平台就無法使用了。&lt;/p>
&lt;br/>
&lt;p>歡迎追蹤『&lt;a href="https://www.facebook.com/jiatool" target="_blank" rel="noopener">
IT空間
&lt;/a>』FB 粉專，取得最新發文通知🔔&lt;/p>
&lt;br/>
&lt;br/>
&lt;hr />
&lt;p>參考：&lt;br />
&lt;a href="https://tdx.transportdata.tw/" target="_blank" rel="noopener">
TDX 運輸資料流通服務平台 | 官網
&lt;/a>&lt;br />
&lt;a href="https://tdx.transportdata.tw/api-service/swagger" target="_blank" rel="noopener">
TDX API 說明 | Swagger 文件工具
&lt;/a>&lt;br />
&lt;a href="https://github.com/tdxmotc/SampleCode" target="_blank" rel="noopener">
TDX 官方介接說明與範例程式碼 | GitHub
&lt;/a>&lt;br />
&lt;a href="https://tdx.transportdata.tw/about/faq" target="_blank" rel="noopener">
TDX 平台常見問題 | 官網
&lt;/a>&lt;/p>
&lt;br/>
&lt;blockquote>
&lt;p>你不可能有先見之明，只能有後見之明，&lt;br />
因此，你必須相信，這些小事一定會和你的未來產生關聯。&lt;/p>
&lt;p align="right">—— 史蒂夫·賈伯斯&lt;/p>
&lt;/blockquote></content:encoded><dc:creator>Jia</dc:creator><media:content url="https://blog.jiatool.comimages/cover/tdx_python.jpg" medium="image"><media:title type="html">featured image</media:title></media:content><media:content url="https://blog.jiatool.comimages/posts/tdx_python_meta.jpg" medium="image"><media:title type="html">meta image</media:title></media:content><category>TDX</category><category>API</category><category>交通</category><category>公共運輸</category><category>Python</category><category>分享</category></item><item><title>「PTX 公共運輸資訊平台」Google Apps Script 範例程式</title><link>https://blog.jiatool.com/posts/ptx_google_apps_script/</link><pubDate>Sat, 14 May 2022 20:45:00 +0800</pubDate><author>jia@jiatool.com (Jia)</author><atom:modified>Thu, 03 Nov 2022 21:50:00 +0800</atom:modified><guid>https://blog.jiatool.com/posts/ptx_google_apps_script/</guid><description>因部內目前已收攏資料於TDX運輸資訊整合流通服務平台，本平台預計將於2022/12/1落日，屆時您的會員金鑰將於2022/12/1起停用。 為</description><content:encoded>&lt;br/>
&lt;div class="notices warning" data-title="P t x 平台將停止使用">
因部內目前已收攏資料於TDX運輸資訊整合流通服務平台，本平台預計將於2022/12/1落日，屆時您的會員金鑰將於2022/12/1起停用。&lt;br />
為避免您短時間內需移轉之困擾，即日起本平台不再受理審核會員註冊。&lt;br />
建議您依據身分類型至TDX申請會員，非常感謝您的支持。
&lt;/div>
&lt;h2 id="前言">前言&lt;/h2>
&lt;p>在之前的兩篇文章裡，我們分別介紹 &lt;a href="https://ptx.transportdata.tw/PTX/" target="_blank" rel="noopener">
PTX 公共運輸整合資訊流通服務平台
&lt;/a> 整合多項大眾運輸的資料、說明 OData（Open Data Protocol）格式如何使用，與實際使用 Python 來串接 PTX 平台。&lt;/p>
&lt;p>本篇文章將帶你換成使用 Google Apps Script 來串接 PTX 平台，取得這些相關數據資料 💾，還可以做出自己的簡易 API。&lt;br />
(Google Apps Script 其實是基於 JavaScript 來的，所以基本的語法大致都相同。)&lt;/p>
&lt;br/>
&lt;p>關於 PTX 平台我寫了三篇文章來介紹與程式教學：&lt;/p>
&lt;ol>
&lt;li>&lt;a href="https://blog.jiatool.com/posts/ptx_intro" target="_blank" rel="noopener">
「PTX 公共運輸資訊平台」API 介紹 (含 Odata 說明)
&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://blog.jiatool.com/posts/ptx_python" target="_blank" rel="noopener">
「PTX 公共運輸資訊平台」Python 範例
&lt;/a>&lt;/li>
&lt;li>「PTX 公共運輸資訊平台」Google Apps Script 範例 &amp;lt;&amp;ndash; 本篇&lt;/li>
&lt;/ol>
&lt;br/>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/ptx_intro/ptx_home.jpg" alt="PTX 公共運輸整合資訊流通服務平台" data-caption="PTX 公共運輸整合資訊流通服務平台" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='650px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:650px;height:;"/>
&lt;figcaption style="text-align: center;">
PTX 公共運輸整合資訊流通服務平台
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;p>後來發現 PTX 竟然只服務到今年(2022年)底，之後會改以 TDX 運輸資訊整合流通服務平台，因此又特別寫一篇文章帶大家快速了解一下 TDX 平台：&lt;br />
&lt;a href="https://blog.jiatool.com/posts/tdx_python" target="_blank" rel="noopener">
「TDX 運輸資料流通服務平臺」含 Python 範例程式，PTX 平台的升級版~
&lt;/a>&lt;/p>
&lt;br/>
&lt;!--adsense-->
&lt;h2 id="創建-google-apps-script-專案">創建 Google Apps Script 專案&lt;/h2>
&lt;p>可能有些人沒接觸過 Google Apps Script，我這邊先說明兩種創建的方式：&lt;/p>
&lt;p>第一種到 &lt;a href="https://script.google.com/home/start" target="_blank" rel="noopener">
Apps Script 頁面
&lt;/a> 創建專案，預設會儲存在你 Google 雲端硬碟的根目錄。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/ptx_intro/create_gas1.jpg" alt="創建 Google Apps Script 專案 -1" data-caption="創建 Google Apps Script 專案 -1" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='800px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:800px;height:;"/>
&lt;figcaption style="text-align: center;">
創建 Google Apps Script 專案 -1
&lt;/figcaption>
&lt;/figure>
&lt;p>第二種在 Google 雲端硬碟裡新增 Google Apps Script 檔案 (如果選單內沒看到，可以點擊 &amp;quot;連結更多應用程式&amp;quot; 去尋找&amp;amp;加入)。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/ptx_intro/create_gas2.jpg" alt="創建 Google Apps Script 專案 -2" data-caption="創建 Google Apps Script 專案 -2" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='500px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:500px;height:;"/>
&lt;figcaption style="text-align: center;">
創建 Google Apps Script 專案 -2
&lt;/figcaption>
&lt;/figure>
&lt;p>選擇一種你順手的方式創建專案即可，好了我們就開始吧 🚌&lt;/p>
&lt;br/>
&lt;h2 id="api-認證授權機制">API 認證授權機制&lt;/h2>
&lt;p>一樣先撰寫放在 Header 裡的 HMAC 認證授權 🔓，關於目前 PTX 平台採用 HMAC 認證授權機制說明，可以參考&lt;a href="https://blog.jiatool.com/posts/ptx_python" target="_blank" rel="noopener">
上一篇文章
&lt;/a>。&lt;/p>
&lt;p>參數如下：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Key&lt;/th>
&lt;th>Value&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>Authorization&lt;/code>&lt;/td>
&lt;td>&lt;code>hmac username=&amp;quot;APP ID&amp;quot;, algorithm=&amp;quot;hmac-sha1&amp;quot;, headers=&amp;quot;x-date&amp;quot;, signature=&amp;quot;Base64(HMAC-SHA1(&amp;quot;x-date: &amp;quot; + x-date , APP Key))&amp;quot;&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>x-date&lt;/code>&lt;/td>
&lt;td>&lt;code>Wed, 19 Apr 2017 08:37:50 GMT&lt;/code>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>※ 建議於每次請求 API 服務當下建立新的 signature，簽章時效性為 5 分鐘。&lt;/p>
&lt;br/>
&lt;p>如果 HMAC 認證有問題、未符合身份驗證，它會回覆下列訊息：&lt;/p>
&lt;ul>
&lt;li>HTTP Status Code 403：
&lt;ul>
&lt;li>(1) HMAC signature cannot be verified, a valid date or x-date header is required for HMAC Authentication （x-date 的間隔時間超過定義的 clock skew 秒數）&lt;/li>
&lt;li>(2) HMAC signature does not match （日期格式正確，但簽章演算法有問題）&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>HTTP Status Code 401：
&lt;ul>
&lt;li>(1) Unauthorized （未帶簽章，未經授權）&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;br/>
&lt;br/>
&lt;p>依照上方說明，使用 Google Apps Script 語法把這個 HMAC 編碼撰寫出來：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-JavaScript" data-lang="JavaScript">&lt;span class="kd">function&lt;/span> &lt;span class="nx">GetAuthorizationHeader&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="kd">var&lt;/span> &lt;span class="nx">AppID&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;&amp;lt;YOUR APP ID&amp;gt;&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="kd">var&lt;/span> &lt;span class="nx">AppKey&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;&amp;lt;YOUR APP KEY&amp;gt;&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="kd">var&lt;/span> &lt;span class="nx">xdate&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nb">Date&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="nx">toGMTString&lt;/span>&lt;span class="p">();&lt;/span>
&lt;span class="c1">//HMAC-SHA1 運算
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="kd">var&lt;/span> &lt;span class="nx">signature&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">Utilities&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">computeHmacSignature&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">Utilities&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">MacAlgorithm&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">HMAC_SHA_1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;x-date: &amp;#34;&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nx">xdate&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">AppKey&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="c1">//轉成Base64
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="kd">var&lt;/span> &lt;span class="nx">HMAC&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">Utilities&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">base64Encode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">signature&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="kd">var&lt;/span> &lt;span class="nx">Authorization&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;hmac username=\&amp;#34;&amp;#39;&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nx">AppID&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="s1">&amp;#39;\&amp;#34;, algorithm=\&amp;#34;hmac-sha1\&amp;#34;, headers=\&amp;#34;x-date\&amp;#34;, signature=\&amp;#34;&amp;#39;&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nx">HMAC&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="s1">&amp;#39;\&amp;#34;&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="s1">&amp;#39;Authorization&amp;#39;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Authorization&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;x-date&amp;#39;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">xdate&lt;/span> &lt;span class="p">,&lt;/span>&lt;span class="s1">&amp;#39;Accept-Encoding&amp;#39;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;gzip&amp;#39;&lt;/span>&lt;span class="p">};&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;br/>
&lt;h2 id="google-apps-script-程式範例">Google Apps Script 程式範例&lt;/h2>
&lt;p>我們一樣使用上次的例子：&lt;/p>
&lt;p>台鐵&amp;quot;台北&amp;quot;車站即時的列車到離站看板資訊，並且只要&amp;quot;逆行&amp;quot;的列車&lt;/p>
&lt;br/>
&lt;p>先進到 PTX 平台的 Swagger 文件，找到 &amp;quot;&lt;a href="https://ptx.transportdata.tw/MOTC/?urls.primaryName=%E8%BB%8C%E9%81%93V2#/TRA/TRAApi_LiveBoard_2153_1" target="_blank" rel="noopener">
取得指定[車站]列車即時到離站電子看板
&lt;/a>&amp;quot;：&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/ptx_intro/LiveBoard.png" alt="列車即時到離站電子看板" data-caption="列車即時到離站電子看板" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='900px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:900px;height:;"/>
&lt;figcaption style="text-align: center;">
列車即時到離站電子看板
&lt;/figcaption>
&lt;/figure>
&lt;p>填入 &amp;quot;台北車站&amp;quot; 代碼 &lt;code>1000&lt;/code> (&lt;a href="https://tip.railway.gov.tw/tra-tip-web/tip/tip001/tip111/view" target="_blank" rel="noopener">
車站代碼表
&lt;/a>)，並依照下方 Responses 欄位說明 &amp;quot;Direction&amp;quot; 代表順逆行(0:'順行', 1:'逆行')，在 $filter 填入 &lt;code>Direction eq 1&lt;/code>，指定 &lt;code>JSON&lt;/code> 格式，最後點擊藍色 &amp;quot;Execute&amp;quot; 按鈕，看看回傳結果是不是我們想要的~&lt;/p>
&lt;p>(關於這些 Odata 的寫法，我在&lt;a href="https://blog.jiatool.com/posts/ptx_intro" target="_blank" rel="noopener">
第一篇文章
&lt;/a>有說明，忘記的可以回去看看)&lt;/p>
&lt;br/>
&lt;p>它顯示的 &amp;quot;Request URL&amp;quot; 長得像這樣：&lt;br />
&lt;code>https://ptx.transportdata.tw/MOTC/v2/Rail/TRA/LiveBoard/Station/1000?%24filter=Direction%20eq%201&amp;amp;%24format=JSON&lt;/code>&lt;/p>
&lt;p>這是以下這句經過 URL 編碼後的結果：&lt;br />
(程式中 requests 會自動幫我們編碼，因此輸入底下這句也可以)&lt;br />
&lt;code>https://ptx.transportdata.tw/MOTC/v2/Rail/TRA/LiveBoard/Station/1000?$filter=Direction eq 1&amp;amp;$format=JSON&lt;/code>&lt;/p>
&lt;br/>
&lt;p>接下來將這些代入程式中：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-JavaScript" data-lang="JavaScript">&lt;span class="kd">function&lt;/span> &lt;span class="nx">doGet&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="kd">var&lt;/span> &lt;span class="nx">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">UrlFetchApp&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">fetch&lt;/span>&lt;span class="p">(&lt;/span>
&lt;span class="s2">&amp;#34;https://ptx.transportdata.tw/MOTC/v2/Rail/TRA/LiveBoard/Station/1000?$filter=Direction eq 1&amp;amp;$format=JSON&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="p">{&lt;/span>&lt;span class="nx">method&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;GET&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;headers&amp;#39;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">GetAuthorizationHeader&lt;/span>&lt;span class="p">()}&lt;/span>
&lt;span class="p">);&lt;/span>
&lt;span class="c1">// console.log(response.getResponseCode());
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="nx">outData&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">JSON&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">parse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">response&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">getContentText&lt;/span>&lt;span class="p">());&lt;/span>
&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">outData&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="nx">ContentService&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">createTextOutput&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">JSON&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">stringify&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">outData&lt;/span>&lt;span class="p">)).&lt;/span>&lt;span class="nx">setMimeType&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ContentService&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">MimeType&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">JSON&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>點擊上方的 &amp;quot;執行&amp;quot;，程式執行後會自動開啟 &amp;quot;執行記錄&amp;quot;，程式碼中 &lt;code>console.log();&lt;/code> 的部分會在此顯示出來。&lt;/p>
&lt;p>如此即可順利取得資料 🎉🎉🎉&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/ptx_intro/gas_run.jpg" alt="執行 GAS 程式" data-caption="&amp;#34;執行&amp;#34; GAS 程式" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='650px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:650px;height:;"/>
&lt;figcaption style="text-align: center;">
&amp;#34;執行&amp;#34; GAS 程式
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;h2 id="完整-google-apps-script-程式範例">完整 Google Apps Script 程式範例&lt;/h2>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-JavaScript" data-lang="JavaScript">&lt;span class="kd">function&lt;/span> &lt;span class="nx">doGet&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="kd">var&lt;/span> &lt;span class="nx">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">UrlFetchApp&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">fetch&lt;/span>&lt;span class="p">(&lt;/span>
&lt;span class="s2">&amp;#34;https://ptx.transportdata.tw/MOTC/v2/Rail/TRA/LiveBoard/Station/1000?$filter=Direction eq 1&amp;amp;$format=JSON&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="p">{&lt;/span>&lt;span class="nx">method&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;GET&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;headers&amp;#39;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">GetAuthorizationHeader&lt;/span>&lt;span class="p">()}&lt;/span>
&lt;span class="p">);&lt;/span>
&lt;span class="c1">// console.log(response.getResponseCode());
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="nx">outData&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">JSON&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">parse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">response&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">getContentText&lt;/span>&lt;span class="p">());&lt;/span>
&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">outData&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="nx">ContentService&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">createTextOutput&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">JSON&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">stringify&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">outData&lt;/span>&lt;span class="p">)).&lt;/span>&lt;span class="nx">setMimeType&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ContentService&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">MimeType&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">JSON&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="kd">function&lt;/span> &lt;span class="nx">GetAuthorizationHeader&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="kd">var&lt;/span> &lt;span class="nx">AppID&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;&amp;lt;YOUR APP ID&amp;gt;&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="kd">var&lt;/span> &lt;span class="nx">AppKey&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;&amp;lt;YOUR APP KEY&amp;gt;&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="kd">var&lt;/span> &lt;span class="nx">xdate&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nb">Date&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="nx">toGMTString&lt;/span>&lt;span class="p">();&lt;/span>
&lt;span class="c1">//HMAC-SHA1 運算
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="kd">var&lt;/span> &lt;span class="nx">signature&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">Utilities&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">computeHmacSignature&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">Utilities&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">MacAlgorithm&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">HMAC_SHA_1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;x-date: &amp;#34;&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nx">xdate&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">AppKey&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="c1">//轉成Base64
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="kd">var&lt;/span> &lt;span class="nx">HMAC&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">Utilities&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">base64Encode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">signature&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="kd">var&lt;/span> &lt;span class="nx">Authorization&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;hmac username=\&amp;#34;&amp;#39;&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nx">AppID&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="s1">&amp;#39;\&amp;#34;, algorithm=\&amp;#34;hmac-sha1\&amp;#34;, headers=\&amp;#34;x-date\&amp;#34;, signature=\&amp;#34;&amp;#39;&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nx">HMAC&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="s1">&amp;#39;\&amp;#34;&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="s1">&amp;#39;Authorization&amp;#39;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Authorization&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;x-date&amp;#39;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">xdate&lt;/span> &lt;span class="p">,&lt;/span>&lt;span class="s1">&amp;#39;Accept-Encoding&amp;#39;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;gzip&amp;#39;&lt;/span>&lt;span class="p">};&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;br/>
&lt;h2 id="部署">部署&lt;/h2>
&lt;p>Google Apps Script 特別的地方就在於可直接把程式部署起來，不需要另外找伺服器、線上服務來處理，非常方便。&lt;/p>
&lt;br/>
&lt;p>像是上面範例的 &lt;code>function doGet()&lt;/code>，就是 Google Apps Script 內定的函式，當我們發佈成 &amp;quot;網頁應用程式&amp;quot; 後，對網址發送 GET 請求(就是在瀏覽器上前往此網址)，就會執行此函式。&lt;br />
另外也有 &lt;code>function doPost()&lt;/code> 對應 POST 請求。&lt;/p>
&lt;p>如果想在網址後方帶入參數，像是這樣：&lt;br />
&lt;code>https://webdomain.com?stationId=1000&lt;/code>&lt;/p>
&lt;p>就使用這種寫法，即可取到對應參數後方的字串：&lt;br />
&lt;code>var stationId = e.parameter.stationId;&lt;/code>&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/ptx_intro/gas_parameter.png" alt="帶入參數" data-caption="帶入參數" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='400px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:400px;height:;"/>
&lt;figcaption style="text-align: center;">
帶入參數
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;br/>
&lt;p>那我們開始來 &amp;quot;部署&amp;quot; 吧~&lt;/p>
&lt;p>確認檔案存檔後(重要！！)，點擊右上角的 &amp;quot;部署&amp;quot; &amp;gt; &amp;quot;新增部署作業&amp;quot; 按鈕。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/ptx_intro/new_deploy.png" alt="選擇新增部署作業" data-caption="選擇 &amp;#34;部署&amp;#34; &amp;gt; &amp;#34;新增部署作業&amp;#34;" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='350px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:350px;height:;"/>
&lt;figcaption style="text-align: center;">
選擇 &amp;#34;部署&amp;#34; &amp;gt; &amp;#34;新增部署作業&amp;#34;
&lt;/figcaption>
&lt;/figure>
&lt;p>左邊齒輪形狀的設定打開，選擇 &amp;quot;網頁應用程式&amp;quot;。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/ptx_intro/select_web_app.png" alt="選擇網頁應用程式" data-caption="選擇 &amp;#34;網頁應用程式&amp;#34;" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='550px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:550px;height:;"/>
&lt;figcaption style="text-align: center;">
選擇 &amp;#34;網頁應用程式&amp;#34;
&lt;/figcaption>
&lt;/figure>
&lt;p>&amp;quot;執行身分&amp;quot; 和 &amp;quot;誰可以存取&amp;quot; 可以自己看需求更改，執行 &amp;quot;部署&amp;quot;。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/ptx_intro/deploy.png" alt="執行部署" data-caption="執行部署" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='550px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:550px;height:;"/>
&lt;figcaption style="text-align: center;">
執行部署
&lt;/figcaption>
&lt;/figure>
&lt;p>用這邊產生的網址，到瀏覽器貼上，就可以取得剛剛的資料了~&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/ptx_intro/web_app_url.png" alt="網頁應用程式 網址" data-caption="網頁應用程式 網址" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='550px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:550px;height:;"/>
&lt;figcaption style="text-align: center;">
網頁應用程式 網址
&lt;/figcaption>
&lt;/figure>
&lt;p>* 如果有編輯，需要存檔後再 &amp;quot;部署&amp;quot; 一次，而且網址會更改。但舊版本還會存在，需要到 &amp;quot;管理部署作業&amp;quot; 將其封存。&lt;/p>
&lt;br/>
&lt;h2 id="部署成網頁">部署成網頁&lt;/h2>
&lt;p>除了像上述的方法當成一支 API，也可以回傳 HTML，顯示成網頁喔~&lt;/p>
&lt;p>左邊檔案新增一個 HTML。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/ptx_intro/create_html.jpg" alt="新增 HTML 檔案" data-caption="新增 HTML 檔案" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='400px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:400px;height:;"/>
&lt;figcaption style="text-align: center;">
新增 HTML 檔案
&lt;/figcaption>
&lt;/figure>
&lt;p>HTML 檔案內容撰寫完成後，在原先的程式碼 doGet() 中，回傳這個 HTML 檔案即可。&lt;br />
(&lt;code>index&lt;/code> 是 HTML 的檔名)&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/ptx_intro/return_html.png" alt="回傳 HTML" data-caption="回傳 HTML" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='650px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:650px;height:;"/>
&lt;figcaption style="text-align: center;">
回傳 HTML
&lt;/figcaption>
&lt;/figure>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-JavaScript" data-lang="JavaScript">&lt;span class="kd">function&lt;/span> &lt;span class="nx">doGet&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="nx">HtmlService&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">createHtmlOutputFromFile&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;index&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>因此你可以在 Google Apps Script 裡，取得 PTX 平台大眾運輸資料後，顯示到自製的網頁上，嘗試做一個🚅查詢時刻表的網頁服務吧~&lt;/p>
&lt;br/>
&lt;h2 id="其他程式範例">其他程式範例&lt;/h2>
&lt;p>除了參考我上面的 Google Apps Script 範例，PTX 官方也提供不同程式語言的範例程式碼提供開發者下載 (&lt;a href="https://github.com/ptxmotc/Sample-code" target="_blank" rel="noopener">
GitHub
&lt;/a>)。&lt;/p>
&lt;ul>
&lt;li>平台提供
&lt;ul>
&lt;li>ASP.NET&lt;/li>
&lt;li>Java&lt;/li>
&lt;li>JavaScript&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>網友提供
&lt;ul>
&lt;li>Bourne Shell&lt;/li>
&lt;li>C# 與 .NET Standard 2.0&lt;/li>
&lt;li>Go&lt;/li>
&lt;li>Go Client SDK - Code generated by go-swagger&lt;/li>
&lt;li>Node.js&lt;/li>
&lt;li>python&lt;/li>
&lt;li>Ruby&lt;/li>
&lt;li>Swift&lt;/li>
&lt;li>PHP&lt;/li>
&lt;li>Postman&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;br/>
&lt;!--adsense-->
&lt;h2 id="結語">結語&lt;/h2>
&lt;p>有關更多 Google Apps Script 語法說明，歡迎參考 &lt;a href="https://developers.google.com/apps-script/guides/web" target="_blank" rel="noopener">
Google Apps Script 官方文件
&lt;/a>。&lt;/p>
&lt;br/>
&lt;p>歡迎追蹤『&lt;a href="https://www.facebook.com/jiatool" target="_blank" rel="noopener">
IT空間
&lt;/a>』FB 粉專，取得最新發文通知🔔&lt;/p>
&lt;br/>
&lt;br/>
&lt;hr />
&lt;p>參考：&lt;br />
&lt;a href="https://ptx.transportdata.tw/PTX/" target="_blank" rel="noopener">
公共運輸整合資訊流通服務平台 | 官網
&lt;/a>&lt;br />
&lt;a href="https://gist.github.com/ptxmotc/383118204ecf7192bdf96bc0197bb981" target="_blank" rel="noopener">
PTX 資料服務使用注意事項 | GitHub
&lt;/a>&lt;br />
&lt;a href="https://github.com/ptxmotc/Sample-code" target="_blank" rel="noopener">
PTX 官方範例程式碼 | GitHub
&lt;/a>&lt;br />
&lt;a href="https://developers.google.com/apps-script/guides/web" target="_blank" rel="noopener">
Google Apps Script 官方文件
&lt;/a>&lt;/p>
&lt;br/>
&lt;blockquote>
&lt;p>我不是神童，只是堅持打好每一顆球。&lt;/p>
&lt;p align="right">—— 林昀儒 (台灣桌球國手)&lt;/p>
&lt;/blockquote></content:encoded><dc:creator>Jia</dc:creator><media:content url="https://blog.jiatool.comimages/cover/ptx_google_apps_script.jpg" medium="image"><media:title type="html">featured image</media:title></media:content><media:content url="https://blog.jiatool.comimages/posts/ptx_google_apps_script_meta.jpg" medium="image"><media:title type="html">meta image</media:title></media:content><category>PTX</category><category>API</category><category>交通</category><category>公共運輸</category><category>GoogleAppsScript</category><category>分享</category></item><item><title>「PTX 公共運輸資訊平台」Python 範例程式</title><link>https://blog.jiatool.com/posts/ptx_python/</link><pubDate>Sat, 30 Apr 2022 20:55:00 +0800</pubDate><author>jia@jiatool.com (Jia)</author><atom:modified>Thu, 03 Nov 2022 21:50:00 +0800</atom:modified><guid>https://blog.jiatool.com/posts/ptx_python/</guid><description>因部內目前已收攏資料於TDX運輸資訊整合流通服務平台，本平台預計將於2022/12/1落日，屆時您的會員金鑰將於2022/12/1起停用。 為</description><content:encoded>&lt;br/>
&lt;div class="notices warning" data-title="P t x 平台將停止使用">
因部內目前已收攏資料於TDX運輸資訊整合流通服務平台，本平台預計將於2022/12/1落日，屆時您的會員金鑰將於2022/12/1起停用。&lt;br />
為避免您短時間內需移轉之困擾，即日起本平台不再受理審核會員註冊。&lt;br />
建議您依據身分類型至TDX申請會員，非常感謝您的支持。
&lt;/div>
&lt;h2 id="前言">前言&lt;/h2>
&lt;p>在上一篇文章中，我們介紹了&lt;a href="https://ptx.transportdata.tw/PTX/" target="_blank" rel="noopener">
PTX 公共運輸整合資訊流通服務平台
&lt;/a>，它整合公車、臺鐵、高鐵、捷運等多項公共運輸的資料服務 API，以及說明 OData（Open Data Protocol）標準介面格式如何使用。&lt;/p>
&lt;p>本篇文章將帶你實際使用 Python 來串接 PTX 平台，取得這些相關數據資料 💾。&lt;/p>
&lt;br/>
&lt;p>關於 PTX 平台我寫了三篇文章來介紹與程式教學：&lt;/p>
&lt;ol>
&lt;li>&lt;a href="https://blog.jiatool.com/posts/ptx_intro" target="_blank" rel="noopener">
「PTX 公共運輸資訊平台」API 介紹 (含 Odata 說明)
&lt;/a>&lt;/li>
&lt;li>「PTX 公共運輸資訊平台」Python 範例 &amp;lt;&amp;ndash; 本篇&lt;/li>
&lt;li>&lt;a href="https://blog.jiatool.com/posts/ptx_google_apps_script" target="_blank" rel="noopener">
「PTX 公共運輸資訊平台」Google Apps Script 範例
&lt;/a>&lt;/li>
&lt;/ol>
&lt;br/>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/ptx_intro/ptx_home.jpg" alt="PTX 公共運輸整合資訊流通服務平台" data-caption="PTX 公共運輸整合資訊流通服務平台" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='650px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:650px;height:;"/>
&lt;figcaption style="text-align: center;">
PTX 公共運輸整合資訊流通服務平台
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;p>後來發現 PTX 竟然只服務到今年(2022年)底，之後會改以 TDX 運輸資訊整合流通服務平台，因此又特別寫一篇文章帶大家快速了解一下 TDX 平台：&lt;br />
&lt;a href="https://blog.jiatool.com/posts/tdx_python" target="_blank" rel="noopener">
「TDX 運輸資料流通服務平臺」含 Python 範例程式，PTX 平台的升級版~
&lt;/a>&lt;/p>
&lt;br/>
&lt;!--adsense-->
&lt;h2 id="api-認證授權機制">API 認證授權機制&lt;/h2>
&lt;p>目前 PTX 平台採用 HMAC 認證授權機制，在送出請求時 headers 需代上規定的資料，它會依照 HTTP header 資訊來判別用戶是否有授權身份 🔓。&lt;/p>
&lt;p>官方說明如下：&lt;/p>
&lt;blockquote>
&lt;p>HMAC 機制：以 HMAC 簽章驗證使用者的身份，用戶在請求 API 服務時，將 APP Key 與當下時間(格式請使用GMT時間) 做 HMAC-SHA1 運算後轉成 Base64 格式，帶入 signature 屬性欄位，服務器端將驗證用戶請求時的 header 欄位，驗證使用者的身份及請求服務的時效性。&lt;/p>
&lt;p>HMAC Signature 簽章時效性：於 MOTC Helper 該網頁測試時，請在最上方輸入 API Key 與 API ID (請再次確認是否有把 APP Key 跟 ID 填寫正確，若欄位資訊相反會無法執行)。點選 Explore ，每次請求下方 API 時，會於 header 帶入 Authorization 及 x-date ，依照請求當下的時間 &amp;amp; API Key 製作簽章。&lt;/p>
&lt;/blockquote>
&lt;p>參數如下：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Key&lt;/th>
&lt;th>Value&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>Authorization&lt;/code>&lt;/td>
&lt;td>&lt;code>hmac username=&amp;quot;APP ID&amp;quot;, algorithm=&amp;quot;hmac-sha1&amp;quot;, headers=&amp;quot;x-date&amp;quot;, signature=&amp;quot;Base64(HMAC-SHA1(&amp;quot;x-date: &amp;quot; + x-date , APP Key))&amp;quot;&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>x-date&lt;/code>&lt;/td>
&lt;td>&lt;code>Wed, 19 Apr 2017 08:37:50 GMT&lt;/code>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>※ 建議於每次請求 API 服務當下建立新的 signature，簽章時效性為 5 分鐘。&lt;/p>
&lt;br/>
&lt;p>如果 HMAC 認證有問題、未符合身份驗證，它會回覆下列訊息：&lt;/p>
&lt;ul>
&lt;li>HTTP Status Code 403：
&lt;ul>
&lt;li>(1) HMAC signature cannot be verified, a valid date or x-date header is required for HMAC Authentication （x-date 的間隔時間超過定義的 clock skew 秒數）&lt;/li>
&lt;li>(2) HMAC signature does not match （日期格式正確，但簽章演算法有問題）&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>HTTP Status Code 401：
&lt;ul>
&lt;li>(1) Unauthorized （未帶簽章，未經授權）&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;br/>
&lt;br/>
&lt;p>依照上方說明，使用 Python 把這個 HMAC 編碼寫出來，這邊我也是參考範例來做修改的。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-Python" data-lang="Python">&lt;span class="k">class&lt;/span> &lt;span class="nc">Auth&lt;/span>&lt;span class="p">():&lt;/span>
&lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">app_id&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">app_key&lt;/span>&lt;span class="p">):&lt;/span>
&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">app_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">app_id&lt;/span>
&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">app_key&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">app_key&lt;/span>
&lt;span class="k">def&lt;/span> &lt;span class="nf">get_auth_header&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">):&lt;/span>
&lt;span class="n">xdate&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">datetime&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">utcnow&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">strftime&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;%a, &lt;/span>&lt;span class="si">%d&lt;/span>&lt;span class="s2"> %b %Y %H:%M:%S GMT&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="n">hashed&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">hmac&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">new&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">app_key&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">encode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;utf8&amp;#39;&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">f&lt;/span>&lt;span class="s1">&amp;#39;x-date: {xdate}&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">encode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;utf8&amp;#39;&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">sha1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="n">signature&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">base64&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">b64encode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hashed&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">digest&lt;/span>&lt;span class="p">())&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">decode&lt;/span>&lt;span class="p">()&lt;/span>
&lt;span class="n">authorization&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">f&lt;/span>&lt;span class="s1">&amp;#39;hmac username=&amp;#34;{self.app_id}&amp;#34;, algorithm=&amp;#34;hmac-sha1&amp;#34;&amp;#39;&lt;/span> &lt;span class="o">+&lt;/span>
&lt;span class="n">f&lt;/span>&lt;span class="s1">&amp;#39;, headers=&amp;#34;x-date&amp;#34;, signature=&amp;#34;{signature}&amp;#34;&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="s1">&amp;#39;Authorization&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">authorization&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s1">&amp;#39;x-date&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">xdate&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s1">&amp;#39;Accept-Encoding&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s1">&amp;#39;gzip&amp;#39;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;br/>
&lt;h2 id="python-程式範例">Python 程式範例&lt;/h2>
&lt;p>我們來實際舉個例子，像是我想抓：&lt;/p>
&lt;p>台鐵&amp;quot;台北&amp;quot;車站即時的列車到離站看板資訊，並且只要&amp;quot;逆行&amp;quot;的列車&lt;/p>
&lt;br/>
&lt;p>先進到 PTX 平台的 Swagger 文件，找到 &amp;quot;&lt;a href="https://ptx.transportdata.tw/MOTC/?urls.primaryName=%E8%BB%8C%E9%81%93V2#/TRA/TRAApi_LiveBoard_2153_1" target="_blank" rel="noopener">
取得指定[車站]列車即時到離站電子看板
&lt;/a>&amp;quot;：&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/ptx_intro/LiveBoard.png" alt="列車即時到離站電子看板" data-caption="列車即時到離站電子看板" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='900px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:900px;height:;"/>
&lt;figcaption style="text-align: center;">
列車即時到離站電子看板
&lt;/figcaption>
&lt;/figure>
&lt;p>填入 &amp;quot;台北車站&amp;quot; 代碼 &lt;code>1000&lt;/code> (&lt;a href="https://tip.railway.gov.tw/tra-tip-web/tip/tip001/tip111/view" target="_blank" rel="noopener">
車站代碼表
&lt;/a>)，並依照下方 Responses 欄位說明 &amp;quot;Direction&amp;quot; 代表順逆行(0:'順行', 1:'逆行')，在 $filter 填入 &lt;code>Direction eq 1&lt;/code>，指定 &lt;code>JSON&lt;/code> 格式，最後點擊藍色 &amp;quot;Execute&amp;quot; 按鈕，看看回傳結果是不是我們想要的~&lt;/p>
&lt;p>(關於這些 Odata 的寫法，我在&lt;a href="https://blog.jiatool.com/posts/ptx_intro" target="_blank" rel="noopener">
上一篇文章
&lt;/a>有說明，忘記的可以回去看看)&lt;/p>
&lt;br/>
&lt;p>它顯示的 &amp;quot;Request URL&amp;quot; 長得像這樣：&lt;br />
&lt;code>https://ptx.transportdata.tw/MOTC/v2/Rail/TRA/LiveBoard/Station/1000?%24filter=Direction%20eq%201&amp;amp;%24format=JSON&lt;/code>&lt;/p>
&lt;p>這是以下這句經過 URL 編碼後的結果：&lt;br />
(程式中 requests 會自動幫我們編碼，因此輸入底下這句也可以)&lt;br />
&lt;code>https://ptx.transportdata.tw/MOTC/v2/Rail/TRA/LiveBoard/Station/1000?$filter=Direction eq 1&amp;amp;$format=JSON&lt;/code>&lt;/p>
&lt;br/>
&lt;p>接下來將這些代入程式中：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-Python" data-lang="Python">&lt;span class="n">auth&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Auth&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">app_id&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">app_key&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="n">url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;https://ptx.transportdata.tw/MOTC/v2/Rail/TRA/LiveBoard/Station/1000?$filter=Direction eq 1&amp;amp;$format=JSON&amp;#34;&lt;/span>
&lt;span class="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">requests&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">headers&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">auth&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get_auth_header&lt;/span>&lt;span class="p">())&lt;/span>
&lt;span class="k">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">json&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>如此應該就能順利取得資料 🎉🎉🎉&lt;/p>
&lt;br/>
&lt;h2 id="完整-python-程式範例">完整 Python 程式範例&lt;/h2>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-Python" data-lang="Python">&lt;span class="kn">import&lt;/span> &lt;span class="nn">hmac&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">base64&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">requests&lt;/span>
&lt;span class="kn">from&lt;/span> &lt;span class="nn">datetime&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">datetime&lt;/span>
&lt;span class="kn">from&lt;/span> &lt;span class="nn">hashlib&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">sha1&lt;/span>
&lt;span class="n">app_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;&amp;lt;YOUR APP ID&amp;gt;&amp;#39;&lt;/span>
&lt;span class="n">app_key&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;&amp;lt;YOUR APP KEY&amp;gt;&amp;#39;&lt;/span>
&lt;span class="k">class&lt;/span> &lt;span class="nc">Auth&lt;/span>&lt;span class="p">():&lt;/span>
&lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">app_id&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">app_key&lt;/span>&lt;span class="p">):&lt;/span>
&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">app_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">app_id&lt;/span>
&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">app_key&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">app_key&lt;/span>
&lt;span class="k">def&lt;/span> &lt;span class="nf">get_auth_header&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">):&lt;/span>
&lt;span class="n">xdate&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">datetime&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">utcnow&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">strftime&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;%a, &lt;/span>&lt;span class="si">%d&lt;/span>&lt;span class="s2"> %b %Y %H:%M:%S GMT&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="n">hashed&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">hmac&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">new&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">app_key&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">encode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;utf8&amp;#39;&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">f&lt;/span>&lt;span class="s1">&amp;#39;x-date: {xdate}&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">encode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;utf8&amp;#39;&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">sha1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="n">signature&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">base64&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">b64encode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hashed&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">digest&lt;/span>&lt;span class="p">())&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">decode&lt;/span>&lt;span class="p">()&lt;/span>
&lt;span class="n">authorization&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">f&lt;/span>&lt;span class="s1">&amp;#39;hmac username=&amp;#34;{self.app_id}&amp;#34;, algorithm=&amp;#34;hmac-sha1&amp;#34;&amp;#39;&lt;/span> &lt;span class="o">+&lt;/span>
&lt;span class="n">f&lt;/span>&lt;span class="s1">&amp;#39;, headers=&amp;#34;x-date&amp;#34;, signature=&amp;#34;{signature}&amp;#34;&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="s1">&amp;#39;Authorization&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">authorization&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s1">&amp;#39;x-date&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">xdate&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s1">&amp;#39;Accept-Encoding&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s1">&amp;#39;gzip&amp;#39;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="vm">__name__&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s1">&amp;#39;__main__&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;span class="n">auth&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Auth&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">app_id&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">app_key&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="n">base_url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;https://ptx.transportdata.tw&amp;#34;&lt;/span>
&lt;span class="c1"># 取得指定[車站]列車即時到離站電子看板(動態前後30分鐘的車次)&lt;/span>
&lt;span class="n">endpoint&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;/MOTC/v2/Rail/TRA/LiveBoard/Station/1000&amp;#34;&lt;/span>
&lt;span class="nb">filter&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;Direction eq 1&amp;#34;&lt;/span> &lt;span class="c1"># 順逆行: [0:&amp;#39;順行&amp;#39;, 1:&amp;#39;逆行&amp;#39;]&lt;/span>
&lt;span class="n">url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="s2">&amp;#34;{base_url}{endpoint}?$filter={filter}&amp;amp;$format=JSON&amp;#34;&lt;/span>
&lt;span class="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">requests&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">headers&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">auth&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get_auth_header&lt;/span>&lt;span class="p">())&lt;/span>
&lt;span class="k">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">json&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;br/>
&lt;h2 id="其他程式範例">其他程式範例&lt;/h2>
&lt;p>除了參考我上面的 Python 範例，PTX 官方也提供不同程式語言的範例程式碼提供開發者下載 (&lt;a href="https://github.com/ptxmotc/Sample-code" target="_blank" rel="noopener">
GitHub
&lt;/a>)。&lt;/p>
&lt;ul>
&lt;li>平台提供
&lt;ul>
&lt;li>ASP.NET&lt;/li>
&lt;li>Java&lt;/li>
&lt;li>JavaScript&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>網友提供
&lt;ul>
&lt;li>Bourne Shell&lt;/li>
&lt;li>C# 與 .NET Standard 2.0&lt;/li>
&lt;li>Go&lt;/li>
&lt;li>Go Client SDK - Code generated by go-swagger&lt;/li>
&lt;li>Node.js&lt;/li>
&lt;li>python&lt;/li>
&lt;li>Ruby&lt;/li>
&lt;li>Swift&lt;/li>
&lt;li>PHP&lt;/li>
&lt;li>Postman&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;br/>
&lt;p>另外，在下一篇文章裡，我會分享 &lt;a href="https://blog.jiatool.com/posts/ptx_google_apps_script" target="_blank" rel="noopener">
Google Apps Script 的範例程式
&lt;/a>，它跟一般 JavaScript 有一些些不同，Google Apps Script 中預設應該是不能使用 jQuery，還有編碼的寫法也有些不同。&lt;/p>
&lt;br/>
&lt;!--adsense-->
&lt;h2 id="結語">結語&lt;/h2>
&lt;p>除了以上文章的解說，也可以看看&lt;a href="https://gist.github.com/ptxmotc/383118204ecf7192bdf96bc0197bb981" target="_blank" rel="noopener">
官方的文件說明
&lt;/a>。&lt;br />
有針對 &amp;quot;航空&amp;quot;、&amp;quot;公車&amp;quot;、&amp;quot;雙鐵(台鐵/高鐵)&amp;quot; 資料做進一步的說明，如果有使用到此 API 服務的人，建議進入先了解。&lt;br />
* 不過我看此文件上次更新時間 2018/04/12，可能有些資料不是最新的。&lt;/p>
&lt;br/>
&lt;p>上一篇了解 &lt;a href="https://blog.jiatool.com/posts/ptx_intro" target="_blank" rel="noopener">
PTX 平台的說明與 OData 介面
&lt;/a>，本篇則教你如何透過 Python 程式來發出請求、獲取資料，快將你有趣的 idea 來動手做實現它吧~&lt;/p>
&lt;br/>
&lt;p>歡迎追蹤『&lt;a href="https://www.facebook.com/jiatool" target="_blank" rel="noopener">
IT空間
&lt;/a>』FB 粉專，取得最新發文通知🔔&lt;/p>
&lt;br/>
&lt;br/>
&lt;hr />
&lt;p>參考：&lt;br />
&lt;a href="https://ptx.transportdata.tw/PTX/" target="_blank" rel="noopener">
公共運輸整合資訊流通服務平台 | 官網
&lt;/a>&lt;br />
&lt;a href="https://gist.github.com/ptxmotc/383118204ecf7192bdf96bc0197bb981" target="_blank" rel="noopener">
PTX 資料服務使用注意事項 | GitHub
&lt;/a>&lt;br />
&lt;a href="https://github.com/ptxmotc/Sample-code" target="_blank" rel="noopener">
PTX 官方範例程式碼 | GitHub
&lt;/a>&lt;/p>
&lt;br/>
&lt;blockquote>
&lt;p>專注你喜歡的事，就能感受到那股強大的力量。&lt;/p>
&lt;p align="right">—— 戴資穎 (台灣羽球國手)&lt;/p>
&lt;/blockquote></content:encoded><dc:creator>Jia</dc:creator><media:content url="https://blog.jiatool.comimages/cover/ptx_python.jpg" medium="image"><media:title type="html">featured image</media:title></media:content><media:content url="https://blog.jiatool.comimages/posts/ptx_python_meta.jpg" medium="image"><media:title type="html">meta image</media:title></media:content><category>PTX</category><category>API</category><category>交通</category><category>公共運輸</category><category>Python</category><category>分享</category></item><item><title>「PTX 公共運輸資訊平台」API 介紹 (含 Odata 說明)</title><link>https://blog.jiatool.com/posts/ptx_intro/</link><pubDate>Sat, 23 Apr 2022 20:45:00 +0800</pubDate><author>jia@jiatool.com (Jia)</author><atom:modified>Thu, 03 Nov 2022 21:50:00 +0800</atom:modified><guid>https://blog.jiatool.com/posts/ptx_intro/</guid><description>因部內目前已收攏資料於TDX運輸資訊整合流通服務平台，本平台預計將於2022/12/1落日，屆時您的會員金鑰將於2022/12/1起停用。 為</description><content:encoded>&lt;br/>
&lt;div class="notices warning" data-title="P t x 平台將停止使用">
因部內目前已收攏資料於TDX運輸資訊整合流通服務平台，本平台預計將於2022/12/1落日，屆時您的會員金鑰將於2022/12/1起停用。&lt;br />
為避免您短時間內需移轉之困擾，即日起本平台不再受理審核會員註冊。&lt;br />
建議您依據身分類型至TDX申請會員，非常感謝您的支持。
&lt;/div>
&lt;h2 id="前言">前言&lt;/h2>
&lt;p>你手機裡很可能有安裝臺鐵、高鐵、捷運、公車等等公共運輸的 APP，或者使用過查詢時刻表、動態相關網站(&lt;a href="https://ptx.transportdata.tw/PTX/DemoApp/Example" target="_blank" rel="noopener">
示範應用列表
&lt;/a>)，但他們是怎麼取得這些資料的呢？&lt;/p>
&lt;p>原來有個政府的平台 — &lt;a href="https://ptx.transportdata.tw/PTX/" target="_blank" rel="noopener">
公共運輸整合資訊流通服務平台
&lt;/a>，它整合並提供這些數據資料，以 Web API 的形式提供我們串接。&lt;/p>
&lt;br/>
&lt;p>關於 PTX 平台我寫了三篇文章來介紹與程式教學：&lt;/p>
&lt;ol>
&lt;li>「PTX 公共運輸資訊平台」API 介紹 (含 Odata 說明) &amp;lt;&amp;ndash; 本篇&lt;/li>
&lt;li>&lt;a href="https://blog.jiatool.com/posts/ptx_python" target="_blank" rel="noopener">
「PTX 公共運輸資訊平台」Python 範例
&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://blog.jiatool.com/posts/ptx_google_apps_script" target="_blank" rel="noopener">
「PTX 公共運輸資訊平台」Google Apps Script 範例
&lt;/a>&lt;/li>
&lt;/ol>
&lt;br/>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/ptx_intro/ptx_home.jpg" alt="PTX 公共運輸整合資訊流通服務平台" data-caption="PTX 公共運輸整合資訊流通服務平台" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='650px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:650px;height:;"/>
&lt;figcaption style="text-align: center;">
PTX 公共運輸整合資訊流通服務平台
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;p>後來發現 PTX 竟然只服務到今年(2022年)底，之後會改以 TDX 運輸資訊整合流通服務平台，因此又特別寫一篇文章帶大家快速了解一下 TDX 平台：&lt;br />
&lt;a href="https://blog.jiatool.com/posts/tdx_python" target="_blank" rel="noopener">
「TDX 運輸資料流通服務平臺」含 Python 範例程式，PTX 平台的升級版~
&lt;/a>&lt;/p>
&lt;br/>
&lt;!--adsense-->
&lt;h2 id="ptx-簡介">PTX 簡介&lt;/h2>
&lt;p>為了推動公共運輸整合資訊開放 (Open Data) 、統整各機關(構)的多元公共運輸資訊，交通部推出了這個 PTX 平台，包含了臺灣的公車、臺鐵、高鐵、捷運、航空、自行車、航運(海運)、觀光景點等公共運輸資料服務 API，非常的多樣，幾乎在臺灣你想的到的大眾交通工具都包在裡頭。&lt;br />
像是：臺鐵票價資料、高雄輕軌時刻表、臺北捷運即時通阻事件、機場即時入境航班、公車即時位置、公共自行車剩餘數量、觀光景點&amp;hellip;&amp;hellip;等等。&lt;/p>
&lt;p>PTX 網站有個「&lt;a href="https://ptx.transportdata.tw/PTX/Service" target="_blank" rel="noopener">
各項服務查詢
&lt;/a>」供檢視平台提供的服務，能依照 &amp;quot;領域類型&amp;quot;、&amp;quot;資料類型&amp;quot;、&amp;quot;業管機關&amp;quot; 去做篩選，每個服務還可以快速點開 API 說明及網址範例，另外還有整理成表格顯示：&lt;a href="https://ptx.transportdata.tw/PTX/Static/PDF_SupplyStatus.html" target="_blank" rel="noopener">
目前各單位資料供應現況表
&lt;/a>。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/ptx_intro/ptx_service.jpg" alt="PTX 各項服務查詢" data-caption="PTX 各項服務查詢" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='850px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:850px;height:;"/>
&lt;figcaption style="text-align: center;">
PTX 各項服務查詢
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;h2 id="加入會員--取得-app-id-與-app-key">加入會員 &amp;amp; 取得 APP ID 與 APP Key&lt;/h2>
&lt;p>為了提供穩定的服務、加強資訊安全管理，要使用此平台的 API 前需要先加入會員。&lt;/p>
&lt;h3 id="加入會員">加入會員&lt;/h3>
&lt;p>進入 &lt;a href="https://ptx.transportdata.tw/PTX/Management/AccountApply" target="_blank" rel="noopener">
加入會員
&lt;/a> 頁面，在此選擇「一般會員」即可，「進階會員」、「專案用戶」有更多的呼叫次數及其他權益，但那還要提供額外的證明，一般會員的 API 呼叫次數上限 20,000 次/日已經很夠我們自己玩玩，或做個小應用了。&lt;br />
(因為我以前註冊過，這邊就不演示了，按照網頁流程操作應該不會有什麼問題。)&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/ptx_intro/account_apply.jpg" alt="申請加入 PTX 會員" data-caption="申請加入 PTX 會員" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='650px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:650px;height:;"/>
&lt;figcaption style="text-align: center;">
申請加入 PTX 會員
&lt;/figcaption>
&lt;/figure>
&lt;h3 id="取得-app-id-與-app-key">取得 APP ID 與 APP Key&lt;/h3>
&lt;p>(APP ID 與 APP Key 下一篇文章使用 Python 實際操作時才會用到)&lt;/p>
&lt;p>註冊完成並等待三個工作日審核後，會收到一封 Email，之後我們會使用到「基礎資料服務(L1)」的「APP ID」與「APP Key」。&lt;br />
而目前提供的資料服務多屬 L1，L2 的服務目前僅有場站空氣品質服務 (不知道現在 L2 是否有加入其他的服務)。&lt;/p>
&lt;br/>
&lt;p>或者也可登入後到 &lt;a href="https://ptx.transportdata.tw/PTX/APIMember/ApplyRecord" target="_blank" rel="noopener">
API金鑰申請
&lt;/a> 取得，如果忘記 APP Key 可點擊「忘記APP Key」即可，但要注意舊的 APP Key 就會失效，所以如果你 APP Key 不小心公開了也可由此來重新產生，避免額度被其他人使用。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/ptx_intro/ptx_api_key.jpg" alt="取得 APP ID 與 APP Key" data-caption="取得 APP ID 與 APP Key" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='900px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:900px;height:;"/>
&lt;figcaption style="text-align: center;">
取得 APP ID 與 APP Key
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;!--adsense-->
&lt;h2 id="api-說明">API 說明&lt;/h2>
&lt;p>可以參考官方提供的「&lt;a href="https://motc-ptx-api-documentation.gitbook.io/motc-ptx-api-documentation/" target="_blank" rel="noopener">
資料使用葵花寶典
&lt;/a>」，此手冊分成以下四點作介紹說明&lt;/p>
&lt;ul>
&lt;li>API 會員：各會員層級進行說明並提供會員申請流程。&lt;/li>
&lt;li>API 使用說明：API 呼叫方法及注意事項進行說明。&lt;/li>
&lt;li>API 特色說明：API 獨特的使用技巧進行說明。&lt;/li>
&lt;li>API 資料使用注意事項：各運具資料面注意事項進行說明。&lt;/li>
&lt;/ul>
&lt;br/>
&lt;p>PTX 的 API 是以 OData（Open Data Protocol）標準介面格式對外開放供使用。&lt;/p>
&lt;br/>
&lt;h3 id="uri-設計概念">URI 設計概念&lt;/h3>
&lt;p>先來看看 URI 的設計概念。&lt;/p>
&lt;p>這邊以臺鐵 &amp;quot;取得指定[日期],[車站]的站別時刻表資料&amp;quot; 為例：&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/ptx_intro/uri_convention.png" alt="臺鐵 &amp;#34;取得指定[日期],[車站]的站別時刻表資料&amp;#34;" data-caption="臺鐵 &amp;#34;取得指定[日期],[車站]的站別時刻表資料&amp;#34;" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='850px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:850px;height:;"/>
&lt;figcaption style="text-align: center;">
臺鐵 &amp;#34;取得指定[日期],[車站]的站別時刻表資料&amp;#34;
&lt;/figcaption>
&lt;/figure>
&lt;p>從這張圖可以清楚的看出來，最前方 &amp;quot;基本網址&amp;quot; 大家都一樣，再來是 &amp;quot;API版本&amp;quot;、&amp;quot;鐵道&amp;quot; 中的 &amp;quot;臺鐵&amp;quot;，我們想抓的 &amp;quot;站別時刻表&amp;quot;，以及最後方帶入的參數 &amp;quot;車站代碼&amp;quot; 與 &amp;quot;日期&amp;quot;。&lt;br />
(車站代碼可以從另外一個 &amp;quot;車站基本資料&amp;quot; API 取得，或臺鐵官網的&lt;a href="https://tip.railway.gov.tw/tra-tip-web/tip/tip001/tip111/view" target="_blank" rel="noopener">
車站代碼表
&lt;/a>)&lt;/p>
&lt;p>雖然每項服務會有些不同，但其網址結構都是差不多的，這邊稍微知道就好，詳細說明請參考官方提供的「&lt;a href="https://docs.google.com/viewer?url=https://github.com/ptxmotc/PTX_Web/blob/master/Swagger%E6%9C%8D%E5%8B%99%E8%AA%AA%E6%98%8E%E4%B8%8A%E5%82%B3%E5%8F%83%E8%80%83%E6%AA%94%E6%A1%88/API_URI_Convention%E6%96%87%E4%BB%B6_v1.pdf?raw=true" target="_blank" rel="noopener">
API URI Convention文件
&lt;/a>」。&lt;/p>
&lt;br/>
&lt;h3 id="swagger-文件工具">Swagger 文件工具&lt;/h3>
&lt;p>至於各項服務 API 說明可以從&lt;a href="https://ptx.transportdata.tw/MOTC/" target="_blank" rel="noopener">
這裡
&lt;/a>進入，或到「&lt;a href="https://ptx.transportdata.tw/PTX/Service" target="_blank" rel="noopener">
PTX 各項服務查詢
&lt;/a>」點擊對應服務右方的&amp;quot;服務說明&amp;quot;，它是透過 Swagger 這個文件工具來呈現。&lt;/p>
&lt;br/>
&lt;p>這邊先講個 Swagger 基本使用。&lt;/p>
&lt;p>找一個感興趣的資源(Resource)，並打開其 API 說明。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/ptx_intro/ptx_swagger01.png" alt="API 介面說明" data-caption="API 說明" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='900px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:900px;height:;"/>
&lt;figcaption style="text-align: center;">
API 說明
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;p>大致分為幾個部分，最上方針對此資源簡短描述，再來 &amp;quot;Parameters&amp;quot; 是此資源可附加上的參數，像是我想看哪一個捷運系統的車站資料，就是在這邊帶上參數，除了標示 required 紅字為必填，其餘我們先暫時不必理會。最後 &amp;quot;Responses&amp;quot; 會寫到當回應哪個 HTTP Status Code 對應是哪個意思，其回應資料格式又是長什麼樣子。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/ptx_intro/ptx_swagger02.jpg" alt="API 介面說明" data-caption="API 說明" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='900px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:900px;height:;"/>
&lt;figcaption style="text-align: center;">
API 說明
&lt;/figcaption>
&lt;/figure>
&lt;p>回應資料有些欄位可能看不出它代表意思，就可以到這邊找找說明。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/ptx_intro/ptx_swagger03.png" alt="API 介面說明" data-caption="API 說明" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='900px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:900px;height:;"/>
&lt;figcaption style="text-align: center;">
API 說明
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;p>在 Parameters 與 Responses 中間有個大大的藍色 &amp;quot;Execute&amp;quot; 按鈕，我們給他用力按下去。&lt;/p>
&lt;p>噹🌟！發現在底下 &amp;quot;Responses&amp;quot; 區塊跑出好幾個新東西：&lt;/p>
&lt;ul>
&lt;li>&amp;quot;Request URL&amp;quot; 代表依照你上方設定的參數，其請求網址會長怎樣。&lt;/li>
&lt;li>&amp;quot;Server response&amp;quot; 顯示回應 HTTP Status Code 及回應內容(Response body)，回應內容就是我們想要的資料啦~&lt;/li>
&lt;/ul>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/ptx_intro/responses.png" alt="API 回應說明" data-caption="API 回應說明" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='900px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:900px;height:;"/>
&lt;figcaption style="text-align: center;">
API 回應說明
&lt;/figcaption>
&lt;/figure>
&lt;p>這個 Swagger 工具大致上的用法就是這樣。&lt;/p>
&lt;br/>
&lt;p>到這邊，都可以如實拿到想要的資料了。&lt;/p>
&lt;p>但可能它每次回傳的資料量很大，我只是需要其中的一部分資料而已，這樣會浪費頻寬、增加回應時間、接收到資料後還要費力處理，那有沒有能在送出請求時，就告訴 Server 我們想要那些資料呢？&lt;br />
有的！！接下來就要講到 Odata 這個介面格式厲害的地方了😮&lt;/p>
&lt;br/>
&lt;h3 id="odata-查詢語法">Odata 查詢語法&lt;/h3>
&lt;p>通過 Odata 協定制定的用法，我們可以去做多樣的查詢、過濾、排序，甚至有更進階的函數語法。&lt;/p>
&lt;p>我會依序介紹以下六種 Odata 的查詢方法：&lt;/p>
&lt;ul>
&lt;li>&lt;code>$format&lt;/code> 指定來源格式&lt;/li>
&lt;li>&lt;code>$orderby&lt;/code> 排序&lt;/li>
&lt;li>&lt;code>$top&lt;/code> 取前幾筆&lt;/li>
&lt;li>&lt;code>$skip&lt;/code> 跳過前幾筆&lt;/li>
&lt;li>&lt;code>$select&lt;/code> 挑選&lt;/li>
&lt;li>&lt;code>$filter&lt;/code> 過濾&lt;/li>
&lt;/ul>
&lt;p>這邊我是依照比較好理解的順序介紹說明，最後再舉幾個複合範例給各位參考。&lt;/p>
&lt;br/>
&lt;h4 id="format-指定來源格式">$format 指定來源格式&lt;/h4>
&lt;p>指定回傳資料要以什麼格式。&lt;/p>
&lt;p>PTX 提供 JSON 與 XML 兩種格式供選擇。&lt;/p>
&lt;ul>
&lt;li>&lt;code>$format=json&lt;/code>&lt;/li>
&lt;li>&lt;code>$format=xml&lt;/code>&lt;/li>
&lt;/ul>
&lt;h4 id="orderby-排序">$orderby 排序&lt;/h4>
&lt;p>指定回傳資料的排序。&lt;/p>
&lt;p>升冪：由小到大；降冪：由大到小。&lt;/p>
&lt;ul>
&lt;li>針對欄位1作升冪 (預設)&lt;br />
&lt;code>$orderby=Field1&lt;/code>&lt;br />
&lt;code>$orderby=Field1 asc&lt;/code>&lt;/li>
&lt;li>針對欄位1作降冪&lt;br />
&lt;code>$orderby=Field1 desc&lt;/code>&lt;/li>
&lt;li>針對欄位1作升冪，欄位2降冪&lt;br />
&lt;code>$orderby=Field1 asc,Field2 desc&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>各 API 服務有不同的回傳資料，例如我在查詢公共自行車即時車位時，希望資料依照 &amp;quot;可租借數量&amp;quot; 由多至少排列：&lt;br />
&lt;code>$orderby=AvailableRentBikes desc&lt;/code>&lt;/p>
&lt;h4 id="top-skip-取前幾筆">$top $skip 取前幾筆&lt;/h4>
&lt;p>這兩個是類似的概念。&lt;/p>
&lt;ul>
&lt;li>&lt;code>$top&lt;/code>：取前幾筆資料。&lt;/li>
&lt;li>&lt;code>$skip&lt;/code> 跳過前幾筆資料。&lt;/li>
&lt;/ul>
&lt;p>像是我想拿取前 11~30 筆資料：&lt;br />
&lt;code>$top=20&amp;amp;$skip=10&lt;/code>&lt;/p>
&lt;h4 id="select-挑選">$select 挑選&lt;/h4>
&lt;p>指定只需要回傳那些欄位，多個欄位可用逗號(&lt;code>,&lt;/code>)隔開，未指定則回傳全部欄位。&lt;br />
(目前只支援第一層欄位)&lt;/p>
&lt;ul>
&lt;li>只回傳欄位1&lt;br />
&lt;code>$select=Field1&lt;/code>&lt;/li>
&lt;li>回傳多個欄位，欄位1和欄位2&lt;br />
&lt;code>$select=Field1,Field2&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>不過它有但書：&lt;/p>
&lt;ul>
&lt;li>回傳資料指定為 json 時，只會回傳被 select 的欄位，除此之外，若其他欄位為非 nullable，也會回傳系統預設值。&lt;/li>
&lt;li>回傳資料指定為 xml，沒有被指定的屬性若為 class 或是 string，不會回傳該欄位，但若是其他屬性(int,bool,enum..)，還是會回傳該欄位，其值為系統預設值。&lt;/li>
&lt;/ul>
&lt;h4 id="filter-過濾">$filter 過濾&lt;/h4>
&lt;p>(來到最複雜、最多變化的一個語法了😅)&lt;/p>
&lt;p>filter 語法是用來對資料做篩選、過濾，提供「邏輯運算子」、「算術運算子」、「規範函數」、「Lambda Operators」可使用。&lt;/p>
&lt;br/>
&lt;p>&lt;strong>邏輯運算子 (Logical Operators)&lt;/strong>&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>邏輯運算子&lt;/th>
&lt;th>意義&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>eq&lt;/td>
&lt;td>等於&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ne&lt;/td>
&lt;td>不等於&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>gt&lt;/td>
&lt;td>大於&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ge&lt;/td>
&lt;td>大於等於&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>lt&lt;/td>
&lt;td>小於&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>le&lt;/td>
&lt;td>小於等於&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>and&lt;/td>
&lt;td>而且、和&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>or&lt;/td>
&lt;td>或者&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>not&lt;/td>
&lt;td>否定&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;ul>
&lt;li>
&lt;p>例如想查詢公共自行車 &amp;quot;可租借數量&amp;quot; 大於 10 台的站點：&lt;br />
&lt;code>$filter=AvailableRentBikes gt 10&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>假如欄位是在第二層，則可透過 &lt;code>/&lt;/code> 連接。&lt;br />
例如想取得高雄捷運 &amp;quot;左營站 &amp;quot; 的即時到離站電子看板：&lt;br />
&lt;code>$filter=StationName/Zh_tw eq '左營'&lt;/code>&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>* 要注意後方比對的字串需使用單引號 &lt;code>'&lt;/code> 框起來，雙引號 &lt;code>&amp;quot;&lt;/code> 是不行的哦。&lt;/p>
&lt;br/>
&lt;p>&lt;strong>算術運算子 (Arithmetic Operators)&lt;/strong>&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>算數運算子&lt;/th>
&lt;th>意義&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>add&lt;/td>
&lt;td>加&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>sub&lt;/td>
&lt;td>減&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>mul&lt;/td>
&lt;td>乘&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>div&lt;/td>
&lt;td>除&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>mod&lt;/td>
&lt;td>餘數&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;ul>
&lt;li>例如我想查詢有哪台公車目前時速是奇數：&lt;br />
&lt;code>$filter=Speed mod 2 eq 1&lt;/code>&lt;br />
(知道這個好像也不能幹嘛&amp;hellip;&amp;hellip;)&lt;/li>
&lt;/ul>
&lt;br/>
&lt;p>&lt;strong>規範函數 (Canonical Functions)&lt;/strong>&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>規範函數&lt;/th>
&lt;th>意義&lt;/th>
&lt;th>&lt;/th>
&lt;th>規範函數&lt;/th>
&lt;th>意義&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>substring&lt;/td>
&lt;td>子字串&lt;/td>
&lt;td>&lt;/td>
&lt;td>year&lt;/td>
&lt;td>年份&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>startswith&lt;/td>
&lt;td>字串開頭&lt;/td>
&lt;td>&lt;/td>
&lt;td>month&lt;/td>
&lt;td>月份&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>endswith&lt;/td>
&lt;td>字串結尾&lt;/td>
&lt;td>&lt;/td>
&lt;td>day&lt;/td>
&lt;td>日&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>length&lt;/td>
&lt;td>字串長度&lt;/td>
&lt;td>&lt;/td>
&lt;td>hour&lt;/td>
&lt;td>小時&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>indexof&lt;/td>
&lt;td>指定字串出現位置&lt;/td>
&lt;td>&lt;/td>
&lt;td>minute&lt;/td>
&lt;td>分&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>tolower&lt;/td>
&lt;td>字串變小寫&lt;/td>
&lt;td>&lt;/td>
&lt;td>second&lt;/td>
&lt;td>秒&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>toupper&lt;/td>
&lt;td>字串變大寫&lt;/td>
&lt;td>&lt;/td>
&lt;td>fractionalseconds&lt;/td>
&lt;td>小數秒&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>trim&lt;/td>
&lt;td>去空白&lt;/td>
&lt;td>&lt;/td>
&lt;td>date&lt;/td>
&lt;td>日期&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>contains&lt;/td>
&lt;td>包含&lt;/td>
&lt;td>&lt;/td>
&lt;td>time&lt;/td>
&lt;td>時間&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>concat&lt;/td>
&lt;td>串接&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;td>round&lt;/td>
&lt;td>四捨五入&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;td>floor&lt;/td>
&lt;td>無條件捨去&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;td>ceiling&lt;/td>
&lt;td>無條件進位&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;td>cast&lt;/td>
&lt;td>轉型&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;ul>
&lt;li>
&lt;p>例如取得公車動態中車牌號碼開頭是 &amp;quot;001&amp;quot; 的資料：&lt;br />
&lt;code>$filter=startswith(PlateNumb, '001')&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>或者想知道車牌號碼長度為 7 個字的公車：&lt;br />
&lt;code>$filter=length(PlateNumb) eq 7&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>去台南旅遊，想找出店名包含 &amp;quot;豆花&amp;quot; 的店家：&lt;br />
&lt;code>$filter=contains(RestaurantName,'豆花')&lt;/code>&lt;/p>
&lt;/li>
&lt;/ul>
&lt;br/>
&lt;p>&lt;strong>Lambda Operators&lt;/strong>&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Lambda Operators&lt;/th>
&lt;th>意義&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>all&lt;/td>
&lt;td>所有項目都要符合&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>any&lt;/td>
&lt;td>其中一項符合&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;ul>
&lt;li>例如我想知道今天有哪幾班高鐵會停 &amp;quot;雲林&amp;quot;：&lt;br />
&lt;code>$filter=StopTimes/any(d:d/StationName/Zh_tw eq '雲林')&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>這部分可能一開始有些難懂，主要是用在判斷陣列中的值。&lt;br />
* 其中的 &lt;code>d&lt;/code> 是可以隨意換其他名稱的。&lt;/p>
&lt;br/>
&lt;br/>
&lt;p>&lt;strong>多種複合用法&lt;/strong>&lt;/p>
&lt;p>為了讓大家更容易理解，這邊我再舉幾個多種語法所組合的用法：&lt;/p>
&lt;ul>
&lt;li>
&lt;p>想看看高鐵在今年(2022年) 3 月之後有發布什麼關於 &amp;quot;左營&amp;quot; 的最新消息：&lt;br />
&lt;code>$filter=year(PublishTime) eq 2022 and month(PublishTime) ge 3 and contains(Title,'左營')&lt;/code>&lt;br />
(啊&amp;hellip;4/1 水泥施工事故導致電力異常，台南-左營站間停駛)&lt;/p>
&lt;/li>
&lt;li>
&lt;p>台南市公車中，路線是 &amp;quot;紅&amp;quot; 字開頭，並且目前 &amp;quot;時速超過 30 公里&amp;quot;，依照時速由大排到小的前五名：&lt;br />
&lt;code>$filter=startswith(SubRouteName/Zh_tw,'紅') and Speed gt 30&amp;amp;$orderby=Speed desc&amp;amp;$top=5&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>因為提著大包小包，想知道台北捷運 &amp;quot;古亭站&amp;quot; 有哪個出口有 &amp;quot;電扶梯&amp;quot; 或 &amp;quot;電梯&amp;quot;，且只回傳 &amp;quot;出口&amp;quot; 和 &amp;quot;地址&amp;quot;：&lt;br />
(備註：Escalator 是否有電扶梯(0:沒有,1:雙向,2:出站,3:入站))&lt;br />
&lt;code>$select=ExitName,LocationDescription&amp;amp;$filter=StationName/Zh_tw eq '古亭' and (Escalator ne 0 or Elevator eq true)&lt;/code>&lt;/p>
&lt;/li>
&lt;/ul>
&lt;br/>
&lt;br/>
&lt;p>有關如何使用 Odata 查詢 PTX API 資料，官方有整裡個簡報—「&lt;a href="https://docs.google.com/viewer?url=https://github.com/ptxmotc/PTX_Web/blob/master/Swagger%E6%9C%8D%E5%8B%99%E8%AA%AA%E6%98%8E%E4%B8%8A%E5%82%B3%E5%8F%83%E8%80%83%E6%AA%94%E6%A1%88/%E5%85%AC%E5%85%B1%E9%81%8B%E8%BC%B8%E6%95%B4%E5%90%88%E8%B3%87%E8%A8%8A%E5%B9%B3%E5%8F%B0%E8%B3%87%E6%96%99%E6%9C%8D%E5%8B%99%E9%96%8B%E7%99%BC%E5%AF%A6%E4%BD%9C.pdf?raw=true" target="_blank" rel="noopener">
PTX APIs開發技術說明(含Odata)
&lt;/a>」，裡頭有更詳細地介紹 Odata，以及其查詢選項、函數語法。&lt;/p>
&lt;p>當然，如果你覺得這些篩選語法太麻煩，也是可以全部抓下來後，再使用程式去處理。&lt;/p>
&lt;br/>
&lt;h2 id="api-呼叫統計">API 呼叫統計&lt;/h2>
&lt;p>PTX 平台也提供「&lt;a href="https://ptx.transportdata.tw/PTX/APIMember/APICallTime" target="_blank" rel="noopener">
服務呼叫統計
&lt;/a>」網頁，可查看自己目前的資料數據傳輸量、呼叫次數以及虛擬點數等等統計數據，確認還有多少額度可以使用，甚至也針對各項服務做統計。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/ptx_intro/api_call_time01.jpg" alt="PTX 服務呼叫統計" data-caption="PTX 服務呼叫統計" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='750px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:750px;height:;"/>
&lt;figcaption style="text-align: center;">
PTX 服務呼叫統計
&lt;/figcaption>
&lt;/figure>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/ptx_intro/api_call_time02.jpg" alt="PTX 服務呼叫統計" data-caption="PTX 服務呼叫統計" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='750px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:750px;height:;"/>
&lt;figcaption style="text-align: center;">
PTX 服務呼叫統計
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;!--adsense-->
&lt;h2 id="結語">結語&lt;/h2>
&lt;p>有了這個 PTX 平台後，想要取得各大眾公共運輸的資料變得相當容易，除了做成應用程式自用外，也可以公開讓大家使用。&lt;/p>
&lt;p>官方也有整理一些 &lt;a href="https://ptx.transportdata.tw/PTX/Common/FAQ" target="_blank" rel="noopener">
常見問題
&lt;/a> 供參考。&lt;/p>
&lt;br/>
&lt;p>如何？看完介紹後你腦中是否產生有趣的 idea 了呢😙&lt;br />
別急，下一篇文章會來教你該&lt;a href="https://blog.jiatool.com/posts/ptx_python" target="_blank" rel="noopener">
如何使用 Python 來串接 PTX API
&lt;/a>，以及需要注意什麼事情。&lt;/p>
&lt;br/>
&lt;p>歡迎追蹤『&lt;a href="https://www.facebook.com/jiatool" target="_blank" rel="noopener">
IT空間
&lt;/a>』FB 粉專，取得最新發文通知🔔&lt;/p>
&lt;br/>
&lt;br/>
&lt;hr />
&lt;p>參考：&lt;br />
&lt;a href="https://ptx.transportdata.tw/PTX/" target="_blank" rel="noopener">
公共運輸整合資訊流通服務平台 | 官網
&lt;/a>&lt;br />
&lt;a href="https://motc-ptx-api-documentation.gitbook.io/motc-ptx-api-documentation/" target="_blank" rel="noopener">
PTX 資料使用葵花寶典
&lt;/a>&lt;br />
&lt;a href="https://ptx.transportdata.tw/MOTC/" target="_blank" rel="noopener">
PTX API 說明 | Swagger
&lt;/a>&lt;br />
&lt;a href="https://docs.google.com/viewer?url=https://github.com/ptxmotc/PTX_Web/blob/master/Swagger%E6%9C%8D%E5%8B%99%E8%AA%AA%E6%98%8E%E4%B8%8A%E5%82%B3%E5%8F%83%E8%80%83%E6%AA%94%E6%A1%88/%E5%85%AC%E5%85%B1%E9%81%8B%E8%BC%B8%E6%95%B4%E5%90%88%E8%B3%87%E8%A8%8A%E5%B9%B3%E5%8F%B0%E8%B3%87%E6%96%99%E6%9C%8D%E5%8B%99%E9%96%8B%E7%99%BC%E5%AF%A6%E4%BD%9C.pdf?raw=true" target="_blank" rel="noopener">
PTX APIs開發技術說明(含Odata)
&lt;/a>&lt;/p>
&lt;br/>
&lt;blockquote>
&lt;p>等待奇蹟，不如為自己留下努力的軌跡；期待運氣，不如堅持自己的勇氣。&lt;/p>
&lt;p align="right">—— 李洋 (台灣羽球國手)&lt;/p>
&lt;/blockquote></content:encoded><dc:creator>Jia</dc:creator><media:content url="https://blog.jiatool.comimages/cover/ptx_intro.jpg" medium="image"><media:title type="html">featured image</media:title></media:content><media:content url="https://blog.jiatool.comimages/posts/ptx_intro_meta.jpg" medium="image"><media:title type="html">meta image</media:title></media:content><category>PTX</category><category>API</category><category>交通</category><category>公共運輸</category><category>分享</category></item><item><title>(圖解) 網路爬蟲 API 常見的 3 種「翻頁」方式</title><link>https://blog.jiatool.com/posts/web_crawler_page_method/</link><pubDate>Sat, 10 Jul 2021 20:40:00 +0800</pubDate><author>jia@jiatool.com (Jia)</author><atom:modified>Sat, 10 Jul 2021 20:50:00 +0800</atom:modified><guid>https://blog.jiatool.com/posts/web_crawler_page_method/</guid><description>前言 之前時不時有網友在詢問有關網路爬蟲&amp;quot;翻頁&amp;quot;的問題： 我該如何抓取下一頁的文章呢？ 使用 limit 最多只能抓到前 100 筆留言，那之後的</description><content:encoded>&lt;h2 id="前言">前言&lt;/h2>
&lt;p>之前時不時有網友在詢問有關網路爬蟲&amp;quot;翻頁&amp;quot;的問題：&lt;/p>
&lt;ul>
&lt;li>我該如何抓取下一頁的文章呢？&lt;/li>
&lt;li>使用 limit 最多只能抓到前 100 筆留言，那之後的該怎麼取得？&lt;/li>
&lt;/ul>
&lt;p>我發覺可能之前 &lt;a href="https://blog.jiatool.com/series/Python%e7%b6%b2%e8%b7%af%e7%88%ac%e8%9f%b2%e5%af%a6%e4%be%8b/" target="_blank" rel="noopener">
Python 網路爬蟲實例系列
&lt;/a>內沒有說明清楚。&lt;br />
因此這篇文章，將整理目前我遇過的網路爬蟲 API 中，常遇見的三種「翻頁」方式，並且搭配簡易圖示，希望讓剛進此領域的網友能更容易理解。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/web_crawler_page_method/turn_page.jpg" alt="圖片來源：Pexels" data-caption="圖片來源：Pexels" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='700px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:700px;height:;"/>
&lt;figcaption style="text-align: center;">
圖片來源：Pexels
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;!--adsense-->
&lt;h2 id="說明">說明&lt;/h2>
&lt;p>想要爬取某個網站，也順利找出網頁是使用動態載入請求 API 的方式，但遇到像 &lt;a href="https://www.dcard.tw/f" target="_blank" rel="noopener">
Dcard
&lt;/a> 這種的文章列表或留言列表，它是往下滾，就會送出新請求來取得下一頁資料。&lt;/p>
&lt;p>那麼，它是如何達成「翻頁」的呢？&lt;/p>
&lt;br/>
&lt;p>底下會依照這三種常遇到的「翻頁」方式來分別說明：&lt;/p>
&lt;ol>
&lt;li>頁數 (page)&lt;/li>
&lt;li>偏移 (limit &amp;amp; offset)&lt;/li>
&lt;li>指定ID (pid)&lt;/li>
&lt;/ol>
&lt;h3 id="頁數-page">頁數 (page)&lt;/h3>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/web_crawler_page_method/turnpage_page.jpg" alt="翻頁方式 - 頁數" data-caption="翻頁方式 - 頁數" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='600px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:600px;height:;"/>
&lt;figcaption style="text-align: center;">
翻頁方式 - 頁數
&lt;/figcaption>
&lt;/figure>
&lt;p>第一種最容易理解、最直覺的是頁數，這就跟我們一般瀏覽網頁一樣，你想要看第幾頁，就給它第幾頁的頁數即可。&lt;/p>
&lt;ul>
&lt;li>&lt;code>page&lt;/code> 代表資料的頁數。&lt;/li>
&lt;/ul>
&lt;p>不過缺點是不能彈性調整每次抓取的量，假如此 API 一頁是 30 則留言，就算我只想取前 5 則留言，一樣一次請求還是會抓到 30 則留言，除了會占用較多流量，也可能花費較多時間。&lt;/p>
&lt;p>舉例來說：&lt;br />
想抓第一頁 &lt;code>page=1&lt;/code>，想抓第二頁是 &lt;code>page=2&lt;/code>，同理第99頁就是 &lt;code>page=99&lt;/code>。&lt;/p>
&lt;p>實際網站範例：&lt;br />
「&lt;a href="https://blog.jiatool.com/posts/pchome_spider01/" target="_blank" rel="noopener">
PChome 線上購物
&lt;/a>」&amp;quot;商品搜尋&amp;quot;中的 &lt;code>page&lt;/code>。&lt;br />
「&lt;a href="https://blog.jiatool.com/posts/udn_spider/" target="_blank" rel="noopener">
聯合新聞網
&lt;/a>」&amp;quot;文章列表&amp;quot;中的 &lt;code>page&lt;/code>。&lt;/p>
&lt;h3 id="偏移-limit--offset">偏移 (limit &amp;amp; offset)&lt;/h3>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/web_crawler_page_method/turnpage_offset.jpg" alt="翻頁方式 - 偏移" data-caption="翻頁方式 - 偏移" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='600px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:600px;height:;"/>
&lt;figcaption style="text-align: center;">
翻頁方式 - 偏移
&lt;/figcaption>
&lt;/figure>
&lt;p>第二種翻頁方式就解決了第一種的問題，變成可「彈性調整抓取量」。&lt;br />
它藉由兩個參數來達成，分別為&amp;quot;limit&amp;quot;與&amp;quot;offset&amp;quot; (不同 API 參數名稱可能不同)&lt;/p>
&lt;ul>
&lt;li>&lt;code>limit&lt;/code> 代表一次請求最大資料筆數。&lt;/li>
&lt;li>&lt;code>offset&lt;/code> 代表資料的偏移值。&lt;/li>
&lt;/ul>
&lt;p>* &lt;code>offset&lt;/code>(foodpanda)參數在不同網站的 API 有不同名稱，例如 &lt;code>newest&lt;/code>(蝦皮購物)、&lt;code>after&lt;/code>(Dcard)。&lt;/p>
&lt;p>舉例來說：&lt;br />
抓前三筆資料是 &lt;code>limit=3 offset=0&lt;/code>。&lt;br />
想取得第六、七筆資料，將資料偏移 5 (從第一筆資料開始往下加五筆的意思)、限制一次 2 筆，就是 &lt;code>limit=2 offset=5&lt;/code>。&lt;/p>
&lt;p>實際網站範例：&lt;br />
「&lt;a href="https://blog.jiatool.com/posts/foodpanda_spider/" target="_blank" rel="noopener">
foodpanda
&lt;/a>」&amp;quot;搜尋餐廳&amp;quot;中的 &lt;code>limit&lt;/code> 和 &lt;code>offset&lt;/code>。&lt;br />
「&lt;a href="https://blog.jiatool.com/posts/shopee_spider/" target="_blank" rel="noopener">
蝦皮購物
&lt;/a>」&amp;quot;搜尋商品&amp;quot;中的 &lt;code>limit&lt;/code> 和 &lt;code>newest&lt;/code>。&lt;br />
「&lt;a href="https://blog.jiatool.com/posts/dcard_api_v2/" target="_blank" rel="noopener">
Dcard API
&lt;/a>」&amp;quot;留言列表&amp;quot;中的 &lt;code>limit&lt;/code> 和 &lt;code>after&lt;/code>。&lt;/p>
&lt;h3 id="指定id-pid">指定ID (pid)&lt;/h3>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/web_crawler_page_method/turnpage_pid.jpg" alt="翻頁方式 - 指定ID" data-caption="翻頁方式 - 指定ID" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='600px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:600px;height:;"/>
&lt;figcaption style="text-align: center;">
翻頁方式 - 指定ID
&lt;/figcaption>
&lt;/figure>
&lt;p>第三種翻頁方式感覺像是第二種的改版，有些網站一樣有&amp;quot;limit&amp;quot;來限制最大資料筆數，但&amp;quot;offset&amp;quot;換成了&amp;quot;pid&amp;quot;參數，&amp;quot;pid&amp;quot;參數需要帶入上一頁最後一筆資料的數值。&lt;br />
但它就限制了你，不能直接跳到後面的頁數，例如我想看第 100 筆資料，你還是要請求一頁才知道下一頁的網址。&lt;/p>
&lt;ul>
&lt;li>&lt;code>limit&lt;/code> 代表一次請求最大資料筆數。&lt;/li>
&lt;li>&lt;code>pid&lt;/code> 上一頁最後一筆的 ID。&lt;/li>
&lt;/ul>
&lt;p>* &lt;code>pid&lt;/code>(NOWnews)參數在不同網站的 API 有不同名稱，例如 &lt;code>before&lt;/code>(Dcard)、&lt;code>pageToken&lt;/code>(YouTube Data API)。而 NOWnews 不需要 &lt;code>limit&lt;/code> 參數，它 API 已經有限制一次的資料量了。&lt;/p>
&lt;p>舉例來說：&lt;br />
抓前三筆資料是 &lt;code>limit=3 pid=&lt;/code>，因為這是最前面的資料，pid 就不需要給值。&lt;br />
而取得第四、五筆資料，因為只要兩筆，limit 帶入 2，前一頁最後一筆資料 ID 為 003，因此 pid 帶入 003，結果就是 &lt;code>limit=2 pid=003&lt;/code>。&lt;/p>
&lt;p>實際網站範例：&lt;br />
「&lt;a href="https://blog.jiatool.com/posts/nownews_spider/" target="_blank" rel="noopener">
NOWnews今日新聞
&lt;/a>」&amp;quot;新聞列表&amp;quot;中的 &lt;code>pid&lt;/code>。&lt;br />
「&lt;a href="https://blog.jiatool.com/posts/dcard_api_v2/" target="_blank" rel="noopener">
Dcard API
&lt;/a>」&amp;quot;文章列表&amp;quot;中的 &lt;code>limit&lt;/code> 和 &lt;code>before&lt;/code>。&lt;br />
「&lt;a href="https://blog.jiatool.com/posts/youtube_spider_api/" target="_blank" rel="noopener">
YouTube Data API
&lt;/a>」&amp;quot;留言列表&amp;quot;中的 &lt;code>maxResults&lt;/code> 和 &lt;code>pageToken&lt;/code>。&lt;/p>
&lt;p>注意：YouTube Data API 的 &lt;code>pageToken&lt;/code> 有點不太一樣，它是帶入上一頁回傳的的 &lt;code>nextPageToken&lt;/code>，而不是最後一筆資料的 ID。&lt;/p>
&lt;h3 id="三者方式比較">三者方式比較&lt;/h3>
&lt;p>我進一步將以上三種翻頁方式放在一起比較，如下圖範例所示。&lt;/p>
&lt;p>假如左邊是此 API 可以獲取的全部資料，可以看到共有五篇文章，各自有 ID(id) 和 標題(title) 欄位，同樣要取得綠色區塊(第三和第四篇文章)，以上三種翻頁方式實際會需要帶入這些數值。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/web_crawler_page_method/compare.jpg" alt="三種翻頁方式比較" data-caption="三種翻頁方式比較" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='800px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:800px;height:;"/>
&lt;figcaption style="text-align: center;">
三種翻頁方式比較
&lt;/figcaption>
&lt;/figure>
&lt;ul>
&lt;li>頁數：假設它一頁是兩筆資料，那我們要帶入 &lt;code>pega=2&lt;/code> 來取到第二頁資料。&lt;/li>
&lt;li>偏移：一次想取兩筆資料，因此 &lt;code>limit=2&lt;/code>；我們要從第三筆開始取，因此 &lt;code>offset=2&lt;/code>。&lt;/li>
&lt;li>指定ID：一次想取兩筆資料，因此 &lt;code>limit=2&lt;/code>；上一頁最後一筆資料的 ID 是 002，因此 &lt;code>pid=002&lt;/code>。&lt;/li>
&lt;/ul>
&lt;br/>
&lt;!--adsense-->
&lt;h2 id="結語">結語&lt;/h2>
&lt;p>不知道透過上面的說明與比較，是不是讓你對於 API 常見的幾種翻頁方式更了解了呢？&lt;br />
提醒各網站 API 本身可能有些微差異，詳細規則還是要查看 API 文件。&lt;/p>
&lt;p>歡迎追蹤『&lt;a href="https://www.facebook.com/jiatool" target="_blank" rel="noopener">
IT空間
&lt;/a>』FB 粉專，取得最新發文通知🔔&lt;/p>
&lt;br/>
&lt;br/>
&lt;hr />
&lt;p>參考：&lt;br />
&lt;a href="https://blog.jiatool.com/posts/dcard_api_v2/" target="_blank" rel="noopener">
Dcard API | IT空間
&lt;/a>&lt;br />
&lt;a href="https://blog.jiatool.com/posts/shopee_spider/" target="_blank" rel="noopener">
蝦皮購物 爬蟲 | IT空間
&lt;/a>&lt;br />
&lt;a href="https://blog.jiatool.com/posts/foodpanda_spider/" target="_blank" rel="noopener">
foodpanda 爬蟲 | IT空間
&lt;/a>&lt;br />
&lt;a href="https://blog.jiatool.com/posts/pchome_spider01/" target="_blank" rel="noopener">
PChome 線上購物 爬蟲 | IT空間
&lt;/a>&lt;br />
&lt;a href="https://blog.jiatool.com/posts/youtube_spider_api/" target="_blank" rel="noopener">
YouTube Data API | IT空間
&lt;/a>&lt;br />
&lt;a href="https://blog.jiatool.com/posts/nownews_spider/" target="_blank" rel="noopener">
NOWnews今日新聞 爬蟲 | IT空間
&lt;/a>&lt;br />
&lt;a href="https://blog.jiatool.com/posts/udn_spider/" target="_blank" rel="noopener">
聯合新聞網 爬蟲 | IT空間
&lt;/a>&lt;/p>
&lt;br/>
&lt;blockquote>
&lt;p>Stay hungry. Stay foolish&lt;br />
求知若飢，虛心若愚。&lt;/p>
&lt;p align="right">—— 史蒂夫·賈伯斯&lt;/p>
&lt;/blockquote></content:encoded><dc:creator>Jia</dc:creator><media:content url="https://blog.jiatool.comimages/cover/web_crawler_page_method.jpg" medium="image"><media:title type="html">featured image</media:title></media:content><media:content url="https://blog.jiatool.comimages/posts/web_crawler_page_method_meta.jpg" medium="image"><media:title type="html">meta image</media:title></media:content><category>API</category><category>網路爬蟲</category></item><item><title>Google 搜尋結果 API — Aves API 完整教學</title><link>https://blog.jiatool.com/posts/aves_api/</link><pubDate>Sat, 30 Jan 2021 20:55:00 +0800</pubDate><author>jia@jiatool.com (Jia)</author><atom:modified>Sat, 18 Nov 2023 21:50:00 +0800</atom:modified><guid>https://blog.jiatool.com/posts/aves_api/</guid><description>(先澄清，以下非業配，純粹依個人使用過程描述。) 前言 因為某原因，需要爬取&amp;quot;Google 搜尋&amp;quot;頁面上的資料，沒多久前舊有的</description><content:encoded>&lt;p>(先澄清，以下非業配，純粹依個人使用過程描述。)&lt;/p>
&lt;h2 id="前言">前言&lt;/h2>
&lt;p>因為某原因，需要爬取&amp;quot;Google 搜尋&amp;quot;頁面上的資料，沒多久前舊有的網頁爬蟲請求失敗了!! (不妙啊&amp;hellip;&lt;/p>
&lt;p>後來多次嘗試，也改用手機網路分享(不同IP)測試，也是遇到同樣的問題&amp;quot;HTTP Error 429: Too Many Requests&amp;quot;，並需要通過 &lt;a href="https://zh.wikipedia.org/wiki/ReCAPTCHA" target="_blank" rel="noopener">
reCAPTCHA 驗證
&lt;/a>。&lt;br />
📢 白話翻譯：你短時間送出太多請求了，一般人不可能這麼頻繁，我嚴重懷疑你是機器人，先出個題目讓你回答(&lt;a href="https://technews.tw/2018/12/17/keying-recaptcha-working-for-google/" target="_blank" rel="noopener">
順便賺個免費勞工幫我們訓練機器學習
&lt;/a>)，等確定你是真人再給你資料。&lt;/p>
&lt;p>後來想說為了穩定及時間因素，不要我們自己手動爬取、絞盡腦汁突破了，請求外援看看XD&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/aves_api/coffee.jpg" alt="Photo by Harry Brewer on Unsplash" data-caption="Photo by Harry Brewer on Unsplash" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='700px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:700px;height:;"/>
&lt;figcaption style="text-align: center;">
Photo by Harry Brewer on Unsplash
&lt;/figcaption>
&lt;/figure>
&lt;h2 id="尋找-google-搜尋-api">尋找 Google 搜尋 API&lt;/h2>
&lt;p>先去 Google 官網看看是否提供對應功能的 API，結果是有 &lt;a href="https://developers.google.com/custom-search/v1/overview" target="_blank" rel="noopener">
Custom Search JSON API
&lt;/a>，但每天只免費提供 100 次搜尋，額外請求的費用為每 1000 個搜尋 5 美元，每天最多 1 萬次搜尋。&lt;br />
這對我來說免費額度遠遠不夠，加購的價格又超貴 QAQ&lt;/p>
&lt;br/>
&lt;p>官方提供的不行，不然找找看網路上，有沒有別人提供免費或付費的 Google 搜尋 API 服務可以使用呢？因為感覺某些資訊公司也會有這種需求。&lt;/p>
&lt;p>後來找到「&lt;a href="https://avesapi.com" target="_blank" rel="noopener">
Aves API
&lt;/a>」，其他有提供類似的服務還有「&lt;a href="https://serpapi.com/" target="_blank" rel="noopener">
SerpApi
&lt;/a>」、「&lt;a href="https://rapidapi.com/apigeek/api/google-search3/endpoints" target="_blank" rel="noopener">
Rapid API
&lt;/a>」、「&lt;a href="https://zenserp.com/" target="_blank" rel="noopener">
zenserp
&lt;/a>」、「&lt;a href="https://scraperbox.com/google-search-scraper" target="_blank" rel="noopener">
ScraperBox
&lt;/a>」、「&lt;a href="https://dataforseo.com/apis/serp-api" target="_blank" rel="noopener">
DataForSEO
&lt;/a>」&amp;hellip;&amp;hellip;等可以參考，多方去比較，找到最符合自己需求的服務。&lt;/p>
&lt;p>最後我選擇 Aves API，會選擇這個最主要的因素 &amp;ndash; 便宜😅，這是我目前找到最便宜的，而且它計費的方式與其他有些不同，它是類似儲值的概念，你用多少就扣多少，沒有時間上的限制，而其他比較常見的是按月付款。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/aves_api/aves_web.jpg" alt="Aves API 官網" data-caption="Aves API 官網" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='700px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:700px;height:;"/>
&lt;figcaption style="text-align: center;">
Aves API 官網
&lt;/figcaption>
&lt;/figure>
&lt;h2 id="測試頁面">測試頁面&lt;/h2>
&lt;p>在 Aves API 官網首頁上有個可以實際輸入關鍵字，查看它回傳的資料是那些、長怎麼樣的 &lt;a href="https://avesapi.com/#try-serpapi" target="_blank" rel="noopener">
即時測試頁面(Live Demo)
&lt;/a>，能在此先測試是否有符合自己的需求。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/aves_api/live_demo.jpg" alt="即時測試頁面(Live Demo)" data-caption="即時測試頁面(Live Demo)" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='700px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:700px;height:;"/>
&lt;figcaption style="text-align: center;">
即時測試頁面(Live Demo)
&lt;/figcaption>
&lt;/figure>
&lt;h2 id="api-相關文檔">API 相關文檔&lt;/h2>
&lt;p>&lt;a href="https://docs.avesapi.com/" target="_blank" rel="noopener">
官方網站 API 文檔
&lt;/a>&lt;/p>
&lt;h3 id="錯誤狀態代碼">錯誤狀態代碼&lt;/h3>
&lt;p>如果請求收到錯誤狀態代碼，可以在&lt;a href="https://docs.avesapi.com/#common-api-error-codes" target="_blank" rel="noopener">
這邊
&lt;/a>查看是什麼原因，例如：API 金鑰無效、缺少查詢參數、帳戶額度不足。&lt;/p>
&lt;h3 id="api-參數">API 參數&lt;/h3>
&lt;p>註冊後會取得唯一的 API Key(金鑰)，送出請求時要代入。&lt;br />
API 網址如下：&lt;/p>
&lt;pre>&lt;code>https://api.avesapi.com/search?apikey=YOUR_API_KEY
&lt;/code>&lt;/pre>&lt;p>(使用 GET 方式送出請求，等同於你在瀏覽器直接輸入網址啦！)&lt;/p>
&lt;br/>
&lt;p>而此 API 目前可帶參數有以下這些：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>參數&lt;/th>
&lt;th>代表意思&lt;/th>
&lt;th>預設&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>apikey&lt;/code>&lt;/td>
&lt;td>[必要] 你的 API Key(金鑰)。&lt;/td>
&lt;td>[無]&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>query&lt;/code>&lt;/td>
&lt;td>[必要] 要搜尋的關鍵字。&lt;/td>
&lt;td>[無]&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>device&lt;/code>&lt;/td>
&lt;td>指定搜尋裝置平台：&lt;code>desktop&lt;/code> 或 &lt;code>mobile&lt;/code>。&lt;/td>
&lt;td>&lt;code>desktop&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>location&lt;/code>&lt;/td>
&lt;td>指定位置參數，有助於取得基於位置的搜索結果。提供的位置在 &lt;a href="https://avesapi.com/places.json" target="_blank" rel="noopener">
places.json
&lt;/a> 此檔案內所列出來的，需要的話就帶入&lt;code>place_slug&lt;/code>中的值。台灣可以使用&lt;code>&amp;quot;tw&amp;quot;&lt;/code>在檔案內搜尋，例如有&lt;code>taipei&lt;/code>、&lt;code>taichung&lt;/code>、&lt;code>tainan&lt;/code>、&lt;code>kaohsiung&lt;/code>等地點。&lt;/td>
&lt;td>[無]&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>google_domain&lt;/code>&lt;/td>
&lt;td>指定 Google 網域，支援的列表 &lt;a href="https://avesapi.com/google-domains.json" target="_blank" rel="noopener">
google-domains.json
&lt;/a>。台灣是 &lt;code>google.com.tw&lt;/code>。&lt;/td>
&lt;td>&lt;code>google.com&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>gl&lt;/code>&lt;/td>
&lt;td>指定查詢的國家代碼(預設美國)，支援的列表 &lt;a href="https://avesapi.com/countries.json" target="_blank" rel="noopener">
countries.json
&lt;/a>。台灣是 &lt;code>tw&lt;/code>。&lt;/td>
&lt;td>&lt;code>us&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>hl&lt;/code>&lt;/td>
&lt;td>指定查詢的語言(預設美語)，支援的列表 &lt;a href="https://avesapi.com/languages.json" target="_blank" rel="noopener">
languages.json
&lt;/a>。繁體中文是 &lt;code>Chinese (Traditional)&lt;/code>。&lt;/td>
&lt;td>&lt;code>en&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>num&lt;/code>&lt;/td>
&lt;td>指定每頁顯示的結果數，測試最多到&lt;code>100&lt;/code>。&lt;/td>
&lt;td>&lt;code>10&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>page&lt;/code>&lt;/td>
&lt;td>指定頁數(1~10頁)。&lt;/td>
&lt;td>&lt;code>1&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>uule&lt;/code>&lt;/td>
&lt;td>Returns the location based search results. (好像可以設定精確位置，但我不了解。)&lt;/td>
&lt;td>[無]&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>output&lt;/code>&lt;/td>
&lt;td>API 回應資料的格式：&lt;code>json&lt;/code> 或 &lt;code>html&lt;/code>。&lt;/td>
&lt;td>&lt;code>json&lt;/code>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>* 還有我發現在它 API Playground 頁面，還有一個的參數是&lt;code>type&lt;/code>，代表搜尋種類，有 &lt;code>web&lt;/code>(預設)、&lt;code>image&lt;/code>、&lt;code>news&lt;/code> 可選，但這個參數我還沒實際試過。&lt;/p>
&lt;h3 id="使用範例">使用範例&lt;/h3>
&lt;p>舉例來說，我想要在 google.com.tw 網域上搜尋&amp;quot;手機&amp;quot;，國家設定台灣、語言當然是繁體中文，想要看前 100 個搜尋結果，就如下代入參數請求：&lt;/p>
&lt;pre>&lt;code>https://api.avesapi.com/search?apikey={your-api-key}&amp;amp;query=手機&amp;amp;google_domain=google.com.tw&amp;amp;gl=tw&amp;amp;hl=zh-tw&amp;amp;num=100
&lt;/code>&lt;/pre>&lt;p>或者參數也可以以字典(Dictionary)的方式呈現。&lt;br />
在此使用 Python 程式來示範。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-Python" data-lang="Python">&lt;span class="n">your_api_key&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;XXXXXXXXXX&amp;#39;&lt;/span>
&lt;span class="n">keyword&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;手機&amp;#39;&lt;/span>
&lt;span class="n">api_url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;https://api.avesapi.com/search&amp;#39;&lt;/span>
&lt;span class="n">params&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="s1">&amp;#39;apikey&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">your_api_key&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s1">&amp;#39;type&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s1">&amp;#39;web&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s1">&amp;#39;query&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">keyword&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s1">&amp;#39;device&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s1">&amp;#39;desktop&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s1">&amp;#39;google_domain&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s1">&amp;#39;google.com.tw&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s1">&amp;#39;gl&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s1">&amp;#39;tw&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s1">&amp;#39;hl&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s1">&amp;#39;zh-tw&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s1">&amp;#39;output&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s1">&amp;#39;json&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s1">&amp;#39;num&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">100&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="n">r&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">requests&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">api_url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">params&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">params&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">timeout&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">35&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="n">r&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">status_code&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="n">requests&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">codes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ok&lt;/span>&lt;span class="p">:&lt;/span>
&lt;span class="k">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">r&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">json&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>回應資料如此：&lt;a href="https://blog.jiatool.com/code/avesapi_data.json" target="_blank" rel="noopener">
avesapi_data.json
&lt;/a>&lt;/p>
&lt;p>除了一般的搜尋結果與排名，如果搜尋結果內還有&amp;quot;圖片&amp;quot;、&amp;quot;影片&amp;quot;、&amp;quot;地圖&amp;quot;、&amp;quot;相關搜尋關鍵字&amp;quot;，它一樣也可以取得。&lt;/p>
&lt;p>&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/aves_api/photo.jpg" alt="圖片 範例" data-caption="圖片 範例" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='500px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:500px;height:;"/>
&lt;figcaption style="text-align: center;">
圖片 範例
&lt;/figcaption>
&lt;/figure>&lt;br />
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/aves_api/video.jpg" alt="影片 範例" data-caption="影片 範例" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='500px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:500px;height:;"/>
&lt;figcaption style="text-align: center;">
影片 範例
&lt;/figcaption>
&lt;/figure>&lt;br />
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/aves_api/map.jpg" alt="地圖 範例" data-caption="地圖 範例" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='500px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:500px;height:;"/>
&lt;figcaption style="text-align: center;">
地圖 範例
&lt;/figcaption>
&lt;/figure>&lt;br />
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/aves_api/related_searches.jpg" alt="相關搜尋關鍵字 範例" data-caption="相關搜尋關鍵字 範例" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='400px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:400px;height:;"/>
&lt;figcaption style="text-align: center;">
相關搜尋關鍵字 範例
&lt;/figcaption>
&lt;/figure>&lt;/p>
&lt;h2 id="網站後台">網站後台&lt;/h2>
&lt;p>當你註冊後登入，即可進入&lt;a href="https://app.avesapi.com/" target="_blank" rel="noopener">
後台頁面
&lt;/a>去查看更多相關資訊。&lt;/p>
&lt;p>其中有幾個重要的頁面：&lt;/p>
&lt;h3 id="儀錶板dashboard">儀錶板(Dashboard)&lt;/h3>
&lt;p>像是你還剩多少額度、最近請求的紀錄、每天使用量等等，以圖表來呈現。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/aves_api/dashboard.jpg" alt="Dashboard 頁面" data-caption="Dashboard 頁面" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='800px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:800px;height:;"/>
&lt;figcaption style="text-align: center;">
Dashboard 頁面
&lt;/figcaption>
&lt;/figure>
&lt;h3 id="api-playground">API Playground&lt;/h3>
&lt;p>API Playground 以圖形化的操作頁面，讓你可以實際選擇參數、送出請求，並查看回傳的資料。如果你發現使用自己寫的程式，都沒有成功的話，也可以在此頁面測試，藉此排除是否是 API 的問題。&lt;br />
但要注意，這邊請求會帶上自己的 API Key，因此每一次請求也是會扣積分的哦！&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/aves_api/api_playground.jpg" alt="API Playground 頁面" data-caption="API Playground 頁面" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='800px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:800px;height:;"/>
&lt;figcaption style="text-align: center;">
API Playground 頁面
&lt;/figcaption>
&lt;/figure>
&lt;br/>
&lt;p>剩下的是一些購買頁面、購買紀錄、個人資料等等，這邊我就不展示了。&lt;/p>
&lt;h2 id="注意事項">注意事項&lt;/h2>
&lt;ol>
&lt;li>有時因他們伺服器問題，會發生請求回應失敗，無法取得資料的情況，這時再重新送出請求一次即可，這個不會扣除積分。&lt;/li>
&lt;li>使用發現有時請求會發生 timeout 逾時，沒有成功取得資料，但卻還是被扣了一個積分額度。&lt;br />
與客服聯繫後得知，此 API 請求最大 timeout 為 30 秒，也就是說如果我requests.get(url, timeout=20) 設定 20 秒，但伺服器在 25 秒時才回覆資料，那我程式這邊雖然是 timeout，但他們是有送出資料(一樣算成功請求，會被扣積分)。所以建議 requests 中 timeout 要設定大於 30 秒。&lt;/li>
&lt;li>API 文檔有列出一些&lt;a href="https://docs.avesapi.com/#prohibited-keywords" target="_blank" rel="noopener">
禁止的關鍵字
&lt;/a>，例如：&lt;code>site:&lt;/code>、&lt;code>domain:&lt;/code>、&lt;code>title:&lt;/code>、&lt;code>before:&lt;/code>&amp;hellip;&amp;hellip;，可能因為這些進階查詢讓他們系統比較容易被檔吧(?)，如果使用這類關鍵字查詢的話，每個成功請求會被扣 25 個積分。&lt;/li>
&lt;/ol>
&lt;h2 id="結語">結語&lt;/h2>
&lt;p>因為 Google 搜尋結果相關的 API 我也只使用過這一款，所以沒辦法跟你說是不是這個就是最好，但其實這類的服務大同小異，需要的話可以自行比較看看，官網文檔是否完整詳細、客服回應速度如何、是否提供台灣中文搜尋參數、計費方式、網路上別人的評價推薦。&lt;br />
最好也註冊一個帳號，用免費的額度嘗試，是否有達到自己的需求。&lt;/p>
&lt;p>* 要注意某些網站可能沒有提供免費額度&lt;/p>
&lt;br/>
&lt;br/>
&lt;br/>
&lt;hr />
&lt;p>參考：&lt;br />
&lt;a href="https://developers.google.com/custom-search" target="_blank" rel="noopener">
Google Custom Search JSON API 官網
&lt;/a>&lt;br />
&lt;a href="https://avesapi.com" target="_blank" rel="noopener">
Aves API 官網
&lt;/a>&lt;/p>
&lt;br/>
&lt;blockquote>
&lt;p>又一天過去了。今天覺得如何呢？&lt;br />
夢想是不是又更遠了？&lt;/p>
&lt;p align="right">—— &lt;a href="https://www.facebook.com/NeEnergy">每天來點負能量&lt;/a>&lt;/p>
&lt;/blockquote></content:encoded><dc:creator>Jia</dc:creator><media:content url="https://blog.jiatool.comimages/cover/aves_api.jpg" medium="image"><media:title type="html">featured image</media:title></media:content><media:content url="https://blog.jiatool.comimages/posts/aves_api_meta.jpg" medium="image"><media:title type="html">meta image</media:title></media:content><category>Google搜尋</category><category>SEO</category><category>API</category><category>Python</category><category>分享</category></item><item><title>[Python爬蟲實例] YouTube-使用 YouTube Data API</title><link>https://blog.jiatool.com/posts/youtube_spider_api/</link><pubDate>Sun, 01 Nov 2020 20:40:00 +0800</pubDate><author>jia@jiatool.com (Jia)</author><atom:modified>Thu, 03 Nov 2022 21:45:00 +0800</atom:modified><guid>https://blog.jiatool.com/posts/youtube_spider_api/</guid><description>前言 來到Python網路爬蟲實例系列 第四篇，本篇使用 Python 透過官方 YouTube Data API v3 來爬取 YouTube ，包含頻道資訊、影片清單、影片資訊等等資料。 從在 Google 雲端平台(G</description><content:encoded>&lt;h2 id="前言">前言&lt;/h2>
&lt;p>來到&lt;a href="https://blog.jiatool.com/series/Python%e7%b6%b2%e8%b7%af%e7%88%ac%e8%9f%b2%e5%af%a6%e4%be%8b/" target="_blank" rel="noopener">
Python網路爬蟲實例系列
&lt;/a>第四篇，本篇使用 Python 透過官方 YouTube Data API v3 來爬取 &lt;a href="https://www.youtube.com" target="_blank" rel="noopener">
YouTube
&lt;/a>，包含頻道資訊、影片清單、影片資訊等等資料。&lt;/p>
&lt;p>從在 Google 雲端平台(Google Cloud Platform)創建一個專案開始，包括獲取 API 授權憑證、取得 API key，到撰寫抓取程式皆會一步步講解如何操作。最後同樣會附上完整程式碼供參考。&lt;/p>
&lt;p>而如果你是想下載 YouTube 影片的話，本篇內容並不會提及，這部分可參考其他套件，例如 &lt;a href="https://github.com/nficano/pytube" target="_blank" rel="noopener">
pytube
&lt;/a>。&lt;br />
之前還有另外一個套件 Youtube-dl，但在寫這篇文章時發現美國唱片業協會（RIAA）點名 Youtube-dl 程式碼違反著作權法，並要求GitHub將它們移除(&lt;a href="https://www.ithome.com.tw/news/140720" target="_blank" rel="noopener">
來源
&lt;/a>)。&lt;/p>
&lt;p>備註：此文僅教育學習，切勿用作商業用途，個人實作皆屬個人行為，本作者不負任何法律責任&lt;/p>
&lt;h2 id="套件">套件&lt;/h2>
&lt;p>本次主要使用到的套件：&lt;/p>
&lt;ul>
&lt;li>Requests [&lt;a href="https://requests.readthedocs.io/en/master/" target="_blank" rel="noopener">
Doc
&lt;/a>] [&lt;a href="https://github.com/psf/requests" target="_blank" rel="noopener">
GitHub
&lt;/a>]&lt;/li>
&lt;/ul>
&lt;br/>
&lt;!--adsense-->
&lt;h2 id="流程">流程&lt;/h2>
&lt;p>要使用官方 YouTube Data API 抓取資料前，要先取得 API Key。&lt;/p>
&lt;p>先到 Google 雲端平台(Google Cloud Platform)創建一個專案，並將 YouTube Data API 加入置專案內，獲取 API 授權憑證、取得 API key。&lt;/p>
&lt;p>爬取資料這邊以 &lt;a href="https://www.youtube.com/c/%E5%85%AD%E6%8C%87%E6%B7%B5Huber" target="_blank" rel="noopener">
&lt;strong>六指淵 Huber&lt;/strong>
&lt;/a> 頻道當範例，到上傳的影片列表抓取最新的前五則影片，再到影片內取得影片相關資訊及留言。&lt;br />
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/youtube_spider_api/huber.jpg" alt="六指淵 Huber 頻道" data-caption="六指淵 Huber 頻道" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='800px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:800px;height:;"/>
&lt;figcaption style="text-align: center;">
六指淵 Huber 頻道
&lt;/figcaption>
&lt;/figure>&lt;/p>
&lt;h2 id="-取得-youtube-data-api-key">🔑 取得 YouTube Data API Key&lt;/h2>
&lt;p>先到&lt;a href="https://console.developers.google.com/" target="_blank" rel="noopener">
Google 雲端平台(Google Cloud Platform)
&lt;/a>創建一個專案，如果是第一次進來會跳出服務條款，點選同意後繼續。&lt;br />
點擊右上角顯示&amp;quot;建立專案&amp;quot;，或到上方的&amp;quot;選取專案&amp;quot;處建立。&lt;br />
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/youtube_spider_api/google_cloud_platform.jpg" alt="服務條款" data-caption="服務條款" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='800px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:800px;height:;"/>
&lt;figcaption style="text-align: center;">
服務條款
&lt;/figcaption>
&lt;/figure>&lt;/p>
&lt;p>右上角&amp;quot;新增專案&amp;quot;。&lt;br />
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/youtube_spider_api/create_project_1.jpg" alt="創建專案" data-caption="創建專案" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='800px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:800px;height:;"/>
&lt;figcaption style="text-align: center;">
創建專案
&lt;/figcaption>
&lt;/figure>&lt;/p>
&lt;p>輸入&amp;quot;專案名稱&amp;quot;，點選&amp;quot;建立&amp;quot;。&lt;br />
(專案ID也可以修改)&lt;br />
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/youtube_spider_api/create_project_2.png" alt="創建專案" data-caption="創建專案" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='600px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:600px;height:;"/>
&lt;figcaption style="text-align: center;">
創建專案
&lt;/figcaption>
&lt;/figure>&lt;/p>
&lt;p>等它跑完後會進到此頁面，接下來要將我們需要使用的 API 加進來啟用。&lt;br />
點選&amp;quot;啟用 API 和服務&amp;quot;。&lt;br />
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/youtube_spider_api/enable_api_1.jpg" alt="啟用 API" data-caption="啟用 API" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='800px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:800px;height:;"/>
&lt;figcaption style="text-align: center;">
啟用 API
&lt;/figcaption>
&lt;/figure>&lt;/p>
&lt;p>找到 YouTube Data API v3。&lt;br />
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/youtube_spider_api/enable_api_2.png" alt="啟用 API" data-caption="啟用 API" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='800px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:800px;height:;"/>
&lt;figcaption style="text-align: center;">
啟用 API
&lt;/figcaption>
&lt;/figure>&lt;/p>
&lt;p>點擊&amp;quot;啟用&amp;quot;。&lt;br />
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/youtube_spider_api/enable_api_3.png" alt="啟用 API" data-caption="啟用 API" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='700px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:700px;height:;"/>
&lt;figcaption style="text-align: center;">
啟用 API
&lt;/figcaption>
&lt;/figure>&lt;/p>
&lt;p>最後要建立憑證，並取得 API Key。&lt;br />
點擊右方&amp;quot;建立憑證&amp;quot;，或者點擊左方的&amp;quot;憑證&amp;quot;。&lt;br />
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/youtube_spider_api/create_credential_1.png" alt="建立憑證" data-caption="建立憑證" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='800px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:800px;height:;"/>
&lt;figcaption style="text-align: center;">
建立憑證
&lt;/figcaption>
&lt;/figure>&lt;/p>
&lt;p>如果你是點擊左方&amp;quot;憑證&amp;quot;，則依下圖所示建立 API 金鑰 (API Key)。&lt;br />
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/youtube_spider_api/create_credential_2.jpg" alt="建立憑證" data-caption="建立憑證" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='800px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:800px;height:;"/>
&lt;figcaption style="text-align: center;">
建立憑證
&lt;/figcaption>
&lt;/figure>&lt;/p>
&lt;p>如果你是點擊右方&amp;quot;建立憑證&amp;quot;，則依下圖選擇，並點擊&amp;quot;我需要那些憑證&amp;quot;。&lt;br />
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/youtube_spider_api/create_credential_3.png" alt="建立憑證" data-caption="建立憑證" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='760px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:760px;height:;"/>
&lt;figcaption style="text-align: center;">
建立憑證
&lt;/figcaption>
&lt;/figure>&lt;/p>
&lt;p>它會跳到此頁面，將 API 金鑰複製下來，待之後程式使用。&lt;br />
(也可以&amp;quot;為金鑰新增限制&amp;quot;)&lt;br />
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/youtube_spider_api/create_credential_4.png" alt="建立憑證" data-caption="建立憑證" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='740px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:740px;height:;"/>
&lt;figcaption style="text-align: center;">
建立憑證
&lt;/figcaption>
&lt;/figure>&lt;/p>
&lt;p>到這邊就完成 YouTube Data API Key 的取得，看似步驟很多，但實際操作過一遍就會了解了。&lt;/p>
&lt;p>* &lt;a href="https://developers.google.com/youtube/v3/getting-started#before-you-start" target="_blank" rel="noopener">
官方說明文檔
&lt;/a>&lt;/p>
&lt;h2 id="-爬蟲程式">📘 爬蟲程式&lt;/h2>
&lt;p>本次是直接使用 API，因此在送出請求時就不需要加入 header 中 User-Agent 欄位了。&lt;/p>
&lt;p>先在上方定義剛剛取得的 API Key，待之後發出請求時帶入。&lt;br />
(&lt;code>YOUR_YOUTUBE_API_KEY&lt;/code> 換成剛剛在上一步取得的 API Key)&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-Python" data-lang="Python">&lt;span class="n">YOUTUBE_API_KEY&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;YOUR_YOUTUBE_API_KEY&amp;#34;&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="api-說明">API 說明&lt;/h3>
&lt;p>在&lt;a href="https://developers.google.com/youtube/v3/docs" target="_blank" rel="noopener">
官方 API 文件
&lt;/a>內，可以查詢各種資源及各種請求方法。&lt;/p>
&lt;p>在本次程式中會用到幾種資源 (這邊都只需使用 list 方法)：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://developers.google.com/youtube/v3/docs/channels/list" target="_blank" rel="noopener">
channels
&lt;/a>：取得頻道&amp;quot;上傳影片清單&amp;quot;的 ID&lt;/li>
&lt;li>&lt;a href="https://developers.google.com/youtube/v3/docs/playlistItems/list" target="_blank" rel="noopener">
playlistItems
&lt;/a>：取得清單中的影片列表&lt;/li>
&lt;li>&lt;a href="https://developers.google.com/youtube/v3/docs/videos/list" target="_blank" rel="noopener">
videos
&lt;/a>：取得影片資訊&lt;/li>
&lt;li>&lt;a href="https://developers.google.com/youtube/v3/docs/commentThreads/list" target="_blank" rel="noopener">
commentThreads
&lt;/a>：取得影片底下留言&lt;/li>
&lt;/ul>
&lt;p>本教學都只使用到 list 方法(GET)，因此網址串好後可以直接貼到瀏覽器上測試，觀看它回傳的資料。&lt;/p>
&lt;br>
&lt;p>因為每一次送出請求皆需要帶上 API Key，而且回傳資料要轉換JSON，所以可以另外寫一個用於組合網址、請求的函式，也可在裡面做當請求失敗的處理。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;span class="lnt">9
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-Python" data-lang="Python">&lt;span class="k">def&lt;/span> &lt;span class="nf">get_html_to_json&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">path&lt;/span>&lt;span class="p">):&lt;/span>
&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;組合 URL 後 GET 網頁並轉換成 JSON&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;span class="n">api_url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="s2">&amp;#34;{self.base_url}{path}&amp;amp;key={self.api_key}&amp;#34;&lt;/span>
&lt;span class="n">r&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">requests&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">api_url&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="n">r&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">status_code&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="n">requests&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">codes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ok&lt;/span>&lt;span class="p">:&lt;/span>
&lt;span class="n">data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">r&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">json&lt;/span>&lt;span class="p">()&lt;/span>
&lt;span class="k">else&lt;/span>&lt;span class="p">:&lt;/span>
&lt;span class="n">data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">None&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="n">data&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>* 為了較清楚的了解流程，以下範例程式內沒有加入太多的例外處理。&lt;/p>
&lt;h3 id="取得頻道上傳影片清單的-id">取得頻道&amp;quot;上傳影片&amp;quot;清單的 ID&lt;/h3>
&lt;p>想要抓取頻道內上傳的影片前，需要先抓到&amp;quot;上傳影片&amp;quot;清單的 ID，再用此 ID 去取的影片清單。&lt;/p>
&lt;p>使用&lt;a href="https://developers.google.com/youtube/v3/docs/channels/list" target="_blank" rel="noopener">
channels
&lt;/a>路徑來取得，需帶上&amp;quot;id&amp;quot;、&amp;quot;key&amp;quot;、&amp;quot;part&amp;quot;等查詢參數。&lt;br />
&lt;code>id&lt;/code> 代表頻道 ID；&lt;code>key&lt;/code> 代表我們的 API Key；&lt;code>part&lt;/code> 代表想取得的資源屬性。&lt;/p>
&lt;p>頻道ID的取得方式可以先到頻道首頁，觀察網址後方帶的數值，例如：&lt;br />
&lt;code>https://www.youtube.com/channel/UC7ia-A8gma8qcdC6GDcjwsQ&lt;/code>&lt;br />
&amp;quot;channel/&amp;quot;後方的 &lt;code>UC7ia-A8gma8qcdC6GDcjwsQ&lt;/code> 就代表此頻道的 ID。&lt;/p>
&lt;p>不過有時會發現網址是呈現以下形式：&lt;br />
&lt;code>https://www.youtube.com/c/六指淵Huber&lt;/code>&lt;br />
別擔心，此時隨便點擊頻道底下的其中一支影片，在點中間的頻道名稱進入後，一樣即可看到網址後方的數值。&lt;br />
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/youtube_spider_api/channel_id.jpg" alt="如何取得頻道 ID" data-caption="如何取得頻道 ID" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='800px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:800px;height:;"/>
&lt;figcaption style="text-align: center;">
如何取得頻道 ID
&lt;/figcaption>
&lt;/figure>&lt;/p>
&lt;p>至於&amp;quot;part&amp;quot;需帶什麼數值可以參考&lt;a href="https://developers.google.com/youtube/v3/docs/channels/list#parameters" target="_blank" rel="noopener">
官方 API 文檔
&lt;/a>說明，也可以一個一個嘗試看看會回傳什麼資料。&lt;br />
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/youtube_spider_api/parameters.jpg" alt="part 查詢參數" data-caption="part 查詢參數" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='600px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:600px;height:;"/>
&lt;figcaption style="text-align: center;">
part 查詢參數
&lt;/figcaption>
&lt;/figure>&lt;/p>
&lt;p>因為請求方式是使用 &lt;code>GET&lt;/code>，因此可以直接貼到瀏覽器上測試。&lt;br />
&lt;code>https://www.googleapis.com/youtube/v3/channels?part=contentDetails&amp;amp;id=UC7ia-A8gma8qcdC6GDcjwsQ&amp;amp;key={YOUR_API_KEY}&lt;/code>&lt;br />
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/youtube_spider_api/channels.png" alt="取得上傳影片清單的 ID" data-caption="取得上傳影片清單的 ID" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='400px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:400px;height:;"/>
&lt;figcaption style="text-align: center;">
取得上傳影片清單的 ID
&lt;/figcaption>
&lt;/figure>&lt;br />
==&amp;gt; &lt;a href="https://blog.jiatool.com/code/youtube_api/channels.json" target="_blank" rel="noopener">
回應資料範例
&lt;/a>&lt;/p>
&lt;p>其中 &lt;code>uploads&lt;/code> 後面那串就是此頻道&amp;quot;上傳影片&amp;quot;清單的 ID，這邊是 &lt;code>UU7ia-A8gma8qcdC6GDcjwsQ&lt;/code>。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;span class="lnt">9
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-Python" data-lang="Python">&lt;span class="k">def&lt;/span> &lt;span class="nf">get_channel_uploads_id&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">channel_id&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">part&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;contentDetails&amp;#39;&lt;/span>&lt;span class="p">):&lt;/span>
&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;取得頻道上傳影片清單的ID&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;span class="n">path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="s1">&amp;#39;channels?part={part}&amp;amp;id={channel_id}&amp;#39;&lt;/span>
&lt;span class="n">data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get_html_to_json&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;span class="n">uploads_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">data&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;items&amp;#39;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="s1">&amp;#39;contentDetails&amp;#39;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="s1">&amp;#39;relatedPlaylists&amp;#39;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="s1">&amp;#39;uploads&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;span class="k">except&lt;/span> &lt;span class="ne">KeyError&lt;/span>&lt;span class="p">:&lt;/span>
&lt;span class="n">uploads_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">None&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="n">uploads_id&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="取得清單中的影片列表">取得清單中的影片列表&lt;/h3>
&lt;p>好的，接下來取得&amp;quot;上傳影片&amp;quot;清單的影片吧~&lt;/p>
&lt;p>使用&lt;a href="https://developers.google.com/youtube/v3/docs/playlistItems/list" target="_blank" rel="noopener">
playlistItems
&lt;/a>路徑來取得，需帶上&amp;quot;playlistId&amp;quot;、&amp;quot;key&amp;quot;、&amp;quot;part&amp;quot;等查詢參數。&lt;br />
&lt;code>playlistId&lt;/code> 代表播放列表 ID；&lt;code>key&lt;/code> 代表我們的 API Key；&lt;code>part&lt;/code> 代表想取得的資源屬性。&lt;/p>
&lt;p>這邊代的參數大部分與上方雷同，可以自行參考官方 API 文檔說明，就不多說介紹了。&lt;br />
我們要抓此頻道上傳影片，所以 &lt;code>playlistId&lt;/code> 帶入上一步抓到的&amp;quot;上傳影片&amp;quot;清單的 ID。&lt;/p>
&lt;p>&lt;code>https://www.googleapis.com/youtube/v3/playlistItems?part=contentDetails&amp;amp;playlistId=UU7ia-A8gma8qcdC6GDcjwsQ&amp;amp;key={YOUR_API_KEY}&lt;/code>&lt;br />
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/youtube_spider_api/playlistItems.png" alt="取得影片ID" data-caption="取得影片ID" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='400px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:400px;height:;"/>
&lt;figcaption style="text-align: center;">
取得影片ID
&lt;/figcaption>
&lt;/figure>&lt;br />
==&amp;gt; &lt;a href="https://blog.jiatool.com/code/youtube_api/playlistItems.json" target="_blank" rel="noopener">
回應資料範例
&lt;/a>&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-Python" data-lang="Python">&lt;span class="k">def&lt;/span> &lt;span class="nf">get_playlist&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">playlist_id&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">part&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;contentDetails&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">max_results&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">10&lt;/span>&lt;span class="p">):&lt;/span>
&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;取得影片清單ID中的影片&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;span class="n">path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="s1">&amp;#39;playlistItems?part={part}&amp;amp;playlistId={playlist_id}&amp;amp;maxResults={max_results}&amp;#39;&lt;/span>
&lt;span class="n">data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get_html_to_json&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">data&lt;/span>&lt;span class="p">:&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;span class="n">video_ids&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;span class="k">for&lt;/span> &lt;span class="n">data_item&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">data&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;items&amp;#39;&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;span class="n">video_ids&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data_item&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;contentDetails&amp;#39;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="s1">&amp;#39;videoId&amp;#39;&lt;/span>&lt;span class="p">])&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="n">video_ids&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="取得影片資訊">取得影片資訊&lt;/h3>
&lt;p>針對每一支影片，來取得其影片相關資訊吧~&lt;/p>
&lt;p>使用&lt;a href="https://developers.google.com/youtube/v3/docs/videos/list" target="_blank" rel="noopener">
videos
&lt;/a>路徑來取得，需帶上&amp;quot;id&amp;quot;、&amp;quot;key&amp;quot;、&amp;quot;part&amp;quot;等查詢參數。&lt;br />
&lt;code>id&lt;/code> 代表影片 ID；&lt;code>key&lt;/code> 代表我們的 API Key；&lt;code>part&lt;/code> 代表想取得的資源屬性。&lt;/p>
&lt;p>&lt;code>https://www.googleapis.com/youtube/v3/videos?part=snippet,statistics&amp;amp;id=lM7ltxkXE40&amp;amp;key={YOUR_API_KEY}&lt;/code>&lt;br />
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/youtube_spider_api/videos.png" alt="取得影片資訊" data-caption="取得影片資訊" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='500px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:500px;height:;"/>
&lt;figcaption style="text-align: center;">
取得影片資訊
&lt;/figcaption>
&lt;/figure>&lt;br />
==&amp;gt; &lt;a href="https://blog.jiatool.com/code/youtube_api/videos.json" target="_blank" rel="noopener">
回應資料範例
&lt;/a>&lt;/p>
&lt;p>回傳資料有影片標題、頻道名稱、發布時間、縮圖網址、觀看數、留言數&amp;hellip;&amp;hellip;等等許多相關資訊。&lt;/p>
&lt;p>* 謝謝網友提醒，&lt;code>dislikeCount&lt;/code> 資訊現在無法取得了，這應該也是因應 YouTube 現在不能看到 Dislike 的數量了。&lt;/p>
&lt;blockquote>
&lt;p>Note: The statistics.dislikeCount property was made private as of December 13, 2021. This means that the property is included in an API response only if the API request was authenticated by the video owner. See the revision history for more information.&lt;/p>
&lt;/blockquote>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-Python" data-lang="Python">&lt;span class="k">def&lt;/span> &lt;span class="nf">get_video&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">video_id&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">part&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;snippet,statistics&amp;#39;&lt;/span>&lt;span class="p">):&lt;/span>
&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;取得影片資訊&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;span class="n">path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="s1">&amp;#39;videos?part={part}&amp;amp;id={video_id}&amp;#39;&lt;/span>
&lt;span class="n">data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get_html_to_json&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">data&lt;/span>&lt;span class="p">:&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;span class="c1"># 以下整理並提取需要的資料&lt;/span>
&lt;span class="n">data_item&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">data&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;items&amp;#39;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">]&lt;/span>
&lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;span class="n">time_&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">datetime&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">strptime&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data_item&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;snippet&amp;#39;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="s1">&amp;#39;publishedAt&amp;#39;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="s1">&amp;#39;%Y-%m-&lt;/span>&lt;span class="si">%d&lt;/span>&lt;span class="s1">T%H:%M:%SZ&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="k">except&lt;/span> &lt;span class="ne">ValueError&lt;/span>&lt;span class="p">:&lt;/span>
&lt;span class="c1"># 日期格式錯誤&lt;/span>
&lt;span class="n">time_&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">None&lt;/span>
&lt;span class="n">url_&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="s2">&amp;#34;https://www.youtube.com/watch?v={data_item[&amp;#39;id&amp;#39;]}&amp;#34;&lt;/span>
&lt;span class="n">info&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="s1">&amp;#39;id&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">data_item&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;id&amp;#39;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;span class="s1">&amp;#39;channelTitle&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">data_item&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;snippet&amp;#39;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="s1">&amp;#39;channelTitle&amp;#39;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;span class="s1">&amp;#39;publishedAt&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">time_&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s1">&amp;#39;video_url&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">url_&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s1">&amp;#39;title&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">data_item&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;snippet&amp;#39;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="s1">&amp;#39;title&amp;#39;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;span class="s1">&amp;#39;description&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">data_item&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;snippet&amp;#39;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="s1">&amp;#39;description&amp;#39;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;span class="s1">&amp;#39;likeCount&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">data_item&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;statistics&amp;#39;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="s1">&amp;#39;likeCount&amp;#39;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;span class="c1"># &amp;#39;dislikeCount&amp;#39;: data_item[&amp;#39;statistics&amp;#39;][&amp;#39;dislikeCount&amp;#39;],&lt;/span>
&lt;span class="s1">&amp;#39;commentCount&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">data_item&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;statistics&amp;#39;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="s1">&amp;#39;commentCount&amp;#39;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;span class="s1">&amp;#39;viewCount&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">data_item&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;statistics&amp;#39;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="s1">&amp;#39;viewCount&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="n">info&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="取得留言資訊">取得留言資訊&lt;/h3>
&lt;p>那該如何取得影片底下的留言呢？&lt;/p>
&lt;p>使用&lt;a href="https://developers.google.com/youtube/v3/docs/commentThreads/list" target="_blank" rel="noopener">
commentThreads
&lt;/a>路徑來取得，需帶上&amp;quot;videoId&amp;quot;、&amp;quot;key&amp;quot;、&amp;quot;part&amp;quot;等查詢參數。&lt;br />
&lt;code>videoId&lt;/code> 代表播放列表 ID；&lt;code>key&lt;/code> 代表我們的 API Key；&lt;code>part&lt;/code> 代表想取得的資源屬性。&lt;/p>
&lt;p>&lt;code>https://www.googleapis.com/youtube/v3/commentThreads?part=snippet&amp;amp;videoId=lM7ltxkXE40&amp;amp;key={YOUR_API_KEY}&lt;/code>&lt;br />
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/youtube_spider_api/commentThreads.png" alt="取得留言資訊" data-caption="取得留言資訊" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='500px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:500px;height:;"/>
&lt;figcaption style="text-align: center;">
取得留言資訊
&lt;/figcaption>
&lt;/figure>&lt;br />
==&amp;gt; &lt;a href="https://blog.jiatool.com/code/youtube_api/commentThreads.json" target="_blank" rel="noopener">
回應資料範例
&lt;/a>&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-Python" data-lang="Python">&lt;span class="k">def&lt;/span> &lt;span class="nf">get_comments&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">video_id&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">page_token&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">part&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;snippet&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">max_results&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">100&lt;/span>&lt;span class="p">):&lt;/span>
&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;取得影片留言&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;span class="n">path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="s1">&amp;#39;commentThreads?part={part}&amp;amp;videoId={video_id}&amp;amp;maxResults={max_results}&amp;amp;pageToken={page_token}&amp;#39;&lt;/span>
&lt;span class="n">data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get_html_to_json&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">data&lt;/span>&lt;span class="p">:&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="p">[],&lt;/span> &lt;span class="s1">&amp;#39;&amp;#39;&lt;/span>
&lt;span class="c1"># 下一頁的數值&lt;/span>
&lt;span class="n">next_page_token&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">data&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;nextPageToken&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="c1"># 以下整理並提取需要的資料&lt;/span>
&lt;span class="n">comments&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;span class="k">for&lt;/span> &lt;span class="n">data_item&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">data&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;items&amp;#39;&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;span class="n">data_item&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">data_item&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;snippet&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;span class="n">top_comment&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">data_item&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;topLevelComment&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;span class="n">time_&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">datetime&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">strptime&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">top_comment&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;snippet&amp;#39;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="s1">&amp;#39;publishedAt&amp;#39;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="s1">&amp;#39;%Y-%m-&lt;/span>&lt;span class="si">%d&lt;/span>&lt;span class="s1">T%H:%M:%SZ&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="k">except&lt;/span> &lt;span class="ne">ValueError&lt;/span>&lt;span class="p">:&lt;/span>
&lt;span class="c1"># 日期格式錯誤&lt;/span>
&lt;span class="n">time_&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">None&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="s1">&amp;#39;authorChannelId&amp;#39;&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">top_comment&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;snippet&amp;#39;&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;span class="n">ru_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">top_comment&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;snippet&amp;#39;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="s1">&amp;#39;authorChannelId&amp;#39;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="s1">&amp;#39;value&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;span class="k">else&lt;/span>&lt;span class="p">:&lt;/span>
&lt;span class="n">ru_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;&amp;#39;&lt;/span>
&lt;span class="n">ru_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">top_comment&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;snippet&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;authorDisplayName&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">ru_name&lt;/span>&lt;span class="p">:&lt;/span>
&lt;span class="n">ru_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;&amp;#39;&lt;/span>
&lt;span class="n">comments&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">({&lt;/span>
&lt;span class="s1">&amp;#39;reply_id&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">top_comment&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;id&amp;#39;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;span class="s1">&amp;#39;ru_id&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">ru_id&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s1">&amp;#39;ru_name&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">ru_name&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s1">&amp;#39;reply_time&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">time_&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s1">&amp;#39;reply_content&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">top_comment&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;snippet&amp;#39;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="s1">&amp;#39;textOriginal&amp;#39;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;span class="s1">&amp;#39;rm_positive&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">top_comment&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;snippet&amp;#39;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="s1">&amp;#39;likeCount&amp;#39;&lt;/span>&lt;span class="p">]),&lt;/span>
&lt;span class="s1">&amp;#39;rn_comment&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data_item&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;totalReplyCount&amp;#39;&lt;/span>&lt;span class="p">])&lt;/span>
&lt;span class="p">})&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="n">comments&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">next_page_token&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="其他查詢參數">其他查詢參數&lt;/h3>
&lt;p>還有其他常用的查詢參數：&lt;/p>
&lt;p>像是 &lt;code>maxResults&lt;/code> 可以限制回傳筆數，搭配 commentThreads 帶入 &lt;code>maxResults=10&lt;/code> 代表最多回傳 10 筆留言。&lt;/p>
&lt;p>又例如 &lt;code>pageToken&lt;/code> 是用來換頁的，什麼意思呢？&lt;br />
像是 API 有限制留言最多一次只能抓 100 筆留言，就算你帶入 &lt;code>maxResults=200&lt;/code> 也只能抓到 100 筆。&lt;br />
但在回傳的資料中可以發現一個 &lt;code>nextPageToken&lt;/code> 欄位，代表你再用一樣的網址請求一次，帶上 &lt;code>pageToken=QURTSl9pMmhMYktBc25xOHZtZTg3enJBTFFXNTFPbldJY05zTFFTTjdMM25VYThmdTBwWS1CNGFPZm1kQUU0WkJac0dWZkw3cWI2V1owOA==&lt;/code> 就能取得下一頁的資料(例如：101~200 筆留言)。&lt;br />
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/youtube_spider_api/nextPageToken.png" alt="nextPageToken 欄位" data-caption="nextPageToken 欄位" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='800px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:800px;height:;"/>
&lt;figcaption style="text-align: center;">
nextPageToken 欄位
&lt;/figcaption>
&lt;/figure>&lt;br />
在像是有多個留言(commentThreads)或多支影片(playlistItems)時需要用到。&lt;br />
以上一段取得留言為例，會像是如下：&lt;br />
&lt;code>https://www.googleapis.com/youtube/v3/commentThreads?part=snippet&amp;amp;videoId=lM7ltxkXE40&amp;amp;pageToken=QURTSl9pMmhMYktBc25xOHZtZTg3enJBTFFXNTFPbldJY05zTFFTTjdMM25VYThmdTBwWS1CNGFPZm1kQUU0WkJac0dWZkw3cWI2V1owOA==&amp;amp;key={YOUR_API_KEY}&lt;/code>&lt;/p>
&lt;br/>
&lt;!--adsense-->
&lt;h2 id="完整程式碼">完整程式碼&lt;/h2>
&lt;p>附上完整程式碼：&lt;a href="https://blog.jiatool.com/code/youtube_spider_api.py" target="_blank" rel="noopener">
youtube_spider_api.py
&lt;/a>&lt;br />
(對超連結右鍵 &amp;gt; 另存連結為)&lt;/p>
&lt;h2 id="注意事項">注意事項&lt;/h2>
&lt;p>要注意的是這個 API 有額度的限制，為每天 10,000 個單位(&lt;a href="https://developers.google.com/youtube/v3/getting-started#quota" target="_blank" rel="noopener">
官方說明
&lt;/a>)，如果想將影片的留言都抓下來，可能就會耗費較多額度。&lt;br />
像上方使用的查詢都是消耗 1 單位。&lt;/p>
&lt;p>在&lt;a href="https://console.cloud.google.com/apis/api/youtube.googleapis.com/quotas" target="_blank" rel="noopener">
配額頁面
&lt;/a>可以查看此專案目前耗費的單位。&lt;br />
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/youtube_spider_api/quotas.png" alt="配額頁面" data-caption="配額頁面" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='800px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:800px;height:;"/>
&lt;figcaption style="text-align: center;">
配額頁面
&lt;/figcaption>
&lt;/figure>&lt;/p>
&lt;h2 id="延伸練習">延伸練習&lt;/h2>
&lt;ol>
&lt;li>
&lt;p>在上方教學中，影片清單只有取得前 5 則影片，那如果我想取得前 100 則影片呢？是不是會遇到什麼問題？需不需要加上什麼參數？&lt;br />
* 提示：參考教學中爬取留言的方法，使用 &lt;code>pageToken&lt;/code> 參數。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>上方教學只說抓取留言，那我如何想取得留言的回覆呢？是要帶入什麼參數嗎？或者是使用不同的方法？&lt;br />
* 提示：&lt;code>part&lt;/code> 中的 &lt;a href="https://developers.google.com/youtube/v3/docs/commentThreads/list#parameters" target="_blank" rel="noopener">
replies
&lt;/a>，或&lt;a href="https://developers.google.com/youtube/v3/docs/comments/list" target="_blank" rel="noopener">
Comments
&lt;/a>資源。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h2 id="結語">結語&lt;/h2>
&lt;p>我會陸續寫一些網站的&lt;a href="https://blog.jiatool.com/series/Python%e7%b6%b2%e8%b7%af%e7%88%ac%e8%9f%b2%e5%af%a6%e4%be%8b/" target="_blank" rel="noopener">
Python網路爬蟲實例
&lt;/a>，如果你正好是剛開始想學爬蟲的新手、想知道某個網站如何爬取資料，或者遇到其他問題，歡迎過來參考和在底下留言~👇&lt;/p>
&lt;br/>
&lt;br/>
&lt;hr />
&lt;p>參考：&lt;br />
&lt;a href="https://developers.google.com/youtube/v3" target="_blank" rel="noopener">
官方 YouTube Data API 範例文檔
&lt;/a>&lt;/p>
&lt;br/>
&lt;blockquote>
&lt;p>每一個成功者都有一個開始。&lt;br />
勇於開始，才能找到成功的路。&lt;/p>
&lt;/blockquote></content:encoded><dc:creator>Jia</dc:creator><media:content url="https://blog.jiatool.comimages/cover/youtube_spider_api.jpg" medium="image"><media:title type="html">featured image</media:title></media:content><category>Python</category><category>YouTube</category><category>API</category><category>網路爬蟲</category><category>Python網路爬蟲實例</category></item><item><title>爬蟲 Dcard API 2.0 版本？！</title><link>https://blog.jiatool.com/posts/dcard_api_v2/</link><pubDate>Sat, 22 Aug 2020 20:30:00 +0800</pubDate><author>jia@jiatool.com (Jia)</author><atom:modified>Thu, 21 Jan 2021 21:24:35 +0800</atom:modified><guid>https://blog.jiatool.com/posts/dcard_api_v2/</guid><description>前言 目前網路上能查詢到 Dcard 爬蟲的文章，幾乎都是使用www.dcard.tw/_api/這個 API 來抓取。 最近透過開發者工具到發現好像還有 2.0 版本的 A</description><content:encoded>&lt;h2 id="前言">前言&lt;/h2>
&lt;p>目前網路上能查詢到 Dcard 爬蟲的文章，幾乎都是使用&lt;code>www.dcard.tw/_api/&lt;/code>這個 API 來抓取。&lt;br />
最近透過開發者工具到發現好像還有 2.0 版本的 API，網路上搜尋不太到什麼資料，不知道是否近期才出來的。&lt;/p>
&lt;br/>
&lt;!--adsense-->
&lt;h2 id="整理">整理&lt;/h2>
&lt;div class="box">API 網址：&lt;strong>&lt;a href="https://www.dcard.tw/service/api/v2">https://www.dcard.tw/service/api/v2&lt;/a>&lt;/strong>&lt;/div>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>說明&lt;/th>
&lt;th>請求方法&lt;/th>
&lt;th>路徑&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>全部文章&lt;/td>
&lt;td>GET&lt;/td>
&lt;td>/posts&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>看板資訊&lt;/td>
&lt;td>GET&lt;/td>
&lt;td>/forums&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>看板內文章列表&lt;/td>
&lt;td>GET&lt;/td>
&lt;td>/forums/{看板名稱}/posts&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>文章內文&lt;/td>
&lt;td>GET&lt;/td>
&lt;td>/posts/{文章ID}&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>文章內引用連結&lt;/td>
&lt;td>GET&lt;/td>
&lt;td>/posts/{文章ID}/links&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>文章內留言&lt;/td>
&lt;td>GET&lt;/td>
&lt;td>/posts/{文章ID}/comments&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>全部皆回傳 JSON 格式資料。&lt;br />
&lt;br/>&lt;br />
* 舊版的只有在 API 網址上不同，其餘的路徑、方法、回傳資料目前觀察起來都一模一樣。&lt;br />
舊版 API 網址：&lt;strong>&lt;a href="https://www.dcard.tw/_api">https://www.dcard.tw/_api&lt;/a>&lt;/strong>&lt;/p>
&lt;h2 id="api-說明">API 說明&lt;/h2>
&lt;h3 id="全部文章">全部文章&lt;/h3>
&lt;div class="box">GET &lt;strong>&lt;a href="https://www.dcard.tw/service/api/v2/posts">https://www.dcard.tw/service/api/v2/posts&lt;/a>&lt;/strong>&lt;/div>
&lt;p>取得不分類全部文章，等同於抓取 &lt;code>https://www.dcard.tw/f&lt;/code> 頁面內文章資訊。&lt;/p>
&lt;p>預設使用 &amp;quot;最新&amp;quot; 作為排序，透過&lt;code>popular&lt;/code>參數可以切換 &amp;quot;最新&amp;quot; 與 &amp;quot;熱門&amp;quot;，如下：&lt;br />
最新文章 -&amp;gt; &lt;a href="https://www.dcard.tw/service/api/v2/posts?popular=false">https://www.dcard.tw/service/api/v2/posts?popular=false&lt;/a>&lt;br />
熱門文章 -&amp;gt; &lt;a href="https://www.dcard.tw/service/api/v2/posts?popular=true">https://www.dcard.tw/service/api/v2/posts?popular=true&lt;/a>&lt;/p>
&lt;p>* 但不知為何，有時候不加上參數，資料出來卻不是最新的，還是建議加上&lt;code>popular=false&lt;/code>參數較準確。&lt;/p>
&lt;p>回傳的文章數量預設是前 30 筆，加上&lt;code>limit&lt;/code>參數來限制文章數量，最多 100 筆，如下：&lt;br />
熱門文章前 100 筆 -&amp;gt; &lt;a href="https://www.dcard.tw/service/api/v2/posts?popular=true&amp;amp;limit=100">https://www.dcard.tw/service/api/v2/posts?popular=true&amp;amp;limit=100&lt;/a>&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/dcard_api_v2/post.png" alt="全部文章回傳資料" data-caption="全部文章回傳資料" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='550px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:550px;height:;"/>
&lt;figcaption style="text-align: center;">
全部文章回傳資料
&lt;/figcaption>
&lt;/figure>
&lt;p>要取得超過 100 筆文章的方式是加上&lt;code>before&lt;/code>參數，例如前一次的路徑是&lt;br />
&lt;a href="https://www.dcard.tw/service/api/v2/posts?popular=true">https://www.dcard.tw/service/api/v2/posts?popular=true&lt;/a>&lt;br />
而從回傳的資料中，取得最後一筆其&lt;code>id&lt;/code>是&lt;code>234267056&lt;/code>，因此下一頁的路徑即是&lt;br />
&lt;a href="https://www.dcard.tw/service/api/v2/posts?popular=true&amp;amp;before=234267056">https://www.dcard.tw/service/api/v2/posts?popular=true&amp;amp;before=234267056&lt;/a>&lt;/p>
&lt;p>文章網址可透過 &lt;code>https://www.dcard.tw/f/{forumAlias}/p/{id}&lt;/code> 組合取得。&lt;/p>
&lt;h3 id="看板資訊">看板資訊&lt;/h3>
&lt;div class="box">GET &lt;strong>&lt;a href="https://www.dcard.tw/service/api/v2/forums">https://www.dcard.tw/service/api/v2/forums&lt;/a>&lt;/strong>&lt;/div>
&lt;p>抓取目前 Dcard 上所有的看板資訊 (包括隱藏的看板!!)。&lt;/p>
&lt;p>看板網址可透過 &lt;code>https://www.dcard.tw/f/{alias}&lt;/code> 組合取得。&lt;/p>
&lt;h3 id="看板內文章列表">看板內文章列表&lt;/h3>
&lt;div class="box">GET &lt;strong>&lt;a href="https://www.dcard.tw/service/api/v2/forums/funny/posts">https://www.dcard.tw/service/api/v2/forums/{看板名稱}/posts&lt;/a>&lt;/strong>&lt;/div>
&lt;p>取得指定看板內的文章，等同於抓取 &lt;code>https://www.dcard.tw/f/{看板名稱}&lt;/code> 頁面內文章資訊。&lt;/p>
&lt;p>預設使用 &amp;quot;最新&amp;quot; 作為排序，透過&lt;code>popular&lt;/code>參數可以切換 &amp;quot;最新&amp;quot; 與 &amp;quot;熱門&amp;quot;。&lt;/p>
&lt;p>回傳的文章數量預設是前 30 筆，加上&lt;code>limit&lt;/code>參數來限制文章數量，最多 100 筆，&lt;br />
而取得下一頁的方式一樣是加上&lt;code>before&lt;/code>參數。&lt;/p>
&lt;p>文章網址可透過 &lt;code>https://www.dcard.tw/f/{forumAlias}/p/{id}&lt;/code> 組合取得。&lt;/p>
&lt;h3 id="文章內文">文章內文&lt;/h3>
&lt;div class="box">GET &lt;strong>&lt;a href="https://www.dcard.tw/service/api/v2/posts/231997531">https://www.dcard.tw/service/api/v2/posts/{文章ID}&lt;/a>&lt;/strong>&lt;/div>
&lt;p>取得指定文章的資訊，等同於抓取 &lt;code>https://www.dcard.tw/f/{看板名稱}/p/{文章ID}&lt;/code> 頁面內文章資訊。&lt;/p>
&lt;p>有些資訊在文章列表就能抓取到了，但像是&amp;quot;文章完整內容&amp;quot;等資料就需要到此路徑取得。&lt;/p>
&lt;h3 id="文章內引用連結">文章內引用連結&lt;/h3>
&lt;div class="box">GET &lt;strong>&lt;a href="https://www.dcard.tw/service/api/v2/posts/231997531/links">https://www.dcard.tw/service/api/v2/posts/{文章ID}/links&lt;/a>&lt;/strong>&lt;/div>
&lt;p>取得指定文章內的引用連結，如下圖所示。&lt;/p>
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/dcard_api_v2/link.png" alt="文章內引用連結" data-caption="文章內引用連結" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='550px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:550px;height:;"/>
&lt;figcaption style="text-align: center;">
文章內引用連結
&lt;/figcaption>
&lt;/figure>
&lt;h3 id="文章內留言">文章內留言&lt;/h3>
&lt;div class="box">GET &lt;strong>&lt;a href="https://www.dcard.tw/service/api/v2/posts/231997531/comments">https://www.dcard.tw/service/api/v2/posts/{文章ID}/comments&lt;/a>&lt;/strong>&lt;/div>
&lt;p>取得指定文章內的留言。&lt;/p>
&lt;p>預設使用 &amp;quot;由舊到新(樓層)&amp;quot; 作為排序，透過&lt;code>popular&lt;/code>參數可以切換 &amp;quot;由舊到新&amp;quot; 與 &amp;quot;熱門&amp;quot;。&lt;/p>
&lt;p>回傳的文章數量預設是前 30 筆，加上&lt;code>limit&lt;/code>參數來限制文章數量，最多 100 筆 (熱門回應只取前3筆)，&lt;br />
而取得下一頁的方式不太一樣，是加上&lt;code>after&lt;/code>參數(樓層)，，例如前一次的路徑是&lt;br />
&lt;a href="https://www.dcard.tw/service/api/v2/posts/234266517/comments">https://www.dcard.tw/service/api/v2/posts/234266517/comments&lt;/a>&lt;br />
而從回傳的資料中，取得最後一筆其&lt;code>floor&lt;/code>(樓層)是&lt;code>30&lt;/code>，因此下一頁的路徑即是&lt;br />
&lt;a href="https://www.dcard.tw/service/api/v2/posts/234266517/comments?after=30">https://www.dcard.tw/service/api/v2/posts/234266517/comments?after=30&lt;/a>&lt;/p>
&lt;p>&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/dcard_api_v2/comments.png" alt="文章內留言回傳資料" data-caption="文章內留言回傳資料" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='550px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:550px;height:;"/>
&lt;figcaption style="text-align: center;">
文章內留言回傳資料
&lt;/figcaption>
&lt;/figure>&lt;br />
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/dcard_api_v2/popular_comments.png" alt="文章內熱門留言" data-caption="文章內熱門留言" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='500px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:500px;height:;"/>
&lt;figcaption style="text-align: center;">
文章內熱門留言
&lt;/figcaption>
&lt;/figure>&lt;/p>
&lt;h2 id="問題">問題&lt;/h2>
&lt;h3 id="1-token-is-expired-please-refresh-the-token">1. Token is expired. Please refresh the token.&lt;/h3>
&lt;p>在用瀏覽器測試時，可能會遇到回應 &lt;code>{&amp;quot;error&amp;quot;:2007,&amp;quot;message&amp;quot;:&amp;quot;Token is expired. Please refresh the token.&amp;quot;}&lt;/code>，此時只要用無痕模式開啟，或打開 &lt;a href="https://www.dcard.tw/f" target="_blank" rel="noopener">
Dcard網頁
&lt;/a> 讓他更新Token，即可解決。&lt;/p>
&lt;p>(不確定是 2.0 版本多出的機制，還是原本舊 API 就有了)&lt;/p>
&lt;h3 id="2-請求回傳-403-error--20210121-新增">2. 請求回傳 403 error (* 2021/01/21 新增)&lt;/h3>
&lt;p>發現抓不到資料，請求的回傳狀態是 403，但使用瀏覽器卻可以正常取得資料，而且就算 &lt;code>Headers&lt;/code> 帶一樣也沒辦法。&lt;/p>
&lt;p>將回傳的資料儲存成 html 網頁檔查看：&lt;br />
&lt;figure >
&lt;img data-src="https://res.cloudinary.com/jiablog/dcard_api_v2/403error.png" alt="403錯誤" data-caption="403錯誤" src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='600px' height='' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath fill='%23aaa' d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1zm-4.44-6.19l-2.35 3.02-1.56-1.88c-.2-.25-.58-.24-.78.01l-1.74 2.23c-.26.33-.02.81.39.81h8.98c.41 0 .65-.47.4-.8l-2.55-3.39c-.19-.26-.59-.26-.79 0z'/%3E%3C/svg%3E" class="lazyload" style="width:600px;height:;"/>
&lt;figcaption style="text-align: center;">
403錯誤
&lt;/figcaption>
&lt;/figure>&lt;/p>
&lt;p>主要是因為 Dcard 使用了 Cloudflare 的驗證，會需要經過渲染 JavaScript 才能進入。&lt;/p>
&lt;p>有幾種方式可以解決，改使用 &lt;a href="https://www.selenium.dev/documentation/" target="_blank" rel="noopener">
Selenium
&lt;/a>、&lt;a href="https://pyppeteer.github.io/pyppeteer/" target="_blank" rel="noopener">
Pyppeteer
&lt;/a> 來模擬瀏覽器操作，或者使用 &lt;a href="https://github.com/venomous/cloudscraper" target="_blank" rel="noopener">
cloudscraper
&lt;/a> 專門就是要拿來繞過 Cloudflare 頁面的套件，而且它是建立在 Requests 之上，因此幾乎不用修改程式碼。&lt;/p>
&lt;br/>
&lt;br/>
&lt;!--adsense-->
&lt;br/>
&lt;p>如有遇到問題或文章內容有誤，歡迎底下留言告知，感謝~🙂&lt;/p>
&lt;hr />
&lt;p>參考：&lt;br />
&lt;a href="https://levirve.github.io/blog/2016/Dccard-crawler/" target="_blank" rel="noopener">
Dccard 爬蟲，透過官方API
&lt;/a>&lt;br />
&lt;a href="https://medium.com/p/969dbfe83dc6" target="_blank" rel="noopener">
#99 串接 Dcard API，模仿開發 Dcard App
&lt;/a>&lt;/p>
&lt;br/>
&lt;blockquote>
&lt;p>有一天，或許你會發現，最感動的不是你完成了，&lt;br />
而是你終於鼓起勇氣開始。&lt;/p>
&lt;p align="right">—— Peter Su&lt;/p>
&lt;/blockquote></content:encoded><dc:creator>Jia</dc:creator><media:content url="https://blog.jiatool.comimages/cover/dcard_api_v2.jpg" medium="image"><media:title type="html">featured image</media:title></media:content><category>Dcard</category><category>API</category><category>網路爬蟲</category></item></channel></rss>