在C ++中将cURL内容结果保存到一个字符串中

54
int main(void)
{
  CURL *curl;
  CURLcode res;

  curl = curl_easy_init();
  if(curl) {
    curl_easy_setopt(curl, CURLOPT_URL, "http://www.google.com");
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
    res = curl_easy_perform(curl);
    curl_easy_cleanup(curl);
  }
  _getch();
  return 0;
}

string contents = "";

我想将curl获取到的HTML内容保存在一个字符串中,该怎么做呢? 这是一个愚蠢的问题,但不幸的是,在C++的curl示例中我找不到任何地方。 谢谢!

8个回答

120

您需要使用CURLOPT_WRITEFUNCTION来设置写入的回调函数。我现在无法测试编译此代码,但该函数应该类似于以下内容:

static std::string readBuffer;

static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp)
{ 
    size_t realsize = size * nmemb;
    readBuffer.append(contents, realsize);
    return realsize;
}

然后通过以下方式调用:

readBuffer.clear();
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
// ...other curl options
res = curl_easy_perform(curl);

调用后,readBuffer应该包含您的内容。

编辑:您可以使用CURLOPT_WRITEDATA传递缓冲字符串,而不是将其设置为静态变量。在这种情况下,我只是为了简单起见将其设置为静态变量。除了上面链接的示例之外,此处还有一个很好的页面用于解释选项。

编辑2:按要求,这是一个完整的工作示例,没有静态字符串缓冲区;

#include <iostream>
#include <string>
#include <curl/curl.h>


static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp)
{
    ((std::string*)userp)->append((char*)contents, size * nmemb);
    return size * nmemb;
}

int main(void)
{
  CURL *curl;
  CURLcode res;
  std::string readBuffer;

  curl = curl_easy_init();
  if(curl) {
    curl_easy_setopt(curl, CURLOPT_URL, "http://www.google.com");
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);
    res = curl_easy_perform(curl);
    curl_easy_cleanup(curl);

    std::cout << readBuffer << std::endl;
  }
  return 0;
}

嗨,Joachim,你能发布一个完整的代码吗?因为我无法让它正常工作。还有,我在哪里可以像“cout << readBuffer;”这样做一些事情,因为我想能够解析HTML以检索重要数据。如果可以的话,请发布一个完整的代码,这样我就可以使其正常工作了。 - Grego
@Grego 添加了一个(在Linux编译)的示例。 - Joachim Isaksson
2
重要提示:如果您在应用程序中使用多个线程,必须添加curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);否则会出现无法解释的段错误。 - B.S.
真的很奇怪。这行代码导致程序锁死。curl = curl_easy_init();即使它没有被调用。这怎么可能? - user3217883
不要忘记检查 curl_easy_perform 返回的 res 值。如果没有遇到错误,它将返回 CURLE_OK。 - anegru

5
使用“新”的C++11 Lambda功能,可以用几行代码完成此操作。
#ifndef WIN32 #define __stdcall "" #endif //For compatibility with both Linux and Windows
std::string resultBody { };
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resultBody);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, static_cast<size_t (__stdcall *)(char*, size_t, size_t, void*)>(
    [](char* ptr, size_t size, size_t nmemb, void* resultBody){
        *(static_cast<std::string*>(resultBody)) += std::string {ptr, size * nmemb};
        return size * nmemb;
    }
));

CURLcode curlResult = curl_easy_perform(curl);
std::cout << "RESULT BODY:\n" << resultBody << std::endl;
// Cleanup etc

请注意,需要使用__stdcall转换以符合C调用约定(cURL是一个C库)。

