• C++(SOCKET)简单爬虫制作


    先给出代码:(我使用的是VS编译器,需要在项目-》project属性-》

    #include <iostream>
    #include <stdio.h>
    #include <string>
    #include <cstdlib>
    #include <fstream>
    #include <WinSock2.h>
    
    using namespace std;
    
    #pragma warning(disable:4996)
    //忽略VS特有警告
    #pragma comment(lib, "ws2_32.lib")
    //加载ws2_32.dll
    #define BUFF_SIZE 1024
    
    int ncount = 0;
    string host, pos;
    
    SOCKET ConnectFunc(string host, string pos) {
        WSADATA wsaData;
        SOCKET serv;
        //创建套接字
        if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {//初始化DLL
            cout << "WSAStartup() Failed:" << WSAGetLastError() << endl;
            system("PAUSE");
            return -1;
        }
    
        serv = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        //初始化套接字
        if (serv == INVALID_SOCKET) {
            cout << "socket() Failed:" << WSAGetLastError() << endl;
            system("PAUSE");
            return -1;
        }
    
        struct hostent *pt = gethostbyname(host.c_str());//解析域名IP
        if (!pt) {
            cerr << "Get IP Error!!!" << endl;
            system("PAUSE");
            return -1;
        }
        struct sockaddr_in serv_addr;
        //创建结构体sockaddr_in结构体变量,绑定套接字
        memcpy(&serv_addr.sin_addr, pt->h_addr, 4);
        //自动响应服务器IP
        serv_addr.sin_family = AF_INET;
        //IPv4
        serv_addr.sin_port = htons(80);
        //端口
    
        /*输出服务器IP
        for (int i = 0; pt->h_addr_list[i]; i++) {
            printf("IP addr %d: %s
    ", i + 1, inet_ntoa(*(struct in_addr*)pt->h_addr_list[i]));
        }
        */
    
        if (connect(serv, (LPSOCKADDR)&serv_addr, sizeof(SOCKADDR)) == SOCKET_ERROR) {//连接服务器
            cout << "connect() Failed:" << WSAGetLastError() << endl;
            system("PAUSE");
            return -1;
        }//与服务器建立连接
    
        string request = "GET " + pos + " HTTP/1.1
    Host:" + host + "
    Connection:Close
    
    ";
        //向服务器请求图片资源(发送到服务器的命令)
        if (send(serv, request.c_str(), request.size(), 0) == SOCKET_ERROR) {
            cout << "send() Failed:" << WSAGetLastError() << endl;
            closesocket(serv);
            system("PAUSE");
            return -1;
        }//发送指令消息
    
        return serv;
        //返回套接字
    }
    
    void DownloadPicture() {
        SOCKET serv_in = ConnectFunc(host, pos);
        //连接服务器
        
        char buffer[BUFF_SIZE] = { 0 };
        //数据缓存文件
    
        string a = "G:\Pictures\", name;
        cout << "picture name:";
        cin >> name;
        a = a + name + ".png";
        //文件命名
    
        FILE *fp = fopen(a.c_str(), "wb+");
        //创建文件
    
        if (NULL == fp) {
            cerr << "Open File" << endl;
            system("PAUSE");
            exit(-1);
        }
    
        ncount = recv(serv_in, buffer, BUFF_SIZE, 0);
        //跳过不需要信息(状态行和消息报头)
    
        char *infor = strstr(buffer, "
    
    ");
        //区分条件
        fwrite(infor + strlen("
    
    "), sizeof(char), ncount - (infor - buffer) - strlen("
    
    "), fp);
        //丢弃不需要数据
        for (; (ncount = recv(serv_in, buffer, BUFF_SIZE, 0)) > 0;) {
            fwrite(buffer, sizeof(char), BUFF_SIZE, fp);
            Sleep(2);
        }//循环写入数据
    
        fclose(fp);
        //关闭文件流指针
        closesocket(serv_in);
        //断开连接,清除套接字
    }
    
    int main()
    {
        CreateDirectory(L"G:Pictures", NULL);
        /*创建文件夹,如果程序报错就用下面这个
        CreateDirectoryA("G:Pictures", NULL);
        */
    
        
        host = "images.cnblogs.com";
        //cin >> host;
        //输入要爬的网站地址
        pos = "/cnblogs_com/Mayfly-nymph/1233628/o_images.png";
        //cin >> pos;
        //图片在服务器中的位置
        DownloadPicture();
        //下载图片
        system("PAUSE");
        return 0;
    }
    程序中:
    1. 这里图片保存在G盘,没有G盘改成其他盘。
    2. 对文件命名可以替换成自动命名,比如命名:1.png, 2.png...
    3. 下载链接可以切换成输入,还可以加上一个循环(加上结束条件)。
    4. 数据操作可以用C++的,个人习惯C的。
    思路(简单来说):
    1. 先把连接服务器的结构搭建好(作客户端),IP地址利用gethostbyname()函数获取。
    2. 向服务器发送获取资源命令。
    3. 接受数据并过滤不需要信息。
    4. 写入文件

    1.搭建框架

    首先我们将网络连接的一个结构搭建好:

     1 WSADATA wsaData;
     2 SOCKET serv;
     3 
     4 WSAStartup(MAKEWORD(2, 2), &wsaData)
     5 
     6 serv = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
     7 
     8 
     9 struct hostent *pt = gethostbyname(host.c_str());
    10 
    11 struct sockaddr_in serv_addr;
    12 memcpy(&serv_addr.sin_addr, pt->h_addr, 4);
    13 serv_addr.sin_family = AF_INET;
    14 serv_addr.sin_port = htons(80);
    15 
    16 connect(serv, (LPSOCKADDR)&serv_addr, sizeof(SOCKADDR))
    17 
    18 send(serv, request.c_str(), request.size(), 0)

    其中唯一需要注意的就是,IP地址的连接。因为我们输入的是域名,所以需要利用到gethostbyname()函数解析域名。(要了解这个函数:点击一下

    这是一个解析域名的小程序:

    #include <stdio.h>
    #include <stdlib.h>
    #include <WinSock2.h>
    #pragma comment(lib, "ws2_32.lib")
    
    int main(){
        WSADATA wsaData;
        WSAStartup( MAKEWORD(2, 2), &wsaData);
    
        struct hostent *host = gethostbyname("www.baidu.com");
        if(!host){
            puts("Get IP address error!");
            system("pause");
            exit(0);
        }
    
        //别名
        for(int i=0; host->h_aliases[i]; i++){
            printf("Aliases %d: %s
    ", i+1, host->h_aliases[i]);
        }
    
        //地址类型
        printf("Address type: %s
    ", (host->h_addrtype==AF_INET) ? "AF_INET": "AF_INET6");
    
        //IP地址
        for(int i=0; host->h_addr_list[i]; i++){
            printf("IP addr %d: %s
    ", i+1, inet_ntoa( *(struct in_addr*)host->h_addr_list[i] ) );
        }
    
        system("pause");
        return 0;
    }

    2.指令发送

    我们与服务器成功连接之后,向服务器发送HTTP请求报头。

    string request = "GET " + pos + " HTTP/1.1 Host:" + host + " Connection:Close ";

    (借用)

    GET 请求获取Request-URI所标识的资源;

    name 所标识的资源;

    HTTP/1.1 表示请求的HTTP协议版本;

    Host:url  指定被请求资源的Internet主机和端口号,通常从HTTP URL中提取出来的,

    比如 我们在浏览器中输入http://baidu.com/index.html浏览器发送的请求消息中,就会包含Host请求报头域,如下: Host:www.baidu.com

    此处使用缺省端口号80,若指定了端口号,则变成:Host:www.baidu.com:port

    Connection:Close Connection字段用于设定是否使用长连接,在http1.1中默认是使用长连接的,即Connection的值为Keep-alive,如果不想使用长连接则需要明确指出connection的值为close

    Connection:Close表明当前正在使用的tcp链接在请求处理完毕后会被断掉。以后client再进行新的请求时就必须创建新的tcp链接了,即必须从新创建socket

    详细了解:点击一下

    3.获取资源

    在接收和解释请求消息后,服务器会返回一个HTTP响应消息。

      与HTTP请求类似,HTTP响应也是由三个部分组成,分别是:状态行,消息报头,相应正文。

      状态行由协议版本,数字形式的状态代码,相应的状态描述组成,各元素之间以空格分隔,除了结尾的CRLF(回车换行)序列外,不允许出现CR或LF字符。格式如下:

      HTTP-Version Status-Code Reason-Phrase CRLF

      HTTP-Version表示服务器HTTP协议的版本,Status-Code表示服务器发回的响应代码,Reason-Phrase表示状态代码的文本描述,CRLF表示回车换行。

    因为相应正文是我们需要的数据,所以我们需要将状态行和消息报头跳过。

    消息报头与相应正文之间可以用 进行区分,当第一次发现接收到的字符串数组中含有 时,则将 前的内容全部忽略,将剩下的内容写到文件中去。

    ncount = recv(serv_in, buffer, BUFF_SIZE, 0);
    
    char *infor = strstr(buffer, "
    
    ");
    
    fwrite(infor + strlen("
    
    "), sizeof(char), ncount - (infor - buffer) - strlen("
    
    "), fp);

    再使用循环获取相应正文,将数据写入到创建的图片文件中。

    for (; (ncount = recv(serv_in, buffer, BUFF_SIZE, 0)) > 0;) {
            fwrite(buffer, sizeof(char), BUFF_SIZE, fp);
            Sleep(2);
        }

    这样一个简单的“爬虫”就实现了,没有正则表达式,也没有BFS,只是试试水。爬虫(图片)有兴趣可以看看这篇(点击进入)  BFS(点击进入

    biubiubiu~有什么不懂可以加我问我,觉得可以就赞一个吧,你们是我最大的动力。

  • 相关阅读:
    iOS编程中比较两个日期的大小
    sqlite第三方类库:FMDB使用
    ios日期格式转换
    UISwipeGestureRecognizer 左右事件捕捉
    iOS7.0中UILabel高度调整注意事项
    【java基础】Java反射机制
    【struts2】ActionContext与ServletActionContext
    【struts2】OGNL
    【struts2】值栈(后篇)
    【struts2】值栈(前篇)
  • 原文地址:https://www.cnblogs.com/Mayfly-nymph/p/9743996.html
Copyright © 2020-2023  润新知