前言:

我目前主要学习方向是c++,看到网上基本上都是用python写的爬虫,我也试过,确实非常方便,几行代码就能解决,但却就是因为python封装的太好,过于简单,使得很多人包括我最开始的时候,都很难理解爬虫原理.所以就想着能不能用c++实现一个简单的爬虫.

最后我成功实现C++版爬虫从某图片网站爬取了将近两万多张图片,便记录一下,供大家学习

有兴趣的同学可直接下载源码对比学习,下载源码点这里

5月17日更新:

也可进入我的公众号,查看升级优化版爬虫代码文章,以及完整的代码,还有持续更新的资源!

文章目录

    • 前言:
  • 一、先手动爬虫,理解爬虫原理
  • 二、尝试手动爬取图片
  • 四、寻找网页的规律
  • 四、从手动爬虫中总结一下爬虫的过程
  • 五、写代码前的知识储备
  • 六、开始写代码
  • 总结

一、先手动爬虫,理解爬虫原理

首先我们得清楚网页的原理,如一页展示图片的网页,右键可查看网页源代码
(考虑到该程序的特殊性,避免不必要的麻烦,不方便展示该网站名称,大家可自行找图片网站替代,尽量与本文的网站格式相近)
(当然也可以下载我的源代码,里面有详细的注释,VS2019环境下亲测可用)

网页源代码如图:

为方便演示爬虫原理,可在源代码页Ctrl+A 全选 ,再Ctrl+C复制,然后在桌面新建一个txt文本,Ctrl+V复制结果如图:

然后保存退出,修改文件后缀名为.html
修改前:

修改后:

然后直接点击该文件,可以发现显示出与先前网站一样内容的网页,只是显示样式发生了变化.

至此,我们就完成了一次手动爬取一张网页

而此次我们想爬取的不是网页,而是网页上展示的图片,所以这还不够

二、尝试手动爬取图片

正如上所诉,图片是在网页上展示的,所以我们就要尝试在网页上找到图片的地址
在网页源代码中,可以很容易找到以<img src="…"形式展示的标签
如图:

当尝试点击img 后紧跟的链接时,发现展示出一张图片,就是先前网页上看到的某张图片


然后右键该图片,另存任意位置

至此,我们就完成了一次手动爬取图片的过程

四、寻找网页的规律

完成了手动爬取一个网页和一张图片后,我们可以发现,即使是使用了程序代替手工,每次爬取的图片都是有限的,因为大量图片都存放在大量不同的网页中,所以我们还需要找到各个网页之间的规律
于是我点开下一页图片,可以看到地址栏发生了变化
第一页:

第二页

第三页:

可以发现,除了第一页的地址后缀为index,之后的其它网页都是index_x.html结尾,x为它的页数

至此,我们就发现了该网站网页的规律:除了第一页,其它网页都是以站名/index_X.html的格式存在,X为网页的页数

四、从手动爬虫中总结一下爬虫的过程

1.将要爬取的网页源代码下载到本地
2.从网页源代码中解析出图片的地址,然后下载到本地.
3.若不再使用网页源代码,可删除,避免占用大量磁盘空间.然后回到第一步循环,直到爬取完成

五、写代码前的知识储备

1.针对下载网页源代码和下载图片的需求,可使用微软提供的一个下载函数可以很方便实现下载功能:
头文件:

#include<Windows.h> //头文件
#pragma comment(lib,"Urlmon.lib") //链接包含该函数的静态库

函数:

HRESULT URLDownloadToFileA(
LPUNKNOWN pCaller, //用于显示下载进度,但需要继承COM接口,过于麻烦,可直接填0
LPCTSTR    zURL,//填写要下载的地址
LPCTSTR   szFileName,//填写下载完成到本地,保存的文件名
DWORD    dwReserved,//保留参数,必须为0
LPBINDSTATUSCALLBACK lpfnCB //接受下载进度的回调函数,不需要,直接填0
);
返回值为S_OK则成功,否则下载失败

该函数功能:将zURL参数地址下载保存为本地szFileName参数文件

关于该函数的更多详细内容,请点击这里!

