證交所最佳五檔的程式解析

註:
  1. 2018/11/29,發現已經無法一次查詢多個代號,只能迴圈一個一個查。
  2. 今天是2017/1/16,一早發現網址又改,原來第3第4點的getSTKInfo.jsp改成 getStockInfo.jsp?,而且也把json亂數表頭的方式移除,已緊急改程式了,請重新下載程式(程式碼在此)
  3. 2018/8/23,一早發現必須透過getStockInfo.jsp才是取得Session的地方,所以修改下面第一點

首先我要說明一下現在的日期是 2017年1月,因為 證交所最佳五檔的程式 的規則有時會變動,不見得適用未來的日子。

證交所提供了股票即時最佳五檔的網頁,供一般民眾查詢(但我相信,會觀注的多半是碼農,從最佳五檔的網址,它主要的Host是 mis.twse.com.tw ,這是我們要注意的第一個點,透過 dig command,我們可以發現twse.com.tw網域用了4台DNS主機來應付輸詢,這也是我要提的第一個建議:
若不是用瀏覽器執行JavaScript程式
  •  URL直接採用網址IP連結,不要使用mis.twse.com.tw網域,輸詢太多次會卡住,且若每次輸詢結果不一致時,會導致Session遺失
再來針對它的程式設計加以說明:
  1. 先說一下,用網頁查詢的網址格式是
    http://mis.twse.com.tw/stock/fibest.jsp?lang=zh_tw&stock={代號}
    ,在瀏覽器下通常lang=zh_tw參數會被省略,但是程式設計時,請不要忘了要加上去。這是最重要的一個關鍵,因為我們必須在這頁取得Session(並且要保留),否則後面的資料存取將無以為繼,所以若是以程式要取得Session的第一件事就是連到
    http://mis.twse.com.tw/stock/fibest.jsp?lang=zh_tw
    我們取得這個網頁只是為了取得Cookie,但現在除了這個功能外,還必須取得網頁並分析其中的3個Function:rA1()、rA2()與rA3(),因為後續取得的JSON屬性表頭不再是固定的字串,而是隨機定義在這三個函數內,我們必須透過分析JavaScript以瞭解JSON內容每個項目所代表的意義,取得範例的部分內容說明如下:
    在以往,我們取得這個網頁只能為了取得Cookie,
    <script>function rA3(item){
      item.o=item.gQ0; //開盤價
      item.z=item.lW1; //最近或最後成交價
      item.y=item.jH2; //昨日收盤價
      item.s=item.wR3; //當盤成交量
     …
    }</script>
    <script>function rA2(item){
      item.h=item.hT4; //最高價
      item.u=item.fL6; //漲停價
      item.a=item.jL7; //五檔賣價
      item.v=item.hJ8; //累積成交量}</script>
    <script>function rA1(item){
      item.pz=item.pW9; //試算參考成交價
      rA=new RegExp('NN11','g');//要清除的字串
      item.b=item.pB11; //五檔買價
      item.f=item.nL12; //五檔賣量
      item.l=item.nN13; //最低價
      item.g=item.nQ16; //五檔買量
      item.w=item.fT17; //跌停價}</script>
    
  2. 然後我們再檢閱最佳五檔的網頁源碼, 可以發現一個重要的JavaScript:ctrl.fibest.js,所有的故事從這裡開始:假設我們查詢的是6294智基,它會先到(參考 function initStock()) http://mis.twse.com.tw/stock/api/getStock.jsp?ch=6294.tw&json=1&_=時間亂數
    取回6294的程式代號
    {
       "msgArray":[
          {
             "ex":"otc",
             "d":"20170109",
             "it":"12",
             "n":"智基",
             "i":"20",
             "ip":"0",
             "w":"110.00",
             "u":"134.00",
             "t":"07:50:00",
             "p":"0",
             "ch":"6294.tw",
             "key":"otc_6294.tw",
             "y":"122.00"
          }
       ],
       "rtmessage":"OK",
       "queryTime":{
          "stockDetail":2315,
          "totalMicroTime":2315
       },
       "rtcode":"0000"
    }
    其實這裡不難看出上市代號前綴 tse_,而上櫃代號則前綴 otc_,作為碼農,您應該找個地方把所有代號的上市櫃狀態記錄在某個地方,並不時更新,若不知道請參考
    http://isin.twse.com.tw/isin/C_public.jsp?strMode=2

    http://isin.twse.com.tw/isin/C_public.jsp?strMode=4
    另外,tse_t00.tw與otc_o00.tw分別為上市櫃指數代號
  3.  2017/8/26 開始在取得最佳5檔前,必須先經過
    http://mis.twse.com.tw/stock/api/getStockInfo.jsp?ex_ch=tse_t00.tw|otc_o00.tw|tse_FRMSA.tw&json=1&delay=0&_={時間亂數}
    請注意,| 必須escape %7c(c必須為小寫)
    然後再參考下面一點取得5檔資料
  4. 最後是取得五檔資訊(參考 function loadStockInfo()),它會載入
    /stock/api/getStockInfo.jsp?ex_ch=otc_6294.tw&json=1&delay=0&_=時間亂數
    實際上這個連結反履變過數次,若是資料出不來,就回報檢查看看是否這裡有所變動,其資料回傳範例如下(BJ4,自已對一下就知道欄位義意,該注意的是它的Json資料型態是Array,可見是為了可以回傳多筆查詢),註:回傳的json其值必須包含 "rtcode":"0000" 項問才可視為查詢成功,若不是,則請重新查詢
    {
       "msgArray":[
          {
             "pB11":"120NN11.50_120.00_119.50_119.00_118.50_NN11",
             "ts":"0",
             "pW9":"123ZG9mt.00ZG9mt",
             "tk0":"6294.tw_otc_20170109_B_9999789751",
             "jH2":"122HJ2.00HJ2",
             "fT17":"110LL17vt.00LL17vt",
             "tk1":"6294.tw_otc_20170109_B_9999784676",
             "nQ16":"7_6WJ16ll_2_6_1_WJ16ll",
             "jL7":"121CN7.50_122.00_122.50_123.00_123.50_CN7",
             "tlong":"1483933295000", //目前時間
             "wR3":"FJ30FJ3",
             "fL6":"134NN6cn.00NN6cn",
             "ex":"otc",
             "d":"20170109",
             "it":"12",
             "c":"6294",
             "mt":"953540",
             "n":"智基",
             "hJ8":"3NF8z5NF8z",
             "gQ0":"123GL0jw.00GL0jw",
             "nN13":"120VV13n.50VV13n",
             "ip":"0",   //1:趨跌,2:趨漲,4:暫緩收盤,5:暫緩開盤
             "i":"20",
             "nL12":"2_5TQ12_6_10_4_TQ12",
             "t":"11:41:35",  //揭示時間
             "tv":"1",
             "p":"0",
             "nf":"智基科技開發股份有限公司",
             "ch":"6294.tw",
             "hT4":"123HP4f.00HP4f",
             "lW1":"121JW1h.00JW1h",
             "ps":"4"   //試算參考成交量
          }
       ],
       "userDelay2":5000,
       "userDelay":5000,
       "rtmessage":"OK",
       "referer":"/",
       "queryTime":{
          "sysTime":"11:46:30",
          "sessionLatestTime":-1,
          "sysDate":"20170109",
          "sessionKey":"otc_6294.tw_20170109|",
          "sessionFromTime":-1,
          "stockInfoItem":538,
          "showChart":false,
          "sessionStr":"UserSession",
          "stockInfo":107575
       },
       "rtcode":"0000"
    }
    以第一行的 "pB11":"120NN11.50_120.00_119.50_119.00_118.50_NN11"為例
    從第1點的function rA1(item)可以知道,它指的是五檔買價,但它的格式看起來卻有點奇怪?
    其實它是用底線(_)做為分隔,而在function內也可以看到NN11已經被清除了,所以第一項的120NN11.50,把其中的NN11清除,它的值就是120.5
    {
       "msgArray":[
          {
             "ts":"0",
             "tk0":"2330.tw_tse_20170116_B_9999925914",
             "tk1":"2330.tw_tse_20170116_B_9999925580",
             "tlong":"1484528728000", //目前時間
             "f":"217_1784_1144_1788_1190_",            //五檔賣量
             "ex":"tse",
             "g":"2165_1133_3126_1611_1962_",           //五檔買量
             "d":"20170116",
             "it":"12",
             "b":"179.00_178.50_178.00_177.50_177.00_", //五檔賣價
             "c":"2330",
             "mt":"549353",
             "a":"179.50_180.00_180.50_181.00_181.50_", //五檔賣價
             "n":"台積電",
             "o":"180.00",  //開盤價
             "l":"179.00",  //最低成交價
             "h":"180.50",  //最高成交價
             "ip":"0",      //1:趨跌,2:趨漲,4:暫緩收盤,5:暫緩開盤
             "i":"24",
             "w":"163.50",  //跌停價
             "v":"6359",    //累積成交量
             "u":"199.50",  //漲停價
             "t":"09:05:28",//揭示時間
             "s":"34",      //當盤成交量
             "pz":"180.00",
             "tv":"34",
             "p":"0",
             "nf":"台灣積體電路製造股份有限公司",
             "ch":"2330.tw",
             "z":"179.50",  //最近成交價
             "y":"181.50",  //昨日成交價
             "ps":"2459"    //試算參考成交量
          }
       ],
       "userDelay":...
       ...
       },
       "rtcode":"0000"
    }
    
  5. 第3點是取得單一股票的方式,另外它也有方法可以同時取得多檔股票記錄,它取得的方式是:
    http://mis.twse.com.tw/stock/api/getStockInfo.jsp?ex_ch=代號1|代號2|...|代號N&cp=0&json=1&delay=0&_=時間亂數
    ,範例如下(要注意HTTP Get 有長度限制):
    http://mis.twse.com.tw/stock/api/getSTKInfo.jsp?ex_ch=tse_2377.tw%7cotc_6294.tw%7c&cp=0&json=1&delay=0&_=1483942971571
    2017-8-12註:|(pipeline)  若要編碼請一定編為%7c(小寫),因為在前一天(8/11),證交所不再接受%7C了
    取回的資料格式跟第3點類似,只不過取回的資料是一次多筆罷了,所以作為碼農,打開IDE,開始寫程式去吧!
另外我把上面的解析,寫成Java程式,可以從此處下載(程式碼在此),下載程式後只要在命令視窗下執行
java -jar StockBest5.jar 股號1 股號2 ...股號N
即可取回股票市況

留言

  1. 謝謝你, 這次的改版還是看了你的網誌才知道怎麼改~

    回覆刪除
  2. 請問最佳五檔是不是抓不到資料了?

    回覆刪除
    回覆
    1. 應該是可以抓得到資料,已重新上傳程式碼與編譯檔(在Linux環境顯示情況比較好),您也可以自已抓程式回去看,基本上上面描述的抓取方式沒有改變

      刪除
    2. 作者已經移除這則留言。

      刪除
    3. 作者已經移除這則留言。

      刪除
  3. 作者已經移除這則留言。

    回覆刪除
  4. 作者已經移除這則留言。

    回覆刪除
  5. 看來是session的問題?(即您說的第一點) 有沒有簡單一點的範例? 只要有session可以讓我查上下五檔的API (即能抓到http://mis.twse.com.tw/stock/api/getStockInfo.jsp?ex_ch=代號&cp=0&json=1&delay=0&_=時間亂數 的回傳值)就好了,我原本就已寫好程式,只是現在那個網址不能用了,只好改網址..;感謝您的回覆!

    回覆刪除
  6. 可能我的問題太淺,還是我自己努力就好,不用勞煩忙碌的貴格主了
    謝了~

    回覆刪除
  7. 我參考其他範例~程式碼愈來愈簡潔~跑得又快!呵~

    回覆刪除
  8. 08/22 晚間證交所修改之後程式就撈不到資料了。

    我是使用 php curl 送兩次 request,第一次原本的目標是 index.jsp,取得回應的 header 中 Set-cookie 的 JSESSIONID 值,夾在第二次對 stock/api/getStockInfo.jsp 的 request 中 cookie 已查詢即時交易資訊。

    看了版大最新修改的註解說明還是有點懵懵懂懂,目前還沒有試出解法~
    請問應該是先向 stock/api/getStockInfo.jsp 還是 stock/fibest.jsp 送出 request 才能取得有效的 cookie 呢?

    回覆刪除
    回覆
    1. 這次改版是取得Session的地方不同,先到http://mis.twse.com.tw/stock/api/getStockInfo.jsp?ex_ch=tse_t00.tw|otc_o00.tw|tse_FRMSA.tw&json=1&delay=0&_={數值亂數}
      別忘了把 | escape %7c(必須小寫,大寫不吃),也別忘了用 curl_setopt 加入 Referer 表頭
      最後所有傳送方法都得是 GET(Post 證交所好像也不吃)

      刪除
  9. 不好意思請問一下,8/25晚上證交所又改做法了
    http://mis.twse.com.tw/stock/api/getStockInfo.jsp?ex_ch=tse_t00.tw|otc_o00.tw|tse_FRMSA.tw&json=1&delay=0&_= 這已經無法取得cookie,然後用http://mis.twse.com.tw/stock/fibest.jsp取回的cookie也無法使用,請問您有發現新的做法嗎? 感謝

    回覆刪除
    回覆
    1. 1.先連到 /stock/fibest.jsp?lang=zh_tw 取得Cookie
      2.必須經過到 /stock/api/getStockInfo.jsp?ex_ch=tse_t00.tw|otc_o00.tw|tse_FRMSA.tw&json=1&delay=0&_=時間亂數
      3.取最佳5檔

      若您用Linux 可用以下指令驗證
      $curl -b c.txt -c c.txt "http://mis.twse.com.tw/stock/fibest.jsp?lang=zh_tw"
      $curl -b c.txt -c c.txt "http://mis.twse.com.tw/stock/api/getStockInfo.jsp?ex_ch=tse_t00.tw|otc_o00.tw|tse_FRMSA.tw&json=1&delay=0&_=1503848179451"
      $curl -b c.txt -c c.txt "http://mis.twse.com.tw/stock/api/getStockInfo.jsp?ex_ch=tse_2330.tw|tse_00642U.tw&json=1&delay=0&_=1503848179451"

      刪除
    2. 喔喔喔~成功了,太感謝大大了~~~~

      刪除
  10. 大大 證交所好像又改做法了 有解法嗎?感恩

    回覆刪除
    回覆
    1. 8/25後應該沒改,我的程式還是正常取值

      刪除
  11. 應該是我搞錯了。現在可以了。謝謝你一連串的教學

    回覆刪除

張貼留言

這個網誌中的熱門文章

企業人員的統一管理-FreeIPA學習筆記

Postgresql HA