close

今天陳陳就來教大家如何抓取上市各支股票歷年最高價吧!!

首先就是開啟Anaconda3的Jupyter Notebook

如果還沒安裝Anaconda的可以先去看上一篇

【python】如何安裝Anaconda3(Jupyter Notebook)

【尋找所需資料的網站】

首先,我們要先找到能爬取我們要的資料網站

這裡我們是去抓取台灣證券交易所的資料

從台灣證券交易所抓資料的優點是網址為GET,所以抓取簡單方便,但缺點是太頻繁抓取會被封鎖,所以從這抓資料會花比較長的時間

進入證交所後,我們點選上方的交易資訊→盤後資訊的個股月成交資料

image

接下來我先隨便打個股票代碼做搜尋,並對「CSV下載」點選右鍵選擇「複製連結網址」

image

https://www.twse.com.tw/exchangeReport/FMSRFK?response=csv&date=20200615&stockNo=2330

這就是我們複製的網址,我們可以從date=後方的20200615知道,這是日期,stockNo=後方的2330知道是代碼

沒錯,這就是證交所爬取資料為什麼那麼簡單,因為規則就直接打在網址上,一看就知道,我們只要改變這兩個地方我們就能查詢不同年份跟不同的股票

那接下來就是要知道每個上市股票代碼才能替換stockNo=後方的代碼

這部分陳陳前也有發過一篇教學,可以先點去看唷

【python】如何利用python輕鬆抓取上市櫃股票清單

那我們找尋資料的部分就告一段落了,接下來就是撰寫程式的部分拉!!

 

【撰寫程式】

在撰寫程式前我們都要先設定一個目標,這樣才能快速清楚的知道自己是要抓取什麼資料

而我們今天的目標就是抓到上市各檔股票從1996年到2020年5月的每月最高價

那目標設定好後,我們就開始撰寫吧!!

 

【step1 import package】

首先我們習慣把要爬取的網站先用##標記在最上方,以方便使用

然後我們要import一些package

1     import requests
2     import pandas as pd

3     import io

4     import datetime

5     import time

6     import calendar

這次一樣會用到基本的requests以及pandas

外加io,io能讓我們讀寫一個虛擬的檔案,因為我們從證交所複製的csv檔網址並非實際下載下來的檔案,所以我們需要透過 io.StringIO 讓我們讀檔

datetime是之後讓我們抓取日期所用

time是為了之後不被證交所封鎖,我們需要time.sleep來限制每次抓取的間隔時間

calendar是為了讓我們的月份更改成英文

其實這一步我們一開始並不會import這麼多,都是往後在抓取時遇到問題我們才會一一加上去,所以剛開始學python的朋友不用太緊張,不用想說怎麼一開始我就要知道import什麼

image

 

【step2 先處理股票代碼】

我們透過【python】如何利用python輕鬆抓取上市櫃股票清單已經將股票代碼抓取下來,但還需要做一些整理,因為我們會遇到一個問題,那就是我們是要抓取1996年之後的每檔股票每月最高價,所以要區分該股票是在1996年之前還之後上市,這會影響我們抓資料的些許不同

那我們首先就是要將抓取的股票清單中的「公開發行/上市(櫃)/發行日」的年跟月分出來

7~12以及19行都是我們先前教的,這裡我們增加了幾行讓我們能分出「年」跟「月」

13、14是先在原先的DataFrame中增加我們要的欄位,也就是年跟月,一開始先設為0

15~17是利用for迴圈將每一個原本在「公開發行/上市(櫃)/發行日」中的年跟月分開加入我們創建的欄位,而年月日本身中間有「/」當作區隔,所以我們就利用split("/")來隔開,split的意思就是只要原文中()內的東西都變成分隔資料的間格,例如:2020/6/15,利用split("/")就會變成['2020', '6', '15'],而年的部分就變成字串[0],月的部分變成字串[1]

最後18就是將我們不要的「公開發行/上市(櫃)/發行日」drop掉,那我們就股票代碼清單整理就告一段落了

7         url2 = "https://isin.twse.com.tw/isin/class_main.jsp?owncode=&stockname=&isincode=&market=1&issuetype=1&industry_code=&Page=1&chklike=Y"
8
        page2 = requests.get(url2)
9
        df = pd.read_html(page2.text)[0]

10       df = df.drop([0,1,4,5,8,9],axis = 1)
11
      df.columns = df.iloc[0]
12
      df = df.iloc[1:]
13
      df["年"] = 0
14
      df["月"] = 0
15
      for t in range(1,len(df)+1):
