謝謝各位的支持,有很多人寄信來問各式各樣不同的研究! 但因為討論熱烈,我目前先挑比較簡單的做,其它的在清單中,依序補上~ 最近發現上櫃的股票也滿多人在看的,今天教大家如何用 python 抓取上櫃公司的收盤價!
![]()
快速重點:用 Python 抓取櫃買中心(TPEX)上櫃股價
上櫃(OTC)股票的每日收盤價,由證券櫃買中心(TPEX)公開提供。用 Python 抓取的流程是:以 requests 對櫃買中心的每日收盤行情下載網址發出 GET 取得 CSV,網址中的 d=107/02/09(民國年/月/日)參數決定查詢日期,可換成其它日期;CSV 前幾行的標頭帶有雙引號("a","b","c")與其它行格式不同,所以先用 lines[3:] 跳過再丟進 pandas 的 StringIO 讀取,最後把 index 設為股票代號、columns 設為欄位名稱,就得到一張乾淨的上櫃股價資料表。
| 步驟 | 工具 / 關鍵 | 說明 |
|---|---|---|
| 取得資料 | requests.get + 櫃買中心下載網址 |
TPEX 每日收盤行情,回傳 CSV |
| 指定日期 | 網址參數 d=107/02/09 |
民國年格式,可改成其它日期 |
| 讀進 pandas | StringIO + lines[3:] |
跳過帶雙引號的標頭行,避免讀檔失敗 |
| 整理資料表 | 設定 index 為代號、columns 為欄位 | 得到以股票代號為索引的收盤價表 |
整個流程不需要申請任何 API 金鑰,只要會用 requests 與 pandas 兩個常見套件就能完成,很適合作為 Python 量化選股的爬蟲入門練習。下面就一步一步把每段程式碼說清楚。
首先,我們只要從這個網址,把資料 get 下來就行了!
爬取上櫃股價網址
顯示程式碼
import requests
link = 'http://www.tpex.org.tw/web/stock/aftertrading/daily_close_quotes/stk_quote_download.php?l=zh-tw&d=107/02/09&s=0,asc,0'
r = requests.get(link)
r.ok
可以看到網址中,包含了日期:107/02/09,這個字樣,可以改成其它日期。 get 下來就是 csv 檔了!直接丟進 pandas:
這段是整個櫃買中心爬蟲的起點,稍微拆解一下這個下載網址:l=zh-tw 指定回傳繁體中文版的欄位名稱,d=107/02/09 是前面提過的民國年日期參數,s=0,asc,0 則是排序設定。requests.get(link) 會把這個網址的內容抓回來,存進變數 r;接著用 r.ok 確認請求是否成功,回傳 True 就代表順利拿到資料、可以往下處理。要抓不同交易日的上櫃收盤價,只要改 d 後面的日期、重新跑一次這段就行,網址其餘部分都不用動。
使用 pandas 讀取
顯示程式碼
lines = r.text.replace('\r', '').split('\n')
import pandas as pd
from io import StringIO
df = pd.read_csv(StringIO("\n".join(lines[3:])), header=None)
df.head()但我發現一個小問題:
- 在 csv 檔中,第一行是:”a”,”b”,”c”
- 但是在其它行,則顯示:a,b,c
有沒有雙引號很重要,會讓 pandas 沒辦法直接讀進來,所以我們前面幾行先去除(上段 code 中 lines[3:])
這一步是很多人用 Python 抓 TPEX 上櫃股價時第一個卡關的地方,所以多說明一下。r.text 是櫃買中心回傳的原始 CSV 文字,我們先用 replace('\r', '') 去掉換行裡多餘的 \r,再用 split('\n') 依換行切成一行一行的 lines 串列。櫃買中心 CSV 最前面幾行是檔案說明與帶雙引號的標頭,欄位數和格式跟真正的資料行對不上,如果整份直接丟進 pd.read_csv 就會報錯或欄位錯位。所以我們用 lines[3:] 從第 4 行開始取,把前面這幾行跳過,再用 StringIO 把剩下的純文字當成檔案丟給 pd.read_csv。如果你換了日期之後又讀檔失敗,多半是跳過的行數對不上,把 lines 印出來看一下前幾行的實際內容,再調整 lines[3:] 裡的數字即可。
就可以得到下圖:

但我們的 index 跟 column 都還沒設定好(都是數字),要把它轉換成有意義的 label:
整理 index 跟 columns
顯示程式碼
df.columns = list(map(lambda l: l.replace(' ',''), lines[2].split(',')))
df.index = df['代號']
df = df.drop(['代號'], axis=1)
df.head()首先先來換 column,裡面有些複雜的程式(第一行)慢慢講解:
lambda l: l.replace(' ','')是一個函式,但是我懶得命名,所以就用lambda賴省略函式名稱,它會讀入,這個函式讀入l,並將l裡面的空白刪除並 outpu 出來。- 其中的
map(func, array)就指:將array裡面所有元素,都用func處理一遍。 line[2].split(','):將字串依照 “,” 分割成好幾個小字串。
總體來說,就是將資串分割->針對每個子字串刪除空白。
另外針對 index,我們就單純用 df.index = df['代號'] 來設定,並用 df.drop 將原本的 column 刪除,
其實知道 pandas 有 df.set_index() 這個函式,但是發現存檔怪怪的,所以改用途法煉鋼(第 2、3 行)
這樣差不多就完成了:

