文章目录
背景:在做一个小的爬取数据工具,由于其他原因得采用C++开发。C++在做爬虫这类应用时不是很方便,主要有两个问题需要解决:一是模拟http请求,二是http响应数据的解析。其他的就只是设计业务逻辑的组织了,倒是问题不大。最终选择采用libcurl来实现http请求,由于选择的目标站点接口返回的是json数据,所以数据解析采用了jsoncpp来实现。
零、环境介绍
服务器:腾讯云服务器
CPU:单核,最大 2000 MHz
内存: 1 GB
系统:Linux VM-122-135-ubuntu 3.13.0-36-generic
#63-Ubuntu SMP Wed Sep 3 21:30:07 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux
G++:g++ (Ubuntu 4.8.4-2ubuntu1~14.04.1) 4.8.4
libcurl:7.50.1
jsoncpp:0.5.0
一、libcurl库
1.1、介绍
libcurl为一个免费开源的、跨平台的网络协议库,支持http, https, ftp, gopher, telnet, dict, file, 和ldap 协议。libcurl同样支持HTTPS证书授权,HTTP POST, HTTP PUT, FTP 上传, HTTP基本表单上传,代理,cookies,和用户认证。同时libcurl是线程安全的,也兼容IPV6,已经被很多知名的大企业以及应用程序所采用。
curl命令行工具,可以通过shell或脚本来运行curl,而curl底层所使用的库是libcurl。不同语言对curl有着不同的封装,比如php的curl、python的pycurl等。他们底层调用的东西是一样的,但可能封装出来的接口参数可能不一样。
1.2、编译
1、下载:https://curl.haxx.se/download/curl-7.50.1.tar.gz
2、编译:
tar -zxvf curl-7.50.1.tar.gz cd curl-7.50.1 ./configure --prefix=xxxx #自己定义编译完安装的目录 make make install
3、编译安装结束后,在xxxx目录下会有变异结果,大致结构如下,需要的主要还是include和lib下的libcurl.a。
xxxx/ bin/ include/ lib/ share/
1.3、使用
1、编码
按自己的习惯组织代码的include文件,然后就可以在程序中直接使用libcurl的接口了,具体接口可以去官网查看文档。为了使用方便可以自己按需求封装一个httpclient类,实现get/post等方法。以get请求为例,部分实现一下代码:
#include "../curl/include/curl.h" static size_t OnWriteData(void* buffer, size_t size, size_t nmemb, void* lpVoid) { std::string* str = dynamic_cast<std::string*>((std::string *)lpVoid); if( NULL == str || NULL == buffer ) { return -1; } char* pData = (char*)buffer; str->append(pData, size * nmemb); return nmemb; } int HttpClient::Get(const std::string url, std::string& respone) { CURLcode res; CURL* curl = curl_easy_init(); if(NULL == curl) { return CURLE_FAILED_INIT; } curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); curl_easy_setopt(curl, CURLOPT_READFUNCTION, NULL); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, OnWriteData); curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&respone); /** * 当多个线程都使用超时处理的时候,同时主线程中有sleep或是wait等操作。 * 如果不设置这个选项,libcurl将会发信号打断这个wait从而导致程序退出。 */ curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 3); curl_easy_setopt(curl, CURLOPT_TIMEOUT, 5); res = curl_easy_perform(curl); curl_easy_cleanup(curl); return res; }
2、编译
当时在编译时遇到了点问题,按常规的g++ http_client.cc -o http_client -L../curl/lib -lcurl编译会有如下错误:
查阅网上资料,原因是没有加-lz,所以完整的编译链接命令如下,在工程中:
g++ http_client.cc -o http_client -L../curl/lib -lcurl -lz
二、jsoncpp库
2.1、介绍
JSON(JavaScript Object Notation)跟xml一样也是一种数据交换格式,现在在网络中普遍存在,其官网为:http://json.org
JsonCpp是C++中对json解析比较好的第三方库。主要包含三种类型:Value Reader Writer。不过注意Value只能处理ANSI类型的字符串,如果C++程序使用Unicode编码的,最好加一个Adapt类来适配。
2.2、编译
1、下载:https://sourceforge.net/projects/jsoncpp/
2、编译
jsoncpp采用scons来编译,所以要先安装scons。scons是一个Python写的自动化构建工具,从构建这个角度说,它跟GNU make是同一类的工具。它是一种改进,并跨平台的gnu make替代工具,其集成功能类似于autoconf/automake 。scons是一个更简便,更可靠,更高效的编译软件。
tar zxvf jsoncpp-src-0.5.0.tar.gz cd jsoncpp-src-0.5.0/ apt-get install scons scons platform=linux-gcc
编译完成后在主目录下会有需要的头文件目录include,和库文件目录libs,我们需要使用头文件和库文件libjson_linux-gcc-4.8_libmt.a。
2.3、使用
1、编码
同样按自己的习惯组织代码的include文件,然后就可以在程序中直接使用jsoncpp的接口了,具体接口可以去官网查看文档。一般在使用中json的格式是给定了了,然后根据格式来解析。部分实现一下代码:
{ "success":"1", "result":{ "status":"ALREADY_ATT", "phone":"18813759486", "area":"020", "postno":"510000", "att":"中国,广东,广州", "ctype":"中国移动188卡", "par":"1881375", "prefix":"188", "operators":"中国移动", "style_simcall":"中国,广东,广州", "style_citynm":"中华人民共和国,广东省,广州市" } }
#include "../jsoncpp/include/json.h" int PhoneSpiderK78::DecodeJsonK78(const std::string& json_str, DataResponeK78& result) { int code = OK; Json::Reader reader; Json::Value root; if (json_str=="") { code = ERROR_K78_JSON_NULL; return code; } if (reader.parse(json_str, root)) { //解析json中的对象 if ((result.success = root["success"].asString() ) == "1") { if (! root["result"].isNull() ) { Json::Value value = root["result"]; // 解析 if ((result.status = value["status"].asString() ) == "ALREADY_ATT") { result.phone = value["phone"].asString(); result.area = value["area"].asString(); result.postno = value["postno"].asString(); result.att = value["att"].asString(); result.ctype = value["ctype"].asString(); result.par = value["par"].asString(); result.prefix = value["prefix"].asString(); result.operators = value["operators"].asString(); result.style_simcall = value["style_simcall"].asString(); result.sstyle_citynmtatus = value["sstyle_citynmtatus"].asString(); code = result.SplitBelongs(); } else { result.msg = value["msg"].asString(); code = ERROR_K78_PHONE_NOEXIST; } } } else { result.msg = root["msg"].asString(); code = ERROR_K78_SERVER_ERROR; } } else { code = ERROR_K78_JSON_FORMAT; } return code; }
2、编译:按正常的库引用格式编译即可,即在连接时加上libjson_linux-gcc-4.8_libmt.a
g++ http_client.cc -o http_client ../jsoncpp/lib/libjson_linux-gcc-4.8_libmt.a
三、逻辑封装
3.1、整体思路
因为该工具主要是用于爬取特定的数据,数据最终处理的格式是统一的。所以整个爬取过程可以分为四部分:生成访问连接、访问、解析结果、格式化存储。考虑到通用性,不同的数据源的访问请求格式和返回结果不一样,所以生成连接和访问结果处理对于不同的数据源需要特殊处理。
于是考虑使用继承来实现整个工具结构,父类主要用于控制整个爬取的流程和最终的数据处理,子类负责具体的爬取和数据解析过程,对于不同的数据源只需要书写相应的子类即可。下面将粗略介绍整个结构,具体实现和细节处理忽略。
3.2、数据结构
1、枚举:SpiderCode 整个爬取工具的异常代码,统一管理
2、结构:PhoneData 统一的数据交互的结构
3、父类:PhoneSpider 父类
4、子类:PhoneSpiderK78 k78对应的子类
5、类:HttpClient 对libcurl的封装,用于网络结构访问
6、类:MysqlDb 对mysql操作的封装,用于和数据库交互
3.3、类和流程
根据目前的构思实现了下面的类结构,父类为PhoneSpider实现整体流程控制,对外开放Run和RunSegment,子类PhoneSpiderK78实现具体的获取和解析数据。
整个调用和数据流程如下,蓝色部分是对外接口:
3.4、扩展和使用
目前这个结构是第一版本,之后可以按照需求修改和调整,比如实现参数配置。下面是调用的接口示例:
int main(int argc, char const *argv[]) { if (argc == 2) { spider::PhoneSpider* phone_spider = new spider::PhoneSpiderK78("10003", "b59bc3ef6191eb9f747dd4e83c99f2a4"); phone_spider->Run(argv[1]); delete phone_spider; } else if (argc == 4) { int sleep_time = atoi(argv[3]); spider::PhoneSpider* phone_spider = new spider::PhoneSpiderK78("10003", "b59bc3ef6191eb9f747dd4e83c99f2a4"); phone_spider->RunSegment(sleep_time, argv[1], argv[2]); delete phone_spider; } else { std::cout<<"error input!"<<std::endl;; } return 0; }
四、参考
1、https://zh.wikipedia.org/wiki/CURL
2、http://www.cnblogs.com/moodlxs/archive/2012/10/15/2724318.html
3、http://www.justwinit.cn/post/4698/
4、http://www.cnblogs.com/johnice/archive/2013/01/17/2864319.html
5、http://blog.csdn.net/huyiyang2010/article/details/7664201
6、http://blog.sina.com.cn/s/blog_6c4a60110101342m.html
7、http://developer.51cto.com/art/201201/311073.htm
转载标明出处:https://blog.evanxia.com/2016/08/935