2019年了,发现以前的很多教程都不能用了。

我自己写的socket发给服务器总是返回301错误——资源永久转移。很多教程都是这样,困扰了我很久。

终于我发现了一篇能用的爬虫代码,参考MSDN以及众多博主的博客,大概给这篇代码做了注解。

#define _WINSOCK_DEPRECATED_NO_WARNINGS

#include <iostream>
#include <vector>
#include <list>
#include <map>
#include <queue>
#include <string>
#include <utility>
#include <regex>
#include <fstream>
#include <WinSock2.h>
#include <Windows.h> #pragma comment(lib, "ws2_32.lib") using namespace std; void startupWSA() //初始化socket
{
WSADATA wsadata;
WSAStartup(MAKEWORD(, ), &wsadata);
//参数1:指定wsa版本
//参数2:传输版本,套接字规范等信息到WSADATA,用于接收WSA套接字详细信息
} inline void cleanupWSA() //释放socket
{
WSACleanup();
//无参数,清理释放WSA资源
} inline pair<string, string> binaryString(const string &str, const string &dilme)
{
pair<string, string> result(str, "");
auto pos = str.find(dilme);
if (pos != string::npos)
{
result.first = str.substr(, pos);
result.second = str.substr(pos + dilme.size());
}
return result;
} inline string getIpByHostName(const string &hostName) //从域名获得IP地址
{
hostent* phost = gethostbyname(hostName.c_str()); //从域名得到IP地址(DNS)
//hostent:该结构通过函数来存储关于一个给定的主机,如主机名,IPv4地址
return phost ? inet_ntoa(*(in_addr *)phost->h_addr_list[]) : ""; //返回得到的点分十进制IP地址,如果转换失败返回""
//inet_ntoa:将一个32位网络字节序的二进制IP地址转换成相应的点分十进制的IP地址
} inline SOCKET connect(const string &hostName) //
{
auto ip = getIpByHostName(hostName); //获得host(IP) (上函数)
if (ip.empty())
return ;
auto sock = socket(AF_INET, SOCK_STREAM, );
//参数1(domain):协议域,又称协议族(family)。
//常用的协议族有AF_INET、AF_INET6、AF_LOCAL(或称AF_UNIX,Unix域Socket)、AF_ROUTE等。
//协议族决定了socket的地址类型,在通信中必须采用对应的地址,
//如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、
//AF_UNIX决定了要用一个绝对路径名作为地址。 //参数2(type):指定Socket类型。
//常用的socket类型有SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等。
//流式Socket(SOCK_STREAM)是一种面向连接的Socket,针对于面向连接的TCP服务应用。
//数据报式Socket(SOCK_DGRAM)是一种无连接的Socket,对应于无连接的UDP服务应用。 //参数3(protocol):指定协议。
//常用协议有IPPROTO_TCP、IPPROTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,
//分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。
//注意:1.type和protocol不可以随意组合,如SOCK_STREAM不可以跟IPPROTO_UDP组合。
//当第三个参数为0时,会自动选择第二个参数类型对应的默认协议。 if (sock == INVALID_SOCKET)
return ;
//INVALID_SOCKET:该返回值代表创建套接字错误
SOCKADDR_IN addr;
addr.sin_family = AF_INET;
addr.sin_port = htons();
addr.sin_addr.s_addr = inet_addr(ip.c_str());
if (connect(sock, (const sockaddr *)&addr, sizeof(SOCKADDR_IN)) == SOCKET_ERROR)
return ;
//参数1:套接字描述(之前创建的套接字)
//参数2:指向结构sockaddr的指针(取地址)
//参数3:结构的大小 //返回值(SOCKET_ERROR):表示连接失败 //SOCKADDR_IN:该结构主要使用三个变量(成员)
//sin_family:指定协议族,可参考前面socket函数的第一个参数解释
//sin_port:网络字节序,指的是整数在内存中保存的顺序,即主机字节顺序
//(使用的函数htons:
//将主机字节顺序转为网络字节顺序, 不同的CPU有不同的字节顺序类型,
//这些字节顺序类型指的是整数在内存中保存的顺序,即主机字节顺序。
//将整型变量从主机字节顺序转变成网络字节顺序, 就是整数在地址空间存储方式变为:
//高位字节存放在内存的低地址处。
//例如 : 12345->0x3039(16进制)->0x930(字节翻转)--> 14640 )
//sin_addr:其中成员s_addr是IPv4地址结构,IN_ADDR结构
//(使用的inet_addr:该函数转换包含IPv4点分十进制地址转换成一个适当的地址的字符串 IN_ADDR结构。)
return sock;
} 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[], http.size(), ); //发送请求
//参数1:socket,之前创建的套接字
//参数2:要发送的数据
//参数3:数据大小
//参数4:调用执行方式,默认写0即可 } inline string recvRequest(SOCKET sock)
{
static timeval wait = { , };
static auto buffer = string( * , '\0'); //初始化string容量
auto len = , reclen = ;
do {
fd_set fd = { };
//fd_set:实际上是一个long型数组,是文件描述符的集合
//每一个数组元素都能与一打开的文件句柄(不管是socket句柄,还是其他文件或命名管道或设备句柄)建立联系,
//建立联系的工作由程序员完成,当调用select()时,
//由内核根据IO状态修改fd_set的内容,
//由此来通知执行了select()的进程哪一socket或文件发生了可读或可写事件。
//总之,这个结构维护一个或者多个socket(的状态)
FD_SET(sock, &fd);
//FD_SET:用于维护fd_set集合的宏
//参数1:socket套接字
//参数2:传入的fd_set
reclen = ;
if (select(, &fd, nullptr, nullptr, &wait) > )
{
reclen = recv(sock, &buffer[] + len, * - len, );
if (reclen > )
len += reclen;
}
//select:非阻塞式的函数,用于确定一个或者多个socket的状态
//对每一个套接口,调用者可查询它的可读性、可写性及错误状态信息,
//用fd_set结构来表示一组等待检查的套接口 //参数1(nfds):socket监视的文件句柄数,视进程中打开的文件数而定。
//参数2(readfds):socket监视的可读文件句柄集合
//参数3(writefds):socket监视的可写文件句柄集合
//参数4(exceptfds):socket监视的异常文件句柄集合
//参数5(timeout):传入参数,本次socket()超时结束时间(可精确到百万分之一秒) //recv:用于从服务器接收数据的函数 //参数1:socket套接字
//参数2:接收数据的缓冲区(buffer)
//参数3:缓冲区长度
//参数4:指定调用方式,默认写0 //返回值:成功接收的字节长度
FD_ZERO(&fd);
//FD_ZERO:用于清空fd_set集合的宏
//参数1:传入fd_set集合参数 //与fd_set配套的宏有:
//FD_CLR(s, *set)
//从集合中删除s这个元素
//FD_ISSET(s, *set)
//判断s是否是集合成员,是返回非0,否则返回0
//FD_SET(s, *set)
//将s作为成员加入集合
//FD_ZERO(*set)
//将集合初始化(为空集合)
} while (reclen > ); return len >
? buffer[] == '' && buffer[] == '' && buffer[] == ''
? buffer.substr(, len)
: ""
: "";
//如果返回的字节长度大于11,那么...
//...如果服务器发送的状态码为200 OK...
//...那么返回发来的数据;
//...如果不是200 OK...
//...返回""
//如果不是大于11...
//...返回""
} 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[].str(),
regex("href=\"(https?:)?//(\\S+)\""),
"$2"));
curIter = result[].second;
}
} void Go(const string &url, int count) //BFS
{
queue<string> urls;
urls.push(url);
for (auto i = ; i != count; ++i)
{
if (!urls.empty())
{
auto &url = urls.front();
auto pair = binaryString(url, "/");
auto sock = connect(pair.first);
if (sock && sendRequest(sock, pair.first, "/" + pair.second))
{
auto buffer = move(recvRequest(sock));
extUrl(buffer, urls);
}
closesocket(sock); //关闭socket
cout << url << ": count=> " << urls.size() << endl; //统计该网页url数量
urls.pop(); }
}
} int main()
{
startupWSA(); //开启WSA
Go("www.hao123.com", ); //从www.hao123.com开始,计数200次
cleanupWSA(); //WSA释放
return ;
}