1
据我所知,__stdcall是Windows专用的东西,在Linux环境中你可以放心地忽略它: static_cast<size_t (*)(char*, size_t, size_t, void*)> 也许正确的方法是,如果你担心跨平台问题,那么就使用一个宏: #ifndef WIN32 #define __stdcall "" #endif - kotakotakota
1
你说得完全正确,我完全忽略了那个。我已经更新了答案。 - Mark Laagland
抱歉,注释中的格式混乱了。#define#endif应该有新行。 - kotakotakota
你不能这样做。Lambda 函数只在 curl_easy_setopt() 的生命周期内存在,并且会在 curl_easy_perform() 之前被销毁。它会随机崩溃。 - Sasq
我错了。类似的结构对我来说崩溃了,但只是因为我没有转换lambda。应该足够将其转换为void*,因为curl不关心类型,我们只需要进行lambda到函数指针的转换即可。 - Sasq
我会告诉你一个我刚学到的弥补方法:不用整个函数,只需在 lambda 前面加上一个 + 符号。 - Sasq

5

在我的博客上,我已经发布了一个简单的封装类来执行此任务。 点击链接查看。

使用示例:

#include "HTTPDownloader.hpp"

int main(int argc, char** argv) {
    HTTPDownloader downloader;
    std::string content = downloader.download("https://stackoverflow.com");
    std::cout << content << std::endl;
}

以下是头文件:

/**
 * HTTPDownloader.hpp
 *
 * A simple C++ wrapper for the libcurl easy API.
 *
 * Written by Uli Köhler (techoverflow.net)
 * Published under CC0 1.0 Universal (public domain)
 */
#ifndef HTTPDOWNLOADER_HPP
#define HTTPDOWNLOADER_HPP

#include <string>

/**
 * A non-threadsafe simple libcURL-easy based HTTP downloader
 */
class HTTPDownloader {
public:
    HTTPDownloader();
    ~HTTPDownloader();
    /**
     * Download a file using HTTP GET and store in in a std::string
     * @param url The URL to download
     * @return The download result
     */
    std::string download(const std::string& url);
private:
    void* curl;
};

#endif  /* HTTPDOWNLOADER_HPP */

这是源代码:

/**
 * HTTPDownloader.cpp
 *
 * A simple C++ wrapper for the libcurl easy API.
 *
 * Written by Uli Köhler (techoverflow.net)
 * Published under CC0 1.0 Universal (public domain)
 */
#include "HTTPDownloader.hpp"
#include <curl/curl.h>
#include <curl/easy.h>
#include <curl/curlbuild.h>
#include <sstream>
#include <iostream>
using namespace std;

size_t write_data(void *ptr, size_t size, size_t nmemb, void *stream) {
    string data((const char*) ptr, (size_t) size * nmemb);
    *((stringstream*) stream) << data;
    return size * nmemb;
}

HTTPDownloader::HTTPDownloader() {
    curl = curl_easy_init();
}

HTTPDownloader::~HTTPDownloader() {
    curl_easy_cleanup(curl);
}

string HTTPDownloader::download(const std::string& url) {
    curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
    /* example.com is redirected, so we tell libcurl to follow redirection */
    curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
    curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); //Prevent "longjmp causes uninitialized stack frame" bug
    curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "deflate");
    std::stringstream out;
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &out);
    /* Perform the request, res will get the return code */
    CURLcode res = curl_easy_perform(curl);
    /* Check for errors */
    if (res != CURLE_OK) {
        fprintf(stderr, "curl_easy_perform() failed: %s\n",
                curl_easy_strerror(res));
    }
    return out.str();
}

4
这可能不会立即生效,但应该能够给你一个想法:
#include <string>
#include <curl.h>
#include <stdio.h>
size_t write_data(void *ptr, size_t size, size_t nmemb, FILE *stream) {
    size_t written;
    written = fwrite(ptr, size, nmemb, stream);
    return written;
}