2.针对要从网页源代码中解析出图片地址,可以使用C++自带的正则表达式库

#include<regex>

还不会正则表达式戳这里马上学习

这里主要用到的是该库的regex_search函数

bool regex_search(
_BidIt _First, //进行匹配的起始迭代器,就是从哪里开始匹配_BidIt _Last, //进行匹配的结束迭代器,就是从哪里结束匹配
match_results<_BidIt, _Alloc>& _Matches,//匹配到的结果返回给这个参数
const basic_regex<_Elem, _RxTraits>& _Re,//填写匹配规则的参数,也就是正则表达式
regex_constants::match_flag_type _Flgs=regex_constants::match_default //匹配模式,用默认的就行,不用填
)

该函数将在_First参数到_Last参数指定的范围匹配符合_Re模式的字符串,将结果存储到_Matches参数中

使用方法是将_Re正则表达式中,匹配到的两个小括号内的内容(.*)送回到_Matches参数,通过_Matches[index].str()获取想要的内容,index为第几个小括号匹配的内容

再通过_Matches[0].second得到当前已经匹配到的位置,方便继续循环匹配

不好理解继续往下看,实践是最好的学习方法!

六、开始写代码

首先,在任意一个盘新建一个空文件夹,方便后面使用

我在D盘下新建了一个叫ps的空文件夹

然后在VS中新建一个控制台项目,我这里项目名称为test,并新建一个叫main.cpp的空文件

添加头文件和库

#include<Windows.h>
#include<regex>
#include<iostream>
#pragma comment(lib,"Urlmon.lib")
using namespace std;
int main() {}


写个循环,生成所有网页的地址,因为是从第二页开始有规律的,所以这里直接从2开始循环(网站不方便展示,可下载我的源代码)

 string url;url.resize(1024);for (int i = 2; i <= 1212; i++) {sprintf_s((char*)url.c_str(), 1024, "http://....../index_%d.htm", i);}


写一个通用函数GetTextFromUrlA,从指定URL中获取想要的文本内容

/// <summary>
/// 从网页中获取指定文本
/// </summary>
/// <param name="url">网页地址</param>
/// <param name="pattern">匹配模式</param>
/// <param name="patternIndex">返回第几个()中的内容</param>
/// <param name="num">返回匹配到的数量</param>
/// <returns>返回的字符串数组</returns>
string* GetTextFromUrlA(const string& url, const string& pattern, int patternIndex, int& num)
{HRESULT ret = URLDownloadToFileA(NULL, url.c_str(), ".\tmp.txt", 0, NULL); //下载网页到tmp.txt文件中if (ret != S_OK) { //如果下载失败返回NULLreturn NULL;}//下载成功,读取文本内容FILE* file;errno_t err = fopen_s(&file, ".\tmp.txt", "r");if (err != 0) {return NULL;}fseek(file, 0, SEEK_END);int nSize = ftell(file);fseek(file, 0, SEEK_SET);std::string buf;buf.resize(nSize + 1);fread((char*)buf.c_str(), sizeof(char), nSize, file);fclose(file);//开始匹配regex r(pattern); //初始化匹配模式变量rsmatch result;    //保存匹配到的结果resultstring::const_iterator begin = buf.begin(); //获取文本开始的迭代器string::const_iterator end = buf.end();     //获取文本结束的迭代器int i = 0; //统计可以匹配到的个数while (regex_search(begin, end, result, r)) { //匹配成功返回true,继续下一次匹配.失败则退出循环i++; //匹配到一个,加一begin = result[0].second;  //获取当前匹配到的位置,更新匹配的开始位置}num = i;//知道了有多少个,分配对应内存,重新开始匹配begin = buf.begin();string* strBuf = new string[i + 1]{}; int index = 0;while (regex_search(begin, end, result, r)) {strBuf[index++] = result[patternIndex].str();begin = result[0].second;}DeleteFileA(".\tmp.txt"); //匹配完成,可以删除下载的文件了return strBuf; //返回匹配到的结果
}


然后写匹配模式,调用该函数,因为我写的匹配模式串中,第一个括号里的是我想要的内容,所以后面填的1,这个请根据大家具体情况写适当的匹配模式串,需要注意的是C++中 " 这个符号需要写成 "