Code

请尽量使用Visual Studio2017(或者VS系列)进行编译,避免IDE听不懂各自的方言。

注释已经非常详细了,接下来是引用的博客:

主要代码: https://www.cnblogs.com/mmc1206x/p/3932622.html

对于关键函数的参数说明: https://www.jianshu.com/p/e3c187da4420

对于fd_set以及select函数通俗易懂的解读: https://blog.csdn.net/rootusers/article/details/43604729

以上是主要思路以及部分函数参考的博客,我的注释中不足之处请看这些博客;

以下是MSDN官方文档以及维基/百度百科等参考的资料:

//对于MSDN以及英文资料的翻译: fanyi.baidu.com 和 translate.google.com

https://docs.microsoft.com/en-us/windows/desktop/api/winsock2/nf-winsock2-select

https://zh.wikipedia.org/wiki/Select_(Unix)

https://baike.baidu.com/item/fd_set/6075513

https://www.ibm.com/support/knowledgecenter/en/SSB23S_1.1.0.15/gtpc2/cpp_fd_set.html

https://docs.microsoft.com/en-us/windows/desktop/api/winsock2/nf-winsock2-fd_set

https://docs.microsoft.com/en-us/windows/desktop/api/wsipv6ok/nf-wsipv6ok-inet_addr