int main() {
    std::string tempname = "temp";
    CURL *curl;
    CURLcode res;
    curl = curl_easy_init();
    if(curl) {
      FILE *fp = fopen(tempname.c_str(),"wb");
      curl_easy_setopt(curl, CURLOPT_URL, "http://www.google.com");
      curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data); 
      curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
      res = curl_easy_perform(curl);
      curl_easy_cleanup(curl);
      fclose(fp);
      fp = fopen(tempname.c_str(),"rb");
      fseek (fp , 0 , SEEK_END);
      long lSize = ftell (fp);
      rewind(fp);
      char *buffer = new char[lSize+1];
      fread (buffer, 1, lSize, fp);
      buffer[lSize] = 0;
      fclose(fp);
      std::string content(buffer);
      delete [] buffer;
    }
}

你能否编辑并发布完整的代码?包括所有的头文件吗?因为你没有定义buffer或fp,我假设fp是FILE * fp,但我不知道buffer是什么。我还是C ++的初学者。 - Grego
1
@Grego,目前我无法编译,已添加那些类型和包含文件。希望这可以帮助到你。 - perreal
嘿伙计,你忘记声明“buffer”变量了,这其实是我的问题。 :D 在缓冲区中应该声明什么?谢谢! - Grego
1
@Grego,它被声明为:char *buffer = new char[lSize+1]; - perreal
你的代码运行得非常好,嗯,是我之前的编辑问题,我的错。:/ 将数据保存到文件中也是我正在考虑的事情,非常感谢你。:) 我会点赞你所有的评论并发布。 - Grego

1

提出了一种有用但简单的解决方案,它重载了std::ostream::operator<<

#include <ostream>

#include <curl/curl.h>

size_t curlCbToStream (
    char * buffer,
    size_t nitems,
    size_t size,
    std::ostream * sout
)
{
    *sout << buffer;

    return nitems * size;
}

std::ostream & operator<< (
    std::ostream & sout,
    CURL * request
)
{
    ::curl_easy_setopt(request, CURLOPT_WRITEDATA, & sout);
    ::curl_easy_setopt(request, CURLOPT_WRITEFUNCTION, curlCbToStream);
    ::curl_easy_perform(request);

    return sout;
}

可能采取的方法的缺点可能是:
typedef void CURL;

这意味着它涵盖了所有已知的指针类型。

0

我使用 Joachim Isaksson 的答案,并结合现代 C++ 改编 CURLOPT_WRITEFUNCTION

没有因 C 风格转换而受到编译器的骚扰。

static auto WriteCallback(char* ptr, size_t size, size_t nmemb, void* userdata) -> size_t {
  static_cast<string*>(userdata)->append(ptr, size * nmemb);
  return size * nmemb;
}

0

根据@JoachimIsaksson的答案,这里提供了一个更详细的输出,它可以处理内存不足,并且具有从curl返回的最大输出限制(因为CURLOPT_MAXFILESIZE仅根据标头信息而不是实际传输大小进行限制)。

#DEFINE MAX_FILE_SIZE = 10485760 //10 MiB

size_t curl_to_string(void *ptr, size_t size, size_t count, void *stream)
{
    if(((string*)stream)->size() + (size * count) > MAX_FILE_SIZE)
    {
        cerr<<endl<<"Could not allocate curl to string, output size (current_size:"<<((string*)stream)->size()<<"bytes + buffer:"<<(size * count) << "bytes) would exceed the MAX_FILE_SIZE ("<<MAX_FILE_SIZE<<"bytes)";
        return 0;
    }
    int retry=0;
    while(true)
    {
        try{
            ((string*)stream)->append((char*)ptr, 0, size*count);
            break;// successful
        }catch (const std::bad_alloc&) {
            retry++;
            if(retry>100)
            {
                cerr<<endl<<"Could not allocate curl to string, probably not enough memory, aborting after : "<<retry<<" tries at 10s apart";
                return 0;
            }
            cerr<<endl<<"Could not allocate curl to string, probably not enough memory, sleeping 10s, try:"<<retry;
            sleep(10);
        }
    }
  return size*count;
}

0
以下是一个完整的示例,演示如何在C++中使用curl将网站内容放入std::string中。
编译时请不要忘记添加-lcurl标志。
    /**
 * 
*/