int nums = 0;
string* str =GetTextFromUrlA(url.c_str(), "<li><a href="(.*?)"\s{0,1}title="(.*?)".*?><img src="(.*?)" .*?</li>", 1, nums);
if (nums <= 0) {cout << "该页面下载失败!" << endl;continue;
}


测试一下下载函数是否有问题


成功获取指定内容,大家可能已经发现,这些字符串都是.htm结尾,说明这还是个html文档,根本不是图片,而且都是缺少站名的部分索引

这是因为我发现,该网站是两层文档展示的,随便点进去一张图片,第二层,长这样

所以接下来,我们要做的就是,补全URL,再从第二层网页中获取图片地址就可以了!

for (int j = 0; j < nums; j++) {Sleep(1000); //避免过快访问网站,被封杀ip,多次实验,1秒爬取一张,可以稳定爬取sprintf_s((char*)url.c_str(), 1024, "http://....%s", str[j].c_str()); //格式化图片urlint nPic{};string* sPic = GetTextFromUrlA(url.c_str(), "<div class="pic">.*?<img src="(.*?)" .*?</p>", 1, nPic);//获取图片地址if (nPic <= 0) {continue;}for (int k = 0; k < nPic; k++) {cout << sPic[k] << endl;}delete[] sPic;
}

成功获取该页面所有图片的地址


最后就简单了,将这些地址下载保存就可以了

因为需要解析名字之类的,所以最好还是写个函数

/// <summary>
/// 将url指定图片下载到本地dir目录下
/// </summary>
/// <param name="dir">存放图片的目录</param>
/// <param name="url">要下载的图片</param>
/// <returns></returns>
bool DownPic(const string& dir,const string& url) {//将图片名字解析到name变量中string name;for (int i = url.size()-1; i >= 0; i--) {if (url[i] == '/') {name = &url[i+1];break;}}name = dir + "\" + name; //将name转化为本地目录文件HRESULT ret=URLDownloadToFileA(NULL, url.c_str(), name.c_str(), 0, NULL);//下载if (ret == S_OK) {return true;}return false;
}


最后测试一下

完美爬取

完整代码(缺少站名):

