C/C++实现最简单的爬虫

C++ 专栏收录该内容
4 篇文章 1 订阅

作为一名程序员我觉得最简单的骚操作还是需要具备的,比如爬虫。本文主要介绍实现最简单的c++爬虫,为什么标题是c/c++呢,因为写的时候用到了一些c++内容但主要结构还是c顺序结构。主要实现了对http协议网站的图片爬取。

主要内容:

  • 爬虫思路
  • 开发时需要注意的地方
  • 运行结果
  • 需要改进的地方
  • 完整源码

爬虫思路:

基本可分为三大步骤

  1. 用户输入起始地址
  2. 创建用来保存图片的文件夹
  3. 遍历搜索

第三步遍历搜索可细分为

  1. 从初始网站获取网页源码
  2. 从网页源码中解析出图片地址、网站地址
  3. 保存地址
  4. 去除重复
  5. 下载图片
  6. 连接下一个网站

需注意的地方:

1、去除重复

博主将解析出的图片url存在了一个vector里,对vector里元素去重采用了先排序再删掉最末尾几个元素。因为在排序时会讲相同元素全部移至容器最末尾

void deletecp()
{
    //去重 
    sort(g_photoAddr.begin(),g_photoAddr.end());                                             //unique只能比较相邻元素是否重复
    g_photoAddr.erase(unique(g_photoAddr.begin(), g_photoAddr.end()), g_photoAddr.end());  
    sort(g_htmlAddr.begin(),g_htmlAddr.end());                                             
    g_htmlAddr.erase(unique(g_htmlAddr.begin(), g_htmlAddr.end()), g_htmlAddr.end());                        //unique将重复的元素移到末尾,返回末尾中第一个重复值的地址
}

2、测试通信

 解析到主机ip后需要向该主机发送一个http请求用以测试能否成功通信,此处注意http请求的编写,操作字符串真的是有一些麻烦

std::string reqInfo = "GET " + (std::string)g_path + " HTTP/1.1\r\nHost:"+(std::string)g_zhuji +
                            "\r\nConnection:Close\r\n\r\n";
    r = send(g_socket,reqInfo.c_str(),reqInfo.size(),NULL);
    if(-1 == r)
    {
        std::cout<<" failed"<<std::endl;
        return;
    }
    std::cout<<" send succeed!"<<std::endl;

3、拿到图片url后下载图片

此处最麻烦的地方仍在图片命名上,也就是字符串字符串字符串操作。下载可采用API  URLDownloadToFile

void downloadImage()
{
    std::string str = "0";
    for(int i=0;i<g_photoAddr.size();i++)
    {
        char *pURL = (char*)g_photoAddr.at(i).c_str();
        std::string path = pathfile;
        path.append("/ .jpg");
        char *str;
        sprintf(str,"%d",count);
        int pos = path.find(' ');
        path.replace(pos,1,str);
        count++;
        std::cout<<path<<std::endl;
        char szBuffer[1024*128] = {0};
        unsigned long iSize = 0;
        char szPreCommand[128] = {0};
        DeleteUrlCacheEntry(pURL);//清空缓存,否则服务器上的文件修改后,无法下载最新的文件
        if (URLDownloadToFile(NULL, pURL, path.c_str(), 0, NULL)==S_OK)
        {      
            printf("URLDownloadToFile OK\n");
        }
        else
        {
            printf("URLDownloadToFile Fail,Error:%d\n", GetLastError());
        }
        //CoUninitialize();
    }
    std::cout << "all is ok" << std::endl;

}

运行结果

需要改进的地方:

  • 对HTTPS的处理
  • 对其他格式文件的处理
  • 网站及文件去重
  • 改进遍历方式

完整源码


/*
filename demo0.cpp
make g++ demo0.cpp -l ws2_32 -l Urlmon -l Wininet
环境:windows
编译器 g++
编辑器 vscode
*/


#include <iostream>
#include <string>
#include <windows.h>
#include <winsock2.h>
#include <vector>
#include <locale>
#include <stdlib.h>
#include <Urlmon.h>
#include <Wininet.h>
 
