[C#] 取得证交所上市及上柜的股票及ETF清单(附范例)

在证券交易所及柜买中心市场上每一天都会有上市或下市的有价证券清单,做程序交易除了每日更新股价之外,还需要定期更新一下新上市或已下市的有价证券清单。
在证交所有一个网址可以查看最新上市或上柜的清单。
查询上市清单网址

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
我网页上只讲解重要的程序码部份,完整范例可至下方下载。

HTML 语法

主要规划一个查询区域和结果列果。

<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>

Javascript 语法

查询动作向後端呼叫 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>

後端 C# 逻辑

後端语法有用到一个新元件 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#] 取得公开资讯观测站股票基本资料(上市、上柜、兴柜、公开发行) (附范例下载)

开发应用网站

WinTog 云投资


<<:  The way of flushing QNAP DOM

>>:  Python cv2 播Video

Day21 - 针对 Metasploitable 3 进行渗透测试(2) - Shell & Reverse Shell基础知识

缘起 透过上一篇,我们透过 nmap 扫描 port,在从版本去查询漏洞资料库,知道目标服务器(受害...

企业为什麽需要实施BPM?

BPM是 Business Process Management 的简称,翻成中文是业务(Busin...

Day 30 整合宝石:【Lab】建构三层式云端架构 (EC2+VPC+S3+RDS+IAM) (下)

本文将继上篇 【Lab】建构三层式云端架构(上),实作下半部分的内容,包含档案控管、RDS/EC2...

浅谈传输层协定(三):牺牲可靠性换取速度的 UDP

前面两天从 TCP 在做什麽开始聊起,今天来看看另外一个也很重要的协定 UDP。 UDP 为 Use...

Day 02 - 登入及下单签署

登入及下单签署 官网对应说明文件:https://sinotrade.github.io/tutor...