在證券交易所及櫃買中心市場上每一天都會有上市或下市的有價證券清單,做程式交易除了每日更新股價之外,還需要定期更新一下新上市或已下市的有價證券清單。
在證交所有一個網址可以查看最新上市或上櫃的清單。
查詢上市清單網址
https://isin.twse.com.tw/isin/C_public.jsp?strMode=2
查詢上櫃清單網址
https://isin.twse.com.tw/isin/C_public.jsp?strMode=4
透過這 2 個網址就可以知道最新的上市或上櫃有價證券清單是什麼。
可是這清單實在是太長了,包含了眾多的有價證券類別,如果只想看最常見的股票及 ETF 清單,
那就需要用程式把清單中只屬於股票及 ETF 的清單篩選出來。
接下來我會示範一下如何利用 C# 取得證交所有價證券清單的上市及上櫃股票 ETF 清單。
先看一下操作畫面
建置環境
前端架構: Vue.js, jQuery, Bootstrap
後端架構: C# ASP.Net MVC .Net Framework
我網頁上只講解重要的程式碼部份,完整範例可至下方下載。
主要規劃一個查詢區域和結果列果。
<main id="Page">
<div class="row">
<div class="col-md-3">
市場別
<select class="form-control" v-model="form.Q_MARKET_TYPE.value">
<option value="TWSE">證交所</option>
<option value="OTC">櫃買中心</option>
</select>
</div>
<div class="col-md-3">
資產別
<select class="form-control" v-model="form.Q_ASSETS_TYPE.value">
<option value="STOCK">股票</option>
<option value="ETF">ETF</option>
</select>
</div>
<div class="col-md-3">
<br />
<button type="button" class="btn btn-default" v-on:click="GetList()">查詢</button>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
{{gridList.CName}}清單
<div style="float:right;">
總筆數: {{gridList.RowCnt}}
</div>
</div>
<div class="panel-body">
<table class="table">
<tr>
<th>代碼</th>
<th>名稱</th>
<th>市場別</th>
<th>資產別</th>
<th>上市日</th>
<th>產業別</th>
</tr>
<tr v-for="(item, index) in gridList.datas">
<td>{{item.SYMBOL_CODE}}</td>
<td>{{item.SYMBOL_NAME}}</td>
<td>{{item.MARKET_TYPE}}</td>
<td>{{item.ASSETS_TYPE}}</td>
<td>{{item.PUBLIC_DATE}}</td>
<td>{{item.INDUSTRY}}</td>
</tr>
</table>
</div>
</div>
</main>
查詢動作向後端呼叫 GetList() 取得資料
<script>
var Page = new Vue({
el: '#Page'
, mixins: [vueMixin]
, data: function () {
var data = {
form: {}
, ctrlName: '@Url.Content("~/Home/")'
};
data.gridList = {
datas: []
, RowCnt: 0
, CName:''
};
return data;
}
, created: function () {
var self = this;
var columnList = [
'Q_MARKET_TYPE','Q_ASSETS_TYPE'
];
self._CreateForm(self.form, columnList);
self.form.Q_MARKET_TYPE.value = "TWSE";
self.form.Q_ASSETS_TYPE.value = "STOCK";
}
, methods: {
GetToken: function () {
var token = '@Html.AntiForgeryToken()';
token = $(token).val();
return token;
}
, GetList: function () {
var self = this;
var postData = self._GetPostData(self.form, "Q_");
showProcess();
$.DoAjax({
url: '@Url.Content("~/Home/GetList")',
data: { inModel: postData, __RequestVerificationToken: self.GetToken() },
success: function (datas) {
if (self._CheckReturnErrorMsg(datas)) {
self.gridList.datas = [];
self._RowDataBound(self.gridList.datas, datas.gridList);
self.gridList.RowCnt = datas.RowCnt;
self.gridList.CName = datas.CName;
hideProcess();
}
}
});
}
}
})
</script>
後端語法有用到一個新元件 HtmlAgilityPack 此套件主要是解析 HTML 標籤,取得 HtmlAgilityPack 方法在 NuGet 上搜尋名稱「HtmlAgilityPack」,我安裝時的版本為 1.11.33 。
安裝之後,在引用時就可以加入
using HtmlAgilityPack;
再來看一下主要的程式碼
/// <summary>
/// 取得證交所上市及上櫃的股票及ETF清單
/// </summary>
/// <param name="inModel"></param>
/// <returns></returns>
[ValidateAntiForgeryToken]
public ActionResult GetList(GetListIn inModel)
{
GetListOut outModel = new GetListOut();
outModel.ErrorMsg = "";
string url = "";
string codeName = "";
string stockCode = "";
string stockName = "";
string marketType = "";
string industry = "";
string publicDate = "";
string assetType = "";
if (inModel.Q_MARKET_TYPE == "TWSE")
{
outModel.CName = "證交所";
// 來源網址
url = "https://isin.twse.com.tw/isin/C_public.jsp?strMode=2";
}
else if (inModel.Q_MARKET_TYPE == "OTC")
{
outModel.CName = "櫃買中心";
// 來源網址
url = "https://isin.twse.com.tw/isin/C_public.jsp?strMode=4";
}
WebClient webClient = new WebClient();
MemoryStream ms = new MemoryStream(webClient.DownloadData(url));
HtmlAgilityPack.HtmlDocument doc = new HtmlAgilityPack.HtmlDocument();
doc.Load(ms, Encoding.Default);
// 取得 HTML 標籤
HtmlNodeCollection trNode = doc.DocumentNode.SelectNodes("//table[2]/tr");
if (trNode != null)
{
// 建立輸出table
DataTable dtTwseStockList = new DataTable();
dtTwseStockList.Columns.Add("SYMBOL_CODE");
dtTwseStockList.Columns.Add("SYMBOL_NAME");
dtTwseStockList.Columns.Add("MARKET_TYPE");
dtTwseStockList.Columns.Add("ASSETS_TYPE");
dtTwseStockList.Columns.Add("PUBLIC_DATE");
dtTwseStockList.Columns.Add("INDUSTRY");
foreach (HtmlNode tr in trNode)
{
stockCode = "";
HtmlNodeCollection tdNode = tr.SelectNodes("td");
if (tdNode.Count == 7)
{
string stockType = tdNode[5].InnerText;
if (inModel.Q_MARKET_TYPE == "TWSE" && inModel.Q_ASSETS_TYPE == "STOCK")
{
// 取得證交所股票
if (stockType == "ESVUFR")
{
codeName = tdNode[0].InnerText;
stockCode = codeName.Split(' ')[0];
stockName = codeName.Split(' ')[1];
publicDate = tdNode[2].InnerText;
marketType = tdNode[3].InnerText;
industry = tdNode[4].InnerText;
assetType = "股票";
}
}
else if (inModel.Q_MARKET_TYPE == "TWSE" && inModel.Q_ASSETS_TYPE == "ETF")
{
// 取得證交所ETF
if (stockType == "CEOGEU" || stockType == "CEOGDU" || stockType == "CEOGMU" || stockType == "CEOJEU" || stockType == "CEOIBU" || stockType == "CEOJLU" || stockType == "CEOGBU" || stockType == "CEOIEU" || stockType == "CEOGCU" || stockType == "CEOIRU")
{
codeName = tdNode[0].InnerText;
stockCode = codeName.Split(' ')[0];
stockName = codeName.Split(' ')[1];
publicDate = tdNode[2].InnerText;
marketType = tdNode[3].InnerText;
industry = tdNode[4].InnerText;
assetType = "ETF";
}
}
else if (inModel.Q_MARKET_TYPE == "OTC" && inModel.Q_ASSETS_TYPE == "STOCK")
{
// 取得櫃買中心股票
if (stockType == "ESVUFR")
{
codeName = tdNode[0].InnerText;
stockCode = codeName.Split(' ')[0];
stockName = codeName.Split(' ')[1];
publicDate = tdNode[2].InnerText;
marketType = tdNode[3].InnerText;
industry = tdNode[4].InnerText;
assetType = "股票";
}
}
else if (inModel.Q_MARKET_TYPE == "OTC" && inModel.Q_ASSETS_TYPE == "ETF")
{
// 取得櫃買中心ETF
if (stockType == "CEOGBU" || stockType == "CEOGEU" || stockType == "CEOIBU" || stockType == "CEOIEU" || stockType == "CEOIRU" || stockType == "CEOJBU")
{
codeName = tdNode[0].InnerText;
stockCode = codeName.Split(' ')[0];
stockName = codeName.Split(' ')[1];
publicDate = tdNode[2].InnerText;
marketType = tdNode[3].InnerText;
industry = tdNode[4].InnerText;
assetType = "ETF";
}
}
if (stockCode != "")
{
// 加入 datatable
DataRow drNew = dtTwseStockList.NewRow();
drNew["SYMBOL_CODE"] = stockCode;
drNew["SYMBOL_NAME"] = stockName;
drNew["MARKET_TYPE"] = marketType;
drNew["ASSETS_TYPE"] = assetType;
drNew["PUBLIC_DATE"] = publicDate;
drNew["INDUSTRY"] = industry;
dtTwseStockList.Rows.Add(drNew);
}
}
}
// 輸出資料
outModel.gridList = new List<StockRow>();
foreach (DataRow dr in dtTwseStockList.Rows)
{
StockRow row = new StockRow();
row.SYMBOL_CODE = dr["SYMBOL_CODE"].ToString();
row.SYMBOL_NAME = dr["SYMBOL_NAME"].ToString();
row.MARKET_TYPE = dr["MARKET_TYPE"].ToString();
row.ASSETS_TYPE = dr["ASSETS_TYPE"].ToString();
row.PUBLIC_DATE = dr["PUBLIC_DATE"].ToString();
row.INDUSTRY = dr["INDUSTRY"].ToString();
outModel.gridList.Add(row);
}
outModel.RowCnt = dtTwseStockList.Rows.Count;
}
// 輸出json
string json = JsonConvert.SerializeObject(outModel);
ContentResult result = new ContentResult();
result.ContentType = "application/json";
result.Content = json;
return result;
}
此段程式碼主要是呼叫 https://isin.twse.com.tw/isin/C_public.jsp?strMode=2 或是 https://isin.twse.com.tw/isin/C_public.jsp?strMode=4 取得 HTML 原始檔,再從原始檔之中擷取表格資料。
擷取股票及 ETF 有使用到一些條件: ESVUFR, CEOGBU, CEOIEU, CEOGCU…
這些是我依照股票或是 ETF 他們分類而歸納的條件,只取得需要的資料加入在 DataTable 裡面。
最後只要將 DataTable 裡面的資料呈現在前端網頁上即可。
這個範例就簡單演示取證交所網頁的資料,大家可以拿去應用在自己的專案裡面,要記得擷取 HTML 原始碼都會因為來源網址改版而失效喔,應用在實務上時需要一些檢查條件和讀取錯誤判斷喔。
付費後可下載此篇文章教學程式碼。
[C#] 取得證交所台股價格的 3 種實用方法(附範例下載)
[C#] 取得公開資訊觀測站股票基本資料(上市、上櫃、興櫃、公開發行) (附範例下載)