开发MFC界面爬取图片工具一(原理简介及使用OpenSSL连接https网站)

引言

最近需要用到大量图片,但直接从网站下载太慢,便想到之前接触过的爬虫,目前关于使用python做爬虫的文章有很多,但关于使用c++来做爬虫的却很少,由于自己对c++及mfc比较熟悉,就想做一个简单界面应用程序,用于批量下载网络图片。虽然使用控制台程序也可以实现功能,但又想着方便使用,便决定做一个爬取图片工具。
接下来几个章节准备记录一下做此工具过程中所遇到的一些问题,并将解决方法记录下来。

网络爬虫原理简介

  1. 首先有一个起始地址url(也就是要爬取的网站地址)
  2. 做一个url队列(当想要爬取多个网网址上的图片时,这个url队列中用来存放要爬取的多个网址,可以理解为多个不同的HTML页面)
  3. 将起始url放到url队列中(当队列中只有一个网址时爬取的就是这一个网址)
  4. 从队列中取出一条url,解析此url(http协议或https协议),进行分段解析(将主机域名,资源分离),有了主机域名就可以连接服务器
  5. 连接服务器(http协议的和https协议的连接服务器方式有所不同,此处使用socket连接http,而https协议则借助OpenSSL来连接)
  6. 连接服务器成功后下载网页(就是下载html代码)
  7. 解析HTML网页(html中有很多重要信息,我们解析自己觉得有用的信息,如想获取图片,此处要解析HTML得到的就是各种图片的网络链接地址)
  8. 通过我们通过解析HTML页面获取的图片链接地址下载图片(这里会使用URLDownloadToFile函数进行图片的下载操作)
  9. 当前HTML页面中的一些图片链接下载完成后,我们要还想获取其他HTML网页中的图片,就要在队列中添加新的网址,重新回到开始进行上面重复的操作。
    10.下面是有关爬虫的简单示意图,图片来源于网络
    在这里插入图片描述

配置VS的项目属性(引入OpenSSL库)

  1. OpenSSL下载地址:https://oomake.com/download/openssl

  2. 到上面的链接下载OpenSSL Windows版本,注意32位和64位是不同的安装包,我虽然是64位的系统但还是下载的32位,可以使用

  3. 下载之后是exe文件,双击按照提示一步步安装就可以了。此处我将其安装到了d盘

  4. 打开VS将项目属性列表打开,进行如下的操作

  5. 将包含目录(include),及库目录(注意此处是lib文件下的vc文件)导入
    在这里插入图片描述

  6. 将4个lib文件导入如下:
    libcrypto32MDd.lib
    libcrypto32MTd.lib
    libssl32MDd.lib
    libssl32MTd.lib
    在这里插入图片描述

  7. 这里使用的Unicode字符集
    在这里插入图片描述

  8. 此处是为了防止后面代码中可能出现的安全报错设置(当出现某个函数如strcopy不安全的报错时,若换成strcopy_s参数会有问题,从网上搜索后直接将此种警报消除),在预处理器中添加_CRT_SECURE_NO_WARNINGS,如下:
    在这里插入图片描述

使用OpenSSL连接https网站

此处主要参考链接https://www.cnblogs.com/yskn/p/9552981.html
通过对上方网址的研究,我对其进行了详细分析,进行了进一步的封装,最终封装成为两个可以获取指定url的html界面的类,连接服务器获取HTML功能完全能实现,http和https的都可以。具体代码如下。

  1. CHttp.h文件:
#pragma once
#include <iostream>
#include <Windows.h>		//#include<WinSock2.h>在windows里边
#include <string>
#include <queue>
#include <regex>
#include <urlmon.h>
#include <openssl/rand.h>
#include <openssl/ssl.h>
#include <openssl/err.h>

#pragma comment(lib, "urlmon.lib")

#pragma comment(lib, "WS2_32")  // 链接到WS2_32.lib
using namespace std;

class CHttp
{
public:
	char g_Host[MAX_PATH];				//获取主机路径
	char g_Object[MAX_PATH];			//获取资源路径

	SOCKET g_sock;
	SSL *sslHandle;
	SSL_CTX *sslContext;
	BIO * bio;

	int numImagesR;			//定义一个下载的总数量,所有网页中的图片相加的数量
	string cururl;			//用来暂存p队列中的链接

	//下方的两个解析模块和下载模块无需改变,主要是根据自己需求改变开始抓取模块和匹配模块
	//解析URL(https)
	bool Analyse(string url);
	//连接https服务器
	bool Connect();
	//建立SSl连接(https)
	bool SSL_Connect();
	//得到html(https)
	bool Gethtml(string url, string& html);
	//将解析https的url,连接服务器,建立SSL连接得到html封装在一起得到(解析https连接得到html)
	bool GetHtmlHttps(string url, string& html);

