如果用知乎, 可以关注专栏:.NET 开源项目和 PowerBI 社区
重点重点: 我没有买股票, 没有买股票, 股市是个坑, 小心割韭菜哦
本文的初衷是数据分析(分析结果就不说了, 就是想看看筛选点数据), 只不过搞下来发现比我想象的要简单多了本文采集的数据是: 2000 年到 2018 年 2 月份, 上证和深证交易所所有的上市股票交易数据, 按天采集, 不是小时哦, 有兴趣的朋友, 可以稍微改造, 做到实时(这和我就无关了)
.NET 开源文章目录: 本博客. NET 开源项目文章目录
1. 数据采集需求
原始需求: 想分析某些股票的历史天交易数据里面满足某些条件的股票
初步分析: 需要股票的基础数据, 如名称, 编码, 交易所等信息, 然后就是每天的开票收盘的价格, 涨幅等信息
以为很简单, 起始搞起来越滚越大, 刚开始以为 2 个表就够了, 没想到搞来搞去, 有 6 个表了
还好, 我们有强大的 XCode 组件, 数据库设计, 开发都极其简单, 总共零零散散也就 10 个小时不到就完工了主要的时间不是写代码, 其实 60% 的时间都是在找资料, 分析接口和想怎么设计上面, 以及跑数据, 还好源数据都保存下来了, 重写跑起来很快
2. 股市数据接口
很早以前, 有朋友也想让我给给他采集股票实时数据, 而且用的是商业接口, 由于时间匆忙, 而且对股票一无所知, 所以就拒绝了
这次开工之前, 心里也很忐忑, 会不会很复杂, 反正是自己想玩和想看, 所以抱着试一试的心态, 没想到比我想的要简单很多
首先, 我们得找到数据来源, 否则一切无从谈起, 而且我需要的是历史数据, 对数据实时性要求不高:
此处省略 1 万字, 因为搜索和找了很多资源, 最终用的是下面的接口, 简单, 实测速度快, 18 年的数据不到 20 分钟刷刷刷搞下来了
2.1 股票基础数据
股票基础数据我用的是这个网址: http://quote.eastmoney.com/stocklist.html
里面包括了上证和深圳交易所的所有股票代码信息, 只需要直接采集即可, 速度很快
如果要做实时, 每天更新一次即可, 注意: 我开始也没注意, 股票代码有很多含义, 除了交易所之外, 还有啥创业板, 基金之类的, 我没仔细研究, 我只把我需要的类型进行了标记可以在这里看看代码的一些类型: https://baike.so.com/doc/4974613-5197406.html
股票基础数据结构比较简单:
编码(唯一), 名称, 交易所, 类型 1 是要分析 ,0 是暂时忽略的, 上市日期
2.2 股票历史天数据
股票历史天数据刚开始想应该很大, 找了一些结构才发现, 基本每天的主要指标也就 10 个字段左右, 计算一下, 每只股票就算 20 年, 也就 6000 条而已
即使 10000 支股票, 最多也就 6000 万而已, 所以刚开始的时候直接全部撸到一个表里面了, 实际上后面在分析的时候, 极其不合理分析的比较很复杂, 搜索非常慢, 所以后来我把历史数据进行了拆分, 然后分析的时候多线程, 速度瞬间提升 10 倍由于 XCode 组件天生对分表分库和数据库反向工程的支持, 所以开发起来非常快
股票历史数据找了很多, 最早用的是搜狐的一个接口: http://www.cnblogs.com/ldlchina/p/5392670.html
它的格式很简单, 拼接股票代码和起始结束日期即可, 后来还发现它还能查询指数信息:
http://q.stock.sohu.com/hisHq?code={code}&start={start}&end={end},{code}替换为股票代码, 大陆股票代码加前缀 cn_
返回的 json 格式很标准, 使用了 Newlife 组件的 JsonParser 类, 轻松搞定根据返回的数据信息, 找了几支股票核对一下, 就知道其意义了在后面的数据库设计中会详细描述说明: 采集的时候我是先用临时表统一把返回的结果保存, 防止程序有 bug, 下次又去请求, 浪费人家的流量也是保存在 sqliet 数据库
2.3 其他附加
在后面分析的时候, 我还用到了板块信息, 相当于给每支股票加一个类型, 属于什么板块, 这样分析的时候有针对性板块有类型, 然后每个类型下面又有一些股票代码, 有 2 个表, 数据来源也是搜狐的:
http://q.stock.sohu.com/cn/bk.shtml
当然如果还要做复杂和完善一点, 还有很多数据要采集, 比如公司的一些基本信息, 我暂时没有用到, 后面我会把代码开源, 大家随意折腾
3. 数据库设计
数据库设计我们采用 XCode 开发的设计规范, 都用 xml 文件, 可以自动生成实体类, 后面有时间我会针对 XCode 写一篇开发实践的文章, 再一次带大家温习了 XCode, 在这里感谢 @大石头, 10 多年码农, X 组件博大精深, 极大的提高了开发效率, 简单, 简单, 简单到你有时候怀疑人生
1. 股票基础信息
- <Table Name="StockBaseInfo" Description="股票基础信息" ConnName="stock_base">
- <Columns>
- <Column Name="Code" DataType="String" PrimaryKey="True" Description="股票编码" />
- <Column Name="Name" DataType="String" Master="True" Description="名称" />
- <Column Name="Exchange" DataType="String" Description="交易所" />
- <Column Name="Kind" DataType="Int32" Description="类型 1 是要分析股票, 0 是暂时不分析" />
- <Column Name="StartDate" DataType="DateTime" Description="上市日期" />
- <Column Name="CreateDate" DataType="DateTime" Description="创建时间" />
- </Columns>
- <Indexes>
- <Index Columns="Name" />
- <Index Columns="StartDate" />
- </Indexes>
- </Table>
2. 股票历史日数据
其实在项目代码的 xml 文件表结构中, 还有一个历史信息, 就是一次性获取所有历史 Josn 文本存储, 避免重复抓取 Josn 数据结构很简单, 就不贴了
- <Table Name="StockDayData" Description="股票日数据" ConnName="stock_day">
- <Columns>
- <Column Name="ID" DataType="String" PrimaryKey="True" Description="编号 code + 日期" />
- <Column Name="Code" DataType="String" Description="股票编码" />
- <Column Name="StatDate" DataType="DateTime" Description="数据日期" />
- <Column Name="StartPrice" DataType="Double" Description="开盘价格" />
- <Column Name="EndPrice" DataType="Double" Description="收盘价格" />
- <Column Name="ChangePrice" DataType="Double" Description="涨跌金额" />
- <Column Name="ChangeRatio" DataType="Double" Description="涨跌幅度" />
- <Column Name="LowPrice" DataType="Double" Description="最低价格" />
- <Column Name="HighPrice" DataType="Double" Description="最高价格" />
- <Column Name="TotalHand" DataType="Int32" Description="总手" />
- <Column Name="TotalAmount" DataType="Double" Description="总金额(万)" />
- <Column Name="HandRate" DataType="Double" Description="换手率" />
- <Column Name="UpdateDate" DataType="DateTime" Description="更新日期" />
- </Columns>
- </Table>
3. 板块分类和股票板块信息
- <Table Name="GroupKind" Description="板块分类" ConnName="stock_base">
- <Columns>
- <Column Name="ID" DataType="String" PrimaryKey="True" Description="编码 url 相关" />
- <Column Name="Name" DataType="String" Master="True" Description="板块名称" />
- <Column Name="Kind" DataType="String" Description="分类 1. 行业, 2 地域, 3. 概念" />
- <Column Name="Total" DataType="Int32" Description="总数" />
- <Column Name="CreateDate" DataType="DateTime" Description="创建时间" />
- </Columns>
- <Indexes>
- <Index Columns="Kind" />
- <Index Columns="Name" />
- </Indexes>
- </Table>
- <Table Name="StockGroup" Description="股票板块信息" ConnName="stock_base">
- <Columns>
- <Column Name="ID" DataType="String" PrimaryKey="True" Description="编号 groupid+stockid" />
- <Column Name="GroupID" DataType="String" Description="板块 ID" />
- <Column Name="Kind" DataType="String" Description="分类 1. 行业, 2 地域, 3. 概念" />
- <Column Name="Code" DataType="String" Description="股票代码" />
- <Column Name="StockName" DataType="String" Description="股票名称" />
- <Column Name="CreateDate" DataType="DateTime" Description="创建时间" />
- </Columns>
- <Indexes>
- <Index Columns="GroupID" />
- <Index Columns="Code" />
- </Indexes>
- </Table>
4. 关键信息采集
下面我们把数据采集过程简单分析一下, 然后把源代码和数据库共享给大家套路很简单, 熟悉起来很快就可以搞定
我的博客中, 前几年, 写过好几篇关于 C# 数据采集的方法, 套路都比较通用, 主要是: HtmlAgilityPack + XCode
C#+HtmlAgilityPack+XPath 带你采集数据(以采集天气数据为例子)
开源分享 2011-2015 年全国城市历史天气数据库 Sqlite+C# 访问程序
HtmlAgilityPack 是. NET 下的一个强大的 HTML 解析类库, 支持用 XPath 配合上自带的 HAPExplorer, 很快就可以解决问题
具体使用可以参考上面 2 篇文章, 下面我们也会上实际代码
4.1 基础数据采集
先打开 http://quote.eastmoney.com/stocklist.html 右键, 源代码, 获取完整的 HTML 代码, 用 HAPExplorer 工具找到上海和深圳列表的位置, 如下图:
我们发现上海和深圳交易所的列表分别在下面位置:
- /html[1]/body[1]/div[9]/div[2]/div[1]/ul[1]
- /html[1]/body[1]/div[9]/div[2]/div[1]/ul[2]
还往下一个层级就是 li 标签列表, 在 HtmlAgilityPack 中有现成的方法获取整个列表, 并进行解析, 如下面代码:
- /// 获取所有股票代码和名称基础信息
- public static void ReadAllStockBaseInfo()
- {
- // 上海:/html[1]/body[1]/div[9]/div[2]/div[1]/ul[1]
- // 下级是 li 列表 ,Text 值就是股票名称和代码 XXX()
- // 深圳: 上海:/html[1]/body[1]/div[9]/div[2]/div[1]/ul[2]
- string url = @"http://quote.eastmoney.com/stocklist.html";
- HtmlWeb htmlweb = new HtmlWeb();
- htmlweb.OverrideEncoding = Encoding.GetEncoding(936);
- HtmlDocument doc = htmlweb.Load(url);
- Dictionary<string, string> dic = new Dictionary<string, string>()
- {
- {"上海",@"/html[1]/body[1]/div[9]/div[2]/div[1]/ul[1]" },
- {"深圳",@"/html[1]/body[1]/div[9]/div[2]/div[1]/ul[2]" }
- };
- #region 获取
- Dictionary<String, StockBaseInfo> list = new Dictionary<string, StockBaseInfo>();
- foreach (var item in dic)
- {
- // 获取所有子节点
- var res = doc.DocumentNode.SelectSingleNode(item.Value).SelectNodes(@"li");
- if (res.Count > 0)
- {
- foreach (var node in res)
- {
- // 获取名称和代码
- var name = node.InnerText.Trim();
- if (name.IsNullOrEmpty()) continue;
- var str = name.Split('(', ')');
- if (str.Length < 2) continue;
- StockBaseInfo et = new StockBaseInfo()
- {
- Code = str[1],
- Name = str[0],
- Exchange = item.Key,
- StartDate = new DateTime(2000, 1, 1),
- CreateDate = DateTime.Now
- };
- if(!list.ContainsKey(et.Code))
- {
- list.Add(et.Code,et);
- }
- }
- }
- }
- list.ToValueArray().Insert(true);
- #endregion
- }
获取到子节点后, 解析名称, 然后用批量 Insert 到数据库
用 XCode 默认都是使用 Sqlite 数据库, 轻量级, 非常方便, 数据库表结构都是自动新建
4.2 股票历史数据
我们使用 2.2 节中提到的接口, 如下, 配合前面采集到的所有股票基础数据, 就可以便利进行历史数据抓取了, 说一下, 这个接口很给力, 速度相当快我是从 2000 年 1 月 1 日开始采集的, 截止时间是 2018 年 2 月 10 日 (放假闲的你懂的) 历史 json 数据按股票 ID 单独保存, 后面写了一个转换程序单独从历史数据库转换即可
http://q.stock.sohu.com/hisHq?code={code}&start={start}&end={end},{code}替换为股票代码, 大陆股票代码加前缀 cn_
这里对老司机来说, 其实没多少难度, 就是拼接 URL, 请求获取 json 数据, 然后解析 json 格式, 我解析用了 Newlife 的 JsonParser, 用起来很简单有空我单独讲一下, 就是把 Json 用字典和 List<Object > 保存下来, 知道结构后, 直接强制转换和取值即可上代码:
- public static void GetHistoryFromWeb(string stockCode, DateTime start, DateTime end,string type="cn")
- {
- string url = @"http://q.stock.sohu.com/hisHq?code={3}_{0}&start={1}&end={2}".F(stockCode, start.ToString("yyyyMMdd"),
- end.ToString("yyyyMMdd"),type);
- WebClientX client = new WebClientX();
- client.Timeout = 1000 * 120;
- var text = client.GetHtml(url);
- var doc = new HtmlDocument();
- doc.LoadHtml(text);
- var value = doc.DocumentNode.InnerText;
- var et = new StockHisText()
- {
- Code = stockCode,
- Start = start,
- End = end,
- HisText = value
- };
- try
- {
- if (type == "zs") et.Code = "{0}_{1}".F("Index",et.Code);// 加前缀区分
- et.Insert();
- }
- catch(Exception err)
- {
- XTrace.WriteException(err);
- }
- }
上面是获取单个股票其指定日期范围内的历史数据, 直接到历史表, 下面是解析部分, 外面套的循环就不贴代码了, 可以下载源代码看
- public static void PraseHistoryData()
- {
- var all = FindAll();
- int index = 1;
- Parallel.For(0, all.Count, new ParallelOptions() { MaxDegreeOfParallelism = 1 }, i =>
- {
- XTrace.WriteLine("进度:{0}/{1}",index ++,all.Count);
- #region 单个文本解析
- JsonParser jp = new JsonParser(all[i].HisText);
- var decode = (List<object>)jp.Decode();
- if (decode.Count < 1) return;
- var main = (Dictionary<string, object>)decode[0];// 字典
- if (main.ContainsKey("hq"))
- {
- var obj = (List<object>)main["hq"];
- if (obj.Count > 0)
- {
- List<StockDayData> res = new List<StockDayData>();
- foreach (var item in obj)
- {
- #region 单条记录解析
- //item 是一个 10 个元素的数组
- // 日期, 今开价格, 今天收盘价格, 涨跌金额, 涨跌幅度, 最低价格, 最高价格, 总手, 总金额(万), 换手率
- //"2018-02-09", "31.46", "31.46", "2.86", "10.00%", "31.46", "31.46", "303", "95.32", "0.15%"
- var list = (List<object>)item;
- StockDayData sd = new StockDayData()
- {
- Code = all[i].Code,
- StatDate = list[0].ToDateTime(),
- StartPrice = list[1].ToDouble(),
- EndPrice = list[2].ToDouble(),
- ChangePrice = list[3].ToDouble(),
- ChangeRatio = ((string)list[4]).Replace("%", "").ToDouble(),
- LowPrice = list[5].ToDouble(),
- HighPrice = list[6].ToDouble(),
- TotalHand = list[7].ToInt(),
- TotalAmount = list[8].ToDouble(),
- HandRate = ((string)list[9]).Replace("%", "").ToDouble(),
- UpdateDate = DateTime.Now
- };
- sd.ID = "{0}_{1}".F(sd.Code, sd.StatDate.ToString("yyyyMMdd"));
- #endregion
- res.Add(sd);
- }
- res.Save(true);
- }
- }
- #endregion
- });
- }
我的代码里面有直接把历史数据解析分库存储的, 方法在股票历史文本数据. Biz.cs 文件的, PraseHistoryDataV2 方法中分库方法非常简单, 保存之前修改一下链接即可
5. 源代码和数据库
代码很简单丑陋, 不要吐槽, 代码如下: https://github.com/asxinyu/Stock (兄台, 搞点数据不容易, 点个赞或者给个 Star 一下吧)
基础数据 Sqlite 数据库: https://pan.baidu.com/s/1qZJIy8s, 密码: 61e3
2000 年到 2018 年历史 Json 源数据: https://pan.baidu.com/s/1jIY70bG, 密码: cmpw
2000 年到 2018 年日历史数据 Sqlite 文件: https://pan.baidu.com/s/1eTxcjdC 密码: ujbn
Sqlite 很好玩, 强烈推荐工具, navicat, 可以去 CSDN 找一个破解版下载玩玩
来源: https://www.cnblogs.com/asxinyu/p/dotnet_stock_data_design.html