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\n", 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\r\nHost:" + host + "\r\nConnection:Close\r\n\r\n"; //向服务器请求图片资源(发送到服务器的命令) 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, "\r\n\r\n"); //区分条件 fwrite(infor + strlen("\r\n\r\n"), sizeof(char), ncount - (infor - buffer) - strlen("\r\n\r\n"), 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; }
程序中:
- 这里图片保存在G盘,没有G盘改成其他盘。
- 对文件命名可以替换成自动命名,比如命名:1.png, 2.png...
- 下载链接可以切换成输入,还可以加上一个循环(加上结束条件)。
- 数据操作可以用C++的,个人习惯C的。
思路(简单来说):
- 先把连接服务器的结构搭建好(作客户端),IP地址利用gethostbyname()函数获取。
- 向服务器发送获取资源命令。
- 接受数据并过滤不需要信息。
- 写入文件
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\n", i+1, host->h_aliases[i]); } //地址类型 printf("Address type: %s\n", (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\n", 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\r\nHost:" + host + "\r\nConnection:Close\r\n\r\n";
(借用)
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表示回车换行。
因为相应正文是我们需要的数据,所以我们需要将状态行和消息报头跳过。
消息报头与相应正文之间可以用\r\n\r\n进行区分,当第一次发现接收到的字符串数组中含有\r\n\r\n时,则将\r\n\r\n前的内容全部忽略,将剩下的内容写到文件中去。
ncount = recv(serv_in, buffer, BUFF_SIZE, 0); char *infor = strstr(buffer, "\r\n\r\n"); fwrite(infor + strlen("\r\n\r\n"), sizeof(char), ncount - (infor - buffer) - strlen("\r\n\r\n"), fp);
再使用循环获取相应正文,将数据写入到创建的图片文件中。
for (; (ncount = recv(serv_in, buffer, BUFF_SIZE, 0)) > 0;) { fwrite(buffer, sizeof(char), BUFF_SIZE, fp); Sleep(2); }
这样一个简单的“爬虫”就实现了,没有正则表达式,也没有BFS,只是试试水。爬虫(图片)有兴趣可以看看这篇(点击进入) BFS(点击进入)
biubiubiu~有什么不懂可以加我问我,觉得可以就赞一个吧,你们是我最大的动力。