前一段时间自己写了一个抓取网页代码的类,来满目一些项目需求,结果发现并不稳定,在海量网页抓取的时候,存在一些异常导致抓取失败。虽然能满足大概的要求,但是功能上还是不能让我100%的满意,于是在站长的建议下,下载了一个CUrl通用库。
第一次写这样的文章,有失偏颇处请谅解,呵呵。
最近把CURL运用在自己的工程里,发现效果非常理想,尤其在海量数据抓取下载的时候,失败率还是非常低的,综合自己的运用,在这里抛砖引玉。在PHP上,CUrl使用的较多,但是在C++上,使用的例子较为简单,而且参考资料较少,在这里我主要想总结一下CUrl在C++下的一些运用。(百度谷歌的资料有的不是很全,在这里补完一下吧。)
Curl是一个跨平台的库,下载地址 http://curl.haxx.se/
安装的时候,如果只需要命令行工具,请编译CUrl下的src,如果需要库引用直接编译主目录下的工程也可以,工程会生成一个src\DLL-Debug的目录,拷贝出libcurl.lib和libcurl.dll。到一个空的文件夹,然后在将include\curl文件夹下的所有.h头文件拿出来放在一个文件夹中。
行了,材料齐备了,拿着这两个文件夹,按照你自己的习惯引入到你的工程项目中,就可以了。
在linux下,你可以选择创建一个build目录.
然后 $ ./configure --prefix=你创建的bulid目录,然后,make,最后在make install一下,就可以了,所有的东西都在build目录里面给你放好了。
下面说一下它的用法,其实很简单,几个关键的API,常用的不超过4个。很方便,倒是一些配置参数相对复杂,这里强烈推荐 http://curl.haxx.se/ 下的帮助页面,里面对所有参数的运用和设置说的很清楚。
恩,呵呵,先说最简单的下载网页吧。
#include "./Include/curl.h"
#include "./Include/types.h"
#include "./Include/easy.h"
这三个头文件是必须引用的。
CURL* m_pCurl;
声明一个CURL对象。这里有一个小建议,就是推荐如果你下载的是一个来源的网站地址,最好就是用一个m_pCurl,这样做的好处是,当它和网站建立链接后,会保持这个链接,如果你下载的页面都是源于此网站,它会最大程度节省你的系统资源。如果每次下载一个网页都new一个m_pCurl对象,你会在netstat -an里面看到无数Time_Wiat的链接对象,消耗资源不说其实也是没有必要的。
m_pCurl = curl_easy_init();
初始化一个Curl对象,它会生成一个CUrl的指针返回。如果返回是NULL,就是建立链接失败。其实这里失败的可能性很小,因为它只做一个初始化的动作。初始化一个soket以及一些缓冲内存Buff,一般这里如果返回为NULL,那就看看你的网卡是否有问题吧。
此后就是最关键的获取网页信息部分,先说GET,再说POST。(一些基于Https的加密传输在这里先不做讨论)
如果是一个普通的Get方法:
bool CDownIcon:ownLoadIcon(const char* pSoftid, const char* pURL)
{
CURLcode CUrlRes;
CHtmlDataBuff m_HtmlBuff;
struct curl_slist *chunk = NULL;
if(m_pCurl != NULL)
{
chunk = curl_slist_append(chunk, "Accept-Encoding: gzip, deflate");
chunk = curl_slist_append(chunk, "User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; CIBA)");
chunk = curl_slist_append(chunk, "Connection: Keep-Alive");
//下载文件
curl_easy_setopt(m_pCurl, CURLOPT_HTTPHEADER, chunk);
curl_easy_setopt(m_pCurl, CURLOPT_TIMEOUT, 120);
curl_easy_setopt(m_pCurl, CURLOPT_URL, pURL);
curl_easy_setopt(m_pCurl, CURLOPT_WRITEFUNCTION, Url_IconWrite);
curl_easy_setopt(m_pCurl, CURLOPT_WRITEDATA, &m_HtmlBuff);
CUrlRes = curl_easy_perform(m_pCurl);
if(CUrlRes == CURLE_OK)
{
//网页下载成功,下载后的网页存在我的CHtmlDataBuff 对象里面,其实这个很简单,如果不涉及到gzip等一些压缩格式的下载,你完全可以用一个string去替代我的CHtmlDataBuff
}
curl_slist_free_all(chunk);
return FTPUpload(pSoftid);
}
else
{
return false;
}
}
如上所述,我一点点解释上面在干什么,呵呵。
首先curl_slist_append()函数是很有用的,因为如果你什么都不写,CUrl会传输一个类似"Get /你的网页 accept: */*"之类的简单协议,在某些验证较为严格的服务器,这样的Http链接协议字会被丢弃的。也就是说啥都不给你返回,不够千万不要郁闷,Curl的设计者早就给你想好了,curl_slist_append()这个API可以让你伪装成一个标准的网页浏览器的请求,诚然,你们也看到了,我追加了一些Http的选项,这些选项将会附加在你的Http请求中。这样就能顺利的通过那些严格验证的服务器,让它给你返回正确的数据。当然,这里的前提是你必须对Http 1.1协议有一些了解。
curl_easy_setopt(m_pCurl, CURLOPT_HTTPHEADER, chunk);
这个API是CUrl的设置选项,你可以通过它设置几乎上百个CUrl控制选项。这里只讨论最常用的几个,如果想继续深入,请去 http://curl.haxx.se/ 这里有完整的解释。(当然你的E文要足够过关)
这句话的意思是,将chunk设置的字符串,附加到Http请求消息头上。
curl_easy_setopt(m_pCurl, CURLOPT_TIMEOUT, 120);
这句话的意思是,设置超时时间,如果服务器在120秒不返回,Curl就会触发一个TimeOUT错误。这里建议设置,防止你的代码在某些特殊时刻无限的等待服务器的返回。
curl_easy_setopt(m_pCurl, CURLOPT_URL, pURL);
pURL就是你的网页地址,比如"Http://www.google.com/",当然,不仅仅是可以网页,也可以是Js,jpg等等文件,比如"http://www.163.com/1.jpg"这样也是可以的。实际上说CURLOPT_URL有些狭隘,它可以下载任何url链接可以指向的东西。包括图片以及swf等。
curl_easy_setopt(m_pCurl, CURLOPT_WRITEFUNCTION, Url_IconWrite);
curl_easy_setopt(m_pCurl, CURLOPT_WRITEDATA, &m_HtmlBuff);
接下来是这两句,Url_IconWrite其实是一个回调接口,可能你会问,为什么要这么做呢?我只要网页本身的东西就好了,呵呵,这样做其实为了服务另一个目的,那就是下载进度,你看见很多浏览器有一个下载进度条在走吧,呵呵,对了,通过这个回调函数,你可以设计你的下载进度条,它会给你下载此时此刻的进度和数据块,尤其在支持chunk模式传输返回协议的时候,它会根据每次chunk触发若干次回调。
CURLOPT_WRITEDATA选项是指定一个对象,用于你在回调函数的时候将收到的数据片拼接成一个完整的。
int Url_IconWrite(void *buffer, size_t size, size_t nmemb, void *stream)
{
size_t stDataLen = size * nmemb;
char* pData = new char[stDataLen];
if(NULL == pData)
{
return 0; //返回错误,触发接收失败,停止接收。
}
CHtmlDataBuff* pHtmlDataBuff = (CHtmlDataBuff* )stream;
memcpy(szData, (char* )buffer, stDataLen);
pHtmlDataBuff->AddData(pData, (int)stDataLen); //将数据包一个个的粘起来,这里可以用你自己的方法。
delete[] pData;
return stDataLen;
}
这个就是绑定的回调函数的写法,当然Url_IconWrite是我起的,你可以用你自己喜欢的名字。
pHtmlDataBuff->AddData(szData, (int)stDataLen);这句话其实就是为了将我收到的数据包拼装在一起。你可以根据你的逻辑自己写一个这样的东西,亦或简单的用一个string +=之类的也行。
buffer是当前数据块的指针,size * nmemb是当前数据的长度,stream其实就是你用CURLOPT_WRITEDATA绑定的对象。
好了,继续说。
CUrlRes = curl_easy_perform(m_pCurl);
这句话就是开始执行你的url下载活动,他返回一个CUrlRes 对象,其实感觉是一个int,如果成功,会返回一个CURLE_OK标记,反之,会给你一个数字,你可以在curl.h里面找到对应的解释。
当你一次抓取执行完毕,你必须设置curl_slist_free_all(chunk);除非你的chunk在下次使用的时候和以前一样,则不必做这样的操作。但是最终你必须curl_slist_free_all(chunk);否则会有内存泄露。
最后,当你执行完你的网页抓取,一定不要忘了curl_easy_cleanup(m_pCurl);释放你这个对象,否则同理,你的内存会泄露。
好了,以上是一个标准的url文件下载或者网页抓取的代码。当然是GET方式的。
接下来说POST,其实也很简单,只要稍微修改一点地方就可以实现POST。
curl_easy_setopt(pCurl, CURLOPT_POST, 8080);
curl_easy_setopt(pCurl, CURLOPT_POSTFIELDS, szData);
CURLOPT_POST参数设置的是你的PSOT端口地址,比如我的例子是8080。
CURLOPT_POSTFIELDS参数附加的是你的具体POST的数据内容,你可以自己去组成这部分。
当然,一般POST协议的时候,最好附加一个chunk。
m_chunk = curl_slist_append(m_chunk, “Content-Length: XXXXX”); XXXXX为你的POST数据的长度。否则有些服务器可能会认为你的请求非法。
好了,再说一个有意思的运用。
FTP作为服务器而言,现在用在很多地方。
那么如何用CUrl做一个FTP的请求呢?这里也是可以的。(下载很容易的,说一下比较复杂的上传吧。)
以代码为例:
bool CDownIcon::FTPUpload(const char* pSoftid)
{
CURLcode CUrlRes;
char szServerPath[HTTP_ICONMAX_1024] = {'\0'};
sprintf(szServerPath, "ftp://127.0.0.1/%s", pSoftid);
FILE* fp = NULL;
fp = fopen(m_szFileName, "rb");
if(NULL == fp)
{
printf("[Main]fopen (%s) fail!.\n", "a.JPG");
return false;
}
fseek(fp, 0l, SEEK_END);
int nFileSize = (int)ftell(fp);
if(nFileSize <= 0)
{
printf("[CReadFile::ReadFile]ftell error(%d)!\n", nFileSize);
}
fseek(fp, 0l, SEEK_SET);
if(m_pFTPCurl != NULL)
{
//curl_easy_setopt(m_pCurl, CURLOPT_VERBOSE, TRUE); //这个参数可以在FTP过程中显示FTP指令,如果你想看到的话。
curl_easy_setopt(m_pFTPCurl, CURLOPT_USERPWD, "freeeyes:freeeyes");
curl_easy_setopt(m_pFTPCurl, CURLOPT_URL, szServerPath);
curl_easy_setopt(m_pFTPCurl, CURLOPT_PUT, 1);
curl_easy_setopt(m_pFTPCurl, CURLOPT_INFILE, fp);
curl_easy_setopt(m_pFTPCurl, CURLOPT_INFILESIZE, (curl_off_t)(size_t)nFileSize);
curl_easy_setopt(m_pFTPCurl, CURLOPT_FTP_CREATE_MISSING_DIRS, 1);
CUrlRes = curl_easy_perform(m_pFTPCurl);
if(CUrlRes == CURLE_OK)
{
fclose(fp);
return true;
}
else
{
fclose(fp);
printf("[CDownIcon::FTPUpload](%s) Upload Fail.\n", pSoftid);
return false;
}
}
else
{
return false;
}
}
看到了吧,其实和HTTP差不多,只不过要注意几个参数。
curl_easy_setopt(m_pFTPCurl, CURLOPT_USERPWD, "freeeyes:freeeyes");
这是设置你的FTP用户名和地址。当然你的ftp没有密码可以忽略这个选项。
curl_easy_setopt(m_pFTPCurl, CURLOPT_INFILE, fp);
这个fp就是你要上传的FILE*指针。
curl_easy_setopt(m_pFTPCurl, CURLOPT_INFILESIZE, (curl_off_t)(size_t)nFileSize);
这是指定你上传文件的大小。
curl_easy_setopt(m_pFTPCurl, CURLOPT_URL, szServerPath);
这指定的是你的上传后的文件名称,比如"/Img/001/1001/1001.jpg"
curl_easy_setopt(m_pFTPCurl, CURLOPT_FTP_CREATE_MISSING_DIRS, 1);
最有意思的就是这个选项了,我很喜欢,这个选项的意思是,如果你在远程FTP上不存在这样的路径,CURL会帮你建立好。省去了我很多mkdir的麻烦,哪怕是多层目录它都能帮你建立,减少了很多的代码量。
呵呵,看到了吧,其实CUrl用在FTP上也挺简单的。而且是跨平台的。
以上是我对CUrl的一些初步理解,放在这里与大家共享。希望那天大家用到的时候,这些经验能帮你的忙。
当然,这只是最基本的运用。抛砖引玉,抛砖引玉啦。