一只C++爬虫

一只c++爬虫

一、原理

爬虫:用于在网页上抓取数据,用队列的思想,进行BFS~(将源URL放入队列,从队头取出一个URL进行遍历,并将其页面上的所有未爬过的URL放入队列中,直到队列为空。

二、实现

代码及解析如下:

int main()

{

    startupWSA();

    Go("music.163.com", 200);

    cleanupWSA();

system("pause");

    return 0;

}

void startupWSA()

{

    WSADATA wsadata;//版本信息

    WSAStartup( MAKEWORD(2,0), &wsadata);

}

解析:为了在应用程序当中调用任何一个Winsock API函数,首先第一件事情就是必须通过WSAStartup函数完成对Winsock服务的初始化,因此需要调用WSAStartup函数。使用Socket的程序在使用Socket之前必须调用WSAStartup函数。该函数的第一个参数指明程序请求使用的Socket版本,其中高位字节指明副版本、低位字节指明主版本;操作系统利用第二个参数返回请求的Socket的版本信息。当一个应用程序调用WSAStartup函数时,操作系统根据请求的Socket版本来搜索相应的Socket库,然后绑定找到的Socket库到该应用程序中。以后应用程序就可以调用所请求的Socket库中的其它Socket函数了。

void Go(const string &url, int count)

{

queue<string> urls;//用于放置所有的url

    urls.push(url);

    for (auto i = 0; i != count; ++i)

    {

        if ( !urls.empty() )

        {

            auto &url = urls.front();

            auto pair = binaryString( url, "/" );

            auto sock = connect(pair.first);//链接到拿出的URL

            if ( sock && sendRequest(sock, pair.first, "/" + pair.second) )

            {

                auto buffer = move( recvRequest(sock) );

                extUrl(buffer, urls);

            }

      closesocket(sock);

            cout << url << ": count=> " << urls.size() <<  endl;

            urls.pop();

        }

    }

}//

inline pair<string, string> binaryString(const string &str, const string &dilme)

{

    //这个函数用于将字符串从‘/’处进行分割,分开URL和主机号,所以这只能做简单的识别,识别出第一个‘/’

    pair<string, string> result(str, "");

    auto pos = str.find(dilme);

    if ( pos != string::npos )

    {

        result.first = str.substr(0, pos);

        result.second = str.substr(pos + dilme.size());

    }

    return result;

}

inline SOCKET connect(const string &hostName)

{

    auto ip = getIpByHostName(hostName);

    if ( ip.empty() )

        return 0;

    auto sock = socket(AF_INET, SOCK_STREAM, 0);

    if ( sock == INVALID_SOCKET )

        return 0;

    SOCKADDR_IN addr;

    addr.sin_family = AF_INET;

    addr.sin_port = htons(80);

    addr.sin_addr.s_addr = inet_addr(ip.c_str());

    if ( connect(sock, (const sockaddr *)&addr, sizeof(SOCKADDR_IN)) == SOCKET_ERROR )

        return 0;

    return sock;

}

inline string getIpByHostName(const string &hostName)

{

//gethostbyname()返回对应于给定主机名的包含主机名字和地址信息的hostent结构指针。结构的//声明与gethostbyaddr()中一致。

    hostent* phost = gethostbyname( hostName.c_str() );

    return phost? inet_ntoa(*(in_addr *)phost->h_addr_list[0]): "";//得到对应的IP

}

inline bool sendRequest(SOCKET sock, const string &host, const string &get)

{

//用于

    string http

        = "GET " + get + " HTTP/1.1\r\n"

        + "HOST: " + host + "\r\n"

        + "Connection: close\r\n\r\n";

    return http.size() == send(sock, &http[0], http.size(), 0);//send函数返回发送的字节//数是否一致

}

inline string recvRequest(SOCKET sock)

{

    static timeval wait = {2, 0};

    static auto buffer = string(2048 * 100, '\0');

    auto len = 0, reclen = 0;

    do {

        fd_set fd = {0};

        FD_SET(sock, &fd);

        reclen = 0;

        if ( select(0, &fd, nullptr, nullptr, &wait) > 0 )

        {

            reclen = recv(sock, &buffer[0] + len, 2048 * 100 - len, 0);//接收返回的数据//流

            if (reclen > 0)

                len += reclen;

        }

        FD_ZERO(&fd);

    } while (reclen > 0);

    return len > 11

        ? buffer[9] == '2' && buffer[10] == '0' && buffer[11] == '0'

        ? buffer.substr(0, len)

        : ""

        : "";

}

inline void extUrl(const string &buffer, queue<string> &urlQueue)

{

    if (buffer.empty())

    {

        return ;

    }

    smatch result;

    auto curIter = buffer.begin();

    auto endIter = buffer.end();

    while ( regex_search(curIter, endIter, result, regex("href=\"(https?:)?//\\S+\"") ) )

    {//正则匹配找到超链接

        urlQueue.push(regex_replace(

            result[0].str(),

            regex("href=\"(https?:)?//(\\S+)\""),

            "$2") );

        curIter = result[0].second;

    }

}

本程序的流程为:启动Winsocket服务——将给的URL加入队列——取出队头元素——进行划分找出URL和主机——连接Socket——接收到返回的数据——判断其中是否有URL——将满足条件的URL加入队列——关闭Socket.

发布了47 篇原创文章 · 获赞 8 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/nanchengyu/article/details/53256551