#pragma comment(lib,"Urlmon.lib")
#pragma comment(lib, "Wininet.lib")s
//#pragma comment(lib,"ws2_32.lib");
#include <windows.h>
#include <tchar.h>
#include <urlmon.h>
#include <regex>    //正则表达式
//存储主机名
char g_zhuji[256];
//存储主机名后的路径
char g_path[256];
//socket
SOCKET g_socket;
//图片序列
int count;
//图片文件夹名
std::string pathfile;
//保存所有图片地址
std::vector<std::string> g_photoAddr;
std::vector<std::string> g_htmlAddr;
/*
1、用户输入起始网址并保存
2、创建文件夹用来保存图片
3、遍历搜索(找所有网址,从网站下载图片)
    3.1从初始网址获取网页源代码
    3.2从网页源代码中解析出 图片地址 和网站地址
    3.3去除重复后保存到一个地方
    3.4下载图片
    3.5连接下一个网站
*/
//3.1.1解析网址,得到主机名
void jiexiAddr(char* addr)
{
    //http://www.win4000.com/meitu.html
    //协议前缀 http:// 
    //主机名 www.win4000.com
    //二级网址 meitu.html
    char* pos = strstr(addr,"http://"); //参数一中参数二部分的首地址
    char* pos1 = strstr(addr,"https://"); //参数一中参数二部分的首地址
    if(NULL == pos &&NULL == pos1)
    {
        return;
    }
    else
    {
        if(pos==NULL)
        {
            pos1 +=8;
            sscanf(pos1,"%[^/]%s",g_zhuji,g_path);
        }else
        {
            pos +=7;
            sscanf(pos,"%[^/]%s",g_zhuji,g_path);
        }
       

    }
    //"%[^/]%s"到斜杠为止
    
    std::cout<<"host "<<g_zhuji<<std::endl;
    std::cout<<"path "<<g_path<<std::endl;

}
//3.1.2 连接主机
void lianjieAddr()
{
    //1 获取协议版本号
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2,2),&wsaData);
    if(LOBYTE(wsaData.wVersion)!=2 || HIBYTE(wsaData.wVersion)!=2)
    {
        std::cout<<" failed"<<std::endl;
        return;
    }
    //2 创建socket
    g_socket = socket(AF_INET,SOCK_STREAM,0);
    if(INVALID_SOCKET == g_socket)
    {
        std::cout<<" failed"<<std::endl;    //创建socket失败
        std::cout<<WSAGetLastError()<<std::endl;    //输出错误码
        return;
    }

    //3 拿到主机协议地址族
    sockaddr_in addr = {0};
    addr.sin_family = AF_INET;
    
    //4 绑定
    int r = bind(g_socket,(sockaddr*)&addr,sizeof addr);
    if(r==-1)
    {
        std::cout<<" failed"<<std::endl;
        return;
    }
    
    //5 通过名字拿到ip地址,域名解析
    struct hostent* p = gethostbyname(g_zhuji);
    if(p==NULL)
    {
        std::cout<<" failed"<<std::endl;
        return;
    }
    
    //6 地址放到协议地址族中
    memcpy(&addr.sin_addr,p->h_addr,4);
    addr.sin_port = htons(80);
    //7 连接服务器
    r = connect(g_socket,(sockaddr*)&addr,sizeof addr);
    if(-1==r)
    {
        std::cout<<" failed"<<std::endl;
        return;
    }

    //8 通信
    std::string reqInfo = "GET " + (std::string)g_path + " HTTP/1.1\r\nHost:"+(std::string)g_zhuji +
                            "\r\nConnection:Close\r\n\r\n";
    r = send(g_socket,reqInfo.c_str(),reqInfo.size(),NULL);
    if(-1 == r)
    {
        std::cout<<" failed"<<std::endl;
        return;
    }
    std::cout<<" send succeed!"<<std::endl;


}

void getImage(std::string& allHtmlData)
{
    std::smatch mat;
    std::regex pattern(" src=\"(.*?\\.jpg)\" ");
    std::string::const_iterator start = allHtmlData.begin();
    std::string::const_iterator end = allHtmlData.end();
    while(std::regex_search(start,end,mat,pattern))
    {
        std::string msg(mat[1].first,mat[1].second);
        g_photoAddr.push_back(msg);
        std::cout<<msg<<std::endl;
        start = mat[0].second;
    }

}
//从网页源代码中解析网站地址
void getHtml(std::string& allHtmlData)
{
    std::smatch mat;
    std::regex pattern("href=\"(http://[^\\s'\"]+)\"");
    std::string::const_iterator start = allHtmlData.begin();
    std::string::const_iterator end = allHtmlData.end();
    while(std::regex_search(start,end,mat,pattern))
    {
        std::string msg(mat[1].first,mat[1].second);
        g_htmlAddr.push_back(msg);
        std::cout<<msg<<std::endl;
        start = mat[0].second;
    }
}
//3.1.3 获取html代码
void huoquHtmlData()
{
    int n;
    char buff[1024];
    std::string allHtmlData;
    while(1)
    {
        n = recv(g_socket,buff,1023,NULL);
        if(n<=0)
            break;
        buff[n] = n;
        allHtmlData += buff;
    }
    //std::cout <<allHtmlData<<std::endl;

    //3.2从网页源代码中解析出 图片地址 和网站地址
    getHtml(allHtmlData);
    getImage(allHtmlData);
    
}
//转义函数


