C++实现图片爬虫
代码是从B站学的,所以并不是很原创
实现思路
- 输入需要爬取的网址加入网址队列;
- 使用BFS:
- 获取网址队列队首中的网址,并从队列中弹出,解析出网址中的主机名以及文件存放的目录(html);
- 通过解析出的解析出的信息使用Socket套接字建立连结,发送GET请求;
- 获取GET得到的HTML文件内容,并使用正则表达式获得其中关于JPG(或者其他格式的图片)的链接和跳转链接分别存于两个vector数组;
- 遍历存放图片链接的vector数组,建立链接,使用GET方法获取JPG图片,存放至文件夹,遍历结束后清空数组(因为当前html页面中的图片都获取了);
- 遍历存放跳转链接的vector数组,如果没有获取过该网址的图片(使用map对爬过的网址进行标记)则加入到网址队列。
实现代码
广度优先遍历该网站 BFS
??BFS需要获得起始网址的链接。
void bfs(string beginUrl)
{
queue<string>q;
q.push(beginUrl);
while (!q.empty())
{
string cur = q.front();
mp[cur]++;
q.pop();
char tmp[800];
strcpy(tmp, cur.c_str());
analyUrl(tmp);
preConnect();
PutHTMLtoSet();
for(int i=0;i<photoUrl.size();i++)
OutImage(photoUrl[i]);
photoUrl.clear();
for(int i=0;i<comUrl.size();i++)
if (mp[comUrl[i]] == 0)
q.push(comUrl[i]);
comUrl.clear();
}
}
解析网址 analyUrl
??analyUrl需要参数url即存放待解析的网址数组的地址 ??使用strstr判断其中是否有"http://"的信息,如果没有则结束,如果有则从http://的下一个位置开始(所以pos要+7)使用sscanf读取到’/'之前的内容即host,以及之后的内容即文件存放的位置othPath。
bool analyUrl(char* url)
{
char* pos = strstr(url, "http://");
if (pos == NULL) return NULL;
else
{
pos += 7;
}
sscanf(pos, "%[^/]%s", host, othPath);
cout << "host: " << host << " repath: " << othPath << endl;
return true;
}
读取HTML中的图片链接 regexGetimage
??其中allHTML为获得的HTML文本内容,参考正则表达式的使用,图片链接是 src=“链接” 的形式,所以正则表达式为"src="“开头(’'反斜杠是转义符),其中的链接应以.jpg结尾,”.?“中.为匹配除”\n"之外的任何单个字符,*是多个字符,最后?是最短匹配即可,那么从allHTML头开始至结尾使用pattern模式进行匹配,mat[0]为匹配上的串,mat[1]为"(.?.jpg)"中匹配上的串,跳转到mat[0]的下一个位置继续匹配。 ?? 正则表达式的使用在文后附上参考博客链接
void regexGetimage(string& allHtml)
{
smatch mat;
regex pattern("src=\"(.*?\.jpg)\"");
string::const_iterator start = allHtml.begin();
string::const_iterator end = allHtml.end();
while (regex_search(start, end, mat, pattern))
{
string msg(mat[1].first, mat[1].second);
photoUrl.push_back("http:"+msg);
start = mat[0].second;
}
}
读取HTML中的跳转链接 regexCom
??其余同上,跳转href=开头包含http://以及一个或多个非空白符和不是单引号双引号的字符(’\s’为匹配任意的空白符,包括空格、制表符、换页符等空白字符的其中任意一个)最后以"结尾。
void regexGetcom(string& allHtml)
{
smatch mat;
regex pattern("href=\"([^\s'\"]+)\"");
string::const_iterator start = allHtml.begin();
string::const_iterator end = allHtml.end();
while (regex_search(start, end, mat, pattern))
{
string msg(mat[1].first, mat[1].second);
comUrl.push_back(msg);
start = mat[0].second;
}
}
与网站建立连接 preConnect
??前半部分为Socket相关,建立连结后发送GET请求格式如下: ??GET 文件存放地址 HTTP/1.1 ??Host: 主机名 ??Connection: Close ??存放地址之前解析出在OthPath,主机名在Host中。
void preConnect()
{
WSADATA wd;
WSAStartup(MAKEWORD(2, 2), &wd);
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET)
{
cout << "建立socket失败,错误码" << WSAGetLastError() << endl;
return;
}
sockaddr_in sa = { AF_INET };
int n = bind(sock, (sockaddr*)&sa, sizeof(sa));
if (n == SOCKET_ERROR)
{
cout << "bind函数失败,错误码" << WSAGetLastError() << endl;
return;
}
struct hostent* p = gethostbyname(host);
if (p == NULL)
{
cout << "主机无法解析出ip,错误码" << WSAGetLastError() << endl;
return;
}
sa.sin_port=htons(80);
memcpy(&sa.sin_addr,p->h_addr, 4);
n = connect(sock, (sockaddr*)&sa, sizeof(sa));
if (n == SOCKET_ERROR)
{
cout << "connect函数失败,错误码" << WSAGetLastError() << endl;
return;
}
string reqInfo = "GET " + (string)othPath + " HTTP/1.1\r\nHost: " + (string)host + "\r\nConnection:Close\r\n\r\n";
if (SOCKET_ERROR == send(sock, reqInfo.c_str(), reqInfo.size(), 0))
{
cout << "send错误,错误码" << WSAGetLastError() << endl;
closesocket(sock);
return;
}
}
输出图片 OutImage
??从imageUrl中得到图片的链接,使用解析出主机名和存放地址并建立链接发送GET请求,从recv中逐段读取接收到的图片信息(接收到的图片命名就从链接最后开始往前读到/为止,因为希望将图片存放在当前目录的imgData文件夹中,所以photoname前在加上./imgData/的路径),接收到的HTML的头部以\r\n\r\n结束,之后为图片内容。
void OutImage(string imageurl)
{
int n;
char temp[800];
strcpy(temp, imageurl.c_str());
analyUrl(temp);
preConnect();
string photoname;
photoname.resize(imageurl.size());
int k = 0;
for (int i = imageurl.length()-1; i >=0 && imageurl[i]!='/' ; i--)
{
photoname = imageurl[i]+ photoname;
}
photoname = "./imgData/" + photoname;
fstream file;
file.open(photoname, ios::out | ios::binary);
char buf[1024];
memset(buf, 0, sizeof(buf));
n = recv(sock, buf, sizeof(buf) - 1, 0);
char* cpos = strstr(buf, "\r\n\r\n");
file.write(cpos + strlen("\r\n\r\n"), n - (cpos - buf) - strlen("\r\n\r\n"));
while ((n = recv(sock, buf,sizeof(buf)-1,0))>0)
{
file.write(buf, n);
}
file.close();
}
主函数 main
??输入网址,建立文件夹,调用BFS之后就没啥事了。
int main()
{
int i = 0;
string srul;
cin >> srul;
CreateDirectoryA("./imgData", 0);
bfs(srul);
}
完整代码
#include<iostream>
#include<fstream>
#include<cstdio>
#include<string>
#include<cstring>
#include<regex>
#include<vector>
#include<queue>
#include<map>
#include<algorithm>
#include<WinSock2.h>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
char host[500];
int num = 1;
char othPath[800];
string allHtml;
vector<string> photoUrl;
vector<string> comUrl;
map<string, int>mp;
SOCKET sock;
bool analyUrl(char* url)
{
char* pos = strstr(url, "http://");
if (pos == NULL) return NULL;
else
{
pos += 7;
}
sscanf(pos, "%[^/]%s", host, othPath);
cout << "host: " << host << " repath: " << othPath << endl;
return true;
}
void regexGetimage(string& allHtml)
{
smatch mat;
regex pattern("src=\"(.*?\.jpg)\"");
string::const_iterator start = allHtml.begin();
string::const_iterator end = allHtml.end();
while (regex_search(start, end, mat, pattern))
{
string msg(mat[1].first, mat[1].second);
photoUrl.push_back("http:"+msg);
start = mat[0].second;
}
}
void regexGetcom(string& allHtml)
{
smatch mat;
regex pattern("href=\"([^\s'\"]+)\"");
string::const_iterator start = allHtml.begin();
string::const_iterator end = allHtml.end();
while (regex_search(start, end, mat, pattern))
{
string msg(mat[1].first, mat[1].second);
comUrl.push_back(msg);
start = mat[0].second;
}
}
void preConnect()
{
WSADATA wd;
WSAStartup(MAKEWORD(2, 2), &wd);
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET)
{
cout << "建立socket失败,错误码" << WSAGetLastError() << endl;
return;
}
sockaddr_in sa = { AF_INET };
int n = bind(sock, (sockaddr*)&sa, sizeof(sa));
if (n == SOCKET_ERROR)
{
cout << "bind函数失败,错误码" << WSAGetLastError() << endl;
return;
}
struct hostent* p = gethostbyname(host);
if (p == NULL)
{
cout << "主机无法解析出ip,错误码" << WSAGetLastError() << endl;
return;
}
sa.sin_port=htons(80);
memcpy(&sa.sin_addr,p->h_addr, 4);
n = connect(sock, (sockaddr*)&sa, sizeof(sa));
if (n == SOCKET_ERROR)
{
cout << "connect函数失败,错误码" << WSAGetLastError() << endl;
return;
}
string reqInfo = "GET " + (string)othPath + " HTTP/1.1\r\nHost: " + (string)host + "\r\nConnection:Close\r\n\r\n";
if (SOCKET_ERROR == send(sock, reqInfo.c_str(), reqInfo.size(), 0))
{
cout << "send错误,错误码" << WSAGetLastError() << endl;
closesocket(sock);
return;
}
}
void OutImage(string imageurl)
{
int n;
char temp[800];
strcpy(temp, imageurl.c_str());
analyUrl(temp);
preConnect();
string photoname;
photoname.resize(imageurl.size());
int k = 0;
for (int i = imageurl.length()-1; i >=0 && imageurl[i]!='/' ; i--)
{
photoname = imageurl[i]+ photoname;
}
photoname = "./imgData/" + photoname;
fstream file;
file.open(photoname, ios::out | ios::binary);
char buf[1024];
memset(buf, 0, sizeof(buf));
n = recv(sock, buf, sizeof(buf) - 1, 0);
char* cpos = strstr(buf, "\r\n\r\n");
file.write(cpos + strlen("\r\n\r\n"), n - (cpos - buf) - strlen("\r\n\r\n"));
while ((n = recv(sock, buf,sizeof(buf)-1,0))>0)
{
file.write(buf, n);
}
file.close();
}
void PutHTMLtoSet()
{
int n;
char buf[1024];
while ((n = recv(sock, buf, sizeof(buf) - 1, 0)) > 0)
{
buf[n] = '\0';
allHtml += string(buf);
}
regexGetimage(allHtml);
regexGetcom(allHtml);
}
void bfs(string beginUrl)
{
queue<string>q;
q.push(beginUrl);
while (!q.empty())
{
string cur = q.front();
mp[cur]++;
q.pop();
char tmp[800];
strcpy(tmp, cur.c_str());
analyUrl(tmp);
preConnect();
PutHTMLtoSet();
for(int i=0;i<photoUrl.size();i++)
OutImage(photoUrl[i]);
photoUrl.clear();
for(int i=0;i<comUrl.size();i++)
if (mp[comUrl[i]] == 0)
q.push(comUrl[i]);
comUrl.clear();
}
}
int main()
{
int i = 0;
string srul;
cin >> srul;
CreateDirectoryA("./imgData", 0);
bfs(srul);
}
总结
- 从HTML中解析信息的过程中链接获取和存放需要根据爬取的网站进行一定的修改,要查看网站HTML文件中的具体信息
- 调试,有可能用到cmd中的telnet命令
- 正则表达式参考链接https://www.cnblogs.com/guozht/p/7610877.html
|