C++编程入门——用curl下载深交所股票基本概况数据

上一篇文章《C++编程入门——HTTP爬虫初步,准备依赖库curl》介绍了如何使用vcpkg安装curl库,本篇文章将在其基础上介绍如何使用curl下载HTTP协议传输的文件。

深交所股票基本概况数据是公开数据,可以直接从深圳证券交易所(简称深交所)的官方网站上找到下载链接,数据下载页的地址是:

股票数据-基本概况https://www.szse.cn/market/stock/indicator/index.html在页面右上角可以找到下载链接,如下图所示:

准备环境

请按照 《C++编程入门——HTTP爬虫初步,准备依赖库curl》的指引准备curl库。

准备工程

使用Visual Studio创建控制台应用程序,工程名填写“StockDataDownloader”,创建成功后如下图所示:

编写代码

把“StockDataDownloader.cpp”的内容改为下面的代码,记得一行一行的手动敲,不要复制粘贴。敲代码的过程其实是在积累经验,经验足够才能“升级”。

#include <fstream>
#include <curl/curl.h>

// 申明用于下载数据的函数
bool downloadData(const char* url, const char* outputFilePath);

int main()
{
	const char* url = "https://www.szse.cn/api/report/ShowReport?SHOWTYPE=xlsx&CATALOGID=1803_after&TABKEY=tab1&txtQueryDate=2025-04-21&random=0.1151430633896432";
	const char* outputFilePath = "D:\\StockDatasSZ.xlsx";

	if (downloadData(url, outputFilePath))
	{
		printf("下载成功,文件已保存在“%s”。\n", outputFilePath);
		return 0;
	}
	else
	{
		printf("下载失败!\n");
		return -1;
	}
}

// 回调函数,由curl自动调用,用于将下载的数据写入文件
size_t onDataArrived(void* contents, size_t size, size_t nmemb, void* userp)
{
	// 把第72行传递过来的参数转换成正确的类型
	std::ofstream* outputFile = (std::ofstream*)userp;
	if (outputFile == nullptr)
	{
		return 0;
	}
	// 向文件写入数据
	outputFile->write((const char*)contents, size * nmemb);
	// 检查是否写入失败
	if (outputFile->fail())
	{
		// 失败了返回0,curl会停止数据传输
		return 0;
	}
	// 成功了返回已写入的字节数,curl会继续传输
	return size * nmemb;
}

// 实现用于下载数据的函数
bool downloadData(const char* url, const char* outputFilePath)
{
	// 打开磁盘文件,收到数据时好直接写入文件
	std::ofstream outputFile(outputFilePath, std::ios::binary);
	if (outputFile.is_open() == false)
	{
		printf("打开文件“%s”失败。\n", outputFilePath);
		return false;
	}

	// 初始化curl
	curl_global_init(CURL_GLOBAL_DEFAULT);
	// 创建一个HTTP请求
	CURL* request = curl_easy_init();
	if (request == nullptr)
	{
		printf("初始化curl失败。\n");
		curl_global_cleanup();
		return false;
	}

	// 设置HTTP请求的URL
	curl_easy_setopt(request, CURLOPT_URL, url);
	// 设置接收数据的回调函数,收到数据时curl会自动调用该函数
	curl_easy_setopt(request, CURLOPT_WRITEFUNCTION, onDataArrived);
	// 设置传递给回调函数的参数
	curl_easy_setopt(request, CURLOPT_WRITEDATA, &outputFile);

	// 执行请求
	CURLcode res = curl_easy_perform(request);
	if (res != CURLE_OK)
	{
		printf("下载“%s”失败,错误信息:%s\n",
			url,
			curl_easy_strerror(res));
		curl_easy_cleanup(request);
		curl_global_cleanup();
		return false;
	}

	// 清理,释放curl内部占用的内存
	curl_easy_cleanup(request);
	curl_global_cleanup();

	return true;
}

运行程序

按“F5”或通过菜单“调试->开始调试”启动程序,在控制台中输出生成是否成功的提示,如下图:

如果下载成功,在D:盘中会保存有一个名为“StockDatasSZ.xlsx”的文件,它是xslx类型的文件,用Excel或者WPS可以打开。用Excel打开后的效果类似下图,软件版本不同,外观可能存在些许差异,但数据应该是一样的。

涨知识

main函数返回值

main函数是有返回值的,返回值的类型是int。main函数的返回值是返回给操作系统的,通常约定成功返回0,失败返回非0,非0值通常被认为是错误代码。

回调函数

代码第25行实现了回调函数onDataArrived,并且在70行通过curl_easy_setopt传递给了curl。回调函数的意义就在于当curl收到数据的时候,它可以调用我们实现的函数,来接收数据。为了让curl知道我们的回调函数,必须通过调用curl_easy_setopt把回调函数的地址传递给curl。

void*类型

代码第25行onDataArrived函数的最后一个参数“userp”是void*类型,“void*”是void类型的指针。在C++中所有的指针都保存的是内存地址,因此void*中也保存的是内存地址,用void表示这块内存中的数据类型是“未知”的,这时候程序员应该知道这块内存中保存的是什么类型的数据。

在本例中,userp参数的值是代码第72行通过curl_easy_setopt传递给onDataArrived的,因此它的类型是“std::ofstream*”。所以在代码第28行,我们可以安全的把userp强制转换成std::ofstream*类型。

强制类型转换不能乱用,只能用来把指针转换成它“本来”的类型,比如下面的用法就是错误的,会造成程序崩溃:

const char* str = "abc";
// 错误的转换
int* num = (int*)str;
*num = 999;

在实际工作中,void*通常用来传递任意类型的指针,因为指针本质上是内存地址,所以通过void*传递的实际上是内存地址。需要操作数据时,再把void*传递的内存地址转换成它本来的类型进行操作。

既然所有指针都是内存地址那么为什么不用int*、char*来传递内存地址呢?这是因为void和int、char这些类型不一样,void不能被直接操作,不能赋值,也不能取值。用void*传递内存地址表示你不能直接操作这块内存,得先弄清楚这块内存里面是什么类型的数据,才能进行操作。这样就能避免用错误的类型操作指针,造成程序崩溃。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

晓云云

阿云辛苦了,请你喝杯咖啡~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值