下面是一篇很有啟發性的文章先分享給大家。
信息論的發展https://zhuanlan.zhihu.com/p/20841617
我們通過上一篇了解了爬蟲具體要實現的工作之后,我們分析得出的網絡爬蟲的基本工作流程如下:
1.首先選取一部分精心挑選的種子URL;
2.將這些URL放入待抓取URL隊列;
3.從待抓取URL隊列中取出待抓取在URL,解析DNS,並且得到主機的ip,並將URL對應的網頁下載下來,存儲進已下載網頁庫中。此外,將這些URL放進已抓取URL隊列。
4.分析已抓取URL隊列中的URL,分析其中的其他URL,並且將URL放入待抓取URL隊列,從而進入下一個循環。
一、爬蟲設計
從爬蟲的角度對互聯網進行划分,可以將互聯網的所有頁面分為五個部分:
1.已下載未過期網頁
2.已下載已過期網頁:抓取到的網頁實際上是互聯網內容的一個鏡像與備份,互聯網是動態變化的,一部分互聯網上的內容已經發生了變化,這時,這部分抓取到的網頁就已經過期了。
3.待下載網頁:也就是待抓取URL隊列中的那些頁面
4.可知網頁:還沒有抓取下來,也沒有在待抓取URL隊列中,但是可以通過對已抓取頁面或者待抓取URL對應頁面進行分析獲取到的URL,認為是可知網頁。
5.還有一部分網頁,爬蟲是無法直接抓取下載的。稱為不可知網頁。
我們爬蟲項目的主事件流程大致如下:
1.獲取命令行參數,執行相應操作
2.讀取配置文件,解析得到各種設置
3.載入各種模塊
4.種子入隊,開啟DNS解析線程(原始隊列不為空時解析)
5.創建epoll,開啟任務,發起請求等等,關注事件
6.while大循環中使用epoll_wait返回活躍的事件,每個事件開啟一個線程處理(線程中主要是解析頁面,保存頁面,url處理等),在線程結束的時候可能會開啟新的任務。(或者程序初始化時創建線程池,在沒任務時阻塞線程,通過pthread_cond_signal來喚醒睡眠隊列中的線程,但是一個頁面分析出的有效url會很多,這時候我們甚至需要在隊列滿時阻塞分析頁面獲得url的線程,使用線程池的優點就是減少了線程創建和銷毀的系統開銷)
在爬蟲系統中,待抓取URL隊列是很重要的一部分。待抓取URL隊列中的URL以什么樣的順序排列也是一個很重要的問題,因為這涉及到先抓取那個頁面,后抓取哪個頁面。而決定這些URL排列順序的方法,叫做抓取策略。下面重點介紹幾種常見的抓取策略(關於爬取策略優劣分析的建議大家讀一下吳軍先生的《數學之美》的第九章和第十章):
1.深度優先遍歷策略
深度優先遍歷策略是指網絡爬蟲會從起始頁開始,一個鏈接一個鏈接跟蹤下去,處理完這條線路之后再轉入下一個起始頁,繼續跟蹤鏈接。
2.廣度優先遍歷策略
廣度優先遍歷策略的基本思路是,將新下載網頁中發現的鏈接直接插入待抓取URL隊列的末尾。也就是指網絡爬蟲會先抓取起始網頁中鏈接的所有網頁,然后再選擇其中的一個鏈接網頁,繼續抓取在此網頁中鏈接的所有網頁。還是以上面的圖為例:
3.反向鏈接數策略
反向鏈接數是指一個網頁被其他網頁鏈接指向的數量。反向鏈接數表示的是一個網頁的內容受到其他人的推薦的程度。因此,很多時候搜索引擎的抓取系統會使用這個指標來評價網頁的重要程度,從而決定不同網頁的抓取先后順序。
在真實的網絡環境中,由於廣告鏈接、作弊鏈接的存在,反向鏈接數不能完全等他我那個也的重要程度。因此,搜索引擎往往考慮一些可靠的反向鏈接數。
4.Partial PageRank策略
Partial PageRank算法借鑒了PageRank算法的思想:對於已經下載的網頁,連同待抓取URL隊列中的URL,形成網頁集合,計算每個頁面的PageRank值,計算完之后,將待抓取URL隊列中的URL按照PageRank值的大小排列,並按照該順序抓取頁面。
如果每次抓取一個頁面,就重新計算PageRank值,一種折中方案是:每抓取K個頁面后,重新計算一次PageRank值。但是這種情況還會有一個問題:對於已經下載下來的頁面中分析出的鏈接,也就是我們之前提到的未知網頁那一部分,暫時是沒有PageRank值的。為了解決這個問題,會給這些頁面一個臨時的PageRank值(比如1):將這個網頁所有入鏈傳遞進來的PageRank值進行匯總(其實就是反復進行大矩陣運算,未知站的PageRank值會逐漸收斂到他應有的值,說實話收斂是我個人認為這個算法最美的地方),這樣就形成了該未知頁面的PageRank值,從而參與排序。
5.OPIC策略策略
該算法實際上也是對頁面進行一個重要性打分。在算法開始前,給所有頁面一個相同的初始現金(cash)。當下載了某個頁面P之后,將P的現金分攤給所有從P中分析出的鏈接,並且將P的現金清空。對於待抓取URL隊列中的所有頁面按照現金數進行排序。
6.大站優先策略
對於待抓取URL隊列中的所有網頁,根據所屬的網站進行分類。對於待下載頁面數多的網站,優先下載。這個策略也因此叫做大站優先策略。
下面
二、通過一個http請求抓取網頁的html存儲到文件的簡單實現
#include<stdio.h> #include<stdlib.h> #include<sys/types.h> #include<string.h> #include<sys/socket.h> #include<netdb.h> #include<unistd.h> #include<fcntl.h> #define MAX_URL_LEN 1024 #define MAX_FILE_NAME_LEN 64 #define MAX_REUQEST_LEN 1024 #define BUFF_MAX_SIZE 512 #define PAGE_MAX_SIZE 4096*128 void parseURL(char * url,char* host,char* path) { //char tmp[MAX_URL_LEN] = {0}; char*ptmp = NULL; strcpy(host,url); if((ptmp = strstr(url,"http://")) != NULL)//https format { ptmp = ptmp + 7; strcpy(host,ptmp); }else if(ptmp = NULL,(ptmp = strstr(url,"https://")) != NULL)//http format { ptmp = ptmp + 8; strcpy(host,ptmp); } ptmp = NULL; if((ptmp = strpbrk(host,"/")) != NULL) { strcpy(path,ptmp); ptmp[0] = '\0'; } } void getPage(char* host,char* path,char* file) { struct hostent *phost; if(0 == (phost = gethostbyname(host))) { printf("host err\n"); exit(1); } struct sockaddr_in pin; int port = 80; bzero(&pin,sizeof(pin)); pin.sin_family=AF_INET; pin.sin_port=htons(port); pin.sin_addr.s_addr=((struct in_addr*)(phost->h_addr))->s_addr; int isock; if((isock = socket(AF_INET,SOCK_STREAM,0)) == -1) { printf("socket err\n"); exit(1); } char requestHeader[MAX_REUQEST_LEN] = "GET "; strcat(requestHeader,path); strcat(requestHeader," HTTP/1.0\r\nHost: "); strcat(requestHeader,host); strcat(requestHeader,"\r\nAccept: */*\r\n"); strcat(requestHeader,"User-Agent: Mozilla/4.0(compatible)\r\n"); strcat(requestHeader,"Connection: Keep-Alive\r\n"); strcat(requestHeader,"\r\n"); if(connect(isock,(struct sockaddr*)&pin,sizeof(pin)) == -1) { printf("connect err\n"); exit(1); } if(send(isock,requestHeader,strlen(requestHeader),0) == -1) { printf("send err\n"); exit(1); } //struct timeval timeout={1,0}; //setsockopt(isock,SOL_SOCKET,SO_RCVTIMEO,(char *)&timeout,sizeof(struct timeval)); char buffer[BUFF_MAX_SIZE]; char page[PAGE_MAX_SIZE]; int len; printf("Start fetch\n"); int fd = open("file",O_RDWR|O_CREAT,0666); int flag = 0; char tmpch; //while(recv(isock,&tmpch,sizeof(char))>0) //{ // if(tmpch == '\r') // { // 如何讀到一個http請求頭的末尾?
// http://www.runoob.com/http/http-messages.html // } //} while((len = recv(isock,buffer,BUFF_MAX_SIZE-1,0))>0) { buffer[len]='\0'; write(fd,buffer,strlen(buffer)+1); } close(isock); close(fd); } int main() { char url[MAX_URL_LEN] = "http://www.runoob.com/http/http-intro.html"; //char url[MAX_URL_LEN] = "https://www.runoob.com/http/http-intro.html"; char host[MAX_URL_LEN] = {0}; char path[MAX_URL_LEN] = {0}; char file[MAX_FILE_NAME_LEN] = "file"; //parse url to get host and page path parseURL(url,host,path); //puts(host); //puts(path); //connect and sv the page into a file getPage(host,path,file); }
下一篇我們將分析這個小程序所做的事情,雖然他很小,卻把包含了一個爬蟲所要做的大部分內容。