zoukankan      html  css  js  c++  java
  • Socket网络编程--网络爬虫(3)

      上一小节我们实现了从博客园的首页获取一些用户的用户名,并保存起来。接下来的这一小节我将对每个用户名构建一个用户的博客主页,然后从这个主页获取所有能获取到的网页,网页的格式现在是http://www.cnblogs.com/yourname/p/xxxxxxxx.html以前是http://www.cnblogs.com/youurname/archive/xxxxxxx.html

      我的做法是把所有用户名处理后得到的一个个url放到一个队列里去,然后每次在这个队列中拿一个url进行解析查找看有没有新的用户。如果有那么把新的用户加入到map中,结束后就从队列中再拿一个url进行判断,查找心得用户。

      下面这个程序是对前两节进行整理

      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(&reg,pattern,0);//编译匹配模式
     67     while(regexec(&reg,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
    ",i,substr(buf,pm[i].rm_so,pm[i].rm_eo));
     78         str=str+pm[0].rm_eo;
     79     }
     80     regfree(&reg);
     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 
    Host: %s 
    Connection: Close 
    
    ",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:]进行匹配。这一部分的代码修改比较简单。

      还有一个问题就是没进行一次连接,都要创建一次socket连接。因为我的HTTP请求中的Connection是Close而不是keep-alive。

      修改BUG后的网络爬虫程序

       ... 
    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 ",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(" %s ",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(&reg,pattern,REG_EXTENDED);//编译匹配模式 113 while(regexec(&reg,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(&reg); 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 }

      获取到的用户名如下

      这一小节到这里就结束了,可以获取用户名了,不过虽然有200页,不过获取来还是很快的。下一节我将对这些用户的关注和粉丝进行用户名的再次提取。然后得到新的用户名,然后再次提取,就这样一直下去。理论上有在博客园活跃过的人都可以爬取的到,想想都激动。(这个是理论,我没敢试,怕管理员找我谈人生和理想。) 

      本文地址: http://www.cnblogs.com/wunaozai/p/3900454.html

  • 相关阅读:
    python面向对象(进阶篇)
    python面向对象三大特性
    点击头像单独把图片拉取出来.然后再次点击回到初始效果
    关于iOS的自动弹出键盘问题
    为UITextView添加通知..来检测UITextView内容的改变
    null 解决方法
    获取手机的唯一标示uuid
    SDK | 声波传输
    Xcode svn import项目 上传.a文件(静态库)
    点击状态栏回到顶部
  • 原文地址:https://www.cnblogs.com/wunaozai/p/3900454.html
Copyright © 2011-2022 走看看