C++ 抓取和批量下载网站上的图片或文件

随便找个图片网页https://esports.zol.com.cn/slide/688/6885385_1.html 来练手抓取和下载图片。首先要分析html代码,看下载目标的链接命名是否有规律,有规律就用regex抓取地址,再用wininet中的函数下载;然后再找出下一图集的链接,反复循环就能遍历网站的图片(如要下载其它文件类型也可以)。原理如下图所示:

源代码如下:

#include <iostream>
#include <iomanip>
#include <windows.h>
#include <wininet.h>
#include <string>
#include <regex>
#include <vector>
#include <sstream>
#include <fstream>
using namespace std;
 
string HttpRequest(string strUrl, string strMethod="GET", string strPostData="")
{
	BOOL bRet;
	short sPort=80;
	char *lpHostName, *lpUrl, *lpMethod, *lpPostData;
	int pos, nPostDataLen=strPostData.size();
	string tmpUrl, strResponse = "";
 
	while ((pos = strUrl.find(" ")) != strUrl.npos) strUrl.erase(pos, 1); //删除网址中的空格 
	if (strUrl.substr(0,7) == "http://") strUrl=strUrl.substr(7, strUrl.size()-1); //删除网址中的协议名 
	if (strUrl.substr(0,8) == "https://") strUrl=strUrl.substr(8, strUrl.size()-1);
	if (strUrl.empty()) return strResponse;
	if ((pos=strUrl.find("/")) != strUrl.npos){
		tmpUrl = strUrl.substr(pos+1, strUrl.size()-1);
		strUrl = strUrl.substr(0, pos);
	} else tmpUrl = "/";
	
	lpUrl = (char*)tmpUrl.data();
	lpMethod = (char*)strMethod.data();
	lpHostName = (char*)strUrl.data();
	lpPostData = (char*)strPostData.data();
	
	if ((pos=strUrl.find(":")) != strUrl.npos){
		tmpUrl = strUrl.substr(pos+1,strUrl.size()-1);
		strUrl = strUrl.substr(0,pos);
		sPort = (short)atoi(tmpUrl.c_str());
	}
	
	HINTERNET hInternet, hConnect, hRequest;
	hInternet = hConnect = hRequest = NULL;
	hInternet = (HINSTANCE)InternetOpen("User-Agent", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
	if (!hInternet) return strResponse;
	
	hConnect = (HINSTANCE)InternetConnect(hInternet, lpHostName, sPort, NULL, "HTTP/1.1", INTERNET_SERVICE_HTTP, 0, 0);
	if (!hConnect){
		if (hInternet) InternetCloseHandle(hInternet);
		return strResponse;
	}
	
	hRequest = (HINSTANCE)HttpOpenRequest(hConnect, lpMethod, lpUrl, "HTTP/1.1", NULL, NULL, INTERNET_FLAG_RELOAD, 0);
	if (!hRequest){
		if (hInternet) InternetCloseHandle(hInternet);
		if (hConnect) InternetCloseHandle(hConnect);
		return strResponse;
	}
 
	bRet = HttpSendRequest(hRequest, NULL, 0, lpPostData, nPostDataLen);
 
	while (true){
		char cReadBuffer[4096];
		unsigned long lNumberOfBytesRead;
		bRet = InternetReadFile(hRequest, cReadBuffer, sizeof(cReadBuffer)-1, &lNumberOfBytesRead);
		if (!bRet || !lNumberOfBytesRead) break;
		cReadBuffer[lNumberOfBytesRead] = 0;
		strResponse = strResponse + cReadBuffer;
	}
 
	if (hRequest) InternetCloseHandle(hRequest);
	if (hConnect) InternetCloseHandle(hConnect);
	if (hInternet) InternetCloseHandle(hInternet);
	
	return strResponse;
}
 
bool DownloadFile(string strUrl, string strFullFileName, unsigned int BUF_SIZE_KB = 1)
{
	size_t pos=0;
	short sPort=80;
	BOOL bRet;
	char *lpHostName, *lpUrl;
	string tmpUrl, strResponse = "";
 
	while ((pos = strUrl.find(" ")) != strUrl.npos) strUrl.erase(pos, 1); //删除网址中的空格 
	if (strUrl.substr(0,7) == "http://") strUrl=strUrl.substr(7, strUrl.size()-1); //删除网址中的协议名 
	if (strUrl.substr(0,8) == "https://") strUrl=strUrl.substr(8, strUrl.size()-1);
	if (strUrl.empty()) return false;
	if ((pos=strUrl.find("/")) != strUrl.npos){
		tmpUrl = strUrl.substr(pos+1, strUrl.size()-1);
		strUrl = strUrl.substr(0, pos);
	} else tmpUrl = "/";
	
	lpUrl = (char*)tmpUrl.data();
	lpHostName = (char*)strUrl.data();
 
	if ((pos=strUrl.find(":")) != strUrl.npos){
		tmpUrl = strUrl.substr(pos+1,strUrl.size()-1);
		strUrl = strUrl.substr(0,pos);
		sPort = (short)atoi(tmpUrl.c_str());
	}
	
	HINTERNET hInternet, hConnect, hRequest;
	hInternet = hConnect = hRequest = NULL;
	hInternet = (HINSTANCE)InternetOpen("User-Agent", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
	if (!hInternet) return false;
	
	hConnect = (HINSTANCE)InternetConnect(hInternet, lpHostName, sPort, NULL, "HTTP/1.1", INTERNET_SERVICE_HTTP, 0, 0);
	if (!hConnect){
		if (hInternet) InternetCloseHandle(hInternet);
		return false;
	}
	
	hRequest = (HINSTANCE)HttpOpenRequest(hConnect, "GET", lpUrl, "HTTP/1.1", NULL, NULL, INTERNET_FLAG_RELOAD, 0);
	if (!hRequest){
		if (hInternet) InternetCloseHandle(hInternet);
		if (hConnect) InternetCloseHandle(hConnect);
		return false;
	}
 
	bRet = HttpSendRequest(hRequest, NULL, 0, NULL, 0);
	
	BUF_SIZE_KB = BUF_SIZE_KB==0 ? 1024 : BUF_SIZE_KB*1024;
	char buf[BUF_SIZE_KB];
    DWORD buf_len, buf_read;
    buf_len = buf_read = BUF_SIZE_KB;
 
    FILE *fp = fopen(strFullFileName.c_str(), "wb");
    while (true) {
       InternetReadFile(hRequest, buf, buf_len, &buf_read);
       if(buf_read == 0) break;
       fwrite(buf, 1, buf_read, fp);
    }
    free(buf);
    fclose(fp);
 
	if (hRequest) InternetCloseHandle(hRequest);
	if (hConnect) InternetCloseHandle(hConnect);
	if (hInternet) InternetCloseHandle(hInternet);
	
	return true;
}
 
int regexSplit(string &str,const string str_reg,vector<string>&vect,int pos=0)
{
	if (pos!=-1) pos=0;  //pos=0 匹配到的位置,pos=-1匹配位置的前一字串 
	regex myPattern(str_reg); 
    sregex_token_iterator it(str.begin(),str.end(),myPattern,pos); 
    sregex_token_iterator end;
    for(;it!=end;++it) vect.push_back(*it); 
    return vect.size();  //if 0 没有匹配到,else 匹配到的个数
 }
 
string replaceAll(string &s, const string sub1, const string sub2)
{	//字符串中指定字符串sub2替换s所有的子串sub1 
	size_t len,pos=0;
	if (s.empty()||sub1.empty()||sub2.empty()) return s;
	if (s.find(sub1)==s.npos) return s; //sub1不是s的子串就退出 
	len=sub1.size();
	while((pos=s.find(sub1,pos))!=s.npos)
		s.replace(pos++,len,sub2);
	return s;
}
 
string int2str(int i)
{
	string s;
	stringstream ss;
	ss<<setw(5)<<setfill('0')<<i;
	s=ss.str();
	ss.clear();
	return s;
}
 
int main()
{
	int i=0;
	bool bRet;
	size_t pos;
	string Html, url, reg;
	vector <string> vect, vimg;
    url = "https://esports.zol.com.cn/slide/688/6885385_1.html";
	Html = HttpRequest(url);
	cout<<"1-"<<endl;
	while ((pos=Html.find("tutie"))!=string::npos){
	    reg="t_s1280x720(.*?).jpg"; //查找下载目标的网址装入vector 
		regexSplit(Html,reg,vect);
		for (auto&v:vect){ //抓取在vector里的图片地址,逐个下载 
			replaceAll(v,"\\/","/");  //网站把图片地址每个/前都插入\字符,全部替换掉 
			url = "http://article-fd.zol-img.com.cn/"; 
			bRet=DownloadFile(url+v,"D:\\Pictures\\zol"+int2str(++i)+".jpg");
                                 //注意:D:\Pictures 文件目录是否存在,代码没作判断
			if (bRet){
				cout<<".";
				if (i%20==0) cout<<endl;
				if (i%100==0){
					system("cls");
					cout<<i+1<<"-"<<endl;
				}
				vimg.push_back(url+v); //记录下载成功的链接 
				if (i>=10000) return 0; //抓取满10000张退出,或者循环到网站没有下一图集标记为止 
			}
		}
		reg="<a href=\"/slide/(.*?)tutie"; //找出下一图集的地址 
		vect.clear();
		regexSplit(Html,reg,vect);
		url = vect.at(0); //格式:<a href="/slide/688/6886276_1.html" class="tutie
		url = "https://esports.zol.com.cn" + url.substr(9,url.size()-23);
		vect.clear();
		Html = HttpRequest(url);
	} 
 
    ofstream out_file("DownList.txt"); //下载成功的链接保存到文件 
    if (out_file){
    	for(auto v:vimg)
    		out_file << v <<endl; //输出到文件
    	out_file.close();
    }
 
    return 0;
}

下载成果:

本代码在Dev-C++ 5.11上通过无错编译,20分钟左右下载到1902张图片,下载速度要看个人的电脑硬件和网络带宽的,完成后图片链接都保存到源码同文件夹里的下载清单DownList.txt中(实际上是与编译好的可执行文件同路径,没有采用边下载边记录所以中途退出是没有下载清单的)。如果用多线程技术,同时间开启多个线程下载速度会成倍加快,感兴趣的话自己去试试吧。关于多线程请看我的另一篇博文《C++ Beep()演奏简谱的改进以及实现背景音乐》。

注:本代码需要用到wininet.lib 请在工具菜单-"编译器选项"里添加如下命令:-std=c++11 -lwininet。如使用vs系列编译器或在using namespace std;之前插入:#pragma comment(lib,"WinInet.lib")

类似网络爬虫,从一个网页“爬”到另一个网页,然后选择图片下载。多线程。 可以用来按照一定规则下载网页中的元素,如图片网页、flash等,举例如下: 1. 下载sohu主页的所有图片 在地址栏中输入www.sohu.com,在“选项”中将最大下载图片数目设为0,最大访问网页数设为1,点开始即可。 2. 下载sohu主页及其子链接中的所有图片,共下载100张 在“选项”中将最大下载图片数目设为100,最大访问网页数设为0 3. 下载人人网中的相册 打开相册中的一张相片,如http://photo.renren.com/getphoto.do?id=975410152&owner=230410031&curpage=0&t=#975410152,将地址复制到软件的地址栏(注意要将窗口拉长,使得地址栏足够容下这个地址,否则地址会被切断,这个bug我一直没找到解决办法)。 接下来需要一点观察。先看要下载图片链接(http://fmn017.xnimg.cn/fmn017/pic001/20080926/17/21/large_wJZc_3213f200058.jpg),再看下一张,是http://fmn014.xnimg.cn/fmn014/pic001/20080926/17/21/large_Ao10_3208l200058.jpg,找到其中的公共部分,不妨取为xnimg.cn/fmn,将其输入“选项”中的“图片路径含有”,并勾选复选框。 再观察“上一张”、“下一张”的链接(http://photo.renren.com/getphoto.do?id=975410152&owner=230410031&curpage=0&t=#975409483)(http://photo.renren.com/getphoto.do?id=975410152&owner=230410031&curpage=0&t=#975410114),取其公共部分photo.renren.com/getphoto.do,填入“网页路径含有”并勾选复选框。 最后将最大下载图片数目最大访问网页数都设为0,点开始即可。上面限制了图片网页路径,只是为了防止下载不需要的图片。 4. 一次性在你的100个好友的页面上留下脚印 观察所有个人主页链接url,取出公共部分http://renren.com/profile.do,剩下的我就不用说了吧……对了,要是一张图片都不想下载,只要在“图片路径含有”中输入一个空格就可以了,因为任何图片url都不包含空格…… 5. 下载一部小说目录中的所有链接 提示:“选项”中有一个小小的“高级”按钮,有兴趣的同志可以研究一下…… ps. 大家可能看出来了,这个软件很类似网络爬虫,从一个网页“爬”到另一个网页,然后选择图片下载。如果有人需要根据关键词从大的图片搜索引擎下载图片,我推荐crazyPic这个软件。我这个软件的用途我暂时就想出这么多,欢迎发掘更多用途,也欢迎挑bug!
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Hann Yang

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

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

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

打赏作者

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

抵扣说明:

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

余额充值