简介:一份面向VC6.0开发者的腾讯股票实时行情数据获取源码工程,解决在老旧Windows开发环境下通过HTTP协议调用腾讯股票接口并解析返回数据的需求,适合金融数据抓取初学者或维护遗留项目的程序员参考。压缩包共27个文件,约1.79MB,包含头文件、C++源文件、项目工程文件(dsw/dsp)、可执行exe及调试文件(pdb/obj)等,可直接打开编译运行,也可对照源码学习WinInet网络编程流程。已有4442人学习。工程演示了核心步骤:初始化WinInet、创建HTTP请求、发送GET请求、循环读取响应并解析,涉及开盘价、收盘价、最新价等关键字段的提取;同时附有ReadMe说明和对话框资源,便于理解程序结构与界面设计。对于想了解VC6.0下网络通信实现或迁移旧代码至现代环境的开发者,这套源码提供了完整可运行范例。 说实话,我这次接到的任务有点“古早味”——客户要求在一台只装了VC6.0的旧电脑上,写一个拉取腾讯股票接口实时行情的小工具。一开始我以为会很痛苦,毕竟VC6.0这玩意连自动补全都费劲,更别说拿来写网络程序。但真正动手后发现,腾讯股票接口自身极其简单,配合VC6.0的WinInet网络封装,反而能做到零依赖、秒开速跑,整个过程踩了几个旧时代特有的坑,今天把完整思路和可用的vc6.0源代码分享出来。
1. 腾讯行情接口的真实模样:一次抓包与协议拆解
先说接口本身。腾讯股票接口的核心URL非常简单,不支持任何签名鉴权机制,直接用HTTP GET请求就能拿到数据:
http://qt.gtimg.cn/q=sh600000,sz000001后面的参数是股票代码,规则是“交易所前缀+股票数字代码”,sh代表上交所,sz代表深交所。多个代码用英文逗号拼接,一次请求最多能拉几十只股票,响应速度快到可以忽略不计。实测下来,单次请求从我发出到拿到完整报文,基本在100毫秒以内。
请求返回的内容长这样:
v_sh600000="1~浦发银行~600000~10.25~10.30~10.20~123456~65432~58024~10.24~100~10.23~200~10.22~300~10.21~400~10.25~500~10.26~600~10.27~700~10.28~800~10.29~900~~20240415155445~-0.05~-0.48~10.31~10.18~10.25/123456/126501120~123456~12650~0.79~7.36~~10.31~10.18~0.13~2543.66~2543.66~0.13~10.25~10.26~-0.05~-0.48~10.25~10.24~1.26~1287890~128.78~0.6900~";这段返回的协议格式有几个关键特征,搞清楚了后面解析才不会懵:
- 每一行以
v_股票代码="开头,双引号结束,分号结尾。 - 双引号内部,所有字段用波浪号
~分隔。 - 字段顺序是固定的,但对新人极其不友好——因为字段非常多,数错一个位置后面全乱。
我针对常用字段整理了一个索引表,因为索引从0开始,网上很多人贴的索引数字其实有偏移,这里我实测校准过的版本:
| 索引 | 含义 | 说明 |
|---|---|---|
| 0 | 未知状态值 | 正常情况是1 |
| 1 | 股票名称 | 如“浦发银行” |
| 2 | 股票代码 | 如“600000” |
| 3 | 当前价格 | 最重要字段 |
| 4 | 昨收价 | 前收盘价 |
| 5 | 今开价 | 开盘价 |
| 6 | 成交量(手) | 注意单位是手 |
| 7 | 外盘 | 外盘成交量 |
| 8 | 内盘 | 内盘成交量 |
| 9 | 买一价 | 买盘第一档价格 |
| 10 | 买一量 | 买盘第一档挂单量 |
| 19 | 卖一价 | 卖盘第一档价格 |
| 20 | 卖一量 | 卖盘第一档挂单量 |
| 30 | 时间戳 | 格式如20240415155445 |
| 31 | 涨跌额 | 当前价-昨收价 |
| 32 | 涨跌幅 | 百分比数值 |
| 33 | 最高价 | 当日最高 |
| 34 | 最低价 | 当日最低 |
| 35 | 价格/成交量/成交额组合 | 斜杠分隔这三个数据 |
| 36 | 成交量(手) | 另一个维度的成交量 |
| 37 | 成交额(万) | 注意单位是万元 |
| 38 | 换手率 | 百分比 |
| 39 | 市盈率 | 动态市盈率 |
| 43 | 振幅 | 百分比 |
| 44 | 流通市值 | 单位是亿 |
| 45 | 总市值 | 单位是亿 |
| 46 | 市净率 | 市净率数值 |
这个表格建议直接存下来,我每次重新写行情工具都会翻出来对照。不过需要提醒的是,腾讯对字段顺序偶尔会做微调,写程序时不要硬编码绝对索引去取所有字段,取了前面几个核心字段就够用,这样即使后面字段有变化也不容易翻车。
2. VC6.0工程的全套实现:请求、解析、定时刷新
环境就不多纠结了,我是在Win10上用兼容模式跑VC6.0,SP6补丁打好之后编译完全没问题。新建一个基于对话框的MFC工程,然后在StdAfx.h里加上网络头文件:
#include <afxinet.h>这是VC6时代Windows下网络编程的标配,它封装了WinInet底层API,用CInternetSession和CHttpFile两个类就能完成HTTP请求,不需要额外引入libcurl这种第三方库,对老项目和裸奔部署环境来说非常友好。
核心请求函数我封装成了这样,接受股票代码,返回原始字符串:
CString GetTencentStockData(const CString& strCode) { CInternetSession session(_T("StockQuote/1.0")); CHttpFile* pFile = NULL; CString strResult; try { CString strUrl = _T("http://qt.gtimg.cn/q=") + strCode; pFile = (CHttpFile*)session.OpenURL(strUrl, 1, INTERNET_FLAG_RELOAD | INTERNET_FLAG_NO_CACHE_WRITE, NULL, 0); if (pFile) { CString strLine; while (pFile->ReadString(strLine)) { strResult += strLine; strResult += _T("\r\n"); } pFile->Close(); delete pFile; pFile = NULL; } } catch (CInternetException* pEx) { TCHAR szErr[256] = {0}; pEx->GetErrorMessage(szErr, 255); OutputDebugString(szErr); pEx->Delete(); if (pFile) { delete pFile; pFile = NULL; } session.Close(); return _T(""); } session.Close(); return strResult; }几个容易出错的地方,我逐个说明:
OpenURL的第四个参数我传了1(INTERNET_SERVICE_HTTP),这是告诉WinInet我们要打开的是HTTP资源。不同的服务类型对应的返回对象类型不一样,只有HTTP的OpenURL才会返回CHttpFile指针,所以必须要强转。INTERNET_FLAG_RELOAD表示强制从服务器重新下载,不读本地缓存,这对行情数据太关键了——不加这个,断线重连后可能拿到陈旧数据。
ReadString是逐行读取,腾讯接口返回的内容虽然长但就是一行,循环两次就完事。
解析函数反而是最需要小心的。VC6的CString有一个非常尴尬的缺陷:它没有Tokenize方法,这个方法是后来VC7才加的。网上流传的很多VC6代码会用AfxExtractSubString按逗号分割,但腾讯接口的分隔符是波浪号,所以干脆自己写一个拆分函数:
int SplitStringByChar(const CString& strSrc, TCHAR cSep, CStringArray& arrOut) { arrOut.RemoveAll(); if (strSrc.IsEmpty()) return 0; int nPos = 0; int nLen = strSrc.GetLength(); while (nPos < nLen) { int nNext = strSrc.Find(cSep, nPos); if (nNext < 0) { arrOut.Add(strSrc.Mid(nPos)); break; } arrOut.Add(strSrc.Mid(nPos, nNext - nPos)); nPos = nNext + 1; } return arrOut.GetSize(); }这个函数思路很朴素,就是循环找分隔符,找到就把中间这段截出来放进数组,直到字符串末尾。用Find而不是逐字符比较,是因为CString的Find内部有优化,速度比手写循环快不少。
有了拆分函数,解析腾讯返回数据就简单了。需要注意原始字符串开头有v_sh600000="这段前缀、结尾有双引号和分号,必须先把双引号内的内容抠出来,再按波浪号拆分:
BOOL ParseStockData(const CString& strRaw, StockInfo& info) { int nQuote1 = strRaw.Find(_T('"')); int nQuote2 = strRaw.Find(_T('"'), nQuote1 + 1); if (nQuote1 < 0 || nQuote2 < 0) return FALSE; CString strBody = strRaw.Mid(nQuote1 + 1, nQuote2 - nQuote1 - 1); CStringArray arr; if (SplitStringByChar(strBody, _T('~'), arr) < 35) return FALSE; info.strName = arr[1]; info.strCode = arr[2]; info.strPrice = arr[3]; info.strPreClose = arr[4]; info.strOpen = arr[5]; info.strChange = arr[31]; info.strChangePct = arr[32]; info.strHigh = arr[33]; info.strLow = arr[34]; info.strTime = arr[30]; return TRUE; }这里判断数组长度小于35就返回失败,是因为核心字段的索引至少到34,如果腾讯返回的字段数不够,说明协议有变或者请求异常,直接退出比硬解析安全得多。
定时刷新用MFC的定时器就可以实现,在初始化函数里SetTimer(1, 5000, NULL);,然后响应OnTimer,5秒刷新一次。对行情工具来说,5秒是股票软件的标准刷新间隔,不频繁也不延迟。这里需要提醒,千万别在OnTimer里直接调UpdateData(TRUE)之类的对话框刷新,最好用PostMessage把数据从函数里发回UI线程,否则界面会卡顿。
3. 界面显示与编码转换:别让中文变乱码
VC6.0默认创建的工程是MBCS(多字节字符集)编码,而腾讯接口返回的中文是GBK编码,这两者其实是兼容的。也就是说,你的CString直接用%s输出或者SetDlgItemText显示中文,不会出现乱码坑里说的那种情况。
但有个前提:必须保证整个工程编译时字符集没有切换成Unicode。VC6.0的工程属性里一旦设置了_UNICODE预处理宏,CString底层变成宽字符,再把腾讯返回的GBK字符串丢给SetDlgItemText就会显示成乱码。
如果你确实要用Unicode字符集,那得做一次GBK到Unicode的显式转换:
CString ConvertGBKToUnicode(const CString& strGBK) { if (strGBK.IsEmpty()) return _T(""); int nLen = MultiByteToWideChar(CP_ACP, 0, strGBK, -1, NULL, 0); if (nLen <= 0) return _T(""); CString strUnicode; MultiByteToWideChar(CP_ACP, 0, strGBK, -1, strUnicode.GetBuffer(nLen), nLen); strUnicode.ReleaseBuffer(); return strUnicode; }这里的CP_ACP代表当前系统的ANSI代码页,在中文Windows上就是GBK。如果你在英文系统上跑,应改为936强制指定GBK代码页,否则中文会变成问号。
界面布局上,我用了最简单的静态文本框加编辑框组合,股票名称显示在左边的GroupBox里,价格、涨跌额、涨跌幅各自占用一个编辑框。有人喜欢用ListView滚动显示多只股票,但那个在VC6里设置列头比较烦,群里面试时经常有人栽在这上面。对单只股票监控来说,直接SetDlgItemText更新文本最省事。
为了让界面更直观,我加了一个红色/绿色显示涨跌的小技巧。在对话框类里加一个OnPaint绘制函数,或者用一个Static控件的自绘,把涨跌数字颜色区分开。简化的做法是设置Static控件的文字颜色,在对话框的OnCtlColor里判断控件ID:
HBRUSH CStockQuoteDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) { HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor); if (pWnd->GetDlgCtrlID() == IDC_PRICE) { pDC->SetTextColor(RGB(255, 0, 0)); // 涨=红 } else if (pWnd->GetDlgCtrlID() == IDC_CHANGE) { pDC->SetTextColor(RGB(0, 0, 255)); // 跌=绿 } return hbr; }A股习惯是红涨绿跌,跟欧美市场相反,这个颜色逻辑别搞反了。如果追求更精细的控制,可以在OnCtlColor里根据当前涨跌值决定返回红色还是绿色,而不是固定一个ID一个颜色。
4. 我踩过的坑:VC6老环境、WinInet超时、字段错位
这个项目看起来简单,实际运行起来问题不少。下面列几个我真实踩过的坑,每个都花了不少时间去定位。
第一个坑是VC6.0在Win10上的安装和编译问题。标题里提到的“win10 vc6.0 setup is searching for installed components”我一开始就遇到了,安装程序卡死在搜索组件那一步。解决办法是右键setup.exe,属性里选“兼容性”标签,勾选“以兼容模式运行”,下拉选Windows XP (Service Pack 3),同时勾选“以管理员身份运行”。装完之后还需要装VC6 SP6补丁包,否则编译器对Win10环境有很多兼容性问题。另外,VC6的编辑器不支持代码自动补全,这是硬伤,装个Visual Assist X老版本能勉强用,但别指望它能像VS2019那样智能提示,能把函数签名列出来就已经谢天谢地了。
第二个坑是WinInet的请求超时设置。默认的OpenURL在对方服务器无响应时,可能要等到天荒地老才返回。行情软件最怕卡死,我给会话加上了超时控制。思路是先用InternetSetOption设置全局的连接超时、接收超时,再创建会话:
DWORD dwTimeout = 3000; // 3秒 InternetSetOption(NULL, INTERNET_OPTION_CONNECT_TIMEOUT, &dwTimeout, sizeof(dwTimeout)); InternetSetOption(NULL, INTERNET_OPTION_RECEIVE_TIMEOUT, &dwTimeout, sizeof(dwTimeout)); InternetSetOption(NULL, INTERNET_OPTION_SEND_TIMEOUT, &dwTimeout, sizeof(dwTimeout));注意InternetSetOption的第一个参数传NULL,表示设置的是全局默认值,这样所有新创建的WinInet会话都会继承这个超时时间。实测下来,断网时OpenURL会在3秒左右抛出异常,程序不会假装死机。不过,这个设置会影响整个进程的所有网络请求,如果你同时还有其他网络调用,要评估影响范围。
第三个坑是代码字段和返回字段对不上。我最早按别的文章里的索引去解析数据,发现取出来的“现价”其实是“昨收价”,差一个字段。原因就是腾讯接口不同时期返回的字段数量略有差异,而网上文章大多没标明自己抓包抓的是哪一天的版本。稳妥做法是:在自己的代码里先打印整段返回报文,数清楚字段位置再写解析。我上面那个索引表是最近实测的记录,但如果你在别的项目里看到不同的索引,也要以实际抓包为准。
第四个坑是OpenURL请求多个股票时的性能问题。如果一只股票请求一次,循环50只就发50次HTTP请求,速度慢且容易被腾讯临时限制。正确做法是把所有代码拼到一个URL里,一次请求拉回所有股票数据,返回的报文按行分割,每行对应一只股票。我实测拉30只股票,单次请求比循环请求快了一倍还多,而且对接口的负担小很多。
第五个坑是UI线程的阻塞问题。如果不是用定时器而是用循环去刷新数据,千万别把Sleep写在界面线程里,否则整个对话框会无响应大半秒,用户体验极差。更好的做法是开一个工作线程拉数据,然后PostMessage给主窗口通知更新。VC6里开线程用AfxBeginThread即可,注意线程里不要碰任何控件句柄。
5. 多股票轮询与日志记录:把这个工具做成能用的状态
单只股票的行情工具做出来之后,总觉得不够实用,我继续扩展了三个功能,让这个程序从“能运行”变成“能日常使用”。
第一是多股票自选列表。用一个ListBox或CMarkList控件列出所有自选股代码,点击某一行,右侧就显示对应的个股详情。这样每次启动程序,只需加载一次自选列表,定时器触发时一次请求拉全部数据,刷新所有显示。代码结构上没有大改动,只是把原来的单股票解析函数循环调用即可。唯一的注意点是:处理完一行数据后要立即更新对应的界面控件,不要在全部解析完再统一刷新,否则用户看着界面会感觉卡顿。
第二是记录日志文件。行情工具跑在无人值守的机器上时,客户最担心的就是它哪天悄悄罢工。我在程序里加了一个日志输出,每次请求、每次解析失败、每次网络异常都会追加写一行到quote_log.txt。这样可以事后排查问题,比如“为什么昨天下午行情没更新”这类问题,看日志发现是网络断开了。日志文件记得按天切割,不然时间长了文件会变得很大。
第三是报价提醒。当现价超过预设的阈值时,程序弹出一个消息框或者播放一段提示音。实现上是把解析出的价格转成double类型,然后和设置好的上下限比较。这里就体现出前面字段解析做扎实的重要性——之前有个同事的提醒程序一直不触发,查了半天发现他把“现价”和“均价”的字段弄反了,阈值设得再准也没用。
这个工具的扩展空间还很大,比如对接MySQL数据库做历史行情存档、用图表控件画分时图、甚至通过邮件发送预警通知。腾讯接口本身不限制调用频率,5秒拉一次对个人使用完全够用。如果你有更复杂的回测需求,建议不要再底层硬啃VC6,直接把取数的这部分逻辑用C++写成命令行工具,然后交给Python调用,两头都省事。
我个人的体会是,这类老接口配老编译器,看似过时,其实是稳定性的代名词。腾讯股票接口的文档虽不公开,但用它做个人行情监控,比很多带鉴权、带加密的新版数据源省心得多。而VC6.0写出来的程序,在XP甚至Win2003的机器上都能零依赖运行,这种“老而弥坚”的优势,在维护存量系统的场景里还是非常有价值的。
本文还有配套的精品资源,点击获取