	//解析URL(http)
	bool Analyse2(string url);
	//连接http服务器
	bool Connect2();
	//得到html(http)
	bool Gethtml2(string url, string& html);
	//将解析http的url,连接服务器,得到html封装在一起得到(解析http连接得到html)
	bool GetHtmlHttp(string url, string& html);

	//UTF转GBK(有些时候显示页面时需要使用GBK编码的,但此处使用UTF-8的,并未用到此函数)
	std::string UtfToGbk(const char* utf8);

};

  1. CHttp.cpp文件

#include "CHttp.h"


//解析url(https)
bool CHttp::Analyse(string url)
{
	char *pUrl = new char[url.length() + 1];
	strcpy(pUrl, url.c_str());

	char *pos = strstr(pUrl, "https://");//找到https://开头的字符串
	if (pos == NULL) return false;
	else pos += 8;//将https://开头省略

	sscanf(pos, "%[^/]%s", g_Host, g_Object);
	//cout << "g_Host:" << g_Host << ",g_Object:" << g_Object << endl;	//用来测试

	delete[] pUrl;
	return true;
}

//解析url(http)
bool CHttp::Analyse2(string url){

	char *pUrl = new char[url.length() + 1];
	strcpy(pUrl, url.c_str());

	char *pos = strstr(pUrl, "http://");//找到http://开头的字符串
	if (pos == NULL) return false;
	else pos += 7;//将http://开头省略

	sscanf(pos, "%[^/]%s", g_Host, g_Object);
	//cout << "g_Host:" << g_Host << ",g_Object:" << g_Object << endl;	//用来测试

	delete[] pUrl;
	return true;
}


//建立TCP连接
bool CHttp::Connect()
{
	//初始化套接字
	WSADATA wsadata;
	if (0 != WSAStartup(MAKEWORD(2, 2), &wsadata)) return false;

	//创建套接字
	g_sock = socket(AF_INET, SOCK_STREAM, 0);//此处注意与http有所不同
	if (g_sock == INVALID_SOCKET) return false;

	//将域名转换为IP地址
	hostent *p = gethostbyname(g_Host);
	if (p == NULL) return false;

	sockaddr_in sa;		//定义服务器地址信息
	memcpy(&sa.sin_addr, p->h_addr, 4);		//将p指针中的ip地址拷贝4个字节到sa.sin_addr中
	sa.sin_family = AF_INET;		//地址符使用此
	sa.sin_port = htons(443);		//将主机字节顺序转换成网络字节顺序(此为https的端口)

	if (SOCKET_ERROR == connect(g_sock, (sockaddr*)&sa, sizeof(sockaddr))) return false;
	return true;
}

//连接http服务器
bool CHttp::Connect2(){
	//初始化套接字
	WSADATA wsadata;
	if (0 != WSAStartup(MAKEWORD(2, 2), &wsadata)) return false;

	//创建套接字
	g_sock = socket(AF_INET, SOCK_STREAM, 0);
	if (g_sock == INVALID_SOCKET) return false;

	//将域名转换为IP地址
	hostent *p = gethostbyname(g_Host);
	if (p == NULL) return false;

	sockaddr_in sa;		//定义服务器地址信息
	memcpy(&sa.sin_addr, p->h_addr, 4);		//将p指针中的ip地址拷贝4个字节到sa.sin_addr中
	sa.sin_family = AF_INET;		//地址符使用此
	sa.sin_port = htons(80);		//将主机字节顺序转换成网络字节顺序(此为http的端口)

	if (SOCKET_ERROR == connect(g_sock, (sockaddr*)&sa, sizeof(sockaddr))) return false;
	return true;
}

//建立SSl连接
bool CHttp::SSL_Connect()
{
	// Register the error strings for libcrypto & libssl

	ERR_load_BIO_strings();
	// SSl库的初始化,载入SSL的所有算法,载入所有的SSL错误信息
	SSL_library_init();
	OpenSSL_add_all_algorithms();
	//加载SSL错误信息
	SSL_load_error_strings();

	// New context saying we are a client, and using SSL 2 or 3
	//建立新的SSL上下文
	sslContext = SSL_CTX_new(SSLv23_client_method());
	if (sslContext == NULL)
	{
		ERR_print_errors_fp(stderr);
		return false;
	}
	// Create an SSL struct for the connection
	sslHandle = SSL_new(sslContext);
	if (sslHandle == NULL)
	{
		ERR_print_errors_fp(stderr);
		return false;
	}
	// Connect the SSL struct to our connection
	if (!SSL_set_fd(sslHandle, g_sock))
	{
		ERR_print_errors_fp(stderr);
		return false;
	}
	// Initiate SSL handshake
	if (SSL_connect(sslHandle) != 1)
	{
		ERR_print_errors_fp(stderr);
		return false;
	}

	return true;
}