////////////////////////////////////////////////////////////////////////////////
// Includes - default libraries - C++
////////////////////////////////////////////////////////////////////////////////
#include <iostream>
#include <string>
#include <sstream> // for ostringstream
#include <memory>

////////////////////////////////////////////////////////////////////////////////
// Includes - others libraries - C++
////////////////////////////////////////////////////////////////////////////////
#include <curl/curl.h>

////////////////////////////////////////////////////////////////////////////////
// show function
////////////////////////////////////////////////////////////////////////////////
std::string
args_to_str(const std::ostringstream& os)
{
  return os.str();
}

template<typename T, typename ... Args>
std::string
args_to_str(std::ostringstream& os, const T val, const Args ... args) 
{
  os << val;
  return args_to_str(os, args ...);
}

template<typename ... Args>
void 
show(const Args ... args) 
{  
    std::ostringstream os;  
    std::cout << args_to_str(os, args ...);
}



////////////////////////////////////////////////////////////////////////////////
// headers
////////////////////////////////////////////////////////////////////////////////
template<typename ... Args>
void error(const Args ... args);

size_t curl_to_string(void *ptr, size_t size, size_t nmemb, void *data);

std::string get_content_from_website(CURL* curl, const std::string& url);

void curl_free(CURL *curl);

////////////////////////////////////////////////////////////////////////////////
// main
////////////////////////////////////////////////////////////////////////////////
int
main()
{
    ////////////////////////////////////////////////////////////////////////////////
    // init libcurl
    ////////////////////////////////////////////////////////////////////////////////
    std::shared_ptr<CURL> curl (curl_easy_init(), curl_free);
    if(!curl.get()) error("cannot initialize CURL.");
    curl_easy_setopt(curl.get(), CURLOPT_VERBOSE, 1L);

    ////////////////////////////////////////////////////////////////////////////////
    // site that will be fetched
    ////////////////////////////////////////////////////////////////////////////////
    const std::string url = "https://www.fundsexplorer.com.br/funds/knsc11"; // put your website here

    ////////////////////////////////////////////////////////////////////////////////
    // fetch the content of the site
    ////////////////////////////////////////////////////////////////////////////////
    const std::string site = get_content_from_website(curl.get(), url);

    ////////////////////////////////////////////////////////////////////////////////
    // print the result
    ////////////////////////////////////////////////////////////////////////////////
    show(site);

    return 0;
}

////////////////////////////////////////////////////////////////////////////////
// functions
////////////////////////////////////////////////////////////////////////////////
template<typename ... Args>
void 
error(const Args ... args)
{  
    show(args ...);  
    abort();
}

std::string 
get_content_from_website(CURL* curl, const std::string& url)
{ 
    CURLcode res;
    res = curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
    if(res != CURLE_OK) error("Cannot set curl url.\n");

    res = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_to_string);
    if(res != CURLE_OK) error("Cannot copy the C_STR to C++ string.\n");

    std::string result;
    res = curl_easy_setopt(curl, CURLOPT_WRITEDATA, &result);
    if(res != CURLE_OK) error("Cannot set the curl write data.\n");

    res = curl_easy_perform(curl);
    if(res != CURLE_OK) error("Cannot perform curl.\n");

    return result;
}

size_t curl_to_string(void *ptr, size_t size, size_t nmemb, void *data)
{
    std::string* str = static_cast<std::string*>(data);
    char* sptr = static_cast<char*>(ptr);
    size_t total = size * nmemb;
    const auto str_old_size = (*str).size();
    (*str).resize(str_old_size + total);

    
    for(size_t x = 0; x < total; ++x)
    {
        (*str)[str_old_size + x] = sptr[x];
    }

    return total;
}

void 
curl_free(CURL *curl)
{
  curl_easy_cleanup(curl);
  curl = NULL;
  curl_global_cleanup();
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接