16
          df["年"][t] = df["公開發行/上市(櫃)/發行日"][t].split("/")[0]
17
         df["月"][t] = df["公開發行/上市(櫃)/發行日"][t].split("/")[1]
18
      df = df.drop("公開發行/上市(櫃)/發行日",axis = 1)
19
      df = df.set_index("有價證券代號")

image

image

將我們處理好的股票代碼清單用一個def來定義函式

image

 

【step3 開始抓取股票每月最高價】

這裡陳陳直接拿已經定義def的最終型態來講解

首先我們先定義def,後方名字可自行取,陳陳命名為stock,()內的名稱也能自行設定,因為這只是讓你方便知道要輸入什麼參數

21~24接下來就是處理先前說的從證交所抓取的csv檔,我們將有規律的date=後方以及stockNO=後方的位置改成我們的參數,而date=後方因為我們只需要更改年就好,所以月跟日我們隨便設,因為不影響我們要抓的資料

25行是用if來判斷我們抓的股票他是在1996年之前還之後上市,這樣才不會有股票在還未上市的年分抓不到資料

26~29行是因為證交所在2015年之後有修改他的版面,多了一行,所以我們要多刪掉一行

30、31行是將中文月份更改成英文,這樣在跑圖表時顯示英文比較好看,但如果你想要看中文也是可以不用這行

32、33行是將「有價證券名稱」這欄位名稱更改成最高價,因為我們最後合併放上圖表時,這行的標頭要顯示最高價

34、35行是將民國的年份更改成西元

36、37行是將修改好的年、月合併,例如:修改後的西元年為2020,月份為5月是Mar,合併後變成2020Mar,這就會變成我們圖表右下角跑動的年月份

38~42行是將我們的資料轉置,因為我們最後要合併需要是橫的形式,然後修改一下成我們要的樣式,然後最後加上產業別,這最後在做圖表時可以加入查看股王都是哪個產業的股票,但陳陳最後是沒有放上產業,因為太多產業圖表看上去太雜亂,但如果是要自己看的可以放上去看

44行是如果我們輸入該股票但年份卻在它上市之前,我們就回傳您輸入的年份該股票尚未上市

45行最後就是輸入我們的回傳值,也就是我們最終的答案

20       def stock(stock_id,date_time):
21           df2 = df_u(stock_id)
22           url = "https://www.twse.com.tw/exchangeReport/FMSRFK?response=csv&date=" + date_time + "0101&stockNo=" + stock_id
23           page = requests.get(url)
24           use_text = page.text.splitlines()
25           if date_time >= str(df2["年"][0]):
26               if date_time > str(2015):
27                   use_text1 = pd.read_csv(io.StringIO(''.join([text[:-1] + "\n" for text in use_text[1:len(use_text)-4]])))

28               else:
29                   use_text1 = pd.read_csv(io.StringIO(''.join([text[:-1] + "\n" for text in use_text[1:len(use_text)-3]])))
30               for i in range(0,len(use_text1)):
31                    use_text1["月份"][i] =  calendar.month_abbr[use_text1["月份"][i]]
32               df_text = use_text1[["年度","月份","最高價"]]
33               df_text1 = df_text.rename(columns={'最高價':df2["有價證券名稱"][0]})
34               for i in range(0,len(df_text1)):
35                     df_text1["年度"][i] = int(df_text1["年度"][i]) + 1911
36               for i in range(0,len(df_text1)):
37                    df_text1["年度"][i] = str(df_text1["年度"][i]) + df_text1["月份"][i]
38                    df_text2 = df_text1.drop(["月份"],axis = 1)
39               df_text2_T = df_text2.T
40               df_text2_T.columns = df_text2_T.iloc[0]
41               df_text2_T = df_text2_T.iloc[1:]
42               df_text2_T.insert(0,"產業別",df2["產業別"][0])
43           else:
44               df_text2_T = "您輸入的年份該股票尚未上市"
45           return df_text2_T

image

 

然後隨便輸入一檔股票跟年份來查看是否為我們要的格式

輸入後檢查一下,2330是台積電,產業是否為半導體業,年份部分對不對,最高價拿去跟證交所查詢的比對一下對不對,並且多查看幾家,如果都沒錯,那恭喜我們又完成一個段落了

image

 

【step4 單一股票年份合併】

接下來的工作就是將每檔股票從1996年到2020年5月的最高價資料合併起來

46行一樣先定義一個def

47行是先抓取現在的時間,因為後續會用到

48行一樣先定義前方寫的股票清單為stock_a

49~65行是判斷該股票是在1996年之前還之後上市,如果是之後,我們就從該股票上市那年開始抓取,如果是之前,我們就從1996年開始抓

