C++使用libcurl库返回数据为乱码的原因分析与解决方案:
广告时间: 我新上了一门C语言课程 <C 语言编程核心突破> , 有入门需求的同学可以看看.
前言:
最近看到一个问题: C++使用libcurl库返回数据出现中文乱码, 这个应该是很常见的, 但题主的描述是错误的, 并非中文乱码, 而是全部乱码.
尝试解决一下, 毕竟libcurl如此经典, 基本做网络的都离不开, 但相关文档貌似没有明确的提供相关问题的解决方案, 或者说没有中文的解决方案.
问题描述
利用 libcurl 进行C++开发, 访问网页一般可正确返回内容, 比如:
www.baidu.com
, 访问一般的XML也可以正常的返回, 比如:
https://www.w3schools.com/xml/simple.xml
但是有些就比较诡异, 比如:
https://comment.bilibili.com/1057102166.xml
返回的内容完全是乱码, 连英文都是乱码.
��o]G�7��~�D�:]���Ձ?8A� �8I� �ױ0�5�=3��)�"e�JQ�%�f�(Y��ŋ�O��k����/U�H����g��<�`�M�U}���UuU����?��ܟz_s��W����'s��:��'�����������'��OO~�i��ַ����S����Ǭ}��s��O����o��~�0��_��~��'�F��O����O�l��/N~y��ϼ�_�ӧ�|���'�f��ӯ{�/�ϯZ_�����oN���N����?}�O�ϟv���>�>3�9'�?B��?D�sœ�B��`o|�>�1|�'��66��?M~�*6V�����woF�gG�O�[��[�����v~�ѧ��-y��0XcNx���1;L�1FcO�̽���m!�g�
原因分析:
通常第一时间我们会想到可能是GBK和utf-8编码的问题, 但这个并不影响英文内容, 所以不成立.
排除了编码问题, 还有什么问题能引起乱码呢?
我能想到的就是源文件出了问题, 排除方法也很简单, 直接用浏览器打开看看, 结果是正常的:
<i>
<chatserver>chat.bilibili.com</chatserver>
<chatid>1057102166</chatid>
<mission>0</mission>
<maxlimit>500</maxlimit>
<state>0</state>
<real_name>0</real_name>
<source>k-v</source>
<d p="95.01700,1,25,16777215,1679053310,0,79c7792f,1274750575577579008,10">真正驯服了尾巴的猫就是这么强大</d>
<d p="42.47200,5,25,15138834,1679049902,0,88e7f73f,1274721985825661184,10">不敢相信1000个人在听猫叫</d>
<d p="56.18500,1,25,16777215,1679048871,0,a577d0c0,1274713336441193216,10">希望下次可以唱雾都雾里七个八</d>
<d p="78.41200,5,25,15138834,1679050479,0,a39d4f40,1274726828300470784,10">这人直接融化了是吧(doge)</d>
<d p="19.67000,5,25,15138834,1679047971,0,de508db1,1274705786559662592,10">主唱大人!</d>
<d p="117.80700,5,18,15138834,1679047962,0,f10a42f8,1274705711632589568,10">为什么这时嘴里没飞进去小虫子doge</d>
<d p="87.90900,5,25,15138834,1679047934,0,d2c8cbb2,1274705480946020608,10">鼓手摸鱼(doge)</d>
<d p="28.68000,5,25,15138834,1679048869,0,66945478,1274713325569447168,10">邻居:很难想象配音者的精神状态</d>
<d p="110.07700,1,25,16777215,1679049083,0,4534326,1274715118617495552,10">比内鱼大部分偶像唱得好(狗头)</d>
<i/>
这就说不通了, 没理由浏览器可用而libcurl的C++程序不可用, 于是用curl命令行进程测试, 结果是:
E:\clangC++>curl https://comment.bilibili.com/1057102166.xml
Warning: Binary output can mess up your terminal. Use "--output -" to tell
Warning: curl to output it to your terminal anyway, or consider "--output
Warning: <FILE>" to save to a file.
返回的是二进制文件, 为了看看究竟返回的是什么, 我用管道将其导入文件:
E:\clangC++>curl https://comment.bilibili.com/1057102166.xml > E:\clangC++\test.txt
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 24059 0 24059 0 0 41325 0 --:--:-- --:--:-- --:--:-- 41409
结果是:
��o]G�7��~�D�:]���Ձ?8A� �8I� �ױ0�5�=3��)�"e�JQ�%�f�(Y��ŋ�O��k����/U�H����g��<�`�M�U}���UuU����?��ܟz_s��W����'s��:��'�����������'��OO~�i��ַ����S����Ǭ}��s��O����o��~�0��_��~��'�F��O����O�l��/N~y��ϼ�_�ӧ�|���'�f��ӯ{�/�ϯZ_�����oN���N����?}�O�ϟv���>�>3�9'�?B��?D�sœ�B��`o|�>�1|�'��66��?M~�*6V�����woF�gG�O�[��[�����v~�ѧ��-y��0XcNx���1;L�1FcO�̽���m!�g�
依然是乱码.
回想为什么是二进制文件, 什么情况下可能是二进制文件?
数据压缩, 这是最大可能的方案, 因为浏览器可以正常查看, 说明文件内容一定是可被解析的, 但curl命令行返回的数据不能正常解析, 是二进制, 那么基本可以断定就是压缩的问题.
解决方案:
先用命令行进行验证, 我们看看curl的帮助:
E:\clangC++>curl -h
Usage: curl [options...] <url>
-d, --data <data> HTTP POST data
-f, --fail Fail fast with no output on HTTP errors
-h, --help <category> Get help for commands
-i, --include Include protocol response headers in the output
-o, --output <file> Write to file instead of stdout
-O, --remote-name Write output to a file named as the remote file
-s, --silent Silent mode
-T, --upload-file <file> Transfer local FILE to destination
-u, --user <user:password> Server user and password
-A, --user-agent <name> Send User-Agent <name> to server
-v, --verbose Make the operation more talkative
-V, --version Show version number and quit
This is not the full help, this menu is stripped into categories.
Use "--help category" to get an overview of all categories.
For all options use the manual or "--help all".
这里并没有压缩选项, 那我们再查看详细文档, 我找到了这一项:
--compressed Request compressed response
那么用命令行试一下:
E:\clangC++>curl --compressed https://comment.bilibili.com/1057102166.xml
curl: option --compressed: the installed libcurl version doesn't support this
curl: try 'curl --help' for more information
没有这个功能支持, 看看我的版本:
E:\clangC++>curl -V
curl 8.0.1 (Windows) libcurl/8.0.1 Schannel WinIDN
Release-Date: 2023-03-20
Protocols: dict file ftp ftps http https imap imaps pop3 pop3s smtp smtps telnet tftp
Features: AsynchDNS HSTS HTTPS-proxy IDN IPv6 Kerberos Largefile NTLM SPNEGO SSL SSPI threadsafe Unicode UnixSockets
8.0.1版本应该挺高了, 还是不行, 那么升级, 通过msys2进行curl库安装, 这个就不详细介绍了
$ pacman -S mingw-w64-clang-x86_64-curl
注意一下, 这个库是有依赖的, 所以需要安装相应的依赖
$ pacman -S mingw-w64-clang-x86_64-libssh2
然后测试:
E:\clangC++>E:\msys64\clang64\bin\curl --compressed https://comment.bilibili.com/1057102166.xml
<?xml version="1.0" encoding="UTF-8"?><i><chatserver>chat.bilibili.com</chatserver><chatid>1057102166</chatid>
<mission>0</mission>
<maxlimit>500</maxlimit>
<state>0</state>
<real_name>0</real_name>
<source>k-v</source>
<d p="95.01700,1,25,16777215,1679053310,0,79c7792f,1274750575577579008,10">真正驯服了尾巴的猫就是这么强大</d>
<d p="42.47200,5,25,15138834,1679049902,0,88e7f73f,1274721985825661184,10">不敢相信1000个人在听猫叫</d>
<d p="56.18500,1,25,16777215,1679048871,0,a577d0c0,1274713336441193216,10">希望下次可以唱雾都雾里七个八</d>
<d p="78.41200,5,25,15138834,1679050479,0,a39d4f40,1274726828300470784,10">这人直接融化了是吧(doge)</d>
<d p="19.67000,5,25,15138834,1679047971,0,de508db1,1274705786559662592,10">主唱大人!</d>
<d p="117.80700,5,18,15138834,1679047962,0,f10a42f8,1274705711632589568,10">为什么这时嘴里没飞进去小虫子doge</d>
<d p="87.90900,5,25,15138834,1679047934,0,d2c8cbb2,1274705480946020608,10">鼓手摸鱼(doge)</d>
<d p="28.68000,5,25,15138834,1679048869,0,66945478,1274713325569447168,10">邻居:很难想象配音者的精神状态</d>
<d p="110.07700,1,25,16777215,1679049083,0,4534326,1274715118617495552,10">比内鱼大部分偶像唱得好(狗头)</d>
<d p="119.89400,4,25,15138834,1679070255,0,eacc5927,1274892716781634304,10">*听完这首歌使你充满了决心</d>
OK, 看来问题已经接近解决了, 那么如何实现代码呢?
继续查文档: CURLOPT_ACCEPT_ENCODING;
大致内容:
CURLOPT_ACCEPT_ENCODING explained
Name
CURLOPT_ACCEPT_ENCODING - automatic decompression of HTTP downloads
Synopsis
#include <curl/curl.h>
CURLcode curl_easy_setopt(CURL *handle, CURLOPT_ACCEPT_ENCODING, char *enc);
也就是需要在调取网页内容前加一句
curl_easy_setopt(CURL *handle, CURLOPT_ACCEPT_ENCODING, char *enc);
处理后的代码:
#include <curl/curl.h>
#include <fstream>
#include <iostream>
#include <string>
// 数据处理回调函数
auto writeCallback(char *contents, size_t size, size_t nmemb,
std::string *response) -> size_t
{
size_t totalSize = size * nmemb;
response->append(contents, totalSize);
return totalSize;
}
auto main() -> int
{
// 初始化
curl_global_init(CURL_GLOBAL_DEFAULT);
// 创建句柄
CURL *curlHandler = curl_easy_init();
if (curlHandler != nullptr)
{
// 设置SSL验证, 可以不验证, 但不推荐
// curl_easy_setopt(curlHandler, CURLOPT_SSL_VERIFYHOST, 0L);
// curl_easy_setopt(curlHandler, CURLOPT_SSL_VERIFYPEER, 0L);
// SSL验证, 需要给出cacert.pem的路径
curl_easy_setopt(curlHandler, CURLOPT_SSL_VERIFYHOST, 2L);
curl_easy_setopt(curlHandler, CURLOPT_SSL_VERIFYPEER, 1L);
curl_easy_setopt(curlHandler, CURLOPT_CAINFO,
"answer\\C++\\cacert.pem");
// 设置要请求的URL
curl_easy_setopt(curlHandler, CURLOPT_URL,
"https://comment.bilibili.com/1057102166.xml");
// "https://www.w3schools.com/xml/simple.xml");
// curl_easy_setopt(curlHandler, CURLOPT_URL, "https://www.baidu.com");
// 设置内容解压
curl_easy_setopt(curlHandler, CURLOPT_ACCEPT_ENCODING, "");
// 设置回调函数,用于处理获取到的网页数据
std::string response;
curl_easy_setopt(curlHandler, CURLOPT_WRITEFUNCTION, writeCallback);
curl_easy_setopt(curlHandler, CURLOPT_WRITEDATA, &response);
// 发起请求
CURLcode res = curl_easy_perform(curlHandler);
if (res != CURLE_OK)
{
std::cerr << "请求失败,错误代码: " << res << std::endl;
}
else
{
// 内容输出到文件
std::ofstream file("answer\\C++\\test271.txt");
file.write(response.c_str(),
static_cast<long long>(response.size()));
file.close();
}
// 清理curl句柄
curl_easy_cleanup(curlHandler);
}
// 清理全局curl资源
curl_global_cleanup();
return 0;
}
验证成功.
总结:
C++对于字符编码和各种解码, 需要程序员付出极大的耐心, 在CSDN上的C/C++问答, 近乎每周都有乱码问题, 然而我在回答了众多问题, 觉得可以解决近乎所有乱码代码的情况下, 仍然会有本文这种新情况出现.
所以, 永远不要有一劳永逸的想法, 每解决一个问题, 都会产生一个新的问题, 这才是程序员的宿命.