//得到html(https)
bool CHttp::Gethtml(string url, string & html)
{
	char temp1[100];
	sprintf(temp1, "%d", 166);
	string c_get;
	c_get = c_get
		//+ "GET " + g_Object + " HTTP/1.1\r\n"
		+ "GET " + url + " HTTP/1.1\r\n"
		+ "Host: " + g_Host + "\r\n"
		+ "Content-Type: text/html; charset=UTF-8\r\n"
		//+ "Content-Length:" + temp1 + "\r\n"
		+ "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299\r\n"
		//+ "User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko\r\n"
		+ "Connection:Close\r\n\r\n";
	//+ temp;

	SSL_write(sslHandle, c_get.c_str(), c_get.length());

	char buff[101];
	int nreal = 0;

	while ((nreal = SSL_read(sslHandle, buff, 100)) > 0)
	{
		buff[nreal] = '\0';
		//html += UtfToGbk(buff);		//此处将所得页面转换为gbk格式的
		html += buff;
		//printf("%s\n", buff);
		memset(buff, 0, sizeof(buff));
	}

	return true;
}

//得到html(http)
bool CHttp::Gethtml2(string url, string& html){
	char temp1[100];
	sprintf(temp1, "%d", 166);
	string c_get;
	c_get = c_get
		//+ "GET " + g_Object + " HTTP/1.1\r\n"
		+ "GET " + url + " HTTP/1.1\r\n"
		+ "Host: " + g_Host + "\r\n"
		+ "Content-Type: text/html; charset=UTF-8\r\n"
		//+ "Content-Length:" + temp1 + "\r\n"
		//+ "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299\r\n"
		+ "Connection:Close\r\n\r\n";
	//+ temp;

	//发送get请求,并判断是否失败
	if (SOCKET_ERROR == send(g_sock, c_get.c_str(), c_get.length(), 0)){
		cout << "发送get请求失败" << endl;
		return false;
	}

	//接收数据并保存到HTML中
	char ch = 0;
	while (recv(g_sock, &ch, 1, 0)){
		html = html + ch;				//此处未进行编码转换
	}
	//printf("%s\n", html);
	//cout << html << endl;
	return true;
}

//将解析url,连接服务器,建立SSL连接得到html封装在一起得到(解析https连接得到html)
bool CHttp::GetHtmlHttps(string url, string& html){
	//解析https的URL
	if (false == Analyse(url))
	{
		//cout << "解析URL失败,错误码:" << GetLastError() << endl;
		return false;
	}

	//连接https服务器
	if (false == Connect())
	{
		//cout << "连接服务器失败,错误代码:" << GetLastError() << endl;
		return false;
	}

	//建立ssl连接
	if (false == SSL_Connect())
	{
		//cout << "建立SSL连接失败,错误代码:" << GetLastError() << endl;
		return false;
	}

	//获取https网页
	if (false == Gethtml(url, html))
	{
		//cout << "获取网页数据失败,错误代码:" << GetLastError() << endl;
		return false;
	}
	return true;
}

//将解析http的url,连接服务器,得到html封装在一起得到(解析http连接得到html)
bool CHttp::GetHtmlHttp(string url, string& html){
	//解析http的URL
	if (false == Analyse2(url))
	{
		//cout << "解析URL失败,错误码:" << GetLastError() << endl;
		return false;
	}

	//连接http服务器
	if (false == Connect2())
	{
		//cout << "连接服务器失败,错误代码:" << GetLastError() << endl;
		return false;
	}

	//获取http网页
	if (false == Gethtml2(url, html))
	{
		//cout << "获取网页数据失败,错误代码:" << GetLastError() << endl;
		return false;
	}
	return true;
}

//UTF转GBK
string CHttp::UtfToGbk(const char* utf8)
{
	int len = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0);
	wchar_t* wstr = new wchar_t[len + 1];
	memset(wstr, 0, len + 1);
	MultiByteToWideChar(CP_UTF8, 0, utf8, -1, wstr, len);
	len = WideCharToMultiByte(CP_ACP, 0, wstr, -1, NULL, 0, NULL, NULL);
	char* str = new char[len + 1];
	memset(str, 0, len + 1);
	WideCharToMultiByte(CP_ACP, 0, wstr, -1, str, len, NULL, NULL);
	if (wstr) delete[] wstr;
	return str;
}


  1. main.cpp文件
#include "CHttp.h"

int main(){

	string starturl = "https://www.tupianzj.com/meinv/mm/";
	//string starturl = "http://www.ivsky.com/Photo/42/42_Index.html";
	CHttp http;
	string html;
	http.GetHtmlHttps(starturl, html);
	//http.GetHtmlHttp(starturl, html);
	cout << html << endl;

	system("pause");
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吾名招财

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值