libcurl开源库实现C++爬虫

libcurl是一个免费且易于使用的客户端URL传输库,是一个轻量级的HTTP编程库,很好使用,curl也是Linux下一个非常著名的下载库,通过这个库,可以很简单的实现文件的下载等操作。当然我们可以用它来实现一个简易的C++爬虫(不是Python才能爬虫的哦=_=)

一.安装libcurl库

sudo wget http://curl.haxx.se/download/curl-7.35.0.tar.gz

二.解压下载下来的软件包

tar -zxvf curl-7.35.0.tar.gz

三.执行编译安装

./configure --prefix=/usr/local/libcurl                //这里可以自行设置路径
make
make install

如上,libcurl库就已经安装好了,非常的方便,当然在编译的时候别忘了加上链接的选项哦

g++ -o  spider spider.cc -w -std=c++11 -lcurl           //最后一个选项-lcurl必须加上

四.libcurl的基本编程流程

1. 调用curl_global_init()初始化libcurl
2. 调用 curl_easy_init()函数得到 easy interface型指针
3. 调用curl_easy_setopt设置传输选项
4. 根据curl_easy_setopt设置的传输选项,实现回调函数以完成用户特定任务
5. 调用curl_easy_perform()函数完成传输任务
6. 调用curl_easy_cleanup()释放内存

具体的操作细节,可以看下面的代码就知道了。

五.实现简易爬虫

这个爬虫要爬的主要信息是新浪新闻首页相应主题下的所有新闻的信息,包括作者,标题和内容

当然如果不需要处理数据的话,只要看spider.hpp 和spider.cc的main函数就可以知道流程了。

spider.hpp

#pragma once 

#include <iostream>
#include <curl/curl.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

using namespace std;

//ibcurl主要提供了两种发送http请求的方式,分别是Easy interface方式和multi interface方式
//前者是采用阻塞的方式发送单条数据,后者采用组合的方式可以一次性发送多条数据

//Easy interface方式最基本的用法如下
//1、在主线程中调用curl_global_init(CURL_GLOBAL_ALL)初始化
//2、调用curl_easy_init获取一个句柄;
//3、调用curl_easy_setopt函数设置此次传输的一些基本参数,如url地址、http头、cookie信息、发送超时时间等,其中,CURLOPT_URL是必设的选项;
//4、设置完成后,调用curl_easy_perform函数发送数据;
//5、数据发送完毕后,调用curl_easy_cleanup清空句柄;
//6、调用curl_global_cleanup()做清理工作。
class HttpCurl
{
public:
    HttpCurl()
        :conn(NULL)
    {}

    ~HttpCurl(){
        //调用该函数清空句柄
        curl_easy_cleanup(conn);                                            
    }

    bool HttpCurlInit(string& context){
        CURLcode code;

        //1、在主线程中调用curl_global_init(CURL_GLOBAL_ALL)初始化
        code = curl_global_init(CURL_GLOBAL_ALL);
        if(CURLE_OK != code){
            cout << "Failed to global init default" << "\n";
            return false;
        }                   
        //2、调用curl_easy_init获取一个句柄;
        conn = curl_easy_init();
        if(NULL == conn){
            cout << "Failed to create CURL" << "\n";
            return false;
        }

        //3、调用curl_easy_setopt函数设置此次传输的一些基本参数
        
        //CURLOPT_WRITEFUNCTION 第三个参数表明的函数将在libcurl接收到数据后被调用,因此函数多做数据保存的功能
        //第三个参数回调函数原型为:size_t function( void *ptr, size_t size, size_t nmemb, void *stream ); 
        code = curl_easy_setopt(conn,CURLOPT_WRITEFUNCTION,HttpCurl::Write);
        if(CURLE_OK != code){
            cout << "Failed to set write" << "\n";
            return false;
        }
        // CURLOPT_WRITEDATA 用于表明CURLOPT_WRITEFUNCTION函数中的第三个参数stream的来源
        // 因此调用完该函数以后,收到的数据就被放到了context中
        code = curl_easy_setopt(conn,CURLOPT_WRITEDATA,&context);
        if(CURLE_OK != code){
            cout << "Failed to set write data" << "\n";
            return false;
        }
        return true;
    }
    
