上一小節咱們實現了從博客園的首頁獲取一些用戶的用戶名,並保存起來。接下來的這一小節我將對每一個用戶名構建一個用戶的博客主頁,而後從這個主頁獲取全部能獲取到的網頁,網頁的格式如今是http://www.cnblogs.com/yourname/p/xxxxxxxx.html之前是http://www.cnblogs.com/youurname/archive/xxxxxxx.htmlhtml
個人作法是把全部用戶名處理後獲得的一個個url放到一個隊列裏去,而後每次在這個隊列中拿一個url進行解析查找看有沒有新的用戶。若是有那麼把新的用戶加入到map中,結束後就從隊列中再拿一個url進行判斷,查找心得用戶。ios
下面這個程序是對前兩節進行整理正則表達式
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <sys/types.h> 5 #include <sys/socket.h> 6 #include <unistd.h> 7 #include <netdb.h> 8 #include <netinet/in.h> 9 #include <arpa/inet.h> 10 #include <regex.h>//正則表達式 11 #include <map> 12 #include <string> 13 #include <iostream> 14 15 using namespace std; 16 #define BUF_SIZE 512 17 18 struct URL 19 { 20 char host[64]; 21 char url[128];//除去域名後的url 22 }; 23 int reptile_regex(char * buf,char *pattern,map<string,int> & user); 24 int createSocket(char *hostname,int port); 25 int closeSocket(int sockfd); 26 int sendHttpRequest(int sockfd,struct URL url); 27 int recvHttpRespond(int sockfd,char *ch); 28 29 int main(int argc,char *argv[]) 30 { 31 int sockfd; 32 char ch[100000];//100k 33 char pattern[128]={0}; 34 struct URL url; 35 string str; 36 map<string,int> user;//第一個是用戶名,第二個保存被加入的次數 37 38 strcpy(url.host,"www.cnblogs.com"); 39 strcpy(url.url,"/"); 40 sockfd=createSocket(url.host,80); 41 sendHttpRequest(sockfd,url); 42 recvHttpRespond(sockfd,ch); 43 strcpy(pattern,"http://www.cnblogs.com/[[:alnum:]\\-\\_]*"); 44 reptile_regex(ch,pattern,user); 45 map<string,int>::iterator it; 46 for(it=user.begin();it!=user.end();++it) 47 { 48 cout<<it->first<<endl; 49 } 50 51 closeSocket(sockfd); 52 return 0; 53 } 54 55 56 //第一個參數是要匹配的字符串,第二個參數是匹配的規則 57 int reptile_regex(char * buf,char *pattern,map<string,int> & user) 58 { 59 size_t nmatch=10; 60 regmatch_t pm[10]; 61 regex_t reg;//正則表達式指針 62 char * str; 63 char ch[32]; 64 int i,j; 65 str=buf; 66 regcomp(®,pattern,0);//編譯匹配模式 67 while(regexec(®,str,nmatch,pm,0)!=REG_NOMATCH) 68 { 69 i=pm[0].rm_so+23; 70 for(j=i;j<pm[0].rm_eo;++j) 71 { 72 ch[j-i]=str[j]; 73 } 74 ch[j-i]=0; 75 string st(ch); 76 user[st]++; 77 //printf("%d=%s\n",i,substr(buf,pm[i].rm_so,pm[i].rm_eo)); 78 str=str+pm[0].rm_eo; 79 } 80 regfree(®); 81 return 0; 82 } 83 84 int closeSocket(int sockfd) 85 { 86 close(sockfd); 87 return 0; 88 } 89 90 int createSocket(char *hostname,int port) 91 { 92 struct sockaddr_in servAddr; 93 struct hostent * host; 94 int sockfd; 95 host=gethostbyname(hostname); 96 if(host==NULL) 97 { 98 perror("dns 解析失敗"); 99 } 100 servAddr.sin_family=AF_INET; 101 servAddr.sin_addr=*((struct in_addr *)host->h_addr); 102 servAddr.sin_port=htons(port); 103 bzero(&(servAddr.sin_zero),8); 104 105 sockfd=socket(AF_INET,SOCK_STREAM,0); 106 if(sockfd==-1) 107 { 108 perror("socket 建立失敗"); 109 } 110 111 if(connect(sockfd,(struct sockaddr *)&servAddr,sizeof(struct sockaddr_in))==-1) 112 { 113 perror("connect 失敗"); 114 } 115 return sockfd; 116 } 117 118 int sendHttpRequest(int sockfd,struct URL url) 119 { 120 char sendBuf[BUF_SIZE]; 121 int sendSize; 122 //構建一個http請求 123 sprintf(sendBuf,"GET %s HTTP/1.1 \r\nHost: %s \r\nConnection: Close \r\n\r\n",url.url,url.host); 124 if((sendSize=send(sockfd,sendBuf,BUF_SIZE,0))==-1) 125 { 126 perror("send 失敗"); 127 } 128 return 0; 129 } 130 131 int recvHttpRespond(int sockfd,char *ch) 132 { 133 char recvBuf[BUF_SIZE]; 134 int recvSize; 135 //獲取http應答信息 136 memset(recvBuf,0,sizeof(recvBuf)); 137 memset(ch,0,sizeof(ch)); 138 while(recvSize=recv(sockfd,recvBuf,BUF_SIZE,0)>0) 139 { 140 strcat(ch,recvBuf); 141 memset(recvBuf,0,sizeof(recvBuf)); 142 } 143 return 0; 144 }
接下來要作的是建立兩個隊列,一個保存新進來的用戶,一個保存新url用來處理的。而後讓兩個隊列一直循環下去。理論上就能夠爬到大多數的用戶名。若是用上面的程序進行匹配的話,有時候會出現匹配錯誤的時候,例若有一個用戶名是wunaozai,可是在匹配的過程當中有時候會有wunao這樣的用戶名出現,一開始覺得是重名,可是後來看了源碼發現沒有這個用戶了,而後屢次獲取,每次都或多或少會有錯誤的用戶名出現。究竟是爲何呢?我把用recv獲取到的網頁ch這個都打印出來,而後用grep過濾一下,會發現根本沒有錯,可是就是會匹配錯誤的用戶名。我就接着把網頁重定向到一個文件中,而後用vim打開,而後查找一下,而後真相大白了,原來這個文本中有時候會在用戶名處有這個符號(^A),UNIX中ctrl-v ctrl-a能夠打印出來,ascii碼的值是0x01.哎弄了那麼久,致使這篇博客那麼晚才發佈。在正則中能夠用[:cntrl:]進行匹配。這一部分的代碼修改比較簡單。vim
還有一個問題就是沒進行一次鏈接,都要建立一次socket鏈接。由於個人HTTP請求中的Connection是Close而不是keep-alive。網絡
修改BUG後的網絡爬蟲程序socket
...
29 int reptile_regex_url(char * buf,char *pattern,map<string,int> & user,queue<string> & qstr); 30 31 32 int main(int argc,char *argv[]) 33 { 34 int sockfd; 35 char ch[100000];//100k 36 char pattern_user[128]={0}; 37 char pattern_url[128]={0}; 38 struct URL url; 39 string str; 40 map<string,int> user;//第一個是用戶名,第二個保存被加入的次數 41 queue<struct URL> qurl; 42 queue<string> qstr; 43 44 strcpy(url.host,"www.cnblogs.com"); 45 strcpy(url.url,"/"); 46 // 47 sockfd=createSocket(url.host,80); 48 //初始化用戶名 49 sendHttpRequest(sockfd,url); 50 recvHttpRespond(sockfd,ch); 51 //printf("%s\n",ch); 52 strcpy(pattern_user,"http://www.cnblogs.com/[[:alnum:][:cntrl:]\\-\\_]*"); 53 reptile_regex_url(ch,pattern_user,user,qstr); 54 map<string,int>::iterator it; 55 for(it=user.begin();it!=user.end();++it) 56 { 57 qstr.push(it->first); 58 strcpy(url.host,"www.cnblogs.com"); 59 strcpy(url.url,"/"); 60 strcat(url.url,it->first.c_str()); 61 strcat(url.url,"/"); 62 qurl.push(url); 63 } 64 //一開始覺得是隻要建立一次socket而後每次均可以進行send&recv的。可是後來測試好像不行,每次都要進行一次socket的建立 65 closeSocket(sockfd); 66 67 while(1) 68 { 69 while(!qurl.empty()) 70 { 71 url=qurl.front(); 72 qurl.pop(); 73 cout<<"如今正在判斷:"; 74 cout<<url.host<<url.url<<endl; 75 //將獲取到的地址進行再次獲取用戶名 76 strcpy(url.host,"www.cnblogs.com"); 77 strcpy(url.url,"/"); 78 sockfd=createSocket(url.host,80); 79 sendHttpRequest(sockfd,url); 80 recvHttpRespond(sockfd,ch); 81 //printf("\n\n\n%s\n",ch); 82 strcpy(pattern_user,"http://www.cnblogs.com/[[:alnum:][:cntrl:]\\-\\_]*"); 83 reptile_regex_url(ch,pattern_user,user,qstr); 84 closeSocket(sockfd); 85 } 86 while(!qstr.empty()) 87 { 88 qstr.pop(); 89 } 90 } 91 sendHttpRequest(sockfd,url); 92 recvHttpRespond(sockfd,ch); 93 strcpy(pattern_url,"http://www.cnblogs.com/[[:alnum:]\\-\\_]*/[[:alnum:]\\-\\_/]*\\.html"); 94 //reptile_regex(ch,pattern_url,qurl); 95 96 97 98 return 0; 99 } 100 101 102 //第一個參數是要匹配的字符串,第二個參數是匹配的規則 103 int reptile_regex_url(char * buf,char *pattern,map<string,int> & user,queue<string> & qstr) 104 { 105 size_t nmatch=10; 106 regmatch_t pm[10]; 107 regex_t reg;//正則表達式指針 108 char * str; 109 char ch[32]; 110 int i,j,k; 111 str=buf; 112 regcomp(®,pattern,REG_EXTENDED);//編譯匹配模式 113 while(regexec(®,str,nmatch,pm,0)!=REG_NOMATCH) 114 { 115 i=pm[0].rm_so+23; 116 k=0; 117 memset(ch,0,sizeof(ch)); 118 for(j=i;j<pm[0].rm_eo;++j)//這裏修改**** 119 { 120 if(str[j]!=0x01) //ctrl-v ctrl-a 121 { 122 ch[k++]=str[j]; 123 } 124 } 125 string st(ch); 126 if(user[st]==0) 127 { 128 cout<<"新加入的用戶名:"<<st<<endl; 129 qstr.push(st); 130 } 131 user[st]++; 132 str=str+pm[0].rm_eo; 133 } 134 regfree(®); 135 return 0; 136 } 137 138 int closeSocket(int sockfd) 139 { 140 close(sockfd); 141 return 0; 142 } 143 144 int createSocket(char *hostname,int port) 145 { ...
169 return sockfd; 170 } 171 172 int sendHttpRequest(int sockfd,struct URL url) 173 { ...
183 return 0; 184 } 185 186 int recvHttpRespond(int sockfd,char *ch) 187 { ...
199 return 0; 200 }
咱們從博客園的首頁中能夠看到最新博客有200頁之多。每一頁的格式爲http://www.cnblogs.com/sitehome/p/1 到 http://www.cnblogs.com/sitehome/p/200 因此咱們能夠根據這個格式進行獲取用戶名,通常也是這種方式獲取的比較多。測試
1 int main(int argc,char *argv[]) 2 { 3 int sockfd; 4 char ch[100000];//100k 5 char pattern_user[128]={0}; 6 char pattern_url[128]={0}; 7 struct URL url; 8 string str; 9 map<string,int> user;//第一個是用戶名,第二個保存被加入的次數 10 queue<struct URL> qurl; 11 queue<string> qstr; 12 13 //http://www.cnblogs.com/sitehome/p/1 - 200 //最新博客的200篇 14 //初始化用戶名 15 for(int i=1;i<=200;++i) 16 { 17 strcpy(url.host,"www.cnblogs.com"); 18 strcpy(url.url,"/sitehome/p/"); 19 char pch[8]; 20 sprintf(pch,"%d",i); 21 strcat(url.url,pch); 22 strcat(url.url,"/"); 23 cout<<"當前正在判斷:"<<url.host<<url.url<<endl; 24 sockfd=createSocket(url.host,80); 25 sendHttpRequest(sockfd,url); 26 recvHttpRespond(sockfd,ch); 27 strcpy(pattern_user,"http://www.cnblogs.com/[[:alnum:][:cntrl:]\\-\\_]*"); 28 reptile_regex_url(ch,pattern_user,user,qstr); 29 30 closeSocket(sockfd); 31 } 32 map<string,int>::iterator it; 33 for(it=user.begin();it!=user.end();++it) 34 { 35 qstr.push(it->first); 36 strcpy(url.host,"www.cnblogs.com"); 37 strcpy(url.url,"/"); 38 strcat(url.url,it->first.c_str()); 39 strcat(url.url,"/"); 40 qurl.push(url); 41 } 42 //一開始覺得是隻要建立一次socket而後每次均可以進行send&recv的。可是後來測試好像不行,每次都要進行一次socket的建立 43 ... ... 73 return 0; 74 }
獲取到的用戶名以下url
這一小節到這裏就結束了,能夠獲取用戶名了,不過雖然有200頁,不過獲取來仍是很快的。下一節我將對這些用戶的關注和粉絲進行用戶名的再次提取。而後獲得新的用戶名,而後再次提取,就這樣一直下去。理論上有在博客園活躍過的人均可以爬取的到,想一想都激動。(這個是理論,我沒敢試,怕管理員找我談人生和理想。) spa