那要如何把檔案存起來呢?
我們可以利用 save file
顯示程式碼
df.to_csv('test.csv')
pd.read_csv('test.csv', index_col='代號').head()來存檔跟讀檔喔!跑出來會是一模一樣的!這裡 df.to_csv('test.csv') 會把整理好的上櫃股價資料表寫成 CSV 檔,pd.read_csv('test.csv', index_col='代號') 再把它讀回來,並指定用「代號」這欄當索引,這樣讀回來的資料表 index 才會跟存檔前一致。確認存檔讀檔結果一模一樣,就代表這份上櫃股價資料可以安心保存、之後再拿出來用。

今天的簡易教學就到這邊!可以下載下來玩玩看喔!
幾個實務上的小提醒
把上面的流程跑通之後,實際在做上櫃(OTC)股價爬蟲時,還有幾個地方值得留意,以下都是針對本文這段程式碼的延伸說明:
- 日期是民國年格式:網址裡的
d=107/02/09用的是民國年/月/日,例如 107 年就是西元 2018 年。如果你要抓某一天的上櫃收盤價,要先把西元年換成民國年(西元年減 1911),再填進這個參數。 - 為什麼要跳過前幾行:櫃買中心 CSV 最前面幾行是說明與帶雙引號的標頭,直接丟進
pd.read_csv會因為格式不一致而讀錯,所以本文用lines[3:]把前面跳過。換不同日期或櫃買中心調整檔頭時,這個跳過的行數可能要跟著微調,建議先把r.text印出來看一下實際內容再決定。 - 存檔讀檔的用處:本文最後用
df.to_csv把整理好的資料表存成檔案,再用pd.read_csv讀回來驗證結果一致。實務上你會把每天抓到的上櫃股價都存起來,一天一個檔案,慢慢累積成一份可以拿來做 Python 量化選股的歷史資料庫。 - 抓多天就是換日期重複跑:本文示範的是抓「單一交易日」。如果要抓一整段期間,概念上就是把日期參數換成不同日期、用迴圈重複呼叫一次這段流程;每次之間記得留一點間隔、不要短時間內大量請求,對資料來源比較友善。
- 整理 columns 與 index 的那段在做什麼:
df.columns = list(map(lambda l: l.replace(' ',''), lines[2].split(',')))這行先用lines[2].split(',')把標頭那一行依逗號切成一個個欄位名稱,再用map搭配一個lambda小函式把每個欄位名稱裡的空白去掉,最後把整理好的名稱設定回df.columns。接著df.index = df['代號']把「代號」這欄設成索引,df.drop再把原本的代號欄移除,這樣資料表的 index 就是股票代號、欄位就是有意義的中文名稱,而不是一開始的數字。 - 整理完的資料表長什麼樣:跑完整段流程後,你會得到一張以股票代號為 index、欄位為櫃買中心提供之收盤行情欄位的 pandas 資料表,可以用
df.head()預覽前幾列確認格式正確,再決定要不要存檔。
如果你的目標是把這些上櫃股價用來選股與回測,可以接著看〈程式交易是什麼?完整入門指南〉這份完整指南,了解從資料到策略的整體流程。
常見問題
Q:櫃買中心(TPEX)的爬蟲資料從哪個網址抓?
從證券櫃買中心的每日收盤行情下載網址抓取,用 requests.get 對該網址發出請求就會回傳 CSV。網址中的 d=107/02/09 是民國年/月/日的日期參數,改成其它日期即可查詢不同交易日的上櫃股價。
Q:用 Python 抓上櫃(OTC)股價,pandas 讀檔為什麼會失敗?
因為櫃買中心 CSV 的第一行標頭帶有雙引號("a","b","c"),格式與其它資料行(a,b,c)不同,pandas 沒辦法直接讀進來。本文的做法是用 lines[3:] 先去掉前面幾行,再用 StringIO 丟進 pd.read_csv,就能正常讀取。
Q:抓下來的上櫃股價,怎麼整理成以股票代號為索引的資料表?
先用 df.columns 把欄位名稱(去掉空白)設好,再用 df.index = df['代號'] 把股票代號設成 index,並用 df.drop 移除原本的代號欄。整理後就能用 df.to_csv 存檔、pd.read_csv 讀回,兩者結果一致。
Q:上櫃和上市股價的爬蟲方式一樣嗎?
資料來源不同:上櫃(OTC)股價來自證券櫃買中心(TPEX),上市股價則來自證券交易所。流程都是用 requests 取得每日收盤資料再丟進 pandas 整理。上市股價爬蟲可參考下方〈超簡單台股每日爬蟲教學〉。
Q:用 Python 抓櫃買中心(TPEX)股價需要 API 金鑰嗎?
本文示範的做法是直接對證券櫃買中心公開的每日收盤行情下載網址用 requests.get 抓取 CSV,不需要額外申請 API 金鑰,新手照著本文步驟就能跑起來。
Q:抓下來的上櫃股價可以用在 Python 量化選股嗎? 可以。把每天抓到的上櫃收盤價整理成以股票代號為 index 的資料表並逐日累積後,就能當作 Python 量化選股與回測的歷史資料來源。想了解從資料到策略的整體流程,可參考〈程式交易是什麼?完整入門指南〉。
延伸閱讀
投資警語:本文僅供教學參考,不構成投資建議。過去績效不代表未來表現,投資有風險。
FinLab AI
想建立自己的策略?
用自然語言描述你的選股想法,AI 自動驗證、回測、給你答案
免費開始


