C++版网络爬虫

C++版网络爬虫

网络爬虫是按照一定规则,自动地抓取万维网信息的程序或脚本。这里用C++语言写一个小程序,实现简单的图片爬取的功能,目的是为了通过这么一个小程序理解爬虫的基本原理及基本流程。其大致实现流程如下:

图一:实现流程图

下面按照流程中的步骤依次用代码实现。

第一步:输入初始的URL

这里要实现的是图片资源的爬取,初始url可以选择一张带图片的网络地址,如http://www.tuxi.com.cn/views-153568413321-1535684133214492.html.其中http://表示协议名称,www.tuxi.com.cn表示主机域名,剩下的部分就表示资源(图片,网页等)在主机上的资源路径

//输入初始url
std::string url;
std::cin >> url;

第二步:创建资源文件夹

创建一个本地文件夹路径,用于存储爬取的资源

//#include <windows.h>
CreateDirectory(L"./Image", NULL);

第三步: URL入队及遍历队列

将初始的url放入队列中,并开始从一个Url开始爬取,首先获取html网页,然后解析网页中的字符串,将包含“http://”字符的字符串提取出来,判断提取的字符串中包含.jpg,.jpeg,.png等图片后缀的字符串,则将其归入资源下载的vector中,待后续下载资源,若不包含的则放入队列中继续循环;

这里注意一点,socket在遍历队列前初始化一次即可,每次遍历一次网页时,连接一次服务器,下载完网页和资源后再释放连接;

//初始化socket
	InitSocket();

	//将url塞入队列
	urlQueue.push(url);
	//若队列不为空
	while(!urlQueue.empty())
	{
		//从队列中取出一个URL
		std::string curUrl = urlQueue.front();
		urlQueue.pop();

		//解析URL,获取主机名及资源路径
		AnalyseURL(curUrl, strHost, strResPath);

		if(strHost.empty())
		{
			continue;
		}

		//连接一次,下载 网页,下载资源
		Connect();

		//下载HTML网页
		DownHtmlPage(strHtml);

		std::vector<std::string> vecImagesUrl;
		//解析网页
		AnalyseHtml(strHtml, vecImagesUrl);

		//遍历vecImagesUrl中的url,开始下载图片
		for(unsigned int i = 0; i < vecImagesUrl.size(); i++)
		{
			DownImage(vecImagesUrl[i]);
		}

	}

注意,要是用windows下的socket相关的函数,需要引入Lib,#pragma comment(lib, “ws2_32.lib”),否则编译不通过

//初始化socket
bool InitSocket()
{
	//协议版本
	WSAData wd;
	if(0 != WSAStartup(MAKEWORD(2,2), &wd))
	{
		return false;
	}

	if(LOBYTE(wd.wVersion) !=2 || HIBYTE(wd.wVersion) != 2)
	{
		return false;
	}

	//创建套接字(流式套接字,TCP)
	m_socket = socket(AF_INET, SOCK_STREAM, 0);
	if(INVALID_SOCKET == m_socket)
	{
		return false;
	}

	return true;

}

// 连接服务器

bool Connect()                    //连接服务器
{
	//根据域名获取主机名
	hostent *p = gethostbyname(strHost.c_str());
	if(NULL == p)
	{
		return false;
	}

	//构建协议地址族
	sockaddr_in sa;
	memcpy(&sa.sin_addr, p->h_addr, 4);
	sa.sin_family = AF_INET;
	sa.sin_port = htons(80);//80 host to net short

	//连接服务
	if(SOCKET_ERROR == connect(m_socket, (sockaddr*)&sa, sizeof(sa)))
	{
		return false;
	}

	return true;
}

