一.主要的原理

  我们通过发送一个http请求,获得目标网页的html源代码,然后通过正则表达式获取到图片的URL,把该网页的所有的图片都保存到一个文件夹,这就是整个软件的流程。

二.具体的实践

  现在很多的网站都是https协议但是有一部分还是http协议,其实https就是http协议的安全版本,相当于http+ssl,SSL是介于HTTP应用层和TCP传输层,和HTTP相比HTTPS发送数据需要SSL加密,然后发送。所以说我们想通过https协议发送数据给服务器,需要经历一下这几个步骤:

首先我们先和服务器进行socket连接,然后将SSL和创建的socket套接字进行绑定,之后我们发送数据都是通过SSL发送即可,下面介绍一下具体的流程:

首先我们先进行socket连接:

//建立TCP连接
bool Connect()
{
//初始化套接字
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);
sa.sin_family = AF_INET;
sa.sin_port = htons(443); if (SOCKET_ERROR == connect(g_sock, (sockaddr*)&sa, sizeof(sockaddr))) return false;
return true;
}

  

HTTPS=HTTP + SSL,因此利用OpenSSL发送请求给HTTPS站点和第二章的SOCKET发送HTTP是非常相似的,只不过要在原生的套接字上套上SSL层,基本流程如下:

a. WSAStartup对Winsock服务进行初始化

b. 建立socket套接字

c. connect连接服务端

d. 建立SSL上下文

e. 建立SSL

f. 将SSL与前面建立的socket套接字绑定

g. SSL_write()发送数据

h. SSL_read()接收数据

bool SSL_Connect()
{
// Register the error strings for libcrypto & libssl ERR_load_BIO_strings();
// SSl库的初始化,载入SSL的所有算法,载入所有的SSL错误信息
SSL_library_init();
OpenSSL_add_all_algorithms();
SSL_load_error_strings(); // New context saying we are a client, and using SSL 2 or 3
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;
}

三.遇到的问题

1.首先VS控制台的编码方式是GBK的方式,但是有的网页就是UTF-8所以我们要进行这方面的转换

string 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;
}

2.OPENSSL的安装

  网上有很多自己下载源码然后自己编译的,但是在这个项目中我们可以直接使用别人编译好的库不用自己进行编译,直接在VS的项目目录中添加即可

3.还有就是图片的下载,是这种LPCWSTR数据类型的,我们通过string转换成LPCWSTR(不知道为什么URLDownloadToFile里面的两个都要这样进行转换不能用其他的函数)

		string savepath = "E:\\c++_file\\网络爬虫1\\网络爬虫\\网络爬虫\\img\\"+to_string(i)+".jpg";
size_t len1 = savepath.length();
wchar_t* imgsavepath = new wchar_t[len1];
int nmlen1 = MultiByteToWideChar(CP_ACP, 0, savepath.c_str(), len1 + 1, imgsavepath, len1);

这是完整的下载的代码:

           //URL生成
per = mat[1].str();
size_t len = per.length();//获取字符串长度
int nmlen = MultiByteToWideChar(CP_ACP, 0, per.c_str(), len + 1, NULL, 0);//如果函数运行成功,并且cchWideChar为零 //返回值是接收到待转换字符串的缓冲区所需求的宽字符数大小。
wchar_t* buffer = new wchar_t[nmlen];
MultiByteToWideChar(CP_ACP, 0, per.c_str(), len + 1, buffer, nmlen);
//保存路径
string savepath = "E:\\c++_file\\网络爬虫1\\网络爬虫\\网络爬虫\\img\\"+to_string(i)+".jpg";
size_t len1 = savepath.length();
wchar_t* imgsavepath = new wchar_t[len1];
int nmlen1 = MultiByteToWideChar(CP_ACP, 0, savepath.c_str(), len1 + 1, imgsavepath, len1);
cout << mat.str() << endl;
cout << savepath << endl;
//下载文件
HRESULT hr = URLDownloadToFile(NULL, buffer, imgsavepath, 0, NULL);
if (hr == S_OK)
{
cout << "-------ok" << endl;
}  

四.完整代码

#include "spider.h"