https://docs.microsoft.com/zh-cn/windows/desktop/api/winsock2/ns-winsock2-in_addr

https://docs.oracle.com/cd/E19620-01/805-4041/6j3r8iu2l/index.html

https://docs.microsoft.com/en-us/windows/desktop/api/winsock/ns-winsock-hostent

https://docs.microsoft.com/en-us/windows/desktop/api/winsock/ns-winsock-wsadata

https://docs.microsoft.com/en-us/windows/desktop/api/winsock/nf-winsock-wsastartup

本注释讲解不足支持,或者想要获得更加详细的资料,请访问以上链接。

【C++&爬虫】C++实现网络爬虫&socket初级教程的更多相关文章

  1. Python 网络爬虫 001 (科普) 网络爬虫简介

    Python 网络爬虫 001 (科普) 网络爬虫简介 1. 网络爬虫是干什么的 我举几个生活中的例子: 例子一: 我平时会将 学到的知识 和 积累的经验 写成博客发送到CSDN博客网站上,那么对于我 ...

  2. python网络爬虫之初始网络爬虫

    第一次接触到python是一个很偶然的因素,由于经常在网上看连载小说,很多小说都是上几百的连载.因此想到能不能自己做一个工具自动下载这些小说,然后copy到电脑或者手机上,这样在没有网络或者网络信号不 ...

  3. Python爬虫《Python网络爬虫相关基础概念》

    引入 之前在授课过程中,好多同学都问过我这样的一个问题:为什么要学习爬虫,学习爬虫能够为我们以后的发展带来那些好处?其实学习爬虫的原因和为我们以后发展带来的好处都是显而易见的,无论是从实际的应用还是从 ...

  4. python网络爬虫之初识网络爬虫

    第一次接触到python是一个很偶然的因素,由于经常在网上看连载小说,很多小说都是上几百的连载.因此想到能不能自己做一个工具自动下载这些小说,然后copy到电脑或者手机上,这样在没有网络或者网络信号不 ...

  5. PYTHON网络爬虫与信息提取[网络爬虫协议](单元二)

    robots.txt在网站的根目录下 遵守 自动或人工识别robots.txt再进行内容爬取 约束性:建议性,不遵守协议,存在法律风险. 基本语法: User-agent: * Disallow: / ...

  6. Python网络爬虫

    http://blog.csdn.net/pi9nc/article/details/9734437 一.网络爬虫的定义 网络爬虫,即Web Spider,是一个很形象的名字. 把互联网比喻成一个蜘蛛 ...

  7. 【Python开发】【神经网络与深度学习】网络爬虫之python实现

    一.网络爬虫的定义 网络爬虫,即Web Spider,是一个很形象的名字. 把互联网比喻成一个蜘蛛网,那么Spider就是在网上爬来爬去的蜘蛛. 网络蜘蛛是通过网页的链接地址来寻找网页的. 从网站某一 ...

  8. [Python]网络爬虫(一):抓取网页的含义和URL基本构成

    一.网络爬虫的定义 网络爬虫,即Web Spider,是一个很形象的名字. 把互联网比喻成一个蜘蛛网,那么Spider就是在网上爬来爬去的蜘蛛.网络蜘蛛是通过网页的链接地址来寻找网页的. 从网站某一个 ...

  9. larbin是一种开源的网络爬虫/网络蜘

    larbin是一种开源的网络爬虫/网络蜘蛛,由法国的年轻人 Sébastien Ailleret独立开发.larbin目的是能够跟踪页面的url进行扩展的抓取,最后为搜索引擎提供广泛的数据来源.Lar ...

