libcurl 是功能強大、支持協議豐富(HTTP/HTTPS/FTP/SMTP 等)的 C 語言網絡傳輸庫。在 C++ 項目中,它常被用於實現網頁抓取、API 調用、文件下載/上傳、爬蟲開發等場景。本文通過一個抓取天貓/微博數據的實戰案例,詳解核心 API 使用、回調處理、超時控制及錯誤處理最佳實踐。
#include <iostream>
#include <string>
#include <curl/curl.h>
#include <memory>
#include <stdexcept>
#include <functional>
using namespace std;
// 寫入回調函數:將接收到的數據追加到 string
static size_t write_callback(void* contents, size_t size, size_t nmemb, void* userp) {
size_t total_size = size * nmemb;
string* str = static_cast<string*>(userp);
if (str) {
str->append(static_cast<char*>(contents), total_size);
}
return total_size; // 返回實際處理的字節數
}
// 進度回調(可選):用於顯示下載進度
static int progress_callback(void* clientp, curl_off_t dltotal, curl_off_t dlnow,
curl_off_t ultotal, curl_off_t ulnow) {
if (dltotal > 0) {
cout << "\r下載進度: " << (double)dlnow / dltotal * 100 << "%" << flush;
}
return 0; // 返回 0 繼續,非 0 取消傳輸
}
// RAII 封裝:自動管理 curl 全局初始化/清理
class CurlGlobalInit {
public:
CurlGlobalInit() { curl_global_init(CURL_GLOBAL_DEFAULT); }
~CurlGlobalInit() { curl_global_cleanup(); }
// 禁用拷貝
CurlGlobalInit(const CurlGlobalInit&) = delete;
CurlGlobalInit& operator=(const CurlGlobalInit&) = delete;
};
// HTTP 客戶端封裝類
class HttpClient {
public:
HttpClient() : curl_(curl_easy_init()) {
if (!curl_) throw runtime_error("curl_easy_init 失敗");
// 設置默認選項
curl_easy_setopt(curl_, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(curl_, CURLOPT_NOSIGNAL, 1L); // 多線程環境下禁用信號
curl_easy_setopt(curl_, CURLOPT_FOLLOWLOCATION, 1L); // 支持重定向
curl_easy_setopt(curl_, CURLOPT_MAXREDIRS, 10L); // 最大重定向次數
curl_easy_setopt(curl_, CURLOPT_USERAGENT, "Mozilla/5.0 (compatible; CppHttpClient/1.0)");
}
~HttpClient() {
if (curl_) curl_easy_cleanup(curl_);
}
// 禁用拷貝,支持移動
HttpClient(const HttpClient&) = delete;
HttpClient& operator=(const HttpClient&) = delete;
HttpClient(HttpClient&& other) noexcept : curl_(other.curl_) { other.curl_ = nullptr; }
HttpClient& operator=(HttpClient&& other) noexcept {
if (this != &other) {
if (curl_) curl_easy_cleanup(curl_);
curl_ = other.curl_;
other.curl_ = nullptr;
}
return *this;
}
// 設置超時(毫秒)
void set_timeout(long connect_ms, long total_ms) {
curl_easy_setopt(curl_, CURLOPT_CONNECTTIMEOUT_MS, connect_ms);
curl_easy_setopt(curl_, CURLOPT_TIMEOUT_MS, total_ms);
}
// 設置請求頭
void set_headers(const vector<string>& headers) {
headers_list_ = nullptr;
for (const auto& h : headers) {
headers_list_ = curl_slist_append(headers_list_, h.c_str());
}
curl_easy_setopt(curl_, CURLOPT_HTTPHEADER, headers_list_);
}
// 執行 GET 請求
string get(const string& url) {
string response;
CURLcode res = perform_request(url, response, nullptr, 0);
if (res != CURLE_OK) {
throw runtime_error("GET 請求失敗: " + string(curl_easy_strerror(res)));
}
return response;
}
// 執行 POST 請求
string post(const string& url, const string& post_data) {
string response;
CURLcode res = perform_request(url, response, post_data.c_str(), post_data.size());
if (res != CURLE_OK) {
throw runtime_error("POST 請求失敗: " + string(curl_easy_strerror(res)));
}
return response;
}
// 獲取 HTTP 響應碼
long get_response_code() const {
long code = 0;
curl_easy_getinfo(curl_, CURLINFO_RESPONSE_CODE, &code);
return code;
}
// 獲取有效 URL(處理重定向後)
string get_effective_url() const {
char* url = nullptr;
curl_easy_getinfo(curl_, CURLINFO_EFFECTIVE_URL, &url);
return url ? string(url) : "";
}
private:
CURL* curl_;
struct curl_slist* headers_list_ = nullptr;
// 通用請求執行邏輯
CURLcode perform_request(const string& url, string& response,
const char* post_data, size_t post_len) {
response.clear();
curl_easy_setopt(curl_, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl_, CURLOPT_WRITEDATA, &response);
if (post_data && post_len > 0) {
curl_easy_setopt(curl_, CURLOPT_POST, 1L);
curl_easy_setopt(curl_, CURLOPT_POSTFIELDS, post_data);
curl_easy_setopt(curl_, CURLOPT_POSTFIELDSIZE, (long)post_len);
} else {
curl_easy_setopt(curl_, CURLOPT_POST, 0L);
}
// 可選:啟用進度回調
// curl_easy_setopt(curl_, CURLOPT_NOPROGRESS, 0L);
// curl_easy_setopt(curl_, CURLOPT_PROGRESSFUNCTION, progress_callback);
return curl_easy_perform(curl_);
}
};
// 使用示例
int main() {
// 全局初始化(進程級,一次即可)
CurlGlobalInit global_init;
try {
HttpClient client;
client.set_timeout(3000, 10000); // 連接 3s,總超時 10s
// 設置自定義請求頭(模擬瀏覽器)
client.set_headers({
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language: zh-CN,zh;q=0.9,en;q=0.8",
"Accept-Encoding: gzip, deflate, br",
"Connection: keep-alive"
});
// 抓取示例頁面
string url = "https://book.weibo.com";
cout << "正在請求: " << url << endl;
string html = client.get(url);
cout << "HTTP 狀態碼: " << client.get_response_code() << endl;
cout << "最終 URL: " << client.get_effective_url() << endl;
cout << "響應長度: " << html.length() << " 字節" << endl;
cout << "前 500 字符:" << endl << html.substr(0, 500) << endl;
} catch (const exception& e) {
cerr << "錯誤: " << e.what() << endl;
return -1;
}
return 0;
}
# 安裝開發包 sudo apt-get install libcurl4-openssl-dev # Ubuntu/Debian # 或 brew install curl # macOS # 編譯 g++ -std=c++17 -O2 -o http_client http_client.cpp -lcurl # 運行 ./http_client
# vcpkg 安裝
vcpkg install curl:x64-windows
# CMakeLists.txt 片段
find_package(CURL REQUIRED)
target_link_libraries(your_target PRIVATE ${CURL_LIBRARIES})
curl_easy_* 系列,同步阻塞,適合簡單請求,本文採用此模式。curl_multi_* 系列,異步非阻塞,支持並發數千連接,適合高性能爬蟲/下載器。| 選項 | 用途 | 推薦值 |
|---|---|---|
| CURLOPT_WRITEFUNCTION | 數據寫入回調 | 自定義回調(追加到 string/vector) |
| CURLOPT_WRITEDATA | 回調的 userp 參數 | 指向 std::string* 或 vector<char>* |
| CURLOPT_NOSIGNAL | 禁用 SIGALRM 超時機制 | 1L(多線程必須) |
| CURLOPT_CONNECTTIMEOUT_MS | 連接階段超時 | 3000-5000 ms |
| CURLOPT_TIMEOUT_MS | 整個請求超時 | 10000-30000 ms |
| CURLOPT_FOLLOWLOCATION | 自動跟隨重定向 | 1L |
| CURLOPT_SSL_VERIFYPEER | 驗證 SSL 證書 | 1L(生產環境),0L(測試/自簽名) |
| CURLOPT_USERAGENT | 設置 User-Agent | 自定義標識 |
| CURLOPT_COOKIEJAR/FILE | Cookie 持久化 | 文件路徑 |
curl_easy_perform 返回 CURLcode,使用 curl_easy_strerror 獲取可讀錯誤信息。CURLE_OK 失敗,需通過 CURLINFO_RESPONSE_CODE 檢查狀態碼。CURLE_COULDNT_CONNECT、CURLE_OPERATION_TIMEDOUT、CURLE_SSL_CONNECT_ERROR 等。對於大規模抓取,同步易用介面效率低下。推薦架構:
epoll/kqueue/IOCP。CURLOPT_DNS_CACHE_TIMEOUT、CURLOPT_FORBID_REUSE=0。robots.txt。CURLOPT_RESUME_FROM_LARGE 實現大文件分片下載。CURL_GLOBAL_SSL)。size*nmemb),返回較小值會導致傳輸中斷。CURLOPT_WRITEDATA 指向的對象必須在 curl_easy_perform 返回前存活。curl_easy_escape)。CURLOPT_CAINFO),不可長期關閉驗證。libcurl 是 C/C++ 網絡編程的基石。通過 RAII 封裝、合理的超時控制、完善的錯誤處理及異步架構設計,可構建出穩定、高效、可維護的 HTTP 客戶端。本文提供的 HttpClient 類可直接集成到項目中,並作為擴展異步爬蟲、文件下載器、API 网關客戶端的基礎組件。