void downloadImage()
{
    std::string str = "0";
    for(int i=0;i<g_photoAddr.size();i++)
    {
        char *pURL = (char*)g_photoAddr.at(i).c_str();
        std::string path = pathfile;
        path.append("/ .jpg");
        char *str;
        sprintf(str,"%d",count);
        int pos = path.find(' ');
        path.replace(pos,1,str);
        count++;
        std::cout<<path<<std::endl;
        char szBuffer[1024*128] = {0};
        unsigned long iSize = 0;
        char szPreCommand[128] = {0};
        DeleteUrlCacheEntry(pURL);//清空缓存,否则服务器上的文件修改后,无法下载最新的文件
        if (URLDownloadToFile(NULL, pURL, path.c_str(), 0, NULL)==S_OK)
        {      
            printf("URLDownloadToFile OK\n");
        }
        else
        {
            printf("URLDownloadToFile Fail,Error:%d\n", GetLastError());
        }
        //CoUninitialize();
    }
    std::cout << "all is ok" << std::endl;

}
void deletecp()
{
    //去重 
    sort(g_photoAddr.begin(),g_photoAddr.end());                                             //unique只能比较相邻元素是否重复
    g_photoAddr.erase(unique(g_photoAddr.begin(), g_photoAddr.end()), g_photoAddr.end());  
    sort(g_htmlAddr.begin(),g_htmlAddr.end());                                             
    g_htmlAddr.erase(unique(g_htmlAddr.begin(), g_htmlAddr.end()), g_htmlAddr.end());                        //unique将重复的元素移到末尾,返回末尾中第一个重复值的地址
}

void snapJpg(const char* addr)
{
    //3.1从网页源代码中解析出 图片地址 和网站地址
    //3.1.1解析网址,得到主机名
    char buff[256] = {0};
    strcpy(buff,addr);
    jiexiAddr(buff);
    //3.1.2 连接主机
    lianjieAddr();
    //3.1.3 获取html代码
    huoquHtmlData();

    //3.3去除重复后保存到一个地方
    deletecp();
    //3.4下载图片
    downloadImage();

    
}




int main()
{
    //1、用户输入起始网址并保存
    std::string str;
    std::cout<<"begin url:"<<std::endl;  
    std::cin>>str;
    count = 0;
    std::string file;
    std::cout<<"files name:"<<std::endl; 
    std::cin>>file;
    std::string path = "./";
    path.append(file);
    std::cout<<path<<std::endl;
    pathfile = path;
    //2、创建文件夹用来保存图片
    //system("mkdir images");
    CreateDirectory(path.c_str(),NULL);

    //3、遍历搜索
    snapJpg(str.c_str());
    //3.5连接下一个网站
    for(int i=0;i<g_htmlAddr.size();i++)
    {
        std::cout<<"this num:"<<i<<std::endl;
        str = g_htmlAddr.at(i);
        snapJpg(str.c_str());

    }
    return 0;
}

 

 

 

