zoukankan      html  css  js  c++  java
  • 一只简单的网络爬虫(基于linux C/C++)————Url处理以及使用libevent进行DNS解析

    Url处理

    爬虫里使用了两个数据结构来管理Url
    下面的这个数据结构用来维护原始的Url,同时有一个原始Url的队列

    //维护url原始字符串
    typedef struct Surl {
        char  *url;
        int    level;//url抓取深度
        int    type;//抓取类型
    } Surl;
    

    原始的Url队列static queue <Surl *> surl_queue;//这个队列存放解析前的
    下面的Url结构体用来维护解析后的url,同样的,配有一个url的队列

    //解析后的
    typedef struct Url {
        char *domain;//域名
        char *path;//路径
        int  port;//端口
        char *ip;//IP
        int  level;//深度
    } Url;

    解析后的url队列static queue<Url *> ourl_queue;//这个队列存放DNS解析后的
    另外,采用一个map容器用来保存域名解析前后的url的主机名称和ip地址

    static map<string, string> host_ip_map;//主机,ip的map容器

    采用这样的方式的原因是,DNS解析是一个比较浪费时间的过程,解析过的主机名我们将其与ip地址采用map关联起来,因为同一个html页面里可能会有多个url是同一个主机名的,这样一来我们可以直接在map容器中查找该主机名对应的ip,而不必每次都进行DNS解析,这样做可以达到提高效率的效果
    两个队列的一些常见的操作(出入队列等)这里就不在弹了
    下面看看一个url解析的函数

    //解析域名,解析域名后surl队列中surl结构体会转化为url结构体放入url队列
    void * urlparser(void *none)
    {
        Surl *url = NULL;
        Url  *ourl = NULL;
        map<string, string>::const_iterator itr;
        //event_base * base = event_base_new();
        //evdns_base * dnsbase = evdns_base_new(base, 1);
        //event_base_loop(base,EVLOOP_NONBLOCK);
    
        while(1) {
            pthread_mutex_lock(&sq_lock);
            while (surl_queue.empty()) //surl队列为空则一直等待,直到被唤醒
            {
                pthread_cond_wait(&sq_cond, &sq_lock);
            }
            url = surl_queue.front();//取出surl队列中的url
            surl_queue.pop();
            pthread_mutex_unlock(&sq_lock);
    
            ourl = surl2ourl(url);//原始的结构体转化为ourl
             //在回调函数中解析完加进去的 
            itr = host_ip_map.find(ourl->domain);//在主机IP的map中寻找
            if (itr == host_ip_map.end())//找不到的才需要解析,找到说明之前解析过了 
            { // not found  
              //解析DNSdns resolve
                event_base * base = event_init();//执行一次libevent库的初始化
                evdns_init();//在使用任何解析器函数之前,必须调用evdns_init()函数初始化函数库
                evdns_resolve_ipv4(ourl->domain, 0, dns_callback, ourl);//dns_callback回调函数
                event_dispatch();
                event_base_free(base);
    
                //evdns_base_resolve_ipv4(dnsbase, ourl->domain, 0, dns_callback, ourl);
                //event_base_loop(base, EVLOOP_ONCE | EVLOOP_NONBLOCK);
            } 
            else 
            {
                ourl->ip = strdup(itr->second.c_str());//之前解析过,直接拷贝
                push_ourlqueue(ourl);//送入队列
            }
        }
    
        //evdns_base_free(dnsbase, 0);
        //event_base_free(base);
        return NULL;
    }
    

    surl2ourl(Surl * surl)函数如下,主要是将原始的url进行分割,分离出域名和路径,端口等,然后填入解析后的url结构

    //原始字符串Surl结构转化为url结构,
    static Url * surl2ourl(Surl * surl)
    {//calloc在动态分配完内存后,自动初始化该内存空间为零,而malloc不初始化,里边数据是随机的垃圾数据
        Url *ourl = (Url *)calloc(1, sizeof(Url));
    //strchr函数原型:extern char *strchr(const char *s,char c);查找字符串s中首次出现字符c的位置。
        char *p = strchr(surl->url, '/');
        if (p == NULL)//原始字符串不存在'/'
        {
            ourl->domain = surl->url;//直接是域名
            ourl->path = surl->url + strlen(surl->url);//路径其实是空的
        } 
        else 
        {
            *p = '';//覆盖'/'
            ourl->domain = surl->url;//提取域名
            ourl->path = p+1;//提取路径
        }
        // port端口,冒号后面是端口
        //查找字符在指定字符串中从正面开始的最后一次出现的位置
        p = strrchr(ourl->domain, ':');//找最后一个出现的冒号
        if (p != NULL) 
        {
            *p = '';
            ourl->port = atoi(p+1);
            if (ourl->port == 0)
                ourl->port = 80;
    
        } 
        else //Url中若没有端口号,则是默认的80端口
        {
            ourl->port = 80;
        }
        // level
        ourl->level = surl->level;
        return ourl;
    }

    urlparser函数主要完成了下面的工作,surl转化为url结构,查找map容器,如果是之前未解析的,采用lievent进行域名解析,然后加入map容器,url结构进入ourl队列

    lievent的DNS解析

    lievent的使用可以参考libevent Documentation
    主要是使用了该函数
    这里写图片描述

    int evdns_resolve_ipv4  (const char *name,
    int flags,
    evdns_callback_type callback,
    void *  ptr 
    )       

    参数

    name:是想要DNS解析的一个主机名
    flags:可以填 0,或者 DNS_QUERY_NO_SEARCH 禁用搜索此查询
    callback:是一个回调函数,在解析完成的时候会回调该函数 
    ptr:    一个传给回调函数的参数

    在回调函数中可以得到解析后的ip地址

    //DNS解析回调函数
    static void dns_callback(int result, char type, int count, int ttl, void *addresses, void *arg) 
    {
        Url * ourl = (Url *)arg;
        struct in_addr *addrs = (in_addr *)addresses;
    
        if (result != DNS_ERR_NONE || count == 0) 
        {
            SPIDER_LOG(SPIDER_LEVEL_WARN, "Dns resolve fail: %s", ourl->domain);
        } 
        else
       {
            char * ip = inet_ntoa(addrs[0]);//网络字节序转化为主机字节序
            SPIDER_LOG(SPIDER_LEVEL_DEBUG, "Dns resolve OK: %s -> %s", ourl->domain, ip);
            host_ip_map[ourl->domain] = strdup(ip);//ip填入domain对应的ip
            ourl->ip = strdup(ip);//ip填入ourl
            push_ourlqueue(ourl);//加入队列
        }
        event_loopexit(NULL); // not safe for multithreads 
    }

    另外,在主函数中,专门开了一个线程用来进行url的DNS解析的

    // 启动用于解析DNS的线程 
        int err = -1;
        if ((err = create_thread(urlparser, NULL, NULL, NULL)) < 0) 
        {//urlparser在url.cpp中
            SPIDER_LOG(SPIDER_LEVEL_ERROR, "创建Url解析线程失败: %s", strerror(err));
        }

    只要原始的url队列不为空,则一直进行DNS的解析,解析后放入另一个队列,若原始url为空(还没有抓取其他的url),则一直在等待,使用的是条件变量

     pthread_cond_wait(&sq_cond, &sq_lock);

    知道surl队列有url入队,被唤醒继续进行DNS解析的服务

    //发送一个信号给另外一个正在处于阻塞等待状态的线程,
        //使其脱离阻塞状态,继续执行.如果没有线程处在阻塞等待状态,
        //pthread_cond_signal也会成功返回
            if (surl_queue.size() == 1)
                pthread_cond_signal(&sq_cond);
  • 相关阅读:
    函数式宏定义与普通函数
    linux之sort用法
    HDU 4390 Number Sequence 容斥原理
    HDU 4407 Sum 容斥原理
    HDU 4059 The Boss on Mars 容斥原理
    UVA12653 Buses
    UVA 12651 Triangles
    UVA 10892
    HDU 4292 Food
    HDU 4288 Coder
  • 原文地址:https://www.cnblogs.com/sigma0-/p/12630467.html
Copyright © 2011-2022 走看看