#include<Windows.h>
#include<regex>
#include<iostream>
#pragma comment(lib,"Urlmon.lib")
using namespace std;
/// <summary>
/// 从网页中获取指定文本
/// </summary>
/// <param name="url">网页地址</param>
/// <param name="pattern">匹配模式</param>
/// <param name="patternIndex">返回第几个()中的内容</param>
/// <param name="num">返回匹配到的数量</param>
/// <returns>返回的字符串数组</returns>
string* GetTextFromUrlA(const string& url, const string& pattern, int patternIndex, int& num)
{HRESULT ret = URLDownloadToFileA(NULL, url.c_str(), ".\tmp.txt", 0, NULL); //下载网页到tmp.txt文件中if (ret != S_OK) { //如果下载失败返回NULLreturn NULL;}//下载成功,读取文本内容FILE* file;errno_t err = fopen_s(&file, ".\tmp.txt", "r");if (err != 0) {return NULL;}fseek(file, 0, SEEK_END);int nSize = ftell(file);fseek(file, 0, SEEK_SET);std::string buf;buf.resize(nSize + 1);fread((char*)buf.c_str(), sizeof(char), nSize, file);fclose(file);//开始匹配regex r(pattern); //初始化匹配模式变量rsmatch result;    //保存匹配到的结果resultstring::const_iterator begin = buf.begin(); //获取文本开始的迭代器string::const_iterator end = buf.end();     //获取文本结束的迭代器int i = 0; //统计可以匹配到的个数while (regex_search(begin, end, result, r)) { //匹配成功返回true,继续下一次匹配.失败则退出循环i++; //匹配到一个,加一begin = result[0].second;  //获取当前匹配到的位置,更新匹配的开始位置}num = i;//知道了有多少个,分配对应内存,重新开始匹配begin = buf.begin();string* strBuf = new string[i + 1]{};int index = 0;while (regex_search(begin, end, result, r)) {strBuf[index++] = result[patternIndex].str();begin = result[0].second;}DeleteFileA(".\tmp.txt"); //匹配完成,可以删除下载的文件了return strBuf; //返回匹配到的结果
}
/// <summary>
/// 将url指定图片下载到本地dir目录下
/// </summary>
/// <param name="dir">存放图片的目录</param>
/// <param name="url">要下载的图片</param>
/// <returns></returns>
bool DownPic(const string& dir,const string& url) {//将图片名字解析到name变量中string name;for (int i = url.size()-1; i >= 0; i--) {if (url[i] == '/') {name = &url[i+1];break;}}name = dir + "\" + name; //将name转化为本地目录文件HRESULT ret=URLDownloadToFileA(NULL, url.c_str(), name.c_str(), 0, NULL);//下载if (ret == S_OK) {return true;}return false;
}int main() {string url; //保存urlurl.resize(1024);for (int i = 2; i <= 1212; i++) {cout << "当前页面:"<<i << endl;sprintf_s((char*)url.c_str(), 1024, "http://.../index_%d.htm", i); //格式化urlint nums = 0;string* str = GetTextFromUrlA(url.c_str(), "<li><a href="(.*?)"\s{0,1}title="(.*?)".*?><img src="(.*?)" .*?</li>", 1, nums);if (nums <= 0) {cout << "该页面下载失败!" << endl;continue;}for (int j = 0; j < nums; j++) {Sleep(1000); //避免过快访问网站,被封杀ip,多次实验,1秒爬取一张,可以稳定爬取sprintf_s((char*)url.c_str(), 1024, "http://...%s", str[j].c_str()); //格式化图片urlint nPic{};string* sPic = GetTextFromUrlA(url.c_str(), "<div class="pic">.*?<img src="(.*?)" .*?</p>", 1, nPic);//获取图片地址if (nPic <= 0) {continue;}for (int k = 0; k < nPic; k++) {if (DownPic("D:\ps", sPic[k])) cout <<sPic[k]<<":下载成功"<<endl;else cout << sPic[k] << ":下载失败"<<endl;}delete[] sPic;}delete[] str;}
}

总结

对比之下,python写爬虫确实非常方便,也推荐大家用python写爬虫.

但在此之前,应该要理解爬虫的原理,C++虽然繁琐,但却是理解原理的好助手.

当大家慢慢积累出属于自己的C++类库时,也许比python更好用也说不一定呢

源码下载

先自我介绍一下,小编13年上师交大毕业,曾经在小公司待过,去过华为OPPO等大厂,18年进入阿里,直到现在。深知大多数初中级java工程师,想要升技能,往往是需要自己摸索成长或是报班学习,但对于培训机构动则近万元的学费,着实压力不小。自己不成体系的自学效率很低又漫长,而且容易碰到天花板技术停止不前。因此我收集了一份《java开发全套学习资料》送给大家,初衷也很简单,就是希望帮助到想自学又不知道该从何学起的朋友,同时减轻大家的负担。添加下方名片,即可获取全套学习资料哦