int main()
{
cout << "*********************************************************" << endl;
cout << "***********************爬取图片系统***********************" << endl;
cout << "*********************************************************" << endl; //创建图片的储存的目录
CreateDirectory(L"./img", NULL); //开始抓取
string starturl = "https://www.shiyanlou.com/#sign-modal"; StartCatch(starturl);
//while (1);
return 0;
} void StartCatch(string startUrl)
{
queue<string> q;
q.push(startUrl); while (!q.empty())
{
string cururl = q.front();
q.pop(); //解析URL
if (false == Analyse(cururl))
{
cout << "解析URL失败,错误码:" << GetLastError() << endl;
continue;
} //连接服务器
if (false == Connect())
{
cout << "连接服务器失败,错误代码:" << GetLastError() << endl;
continue;
} //建立ssl连接
if (false == SSL_Connect())
{
cout << "建立SSL连接失败,错误代码:" << GetLastError() << endl;
continue;
} //获取网页
string html;
if (false == Gethtml(html))
{
cout << "获取网页数据失败,错误代码:" << GetLastError() << endl;
continue;
}
if (false == RegexIamage(html))
{
cout << "获取网页数据失败,错误代码:" << GetLastError() << endl;
continue;
}
//cout << html << endl;
}
//释放
SSL_shutdown(sslHandle);
SSL_free(sslHandle);
SSL_CTX_free(sslContext);
closesocket(g_sock);
WSACleanup();
} //解析url
bool Analyse(string url)
{
char *pUrl = new char[url.length() + 1];
strcpy(pUrl, url.c_str()); char *pos = strstr(pUrl, "https://");//找到http://开头的字符串
if (pos == NULL) return false;
else pos += 8;//将http://开头省略 sscanf(pos, "%[^/]%s", g_Host, g_Object); delete[] pUrl;
return true;
} //建立TCP连接
bool Connect()
{
//初始化套接字
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);
sa.sin_family = AF_INET;
sa.sin_port = htons(443); if (SOCKET_ERROR == connect(g_sock, (sockaddr*)&sa, sizeof(sockaddr))) return false;
return true;
} bool SSL_Connect()
{
// Register the error strings for libcrypto & libssl ERR_load_BIO_strings();
// SSl库的初始化,载入SSL的所有算法,载入所有的SSL错误信息
SSL_library_init();
OpenSSL_add_all_algorithms();
SSL_load_error_strings(); // New context saying we are a client, and using SSL 2 or 3
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;
} bool Gethtml(string & html)
{
char temp1[100];
sprintf(temp1, "%d", 166);
string c_get;
c_get = c_get
+ "GET " + g_Object + " 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; 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);
//printf("%s\n", buff);
memset(buff, 0, sizeof(buff));
}
//printf("%s\n", html);
return true;
} string 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;
} bool RegexIamage(string & html)
{
smatch mat;
regex rgx("src=\"(.*(png|svg|jpg))\"");
string::const_iterator start = html.begin();
string::const_iterator end = html.end();
string per;
int i = 1;
while (regex_search(start, end, mat, rgx))
{
//URL生成
per = mat[1].str();
size_t len = per.length();//获取字符串长度
int nmlen = MultiByteToWideChar(CP_ACP, 0, per.c_str(), len + 1, NULL, 0);//如果函数运行成功,并且cchWideChar为零 //返回值是接收到待转换字符串的缓冲区所需求的宽字符数大小。
wchar_t* buffer = new wchar_t[nmlen];
MultiByteToWideChar(CP_ACP, 0, per.c_str(), len + 1, buffer, nmlen);
//保存路径
string savepath = "E:\\c++_file\\网络爬虫1\\网络爬虫\\网络爬虫\\img\\"+to_string(i)+".jpg";
size_t len1 = savepath.length();
wchar_t* imgsavepath = new wchar_t[len1];
int nmlen1 = MultiByteToWideChar(CP_ACP, 0, savepath.c_str(), len1 + 1, imgsavepath, len1);
cout << mat.str() << endl;
cout << savepath << endl;
//下载文件
HRESULT hr = URLDownloadToFile(NULL, buffer, imgsavepath, 0, NULL);
if (hr == S_OK)
{
cout << "-------ok" << endl;
}
start = mat[0].second;
i++;
}
return true;
} LPCWSTR stringToLPCWSTR(string orig)
{
size_t origsize = orig.length() + 1;
const size_t newsize = 100;
size_t convertedChars = 0;
wchar_t *wcstring = (wchar_t *)malloc(sizeof(wchar_t) *(orig.length() - 1));
mbstowcs_s(&convertedChars, wcstring, origsize, orig.c_str(), _TRUNCATE); return wcstring;
}

  下面是头文件:

#pragma once

#include <iostream>
#include <Windows.h>
#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, "libeay32.lib" )
#pragma comment( lib, "ssleay32.lib" ) #pragma comment(lib, "WS2_32") // 链接到WS2_32.lib using namespace std; char g_Host[MAX_PATH];
char g_Object[MAX_PATH]; SOCKET g_sock;
SSL *sslHandle;
SSL_CTX *sslContext;
BIO * bio; //开始抓取
void StartCatch(string startUrl);
//解析URL
bool Analyse(string url);
//连接服务器
bool Connect();
//建立SSl连接
bool SSL_Connect();
//得到html
bool Gethtml(string& html);
//UTF转GBK
std::string UtfToGbk(const char* utf8);
//正则表达式
bool RegexIamage(string & html); LPCWSTR stringToLPCWSTR(std::string orig);

  

