手把手教你寫基於C++ Winsock的圖片下載的網路爬蟲
阿新 • • 發佈:2019-02-19
先來說一下主要的技術點:
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; }
效果圖: