博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
开发MFC界面爬取图片工具一(原理简介及使用OpenSSL连接https网站)
阅读量:3959 次
发布时间:2019-05-24

本文共 8953 字,大约阅读时间需要 29 分钟。

OpenSSL连接https网站并获取HTML页面

引言

最近需要用到大量图片,但直接从网站下载太慢,便想到之前接触过的爬虫,目前关于使用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 
#include
//#include
在windows里边#include
#include
#include
#include
#include
#include
#include
#pragma comment(lib, "urlmon.lib")#pragma comment(lib, "WS2_32") // 链接到WS2_32.libusing 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转GBKstring 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;}
你可能感兴趣的文章
java除去字符串空格
查看>>
jsp 2.0标记文件
查看>>
Hibernate中Criteria的完整用法
查看>>
sql jsp
查看>>
Word生成目录
查看>>
JSP彩色验证码源程序编写
查看>>
java操作Excel、PDF文件
查看>>
java 获得系统变量
查看>>
window.event对象用法讲解
查看>>
jive license保护原理
查看>>
java des加密
查看>>
struts&hibernate&spring例子
查看>>
inno使用教程
查看>>
网吧系统母盘制作(系统分区整体考虑优化配置篇)
查看>>
spring beans beanfactory applicationcontext
查看>>
使用ORM工具进行数据访问
查看>>
使用ORM工具进行数据访问
查看>>
Quartz 使用手记 --转
查看>>
编译与部署Eclipse+Tomcat+MySQL+Liferay4.1.2
查看>>
MySQL用户授权
查看>>