OpenSSL连接https网站并获取HTML页面
引言
最近需要用到大量图片,但直接从网站下载太慢,便想到之前接触过的爬虫,目前关于使用python做爬虫的文章有很多,但关于使用c++来做爬虫的却很少,由于自己对c++及mfc比较熟悉,就想做一个简单界面应用程序,用于批量下载网络图片。虽然使用控制台程序也可以实现功能,但又想着方便使用,便决定做一个爬取图片工具。
接下来几个章节准备记录一下做此工具过程中所遇到的一些问题,并将解决方法记录下来。
网络爬虫原理简介
- 首先有一个起始地址url(也就是要爬取的网站地址)
- 做一个url队列(当想要爬取多个网网址上的图片时,这个url队列中用来存放要爬取的多个网址,可以理解为多个不同的HTML页面)
- 将起始url放到url队列中(当队列中只有一个网址时爬取的就是这一个网址)
- 从队列中取出一条url,解析此url(http协议或https协议),进行分段解析(将主机域名,资源分离),有了主机域名就可以连接服务器
- 连接服务器(http协议的和https协议的连接服务器方式有所不同,此处使用socket连接http,而https协议则借助OpenSSL来连接)
- 连接服务器成功后下载网页(就是下载html代码)
- 解析HTML网页(html中有很多重要信息,我们解析自己觉得有用的信息,如想获取图片,此处要解析HTML得到的就是各种图片的网络链接地址)
- 通过我们通过解析HTML页面获取的图片链接地址下载图片(这里会使用URLDownloadToFile函数进行图片的下载操作)
- 当前HTML页面中的一些图片链接下载完成后,我们要还想获取其他HTML网页中的图片,就要在队列中添加新的网址,重新回到开始进行上面重复的操作。
10.下面是有关爬虫的简单示意图,图片来源于网络
配置VS的项目属性(引入OpenSSL库)
-
OpenSSL下载地址:https://oomake.com/download/openssl
-
到上面的链接下载OpenSSL Windows版本,注意32位和64位是不同的安装包,我虽然是64位的系统但还是下载的32位,可以使用
-
下载之后是exe文件,双击按照提示一步步安装就可以了。此处我将其安装到了d盘
-
打开VS将项目属性列表打开,进行如下的操作
-
将包含目录(include),及库目录(注意此处是lib文件下的vc文件)导入
-
将4个lib文件导入如下:
libcrypto32MDd.lib
libcrypto32MTd.lib
libssl32MDd.lib
libssl32MTd.lib
-
这里使用的Unicode字符集
-
此处是为了防止后面代码中可能出现的安全报错设置(当出现某个函数如strcopy不安全的报错时,若换成strcopy_s参数会有问题,从网上搜索后直接将此种警报消除),在预处理器中添加_CRT_SECURE_NO_WARNINGS,如下:
使用OpenSSL连接https网站
此处主要参考链接https://www.cnblogs.com/yskn/p/9552981.html
通过对上方网址的研究,我对其进行了详细分析,进行了进一步的封装,最终封装成为两个可以获取指定url的html界面的类,连接服务器获取HTML功能完全能实现,http和https的都可以。具体代码如下。
- 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);
};
- 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;
}
- 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;
}