随机推荐

  1. mysql通配符进行模糊查询

    在mysql数据库中,当我们需要模糊查询的时候 ,我们会使用到通配符. 首先我们来了解一下2个概念,一个是操作符,一个是通配符. 操作符 like就是SQL语句中的操作符,它的作用是指示在SQL语句后 ...

  2. squid正向代理使用

     环境: Squid Cache: Version 3.5.20 操作系统: centos7.6 squid安装配置 yum install -y squid systemctl  start  sq ...

  3. linux下硬盘分区、格式化以及文件管理系统

    1.添加虚拟硬盘 (1)点击编辑虚拟机位置,然后点击添加   (2)点击添加硬盘 (3)点击下一步 (4)创建新虚拟磁盘并点击下一步 (5)指定磁盘容量并且点击下一步 (6)点击完成 2.系统分区 当 ...

  4. 商业研究(20):滴滴出行,进军海外包车?与OTA携程和包车创业公司,共演“三国杀”?看看分析师、投资人和权威人士等10个人的观点碰撞

     小雷友情提示:创业有风险,投资需谨慎.      前一篇文章,在探讨境外游创业公司-皇包车和易途8的时候,提到"滴滴如果进军海外包车,为海外华人提供打车和包车服务,有较大可能对海外包车公司 ...

  5. 如何转成libsvm支持的数据格式并做回归分析

    本次实验的数据是来自老师给的2006-2008年的日期,24小时的温度.电力负荷数据,以及2009年的日期,24小时的温度数据,目的是预测2009年每天24小时的电力负荷,实验数据本文不予给出. 用l ...

  6. 第一个web项目

    1)       创建Java Web Project 2)       创建相应的包 3)       创建类并继承于HttpServlet 4)       重写service()方法 5)    ...

  7. BNUOJ 1268 PIGS

    PIGS Time Limit: 1000ms Memory Limit: 10000KB This problem will be judged on PKU. Original ID: 11496 ...

  8. nyoj 2 括号配对问题(stack)

    括号配对问题 时间限制:3000 ms  |            内存限制:65535 KB 难度:3   描述 现在,有一行括号序列,请你检查这行括号是否配对.   输入 第一行输入一个数N(0& ...

  9. ElasticSearch全文搜索引擎(A)

    文章:[Elasticsearch] 全文搜索 (一) - 基础概念和match查询 全文检索,是从最初的字符串匹配和简单的布尔逻辑检索技术,演进到能对超大文本.语音.图像.活动影像等非结构化数据进行 ...

  10. HDU-1083Courses,二分图模板题!

    Courses                                                                                             ...