    bool SetUrl(string& url){
        CURLcode code;
        //CURLOPT_URL 用于设置访问的url
        code = curl_easy_setopt(conn,CURLOPT_URL,url.c_str());
        if(CURLE_OK != code){
            cout << "Failed to set URL" << "\n";
            return false;                                                                                                                        
        }
        return true;
    }
                                             
    bool GetHttpResponse(){
        CURLcode code;
        // curl_easy_perform函数完成curl_easy_setopt指定的所有选项
        code = curl_easy_perform(conn);
        if(CURLE_OK != code){
            cout << "Failed to get response" << "\n";
            return false;                                                                                                                                                
        }
        return true;                                                                                                                        
    }
                                                 
    static size_t Write(void* data,size_t size,size_t nmemb,string& context){
       long sizes = size*nmemb;
       string temp((char*)data,sizes);
       context += temp;
       return sizes;
    }

private:
    CURL* conn;
};

filter.hpp(这个文件主要是用来设置爬取的信息格式,注意需要用到boost库,没安装的可以去看下这个boost库的安装

#pragma once

#include <iostream>
#include <vector>
using namespace std;
#include <boost/regex.hpp>
#include <boost/algorithm/string.hpp>

class filter{
public:
    //该函数用来设置正则表达式匹配
    void SetReg(string reg){
        _reg = reg;
    }

    //该函数将匹配的结果连成一个字符串放在一起
    string filterUrlString(string url){
        string::const_iterator begin = url.begin();
        string::const_iterator end = url.end();

        string results;
        while(regex_search(begin,end,_what,_reg)){

            string result(_what[0].first,_what[0].second);
            results += result;
            
            begin = _what[0].second;
        }       

        return results;
    }

    //该函数将匹配的结果放到vector中
    vector<string> filterUrlVector(string url){
        string::const_iterator begin = url.begin();
        string::const_iterator end = url.end();

        vector<string> results;
        while(regex_search(begin,end,_what,_reg)){

            string result(_what[0].first,_what[0].second);
            results.push_back(result);
            
            begin = _what[0].second;
        }       

        return results;
    }

private:
    boost::regex _reg;
    boost::smatch _what;
};

spider.cc(这个源文件用来爬取新浪首页的内容,当然不可能所有的新闻页眉都是可以被我们设置的正则表达式匹配,只是大多是的页面,不匹配的大不了我们不要了嘛=_=)

#include "spider.hpp"
#include "../HTTP_server/cgi/operate.hpp"   //这个文件是我自己写的一个数据库交互库,下面在用到的时候会说明
#include "filter.hpp"
#include <map>


//将新闻信息存放到一个结构体中
struct NewsInfo{
    string _topic;
    string _title;
    string _author;
    string _content;

    NewsInfo(string topic,string title,string author,string content)
        :_topic(topic),
         _title(title),
         _author(author),
         _content(content)
    {}
};

//替换结果中的标签部分,取出文字部分
//该函数只使用于在文字一边<>成对出现的情况
//比如<h1>xxx</h1>
//不适用与这种情况<meta property="og:title" content="xxx" />
void Replace(string& src){
    while(src.find('<') != string::npos){
        boost::regex expr("<.*?>");
        src = boost::regex_replace(src,expr,"");
    }
}

//获取网页的html源码
bool GetHtml(HttpCurl curl,string& context,string url){
    curl.HttpCurlInit(context);
    curl.SetUrl(url);
    //调用完GetHttpResponse函数以后,响应的body部分就在context中了
    return curl.GetHttpResponse();
}

//获取相应主题的新闻链接
map<string,string> GetTopicLinks(string context,filter ft,MYSQL* conn){
    //下面这个函数用来取出我的数据库中的所有主题
    vector<string> topics = OperateTopic::AllTopic(conn);

    //我们要匹配到新浪首页对应标签栏的部分的内容
    //这个部分不是固定的,是根据你要爬的网页自己找出共性,自己分析的
    ft.SetReg("<div class=\"nav-mod-1.*?</div>");
    string result = ft.filterUrlString(context);

    //再拿到能点击进入链接部分(这是点击不同主题进入的链接,需要有主题的标题)
    ft.SetReg("<a href.*?</a>");
    vector<string> hrefs = ft.filterUrlVector(result);
    map<string,string> links;

    //最后拿到http链接
    //这里因为结尾最后都有一个",所以到时候返回的时候这个"得删去
    ft.SetReg("http.*?\"");
    for(auto i:topics){
        //只查找href中有对应主题的几个链接
        for(auto j:hrefs){
            //下面的函数用来判断在href中是否有主题名称
            boost::iterator_range<string::iterator> r = boost::algorithm::find_first(j,i);
            string link;
            if(!r.empty()){
                link = ft.filterUrlString(j);
                links[i] = link.substr(0,link.size() - 1);
            }
        }
    }
    return links;
}

//该函数用于拿到对应主题下的新闻链接
vector<pair<string,string>> GetNewsLinks(map<string,string> topicLinks,filter ft){
    HttpCurl curl;
    vector<pair<string,string>> v; 
    for(auto i : topicLinks){
        string context;
        if(!GetHtml(curl,context,i.second))
            continue;
        string flag;
        //现在已经拿到了点进每个主题下的html
        

        //根据每个主题下的内容不同,设置对应的flag
        //这个也是新浪对应主题下的新闻链接的格式
        //因为每个主题的格式各不相同,因此也是需要你去对应的新闻下查出来有新闻链接部分的共性内容
        //下面这8个主题是我数据库中的8个主题
        if(i.first == "文化"){
            ft.SetReg("div class=\"action\".*?</div>");;
            flag = "cul"; 
        }else if(i.first == "体育"){
            ft.SetReg("<div style=\"display:none!important;\">.*?</div>");
            flag = "sports";
        }else if(i.first == "NBA"){
            ft.SetReg("<ul class=\"list layout-mb-b\">.*?</ul>");
            flag = "sports";
        }else if(i.first == "国际"){
            ft.SetReg("<div class=\"news-item  \">.*?</div>");
            flag = "news";
        }else if(i.first == "股票"){
            ft.SetReg("<ul class=\"list04\">.*?</ul>");
            flag = "finance";
        }else if(i.first == "娱乐"){
            ft.SetReg("<ul class=\"seo_data_list\">.*?</ul>");
            flag = "ent";
        }else if(i.first == "教育"){
            ft.SetReg("<ul class=\"seo_data_list\">.*?</ul>");
            flag = "edu";
        }else if(i.first == "科技"){
            ft.SetReg("<ul class=\"seo_data_list\">.*?</ul>");
            flag = "tech";
        }
        
        string result = ft.filterUrlString(context);
        string tmp = "http://" + flag + ".*?shtml";
        ft.SetReg(tmp);
        vector<string> links = ft.filterUrlVector(result);
        //有些网页会有些错误,比如说下面这种
        //http://tech.sina.cn/digi/nb/2018-07-09/detail-ihezpzwt8158157.d.html" target="_blank">新老冰箱保鲜效果大比拼 结果让人大吃一惊a>li>
        //<li><a href="https://tech.sina.com.cn/i/2018-07-09/doc-ihezpzwt8148659.shtml
        //但没关系,到时候在GetHtml这个函数中就不会继续了
        for(auto item:links)
            v.push_back(make_pair(i.first,item));
    }

    return v;
}

//获取新闻信息
NewsInfo* GetNewsInfo(pair<string,string> item,filter ft){
    HttpCurl curl;
    string context;
    if(!GetHtml(curl,context,item.second)){
        cout << item.first << ":" << item.second << ":" << "link error";
        return NULL;
    }

    //取出新闻标题
    string title;
    //基本上的新闻的标题都是下面两个格式之一
    vector<string> reg = {"<h1 class=\"main-title\">.*?</h1>","<meta property=\"og:title\" content=\".*?\""};
    for(auto i : reg){
        ft.SetReg(i);
        title = ft.filterUrlString(context);
        if(title.empty())
            continue; 
        else{
            if(i == "<h1 class=\"main-title\">.*?</h1>")
                Replace(title);
            else{
                //这两个函数用于替换中文标题两边的所有内容为空
                boost::algorithm::replace_all(title,"<meta property=\"og:title\" content=\"","");
                boost::algorithm::replace_all(title,"\"","");
            }
            break;
        }
    }
    if(title.empty()){
        //认为没有标题,则放弃这个新闻,不要了
        cout << item.first << ":" << item.second << ":" << "no title" << endl;
        return NULL;
    }
    
    //取出新闻作者
    //基本上的新闻的作者都是下面两个格式之一
    reg = {"<p class=\"article-editor\">.*?</p>","article:author\" content=\".*?\""};
    string author;
    for(auto i : reg){
        ft.SetReg(i);
        author = ft.filterUrlString(context);
        if(author.empty())
            continue; 
        else{
            if(i == "<p class=\"article-editor\">.*?</p>")
                Replace(author);
            else{
                boost::algorithm::replace_all(author,"article:author\" content=\"","");
                boost::algorithm::replace_all(author,"\"","");
            }
            break;
        }
    }
    if(author.empty()){
        //认为没有作者,则放弃这个新闻,不要了
        cout << item.first << ":" << item.second << ":" << "no author" << endl;
        return NULL;
    }

    //取出新闻内容题
    //基本上的新闻的内容都是下面两个格式之一
    reg = {"<span class=\"img_descr\">.*?<div","<div class=\"article\".*?</div>"};
    string result;
    for(auto i : reg){
        ft.SetReg(i);
        result = ft.filterUrlString(context);
        if(result.empty())
            continue; 
        else
            break;
    }
    if(result.empty()){
        //认为没有内容,则放弃这个新闻,不要了
        cout << item.first << ":" << item.second << ":" << "no content" << endl;;
        return NULL;
    }

    //<p></p>包含着的部分就是新闻正文部分
    ft.SetReg("<p.*?</p>");
    vector<string> paragraphs = ft.filterUrlVector(result);
    if(paragraphs.empty()){
        //认为没有内容,则放弃这个新闻,不要了
        cout << item.first << ":" << item.second << ":" << "no content" << endl;;
        return NULL;
    }

    string content = "";
    for(size_t i = 0; i < paragraphs.size();++i){
        Replace(paragraphs[i]);
        content += paragraphs[i] + '\n';//在每一个段落末尾加上一个\n换行
    }

    NewsInfo* info = new NewsInfo(item.first,title,author,content);
    return info;
}

int main(){
    string context;
    HttpCurl curl;
    //找新浪是因为新浪的网页编码是utf8
    string url = "http://news.sina.com.cn/";

    if(!GetHtml(curl,context,url)){
        cout << "The link is error!" << endl;
        return 0;
    }
    
    //下面两个函数用于连接数据库
    //具体爬下来的数据怎么处置,由你自己定,只是我这里是放到数据库里罢了
    MYSQL* conn;
    DataBaseInit(conn);

    filter ft;
    map<string,string> topicLinks = GetTopicLinks(context,ft,conn);

    vector<pair<string,string>> newsLinks = GetNewsLinks(topicLinks,ft);
    int count = newsLinks.size();
    //这个count表示我们拿到的所有链接数
    cout << count  << endl;
    
    for(auto i:newsLinks){
        NewsInfo* info = GetNewsInfo(i,ft);
        if(info == NULL){
            //返回空表示给的链接是错误的
            count--;
            continue;
        }
        
        //将数据放到数据库中
        int topicid = OperateTopic::IdOfTopic(conn,i.first);
        OperateNews::AddNews(conn,topicid,info->_title,info->_author,info->_content);
    }

    //这个count表示我们实际获取到的匹配到的新闻数
    cout << count << endl;

    return 0;
}

猜你喜欢

转载自blog.csdn.net/lvyibin890/article/details/81040971
今日推荐