解析URL

  • 完整的URL包含协议+主机(端口,默认80)+资源路径+参数
  • 将URL字符串统一转成小写
  • 查若字符串中不包含"http://",则return false
  • 若字符串长度是否小于7(http://的长度),也return false
  • 提取主机名和资源路径
//解析URL
bool AnalyseURL(std::string& url, std::string& strHost, std::string& strResPath)
{
	//http://www.tuxi.com.cn/views-153568413321-1535684133214492.html
	//将url 字符串转为小写
	transform(url.begin(),url.end(),url.begin(),::tolower);
	//暂时只找http://,不包含https://
	if(std::string::npos == url.find("http://"))//若未查找到
	{
		return false; 
	}

	if(url.length() <= 7)//若字符长度小于7
	{
		return false;
	}


	int pos = url.find('/', 7);//从第7个位置开始查找'/',返回查找到的位置
	//http://www.tuxi.com.cn
	if(pos == std::string::npos)//若没找到'/'
	{
		strHost = url.substr(7);//从第7个位置一直截到最后
		strResPath = "/";
	}
	else
	{
		strHost = url.substr(7, pos - 7);//截取7~pos之间的字符串, 第二个参数表示要截取字符的个数
		strResPath = url.substr(pos);
	}


	if(strHost.empty())
	{
		return false;
	}
	return true;
}

下载HTML

  • 发送Get请求
  • 接收返回头信息
  • 接收返回的HTML信息
  • 不知道GET请求信息格式,可使用Fiddler工具抓取一个网页,
//下载HTML网页
bool DownHtmlPage(std::string& strHtml)
{
	//构建GET/POST请求
	//利用Fiddler抓取的一条HTTP请求
	//GET http://img0.imgtn.bdimg.com/it/u=928937394,3734864397&fm=27&gp=0.jpg HTTP/1.1
	//Host: img0.imgtn.bdimg.com
	//Connection: keep-alive
	//User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36
	//Accept: image/webp,image/apng,image/*,*/*;q=0.8
	//Referer: http://image.baidu.com/search/detail?ct=503316480&z=0&ipn=false&word=%E8%83%A1%E6%AD%8C%20%E5%9B%BE%E7%89%87&hs=2&pn=1&spn=0&di=7700&pi=0&rn=1&tn=baiduimagedetail&is=0%2C0&ie=utf-8&oe=utf-8&cl=2&lm=-1&cs=2723770182%2C1520177662&os=1928506863%2C3402861817&simid=68093457%2C629089172&adpicid=0&lpn=0&ln=30&fr=ala&fm=&sme=&cg=star&bdtype=0&oriquery=%E8%83%A1%E6%AD%8C%20%E5%9B%BE%E7%89%87&objurl=http%3A%2F%2Fpic1.win4000.com%2Fwallpaper%2Fd%2F56775a8644c9d.jpg%3Fdown&fromurl=ippr_z2C%24qAzdH3FAzdH3Fo_z%26e3Bfi7uw1wfit_z%26e3Bv54AzdH3Fetjof-8cncmb98nnd8-8cncmb98nnd899ld_z%26e3Bip4s&gsm=0&islist=&querylist=
	//Accept-Encoding: gzip, deflate
	//Accept-Language: zh-CN,zh;q=0.9
	std::string strRequest;
	strRequest = strRequest + "GET " + strResPath + " HTTP/1.1\r\n";
	strRequest  = strRequest + "Host: " + strHost + "\r\n";
	strRequest = strRequest + "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36\r\n";//用于检测客户端浏览器
	strRequest = strRequest + "Connection: Close\r\n\r\n";//连接完后直接关闭,注意结束时必须换两行 


	//发送请求至服务器
	if(SOCKET_ERROR == send(m_socket, strRequest.c_str(), strRequest.length(), 0))
	{
		return false;
	}

	//char* buffer =  new char[strHtml.length()+1];
	编码装换
	//wchar_t* pBuffer = new wchar_t[(strHtml.length()+1)*2];
	//WideCharToMultiByte(CP_UTF8, 0, (LPCWCH)html.c_str(), html.length(),pBuffer,html.(length()+1)*2,NULL,NULL);

	//WideCharToMultiByte(CP_ACP, 0,pBuffer , wcslen(pBuffer),buffer,html.length()+1,NULL,NULL);


	//从服务器返回的消息一般会包含头信息,解析网页时不需要要用到,这里将其过滤
	//同发送消息头一样,接收消息头也是以"\r\n\r\n"结尾的

		//接收
	char ch = 0;
	int res = 0;
	while((res = recv(m_socket, &ch, sizeof(ch), 0)) > 0)//一个字节一个字节的接收
	{
		if(ch == '\r')
		{
			recv(m_socket, &ch, sizeof(ch), 0);
			if(ch == '\n')
			{
				recv(m_socket, &ch, sizeof(ch), 0);
				if(ch == '\r')
				{
					recv(m_socket, &ch, sizeof(ch), 0);
					if(ch == '\n')
					{
						break;
					}
				}
			}
		}
	}


	//接收HTML
	while((res = recv(m_socket, &ch, sizeof(ch), 0)) > 0)//一个字节一个字节的接收
	{
		strHtml += ch;
	}

	return true;
}

解析HTML

  • 用正则表达式匹配HTML字符串中的URL(注意C++11才支持regex)
  • 将匹配出来的url分成两类,一类包含".jpg",".jpeg",".png"的放入vecImages中,剩下的放入队列继续循环
  • 遍历完该网页,则下载该网页包含的图片资源
//解析网页
bool AnalyseHtml(const std::string& strHtml, std::vector<std::string>& vecImagesUrl)
{
	注意,C++ 11之前标准库中未提供正则表达式,可使用boost
	需要用到正则表达式
	先匹配出网页中所有的http请求
	std::smatch mat;
	//注意正则表达式的格式一定要正确
	std::regex rex("http://[^\\s'\"<>()]+");//以http://开头,切不包含中括号中的字符一次及以上的字符串

	//C++11 的用法
	std::string::const_iterator start = strHtml.begin();
	std::string::const_iterator end = strHtml.end();

	while(std::regex_search(start, end, mat, rex))
	{
		std::string per(mat[0].first, mat[0].second);

		//std::cout << per << std::endl;


		//若url中包含图片资源
		if(per.find(".jpg") != std::string::npos || per.find(".jpeg") != std::string::npos||per.find(".png") != std::string::npos)
		{
			vecImagesUrl.push_back(per);
		}
		else
		{
			//去掉w3c网页
			if(per.find("http://www.w3.org/") == std::string::npos)
			{
				urlQueue.push(per);//放入队列
			}
		}
		start = mat[0].second;	
	}
	return true;
}

下载图片

  • 构建GET请求
  • 发送请求至服务器
  • 过滤接收头信息
  • 接收图片信息并写入文件
//下载图片
bool DownImage(const std::string& imgUrl)
{
   //注意,下载图片时,url可能发生变化,需要清空之前的socket,重新连接服务器
   		closesocket(m_socket);
        strHost.empty();
		strResPath.empty();
		m_socket = 0;
		InitSocket();
		Connect();
	//同先构建GET请求
	std::string strRequest;
	strRequest = strRequest + "GET " + strResPath + " HTTP/1.1\r\n";
	strRequest  = strRequest + "Host: " + strHost + "\r\n";
	strRequest = strRequest + "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36\r\n";//用于检测客户端浏览器
	strRequest = strRequest + "Connection: Close\r\n\r\n";//连接完后直接关闭,注意结束时必须换两行 


	//发送请求至服务器
	if(SOCKET_ERROR == send(m_socket, strRequest.c_str(), strRequest.length(), 0))
	{
		return false;
	}


	std::string filename = ".\\Image\\";
	filename += strResPath.substr(strResPath.find_last_of('/'));
	//打开文件,二进制
	FILE* fp = fopen(filename.c_str(), 'wb');
	if(NULL == fp)
	{
		return false;
	}

	//过滤头信息
		//接收
	char ch = 0;
	int res = 0;
	while((res = recv(m_socket, &ch, sizeof(ch), 0)) > 0)//一个字节一个字节的接收
	{
		if(ch == '\r')
		{
			recv(m_socket, &ch, sizeof(ch), 0);
			if(ch == '\n')
			{
				recv(m_socket, &ch, sizeof(ch), 0);
				if(ch == '\r')
				{
					recv(m_socket, &ch, sizeof(ch), 0);
					if(ch == '\n')
					{
						break;
					}
				}
			}
		}
	}

	while((res = recv(m_socket, &ch, sizeof(ch), 0)) > 0)//一个字节一个字节的接收
	{
		//将接收的字符写入文件中
		fwrite(&ch, 1, 1, fp);
	}

	//关闭文件
	fclose(fp);
	return true;
}

关闭连接,释放socket

  • 每遍历完一次URL,则关闭连接
  • 遍历完所有的URL后,将socket置为0
		//断开连接
		closesocket(m_socket);
        strHost.empty();
		strResPath.empty();

	m_socket = 0;

猜你喜欢

转载自blog.csdn.net/tianzhiyi1989sq/article/details/98791167