|
地址: http://blog.csdn.net/hujkay
作者:Jekkay Hu([email protected])
關鍵詞:Windows,curl,ssl, visual c++ 2005, libcurl, https,網頁抓取
時間: 2014/2/18
1. 概述
由於Curl提供強大的網絡功能,支持HTTP,HTTPS, DICT, FILE, FTP, FTPS, Gopher, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, POP3, POP3S, RTMP, RTSP, SCP, SFTP, SMTP, SMTPS, Telnet ,TFTP等,已成爲應用最爲廣泛的輕量級網絡庫之一。libCurl支持Windows,但如果在Win 平臺使用VC開發的話,則需要下載msvc的版本,其下載地址是:http://curl.haxx.se/download/,如:libcurl-7.19.3-win32-ssl-msvc.zip。
目前Curl的的最新版本已經是7.35.0,但是官網提供的msvc的版本仍然是2009年2月發佈的7.19.3版本,而且還沒有含靜態openssl的lib,這就意味寫個小exe程序的話,還得打包好幾個Openssl DLL進去,挺麻煩的,所以我就重新編譯了一個含Openssl靜態庫,這個庫算是我編譯的最大的庫了,達到25M,下載地址:
已編譯好含SSL的靜態libcurl 7.35.0[VC2005].zip
http://download.csdn.net/detail/hujkay/6931345
2. 使用
我以MFC Dialog based工程爲例,介紹如何在Windons+VC2005上使用libcurl 7.35.0靜態庫。
2.1. 創建工程
打開Visual studio 2005,直接創建一個MFC工程,工程類型選擇基於對話框[Dialog based]的就行,編碼方式取消Unicode,這樣就可以使用ANSI編碼.
2.2 配置工程屬性
右鍵工程屬性,設置Curl的頭文件目錄路徑,如下圖:
配置庫的鏈接方式和編碼方式,如下圖:
配置Runtime library,Debug模式爲/MTD,Rlease模式爲/MT
然後在Preprocesser裏面添加預訂義宏CURL_STATICLIB,如下圖:
Debug模式和Release模式,配置的內容是一樣的。
然後在stdafx.h文件最後面,添加如下代碼:
//// 添加CURL庫
#include <curl/curl.h>
//// 帶SSL的靜態鏈接庫
#ifdef _DEBUG
#pragma message("======編譯======[DEBUG] CURL庫=====")
#pragma comment(lib,"libcurld.lib")
#else
#pragma message("======編譯======[Release] CURL庫=====")
#pragma comment(lib,"libcurl.lib")
#endif
#pragma comment(lib,"wldap32.lib")
#pragma comment(lib,"ws2_32.lib")
2.3 封裝Curl庫訪問
爲了使得Curl訪問更加方便,我簡單封裝了一下Curl的訪問類,代碼如下:
VVCurl.h的源碼如下:
#pragma once
#include <string>
enum CURL_TYPE {CURL_GET=1,CURL_POST=2};
class CVVCurl
{
public:
CVVCurl(void);
~CVVCurl(void);
BOOL Init(CString strProxyAddr=_T(""),INT nPort=80) ;
// 釋放資源
void Release();
// 打開指定的網頁
BOOL OpenURL(std::string strURL,CURL_TYPE ntype = CURL_GET);
BOOL OpenURL(std::string strURL,std::string strPostData,CURL_TYPE ntype = CURL_POST);
// 獲取網頁內容
const char * GetHeadContent() { return m_headcontent.c_str() ;} ;
size_t GetHeadContentLength() { return m_headcontent.size() ;} ;
const char * GetBodyContent() { return m_bodycontent.c_str() ;} ;
size_t GetBodyContentLength() { return m_bodycontent.size() ;} ;
protected:
BOOL InitCurlHandle() ;
BOOL ReleaseCurlHandle() ;
BOOL DeleteCookieFile() ;
BOOL SetCurlHandleOpt() ;
protected:
// 句柄
CURL * m_pcurl;
// 獲取的內容
std::string m_headcontent ;
std::string m_bodycontent ;
std::string m_debugcontent ;
// agent
std::string m_agent ;
// cookie
std::string m_cookiepath ;
// proxy
std::string m_strProxyServer ;
// port
int m_nPort ;
};
VVCurl.cpp的源碼如下:
#include "StdAfx.h"
#include "VVCurl.h"
//#include "../include/Util.h"
/*
ptr是指向存儲數據的指針,
size是每個塊的大小,
nmemb是指塊的數目,
stream是用戶參數。
所以根據以上這些參數的信息可以知道,ptr中的數據的總長度是size*nmemb
*/
static size_t call_wirte_func(const char *ptr, size_t size, size_t nmemb, std::string *stream)
{
size_t len = size * nmemb;
stream->append(ptr, len);
return len;
}
// 返回http header回調函數
static size_t header_callback(const char *ptr, size_t size, size_t nmemb, std::string *stream)
{
size_t len = size * nmemb;
stream->append(ptr, len);
return len;
}
static int debug_callback (CURL * pcurl, curl_infotype ntype, char * ptr, size_t size, std::string * stream)
{
int len = (int)size;
stream->append(ptr, len);
return len;
}
CVVCurl::CVVCurl(void)
{
m_pcurl = NULL ;
m_agent = _T("Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.66 Safari/537.36 LBBROWSER") ;
m_cookiepath = _T("cookie.txt") ; //CUtil::GetRandTempPath(_T("_cookie.txt"));
}
CVVCurl::~CVVCurl(void)
{
Release() ;
}
BOOL CVVCurl::Init(CString strProxyAddr,INT nPort)
{
m_nPort = nPort ;
m_strProxyServer = strProxyAddr ;
return InitCurlHandle() ;
}
void CVVCurl::Release()
{
ReleaseCurlHandle() ;
DeleteCookieFile() ;
}
BOOL CVVCurl::InitCurlHandle()
{
if( NULL == m_pcurl)
{
m_pcurl = curl_easy_init() ;
}
if( NULL == m_pcurl ){
ASSERT(FALSE) ;
return FALSE ;
}
SetCurlHandleOpt() ;
return TRUE ;
}
BOOL CVVCurl::SetCurlHandleOpt()
{
if( NULL == m_pcurl )
return FALSE ;
// 設置Agent
curl_easy_setopt(m_pcurl, CURLOPT_USERAGENT, m_agent.c_str());
// 官方下載的DLL並不支持GZIP,Accept-Encoding:deflate, gzip
curl_easy_setopt(m_pcurl, CURLOPT_ENCODING, "");
//跳過服務器SSL驗證,不使用CA證書
curl_easy_setopt(m_pcurl, CURLOPT_SSL_VERIFYPEER, 0L);
//如果不跳過SSL驗證,則可指定一個CA證書目錄
//curl_easy_setopt(curl, CURLOPT_CAPATH, "this is ca ceat");
//驗證服務器端發送的證書,默認是 2(高),1(中),0(禁用)
curl_easy_setopt(m_pcurl, CURLOPT_SSL_VERIFYHOST, 0L);
/* 與服務器通信交互cookie,默認在內存中,可以是不存在磁盤中的文件或留空 */
curl_easy_setopt(m_pcurl, CURLOPT_COOKIEFILE, m_cookiepath.c_str());
/* 與多個CURL或瀏覽器交互cookie,會在釋放內存後寫入磁盤文件 */
curl_easy_setopt(m_pcurl, CURLOPT_COOKIEJAR, m_cookiepath.c_str()) ;
//設置重定向的最大次數
curl_easy_setopt(m_pcurl, CURLOPT_MAXREDIRS, 5);
// 設置自動設置refer字段
curl_easy_setopt ( m_pcurl, CURLOPT_AUTOREFERER, 1 );
//設置301、302跳轉跟隨location
curl_easy_setopt(m_pcurl, CURLOPT_FOLLOWLOCATION, 1);
//抓取內容後,回調函數
curl_easy_setopt(m_pcurl, CURLOPT_WRITEFUNCTION, call_wirte_func);
curl_easy_setopt(m_pcurl, CURLOPT_WRITEDATA, &m_bodycontent );
//抓取頭信息,回調函數
curl_easy_setopt(m_pcurl, CURLOPT_HEADERFUNCTION, header_callback );
curl_easy_setopt(m_pcurl, CURLOPT_HEADERDATA, &m_headcontent);
// 設置超時時間
curl_easy_setopt(m_pcurl, CURLOPT_TIMEOUT, 10);
// 禁用掉alarm這種超時
curl_easy_setopt(m_pcurl, CURLOPT_NOSIGNAL, 1L);
// 禁止重用TCP連接
curl_easy_setopt(m_pcurl, CURLOPT_FORBID_REUSE, 1);
// 打開調試
curl_easy_setopt(m_pcurl, CURLOPT_VERBOSE, 1);
curl_easy_setopt(m_pcurl, CURLOPT_DEBUGFUNCTION, debug_callback);
curl_easy_setopt(m_pcurl, CURLOPT_DEBUGDATA, &m_debugcontent);
// 判斷是否是需要代理
if( m_strProxyServer.size() > 0 && m_nPort > 0)
{
// 打開,允許重用TCP連接
curl_easy_setopt(m_pcurl, CURLOPT_FORBID_REUSE, 0);
// 第一種方法
curl_easy_setopt(m_pcurl,CURLOPT_PROXY,m_strProxyServer.c_str());
curl_easy_setopt(m_pcurl, CURLOPT_PROXYPORT, m_nPort);
curl_easy_setopt(m_pcurl, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
curl_easy_setopt(m_pcurl, CURLOPT_HTTPPROXYTUNNEL, 1L);
}
return TRUE ;
}
BOOL CVVCurl::ReleaseCurlHandle()
{
if( NULL != m_pcurl)
{
curl_easy_cleanup(m_pcurl);
}
m_pcurl = NULL ;
return TRUE ;
}
BOOL CVVCurl::DeleteCookieFile()
{
::DeleteFile(m_cookiepath.c_str());
return TRUE ;
}
BOOL CVVCurl::OpenURL(std::string strURL,CURL_TYPE ntype)
{
return OpenURL(strURL,_T(""),ntype) ;
}
BOOL CVVCurl::OpenURL(std::string strURL,std::string strPostData,CURL_TYPE ntype)
{
m_headcontent = m_bodycontent = m_debugcontent = _T("");
if( NULL == m_pcurl )
{
ASSERT(FALSE) ;
return FALSE ;
}
if( ntype == CURL_POST || strPostData.size() > 0)
{
//curl_easy_setopt(m_pcurl,CURLOPT_HTTPGET,0);
/* POST 數據 */
curl_easy_setopt(m_pcurl,CURLOPT_POST,1);
if(strPostData.size() > 0)
{
curl_easy_setopt(m_pcurl, CURLOPT_POSTFIELDS, strPostData.c_str());
curl_easy_setopt(m_pcurl, CURLOPT_POSTFIELDSIZE, strPostData.size());
}
else
{
curl_easy_setopt(m_pcurl, CURLOPT_POSTFIELDS, NULL);
curl_easy_setopt(m_pcurl, CURLOPT_POSTFIELDSIZE, 0);
}
//SetCurlHandleOpt() ;
}else
{
// 禁用POST,直接GET請求
//curl_easy_setopt(m_pcurl,CURLOPT_POST,0);
//curl_easy_setopt(m_pcurl, CURLOPT_POSTFIELDS, NULL);
curl_easy_setopt(m_pcurl,CURLOPT_HTTPGET,1);
//SetCurlHandleOpt() ;
}
try
{
// 遠程URL,支持 http, https, ftp
curl_easy_setopt(m_pcurl, CURLOPT_URL, strURL.c_str());
CURLcode nRet = curl_easy_perform(m_pcurl);
return CURLE_OK == nRet ;
}
catch (...)
{
}
return FALSE ;
}
2.4 編寫代碼
在使用CVVCurl封裝類之前必須先調用函數cur_global_init進行全局初始化,再關閉時在調用函數curl_global_cleanup掃尾。我們可以在函數CTestlibCurlApp::InitInstance()中,添加這個兩個函數,如下圖:
然後就可以在程序的任何地方調用了CVVCurl類來訪問網頁了,比如我在一個函數響應出使用如下代碼獲取網頁數據:
void CTestlibCurlDlg::OnBnClickedVisitButton()
{
UpdateData(TRUE);
m_Url = m_Url.Trim();
if( m_Url.GetLength() <= 0)
return ;
CVVCurl vvcurl ;
vvcurl.Init() ;
if( vvcurl.OpenURL(m_Url.GetBuffer()))
{
m_ContentEdit.Clear() ;
m_ContentEdit.SetSel(0,-1,FALSE);
m_ContentEdit.ReplaceSel(vvcurl.GetBodyContent(),FALSE) ;
}
}
2.5 調試
編譯程序,可能會有許多沒有調試符號警告,這個是無所謂的。
Linking...
libcurld.lib(asyn-thread.obj) : warning LNK4204: 'c:\resource\vccode\testlibcurl\testlibcurl\debug\vc80.pdb' is missing debugging information for referencing module; linking object as if no debug info
libcurld.lib(base64.obj) : warning LNK4204: 'c:\resource\vccode\testlibcurl\testlibcurl\debug\vc80.pdb' is missing debugging information for referencing module; linking object as if no debug info
libcurld.lib(bundles.obj) : warning LNK4204: 'c:\resource\vccode\testlibcurl\testlibcurl\debug\vc80.pdb' is missing debugging information for referencing module; linking object as if no debug info
libcurld.lib(conncache.obj) : warning LNK4204: 'c:\resource\vccode\testlibcurl\testlibcurl\debug\vc80.pdb' is missing debugging information for referencing module; linking object as if no debug info
libcurld.lib(connect.obj) : warning LNK4204: 'c:\resource\vccode\testlibcurl\testlibcurl\debug\vc80.pdb' is missing debugging information for referencing module; linking object as if no debug info
libcurld.lib(cookie.obj) : warning LNK4204: 'c:\resource\vccode\testlibcurl\testlibcurl\debug\vc80.pdb' is missing debugging information for referencing module; linking object as if no debug info
libcurld.lib(curl_addrinfo.obj) : warning LNK4204: 'c:\resource\vccode\testlibcurl\testlibcurl\debug\vc80.pdb' is missing debugging information for referencing module; linking object as if no debug info
執行程序結果如下,測試HTTP訪問和HTTPS訪問:
3. 總結
我封裝的CVVCurl訪問類是可以支持HTTPS POST的,具體的請看下訪問接口就可以了,此外還可以指定Cookie文件 ,是線程安全的封裝類。如果需要支持多個賬號同時登陸Web,那麼只需要爲每個不同的賬號指定不同的Cookie文件就可以了。
對於抓取的網頁內容,如果用的UTF8編碼的網頁內容可能需要進行編碼轉換一下,才能正確顯示中文,工程中含有代碼轉換的類CStringConvert,已經加到工程代碼中,可直接使用,如果還不懂的話,就請打發一杯咖啡錢給我,讓老衲細細道來。【點此打發咖啡】[https://me.alipay.com/jekkay]
以上的測試工程代碼,可以在下面網址中下載:
: VC2005使用含SSL的靜態libcurl庫代碼工程
: http://download.csdn.net/detail/hujkay/6932541
【點此打發咖啡】[https://me.alipay.com/jekkay]
胡楊, Jekkay Hu
2014/2/18