c++ 实现https网页上的图片爬取的更多相关文章

  1. Python爬虫入门教程 26-100 知乎文章图片爬取器之二

    1. 知乎文章图片爬取器之二博客背景 昨天写了知乎文章图片爬取器的一部分代码,针对知乎问题的答案json进行了数据抓取,博客中出现了部分写死的内容,今天把那部分信息调整完毕,并且将图片下载完善到代码中 ...

  2. scrapy之360图片爬取

    #今日目标 **scrapy之360图片爬取** 今天要爬取的是360美女图片,首先分析页面得知网页是动态加载,故需要先找到网页链接规律, 然后调用ImagesPipeline类实现图片爬取 *代码实 ...

  3. [Python_scrapy图片爬取下载]

    welcome to myblog Dome地址 爬取某个车站的图片 item.py 中 1.申明item 的fields class PhotoItem(scrapy.Item): # define ...

  4. 爬虫07 /scrapy图片爬取、中间件、selenium在scrapy中的应用、CrawlSpider、分布式、增量式

    爬虫07 /scrapy图片爬取.中间件.selenium在scrapy中的应用.CrawlSpider.分布式.增量式 目录 爬虫07 /scrapy图片爬取.中间件.selenium在scrapy ...

  5. 4k图片爬取+中文乱码

    4k图片爬取+中文乱码 此案例有三种乱码解决方法,推荐第一种 4k图片爬取其实和普通图片爬取的过程是没有本质区别的 import requests import os from lxml import ...

  6. python requests库爬取网页小实例:爬取网页图片

    爬取网页图片: #网络图片爬取 import requests import os root="C://Users//Lenovo//Desktop//" #以原文件名作为保存的文 ...

  7. Python|网页转PDF,PDF转图片爬取校园课表~

    import pdfkit import requests from bs4 import BeautifulSoup from PIL import Image from pdf2image imp ...

  8. Python爬虫入门教程 8-100 蜂鸟网图片爬取之三

    蜂鸟网图片--啰嗦两句 前几天的教程内容量都比较大,今天写一个相对简单的,爬取的还是蜂鸟,依旧采用aiohttp 希望你喜欢 爬取页面https://tu.fengniao.com/15/ 本篇教程还 ...

  9. Python爬虫入门教程 7-100 蜂鸟网图片爬取之二

    蜂鸟网图片--简介 今天玩点新鲜的,使用一个新库 aiohttp ,利用它提高咱爬虫的爬取速度. 安装模块常规套路 pip install aiohttp 运行之后等待,安装完毕,想要深造,那么官方文 ...

随机推荐

  1. 在本地局域网 windows server 2008 下安装 Nginx 1.12.1

    简介: Nginx ("engine x") 是高性能 HTTP.反向代理服务器,也是 IMAP/POP3/SMTP 代理服务器. Nginx 是由 Igor Sysoev 为俄罗 ...

  2. user_tab_columns和user_col_comments区别

    SELECT USER_TAB_COLUMNS.COLUMN_NAME, USER_COL_COMMENTS.COMMENTS, CASE WHEN INSTR(USER_TAB_COLUMNS.DA ...

  3. war包的解压与打包

    转: war包的解压与打包 2018年03月22日 14:59:56 Jitwxs 阅读数:21421   版权声明:本文版权归Jitwxs所有,欢迎转载,但未经作者同意必须保留原文链接. https ...

  4. docker commit命令

    docker commit命令用于基于一个容器来创建一个新的docker镜像. docker commit制作的镜像,除了制定镜像的人知道执行过什么命令,怎么生成的镜像,别人根本无从得知.建议使用的是 ...

  5. 【D3D12学习手记】4.1.6 Resources and Descriptors

    在渲染过程中,GPU将写资源(resources)(例如,后缓冲区,深度/模板缓冲区),读资源(例如,描述表面外观的纹理,存储场景中几何体3D位置的缓冲区).在我们发出绘图命令之前,我们需要将资源绑定 ...

  6. java:LeakFilling (Linux)

    1.Nosql 列数据库,没有update,非关系型数据库: 为了解决高并发.高可扩展.高可用.大数据存储问题而产生的数据库解决方案,就是NoSql数据库.  NoSQL,泛指非关系型的数据库,NoS ...

  7. docker中tomcat日志输出自定义

    一,默认tomcat日志配置文件 /data/tomcat/conf/logging.properties 1,修改tomcat/conf下的logging.properties [root@harb ...

  8. 如何将其它javaweb项目变成可以成功在自己eclipse环境中运行的javaweb项目?

    说明:此文档仅适用于以下两种情况     (1)myeclipse项目需要在eclipse环境中运行     (2)eclipse项目,但是无法在自己的电脑eclipse环境中运行     注意:以下 ...

  9. java Proxy InvocationHandler 动态代理实现详解

    spring 两大思想,其一是IOC,其二就是AOP..而AOP的原理就是java 的动态代理机制.这里主要记录java 动态代理的实现及相关类的说明. java  动态代理机制依赖于Invocati ...

  10. MFC之MessageBox、AfxMessageBox用法

    在软件中我们经常会弹出个小窗口,给一点点提示.这就会用到消息对话框. 在Win32 API程序中只有MessageBox这一种用法. 而在MFC中就有三各方法: 1.调用API中的MessageBox ...