C++实现爬虫,深入理解爬虫原理(最详细,最简单的教程)相关推荐

  1. (1) 简要说明 html 的基本工作原理.,理解爬虫原理

    1. 简单说明爬虫原理 什么是爬虫 爬虫:请求网站并提取数据的自动化程序 百科:网络爬虫(又被称为网页蜘蛛,网络机器人,在FOAF社区中间,更经常的称为网页追逐者),是一种按照一定的规则,自动地抓取万 ...

  2. python爬虫百度百科-python爬虫(一)_爬虫原理和数据抓取

    本篇将开始介绍Python原理,更多内容请参考:Python学习指南 为什么要做爬虫 著名的革命家.思想家.政治家.战略家.社会改革的主要领导人物马云曾经在2015年提到由IT转到DT,何谓DT,DT ...

  3. python爬虫哪个选择器好用_Python网络爬虫四大选择器用法原理总结

    前几天小编连续写了四篇关于Python选择器的文章,分别用正则表达式.BeautifulSoup.Xpath.CSS选择器分别抓取京东网的商品信息.今天小编来给大家总结一下这四个选择器,让大家更加深刻 ...

  4. [Python爬虫] 一、爬虫原理之HTTP和HTTPS的请求与响应

    一.HTTP和HTTPS HTTP协议(HyperText Transfer Protocol,超文本传输协议):是一种发布和接收 HTML页面的方法. HTTPS(Hypertext Transfe ...

  5. 【爬虫学习笔记day03】1.1. (了解)通用爬虫和聚焦爬虫+通用搜索引擎(Search Engine)工作原理+聚焦爬虫

    文章目录 1.1. (了解)通用爬虫和聚焦爬虫 通用爬虫和聚焦爬虫 通用爬虫 通用搜索引擎(Search Engine)工作原理 第一步:抓取网页 搜索引擎如何获取一个新网站的URL: 1. 新网站向 ...

  6. 爬虫学习2-相关原理

    原理 HTTP基本原理 网页基础 网页的基本组成 HTML CSS JavaScript HTML DOM 爬虫的基本原理 爬虫抓取的数据 JavaScript渲染的页面 会话和Cookies 静态网 ...

  7. 一篇文章带你理解爬虫究竟是什么?

    目录 前言 爬虫的应用场景 爬虫的技术选型 简单的爬虫 脑洞大开的爬虫解决思路 复杂的爬虫设计 音视频爬虫实战 一.先从几个方面来简单介绍我们音视频爬虫项目的体系 二.分步来讲下细节 三.遇到的问题和 ...

  8. [Python爬虫] 二、爬虫原理之定义、分类、流程与编码格式

    往期内容提要: [Python爬虫] 一.爬虫原理之HTTP和HTTPS的请求与响应 一.爬虫的定义.分类和流程 爬虫定义 网络爬虫(又被称为网页蜘蛛,网络机器人)就是模拟浏览器发送网络请求,接收请求 ...

  9. 谈谈对Python爬虫的理解

    爬虫也可以称为Python爬虫 不知从何时起,Python这门语言和爬虫就像一对恋人,二者如胶似漆 ,形影不离,你中有我.我中有你 一提起爬虫,就会想到Python,一说起Python,就会想到人工智 ...

最新文章

  1. 什么是SQL数据库?
  2. 解决vue中绝对定位或固定定位在底部的按钮随键盘移动的问题
  3. 【组合数学】组合恒等式 ( 变下项求和 3 组合恒等式 | 变下项求和 4 组合恒等式 | 二项式定理 + 求导 证明组合恒等式 | 使用已知组合恒等式证明组合恒等式 )
  4. Fedora设置DVD为yum源
  5. java对象实例化的方式
  6. Java编程提高性能的26个方法
  7. erp采购总监个人总结_《用友 ERP 培训教程:财务核算/供应链管理/物料需求计划》ERP概述 : ERP基础知识...
  8. mysql(安装、启动、删除)服务
  9. C/C++深度分析(二)
  10. 校验php语法是否正确,PHP正则校验email语法详解
  11. (转)江南愤青丨丨监管办法之后,网贷一地鸡毛(2016年)
  12. Luence简单实现2
  13. 如何完整删除Windows.old(详细教程)
  14. codeforces 417D Cunning Gena
  15. 服务器显示器白屏,电脑显示器白屏的原因以及处理方法
  16. Imagination官方信息速递2020年11月期
  17. Java 课程设计--数据库管理系统
  18. 网络流量大数据分析平台(2)
  19. 中国网络教育行业市场需求及十四五发展新挑战研究报告2021-2027年
  20. 中国天气网api接口调用,key获取方式,数据请求秘钥获取,城市id获取方法

热门文章

  1. win 8 store app 国通快递查询 隐私声明
  2. bzoj 4245: [ONTAK2015]OR-XOR
  3. JS的三种引入方式[javaScript入门]
  4. vue/uni-app一键复制
  5. CAD高程点转入ArcGIS
  6. 机电传动控制第一周笔记
  7. 介绍一下什么是“云计算”
  8. 疯狂java讲义第六章课后第4题答案
  9. EasyExcel 实现多个Sheet页导出
  10. mysql检测不到运行库_请自行检查是否安装VC9运行库??