WEBCRAWLER 网络爬虫实训项目 1 WEBCRAWLER 网 络 爬 虫 实 训 项 目 文档版本: 1.0.0.1 编写单位: 达内IT培训集团 C++教学研发部 编写人员: 闵卫 定稿日期: 2015年11月20日 星期五WEBCRAWLER 网络爬虫实训项目 2 1. 项目概述 互联网产品形形色色,有产品导向的,有营销导向的,也有技术导向的,但是 以技术见长的互联网产品比例相对小些。搜索引擎是目前互联网产品中具技 术含量的产品,如果不是唯一,至少也是其中之一。 经过十几年的发展,搜索引擎已经成为互联网的重要入口之一,Twitter联合创 始人埃文•威廉姆斯提出了“域名已死论”,好记的域名不再重要,因为人们会 通过搜索进入网站。搜索引擎排名对于中小网站流量来说至关重要。了解搜索 引擎简单界面背后的技术原理其实对每一个希望在互联网行业有所建树的信息 技术人员都很重要。 1.1. 搜索引擎 作为互联网应用中具技术含量的应用之一,优秀的搜索引擎需要复杂的架构 和算法,以此来支撑对海量数据的获取、 存储,以及对用户查询的快速而准确 地响应。 从架构层面,搜索引擎需要能够对以百亿计的海量网页进行获取、 存 储、 处理的能力,同时要保证搜索结果的质量。 如何获取、 存储并计算如此海WEBCRAWLER 网络爬虫实训项目 3 量的数据?如何快速响应用户的查询?如何使得搜索结果尽可能满足用户对信 息的需求?这些都是搜索引擎的设计者不得不面对的技术挑战。 下图展示了一个通用搜索引擎的基本结构。商业级别的搜索引擎通常由很多相 互独立的模块组成,各个模块只负责搜索引擎的一部分功能,相互配合组成完 整的搜索引擎: 搜索引擎的信息源来自于互联网网页,通过“网络爬虫” 将整个“互联网” 的 信息获取到本地,因为互联网页面中有相当大比例的内容是完全相同或者近似 重复的,“网页去重”模块会对此做出检测,并去除重复内容。 在此之后,搜索引擎会对网页进行解析,抽取网页主体内容,以及页面中包含 的指向其它页面的所谓超链接。 为了加快用户查询的响应速度,网页内容通过 “倒排索引”这种高效查询数据结构来保存,而网页之间的链接关系也会予以 保存。之所以要保存链接关系,是因为这种关系在网页相关性排序阶段是可利 用的,通过“链接分析”可以判断页面的相对重要性,对于为用户提供准确的 搜索结果帮助很大。 由于网页数量太多,搜索引擎不仅需要保存网页的原始信息,还要保存一些中 间处理结果,使用单台或者少量的计算机明显是不现实的。 Google等商业搜索 引擎提供商,为此开发了一整套云存储与云计算平台,使用数以万计的普通PCWEBCRAWLER 网络爬虫实训项目 4 搭建了海量信息的可靠存储与计算架构,以此作为搜索引擎及其相关应用的基 础支撑。优秀的云存储与云计算平台已经成为大型商业搜索引擎的核心竞争 力。 以上所述是搜索引擎如何获取并存储海量的网页相关信息。这些功能因为不需 要实时计算,所以可以被看作是搜索引擎的后台计算系统。搜索引擎的首要目 标当然是为用户提供准确而全面的搜索结果,因此响应用户查询并实时提供准 确结果便构成了搜索引擎的前台计算系统。 当搜索引擎接收到用户的查询请求后,首先需要对查询词进行分析,通过与用 户信息的结合,正确推导出用户的真实搜索意图。 此后,先在“Cache系统” 所维护的缓存中查找。搜索引擎的缓存存储了不同的搜索意图及其相对应的搜 索结果。如果在缓存中找到满足用户需求的信息,则直接将搜索结果返回给用 户。这样既省掉了重复计算对资源的消耗,又加快了整个搜索过程的响应速 度。而如果在缓存中没有找到满足用户需求的信息,则需要通过“网页排 序”,根据用户的搜索意图,实时计算哪些网页是满足用户需求的,并排序输 出作为搜索结果。 而网页排序重要的两个参考因素,一个是“内容相似 性”,即哪些网页是和用户的搜索意图密切相关的;一个是网页重要性,即哪 些网页是质量较好或相对重要的,而这往往可以从“链接分析”的结果中获 得。综合以上两种考虑,前台系统对网页进行排序,作为搜索的终结果。 除了上述功能模块,搜索引擎的“反作弊”模块近年来越来越受到重视。搜索 引擎作为互联网用户上网的入口,对于网络流量的引导和分流至关重要,甚至 可以说起着决定性的作用。因此,各种“作弊”方式也逐渐流行起来,通过各 种手段将网页的搜索排名提前到与其网页质量不相称的位置,这会严重影响用 户的搜索体验。所以,如何自动发现作弊网页并对其给于相应的惩罚,就成了 搜索引擎非常重要的功能之一。 1.2. 网络爬虫 通用搜索引擎的处理对象是互联网网页,截至目前的网页数量数以百万计,所 以搜索引擎首先面临的问题就是如何能够设计出高效的下载系统,将如此海量 的网页数据传送到本地,在本地形成互联
评论 4 您还未登录,请先 登录 后发表或查看评论
©️2022 CSDN 皮肤主题:技术黑板 设计师:CSDN官方博客 返回首页

打赏作者

Mason_Zhao

互相学习

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

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

打赏作者

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

抵扣说明:

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

余额充值