1. 程式人生 > >手把手教你寫基於C++ Winsock的圖片下載的網路爬蟲

手把手教你寫基於C++ Winsock的圖片下載的網路爬蟲

先來說一下主要的技術點:

1. 輸入起始網址,使用ssacnf函式解析出主機號和路徑(僅處理http協議網址)

2. 使用socket套接字連線伺服器,,獲取網頁html程式碼(使用http協議的GET請求),然後使用正則表示式解析出圖片url和其他的url

3. 下載圖片至建立的資料夾中,同時其他的url push進佇列。

4. 為了使爬蟲能夠連續的工作,這裡使用了BFS寬度優先搜尋,也就是說一開始輸入的網址作為起始網址,push進佇列,然後把能解析出來的網址在不重複的情況下都push進佇列,每次取佇列的top來執行下載操作,直到佇列為空時終止。

下面附上技術點的學習資料供參考:

ssanf函式的用法:

C++11正則表示式

http協議:

Socket程式設計:

另外,這個小爬蟲結構簡陋,還存在很多不足,例如佇列中的url太多會爆記憶體,正則表示式匹配不夠準確等,僅僅適合大家學習的時候練手哈。也歡迎大家發現bug,給出好的建議。

/*下載圖片 C++ Winsock 網路程式設計*/
#define _CRT_SECURE_NO_WARNINGS   //vs 2013用於忽略c語言安全性警告
#include <cstdio>
#include <iostream>
#include <fstream>
#include <string>
#include <cstring>
#include <regex>
#include <vector>
#include <queue>
#include <algorithm>
#include <winsock2.h>
#include <map>
using namespace std;
#pragma comment(lib, "ws2_32.lib")
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) //僅支援http協議,解析出主機和IP地址
{
	char *pos = strstr(url, "http://");
	if (pos == NULL)
		return false;
	else
		pos += 7;
	sscanf(pos, "%[^/]%s", host, othPath);   //http:// 後一直到/之前的是主機名
	cout << "host: " << host << "   repath:" << othPath << endl;
	return true;
}


void regexGetimage(string &allHtml)  //C++11 正則表示式提取圖片url
{
	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(msg);
		start = mat[0].second;
	}
}

void regexGetcom(string &allHtml) //提取網頁中的http://的url
{
	smatch mat;
	//regex pattern("href=\"(.*?\.html)\"");
	regex pattern("href=\"(http://[^\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()  //socket進行網路連線
{
	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);//   with some problems ???
	//sa.sin_addr.S_un.S_addr = inet_addr(*(p->h_addr_list));
	//cout << *(p->h_addr_list) << endl;
	n = connect(sock, (sockaddr*)&sa, sizeof(sa));
	if (n == SOCKET_ERROR)
	{
		cout << "connect函式失敗! 錯誤碼: " << WSAGetLastError() << endl;
		return;
	}
	//像伺服器傳送GET請求,下載圖片
	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 error! 錯誤碼: " << WSAGetLastError() << endl;
		closesocket(sock);
		return;
	}
	//PutImagtoSet();
	
}

void OutIamge(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 = 0; i<imageUrl.length(); i++){
		char ch = imageUrl[i];
		if (ch != '\\'&&ch != '/'&&ch != ':'&&ch != '*'&&ch != '?'&&ch != '"'&&ch != '<'&&ch != '>'&&ch != '|')
			photoname[k++] = ch;
	}
	photoname = "./img/"+photoname.substr(0, k) + ".jpg";

	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");

	//allHtml = "";
	//allHtml += string(cpos);
	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);
		//buf[n] = '\0';
		//allHtml += string(buf);
	}
	//file.write(allHtml.c_str(), allHtml.length());
	file.close();
	//closesocket(sock);
}
void PutImagtoSet()  //解析整個html程式碼
{
	int n;
	//preConnect();
	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();
		PutImagtoSet();
		vector<string>::iterator ita = photoUrl.begin();
		for (ita; ita != photoUrl.end(); ++ita)
		{
			OutIamge(*ita);
		}
		photoUrl.clear();
		vector <string>::iterator it = comUrl.begin();
		for (it; it != comUrl.end(); ++it)
		{
			if (mp[*it]==0)
			q.push(*it);
		}
		comUrl.clear();
	}
}
int main()
{
	CreateDirectoryA("./img", 0);  //在工程下建立資料夾
	string beginUrl= "http://news.qq.com/"; //輸入起始網址
	bfs(beginUrl);
	return 0;
	
}

效果圖: