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

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

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


image.png

源代码如下:

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

下载成果:


20210220135937379.png


本代码在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")



目录
相关文章
|
5月前
|
数据安全/隐私保护 C++ 计算机视觉
Qt(C++)开发一款图片防盗用水印制作小工具
文本水印是一种常用的防盗用手段,可以将文本信息嵌入到图片、视频等文件中,用于识别和证明文件的版权归属。在数字化和网络化的时代,大量的原创作品容易被不法分子盗用或侵犯版权,因此加入文本水印成为了保护原创作品和维护知识产权的必要手段。 通常情况下,文本水印可以包含版权声明、制作者姓名、日期、网址等信息,以帮助识别文件的来源和版权归属。同时,为了增强防盗用效果,文本水印通常会采用字体、颜色、角度等多种组合方式,使得水印难以被删除或篡改,有效地降低了盗用意愿和风险。 开发人员可以使用图像处理技术和编程语言实现文本水印的功能,例如使用Qt的QPainter类进行文本绘制操作,将文本信息嵌入到图片中,
193 1
Qt(C++)开发一款图片防盗用水印制作小工具
|
6月前
|
存储 分布式数据库 API
技术好文:VisualC++查看文件被哪个进程占用
技术好文:VisualC++查看文件被哪个进程占用
|
2月前
|
Linux C++
Linux c/c++文件的基本操作
在Linux环境下使用C/C++进行文件的基本操作,包括文件的创建、写入、读取、关闭以及文件描述符的定位。
22 0
Linux c/c++文件的基本操作
WK
|
1月前
|
安全 Java 编译器
C++和Java哪个更适合开发web网站
在Web开发领域,C++和Java各具优势。C++以其高性能、低级控制和跨平台性著称,适用于需要高吞吐量和低延迟的场景,如实时交易系统和在线游戏服务器。Java则凭借其跨平台性、丰富的生态系统和强大的安全性,广泛应用于企业级Web开发,如企业管理系统和电子商务平台。选择时需根据项目需求和技术储备综合考虑。
WK
70 0
|
3月前
|
C++ 内存技术
[转]Visual C++内嵌swf文件并播放
[转]Visual C++内嵌swf文件并播放
|
2月前
|
Linux C++
Linux c/c++文件虚拟内存映射
这篇文章介绍了在Linux环境下,如何使用虚拟内存映射技术来提高文件读写的速度,并通过C/C++代码示例展示了文件映射的整个流程。
57 0
|
2月前
|
Linux C++
Linux c/c++文件移动
这篇文章介绍了在Linux环境下,使用C/C++语言通过命令方式和文件操作方式实现文件移动的方法。
81 0
|
3月前
|
Linux API C++
超级好用的C++实用库之文件目录操作
超级好用的C++实用库之文件目录操作
39 0
|
3月前
|
JavaScript 前端开发 测试技术
一个google Test文件C++语言案例
这篇文章我们来介绍一下真正的C++语言如何用GTest来实现单元测试。
25 0
|
4月前
|
存储 算法 C++
【C++】C++ QT实现Huffman编码器与解码器(源码+课程论文+文件)【独一无二】
【C++】C++ QT实现Huffman编码器与解码器(源码+课程论文+文件)【独一无二】
106 4