文章目录
- 1.引言
- 2.演示效果
- 3.源码
- 4.补充
1.引言
哈喽,各位小伙伴们,今天我给大家分享的是如何用C++实现爬取网页源代码。
本人用的开发环境为visual studio 2013,涉及到的知识点有:构造函数、析构函数、queue队列、分文件编写、WinSock2.h网络编程、fstream文件流等等,总之对于新手而言是非常好的练习代码能力的一个作业。
2.演示效果
先是给大家看看演示效果,下图所示的是代码运行后的初始界面:
下图是我将要爬取的源地址,这里我随便在网上搜了个网址。
另外附上这个界面的源码图片,这个可以在浏览器中按F12获取得到,也可以右键选择打开。
下图是我将网址输入后实现的效果:
可以看到在D盘已经生成了一个html.txt文件,用于存放原网页的源代码。大小为11kb。
同时,这里我们以黑窗口打印出html的返回数据,用于验证返回的源代码。
好了,以上就是我们的演示效果,下面废话不多说,直接给大家源码吧,相关注释我已经写得很详细了,有不懂的欢迎留言或者私信我。
3.源码
一共有三个代码块、分别是http.h、http.cpp和main.cpp
3.1 http.h
#ifndef HTTP_H //目的是为了防止头文件重复包含
#define HTTP_H
#include<WinSock2.h>
class CHttp
{
public:
std::string m_host; //域名
std::string m_object; //资源路径
bool m_bHttps;
SOCKET m_socket; //套接字
public:
CHttp(); //构造函数
~CHttp(); //析构函数
//初始化网络
bool Init();
//解析URL
bool AnalyseURL(std::string url);
//连接服务器
bool CHttp::Connect();
//下载网页及保存
bool GetHtml(std::string& html); //引用类型的变量
};
#endif
3.2 http.cpp
#include<iostream>
#include"http.h"
#include<WinSock2.h> //使用套接字的头文件
#include <fstream> //包含文件流的头文件
#define _CRT_SECURE_NO_WARNINGS
#pragma comment(lib,"ws2_32.lib")
//构造函数
CHttp::CHttp()
{
m_bHttps = false; //默认一开始不是https协议
m_socket = NULL;
}
//析构函数
CHttp::~CHttp()
{
}
//解析URL函数
bool CHttp::AnalyseURL(std::string url)
{
//https://www.microsoft.com/zh-cn/download/confirmation.aspx?id=40770 示例 https
//http://www.163.com/ 示例 http
//将字符串分别转化为大、小写的函数
//toupper(); tolower(); 因为有些网站用的是大写的HTTPS\HTTP
std::string str = url.substr(0, 8); //substr(string, start<,length>):从string的start位置开始提取字符串,length:要提取字符串的长度
if ("https://" == str)
{
m_bHttps = true;
}
else if (str.find("http://") !=std::string::npos)
{
m_bHttps = false;
}
else
return false;
//找主机网址的反斜杠位置
int nPos = url.find('/', m_bHttps ? 8 : 7); //如果m_bHttps为真,那么从第8个之后的位置开始找,否则从第七个位置之后开始找
if (nPos == std::string::npos) //这句话的意思就是说如果把最后一个位置都找完了,还没找到。 npos表示string的结束位置,
{
//http://www.163.com
m_host=url.substr(m_bHttps ? 8 : 7); //例如上面这种,如果主机后面没有/,那么直接从http://开始截取,截到最后
m_object = "/"; //像上面这种没有资源路径,那我们就给他们一个斜杠
}
else
{
//如果是这种情况https://www.microsoft.com/zh-cn/download/confirmation.aspx?id=40770
m_host = url.substr(m_bHttps ? 8 : 7, nPos - (m_bHttps ? 8 : 7));
m_object = url.substr(nPos);
}
if (m_host.empty()) //如果主机内容为空,意味着截取不到
return false;
return true;
}
//初始化网络
bool CHttp::Init()
{
WSADATA wd;
if (0 != WSAStartup(MAKEWORD(2, 2), &wd))
return false;
if (LOBYTE(wd.wVersion) != 2 || HIBYTE(wd.wVersion) != 2) //判断请求的是不是2.2版本
return false;
//创建套接字
m_socket=socket(AF_INET, SOCK_STREAM, 0);
}
//连接服务器
bool CHttp::Connect()
{
//将域名解析成对应的IP地址
HOSTENT * p=gethostbyname(m_host.c_str()); //P存放的内容就是由主机域名解析好后的ip地址,
if (p == NULL)
return false; //解析失败
//连接服务器
sockaddr_in sa;
sa.sin_family = AF_INET;
sa.sin_port = htons(80);
memcpy(&sa.sin_addr, p->h_addr, 4);
if (SOCKET_ERROR == connect(m_socket, (sockaddr*)&sa, sizeof(sockaddr)))
return false;
return true;
}
bool CHttp::GetHtml(std::string& html)
{
std::string get;
get += "GET " + m_object + " HTTP/1.1\r\n";
get += "Host: " + m_host + "\r\n";
get += "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36 Edg/91.0.864.37";
get += "Connection: Close\r\n";
get += "\r\n";
//发送GET请求
if(SOCKET_ERROR==send(m_socket, get.c_str(), get.length(), 0)); //套接字、发送的内容、发送多长、flag
std::cout << "GET请求发送失败" << std::endl;
//接收数据
char ch = 0;
std::fstream dataFile; //创建一个文件,用于存放html的内容dataFile.open("D:\\html.txt", std::ios::out);
dataFile.open("D:\\html.txt", std::ios::out);
if (!dataFile)
{
printf("文件打开失败!\n");
return false;
}
while (recv(m_socket, &ch, sizeof(ch), 0))
{
html += ch;
dataFile << ch;
}
dataFile.close();
return true;
}
3.3 main.cpp
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<string>
#include<queue>
#include"http.h"
using namespace std;
//欢迎界面
void Welcome();
//开始抓取
bool StartCatch(string url);
int main()
{
Welcome();
cout << "请输入要抓取的URL的地址:"<<endl;
string url;
cin >> url;
StartCatch(url);
system("pause");
return EXIT_SUCCESS;
}
//欢迎界面
void Welcome()
{
cout << endl;
cout << endl;
cout << "\t\t-----------------------------------------" << endl;
cout << "\t\t-----------------------------------------" << endl;
cout << "\t\t-\t\t\t\t\t-" << endl;
cout << "\t\t-\t\t\t\t\t-" << endl;
cout << "\t\t-\t\t\t\t\t-" << endl;
cout << "\t\t-\t欢迎使用C++智能爬虫系统\t\t-" << endl;
cout << "\t\t-\t\t\t\t\t-" << endl;
cout << "\t\t-\t\t\t\t\t-" << endl;
cout << "\t\t-\t\t\t\t\t-" << endl;
cout << "\t\t-\t 某某大学某某实验室\t\t-" << endl;
cout << "\t\t-\t\t\t\t\t-" << endl;
cout << "\t\t-\t\t\t\t\t-" << endl;
cout << "\t\t-----------------------------------------" << endl;
cout << "\t\t-----------------------------------------" << endl;
}
//开始抓取
bool StartCatch(string url)
{
queue<string> q; //创建url队列 因为url是先获取到先处理,所以用queue的数据结构
q.push(url); //将获取到的url队列放入queue中
while (!q.empty()) //判断队列是否为空,如果不为空,那么久一直采集
{
string currentUrl = q.front(); //将当前队列中的第一个url取出来
q.pop();
//解析URL ----就是把协议、主机、资源路径给分割出来
CHttp http;
http.Init();
http.AnalyseURL(currentUrl);
cout << http.m_host << "\t\t" << http.m_object << endl;
if (false == http.Connect())
cout << "连接服务器失败" << endl;
else
cout << "连接服务器成功" << endl;
//获取html信息
string html;
http.GetHtml(html);
cout << html << endl; //这一行可有可无,不过第一次跑的时候最好用上
}
return true;
}
4.补充
在网络编程这一块,如果不了解的朋友可以去网上搜一下,有相应的博客说的很好,在http.cpp程序中GET那一块的代码,我用到了fiddler软件用于查找一些信息。有需要的朋友可以去太平洋下载中心进行下载,里面也有汉化教程。