接下來就是一連串的合併動作,中間記得要間隔8秒,因為在這間隔5秒一樣會被檔

66行是回傳我們要定義值出現的值

那單一股票合併的工作就也完成了

46           def stock_tw1(stock_id): 
47               z = datetime.datetime.now()
48               stock_a = df_u(stock_id)
49               if stock_a["年"][0] > 1996:
50                   s_0 = stock(stock_id,str(stock_a["年"][0]))
51                   s_0_T = s_0.T
52                   for b in range(stock_a["年"][0]+1,z.year+1): 
53                       time.sleep(8)
54                       s_1 = stock(stock_id,str(b))
55                       s_1 = s_1.drop(["產業別"],axis = 1)
56                       s_0_T = s_0_T.append(s_1.T)
57                   return s_0_T.T
58               else:
59                   s_0 = stock(stock_id,"1996")
60                   s_0_T = s_0.T
61                   for b in range(1997,z.year+1):
62                       time.sleep(8)
63                       s_1 = stock(stock_id,str(b))
64                       s_1 = s_1.drop(["產業別"],axis = 1)
65                       s_0_T = s_0_T.append(s_1.T)
66                   return s_0_T.T

image

然後出來結果大概會像下方這樣,因為康控-KY是2016年才上市,所以資料是從2016年開始到2020年

image

 

【step5 將各家股票下載下來】

接下來就是將股票一一下載成CSV檔,這裡可能會有人說為什麼不一起合併成一個檔案再下載下來,因為我們先前有說證交所會有阻擋機制,太頻繁抓取就會被檔,陳陳試了很多次,發現間隔5秒是最適合的抓取時間,但還是有機率會被檔,所以陳陳選擇一檔一檔抓取完後再進行合併,而且我們光上市公司就有900多家,如果每檔都沒失敗都抓成功,外加每檔每年間隔8秒,要抓20幾年,換算下來就要抓個2到3天的時間了,所以使用一檔一檔抓取方式就可以中斷,有空的時候再繼續

66行是先定義我們要存放CSV檔的位置

67行是用for迴圈將股票代碼一一跑過,所以如果中間有中斷,我們能先將df.index[0]額外取出來先看自己目前抓到的檔案位於股票清單第幾個位置,如果找到了就將該數字+1放回index去繼續跑,67行就會變成for a in df.index[要開始抓的股票代碼位置+1:]:

然後剩下的就是抓取每檔股票,try是嘗試執行,失敗話就會跳到except,所以我們在抓取成功的股票後方加上print("Successful!!" + " " + a),當我們成功時就會顯示successful!!,失敗的話print("Fails at" + ' ' + a + "1"),也就是出現failes,我們才知道我們該檔股票是成功還失敗,而陳陳還會在多加一個try然後間隔10秒是怕在抓取的過程中被封鎖IP,所以休息個10秒看看能不能被打開繼續抓,如果都失敗,我們之後抓完後再例外處理,像陳陳就有遇到一些股票上市日期跟資料提供開始日不同,這就可以事後單獨小修程式抓取出來

66                   file_path = r"D:\python\stock_high"
67                   for a in df.index[0:]:
68                       time.sleep(5)
69                       try:
70                           stock_tw2 = stock_tw1(str(a))
71                           stock_tw2.to_csv(file_path + "\\" + str(a) + '.csv',encoding= 'utf_8_sig')
72                           print("Successful!!" + " " + a)
73                       except: 
74                           print("Fails at" + ' ' + a + "1")
75                           time.sleep(10)
76                           try:
77                               stock_tw2 = stock_tw1(str(a))
78                               stock_tw2.to_csv(file_path + "\\" + str(a) + '.csv',encoding= 'utf_8_sig')
79                               print("Successful!!" + " " + a)
80                           except:   
81                                print("Fails at" + ' ' + a + "2")

image

 

 

【總結】

上市的部分做到這裡算是大功告成了,那最終合併的資料陳陳會跟上櫃的一起合併,所以留在下篇上櫃教學完後會一起教合併

其實爬蟲說難不難,說簡單也沒很簡單,但只要有心要學習,就一定能做出自己想要的東西,對未來一定有很大的幫助,因為在未來不論你從事什麼行業都會跟網路脫離不了關係,而python又是比較好入門的程式語言,所以大家一起加油的把python學好吧!!

 

image

arrow
arrow
    創作者介紹
    創作者 陳陳 的頭像
    陳陳

    陳陳的嘉理

    陳陳 發表在 痞客邦 留言(0) 人氣()