zoukankan      html  css  js  c++  java
  • 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~有什么不懂可以加我问我,觉得可以就赞一个吧,你们是我最大的动力。

  • 相关阅读:
    字符串替换
    字符串查找
    字符串比较
    字节与字符串相互转换
    1365. How Many Numbers Are Smaller Than the Current Number
    1486. XOR Operation in an Array
    1431. Kids With the Greatest Number of Candies
    1470. Shuffle the Array
    1480. Running Sum of 1d Array
    【STM32H7教程】第56章 STM32H7的DMA2D应用之刷色块,位图和Alpha混合
  • 原文地址:https://www.cnblogs.com/Mayfly-nymph/p/9743996.html
Copyright © 2011-2022 走看看