正文
Socket网络编程--网络爬虫(2)
小程序:扫一扫查出行
【扫一扫了解最新限行尾号】
复制小程序
【扫一扫了解最新限行尾号】
复制小程序
上一小节,我们实现了下载一个网页。接下来的一步就是使用提取有用的信息。如何提取呢?一个比较好用和常见的方法就是使用正则表达式来提取的。想一想我们要做个什么样的网络爬虫好呢?我记得以前好像博客园里面有人写过一个提取博客园用户名的博客。我这次就实现这个好了。
第一步我们要分析博客园一个URL的组成,我们每一个用户对应都有这样的一个主目录http://www.cnblogs.com/XXXXXXX 这样的一个主页(现在有了http://XXXXXXX.cnblogs.com这样的主页了,但是不常用)。所以我们判断一个字符串是不是博客园的有效用户,我们的做法就是提取一个像上面一样的URL,然后截取后面的用户名即可。
带正则表达式的网页下载程序
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <regex.h>//正则表达式 #define BUF_SIZE 512 int reptile_regex(char * buf,char *pattern); char ch[];//100k int main(int argc,char *argv[])
{
struct sockaddr_in servAddr;
struct hostent * host;
int sockfd;
char sendBuf[BUF_SIZE],recvBuf[BUF_SIZE];
int sendSize,recvSize; host=gethostbyname(argv[]);
if(host==NULL)
{
perror("dns 解析失败");
}
servAddr.sin_family=AF_INET;
servAddr.sin_addr=*((struct in_addr *)host->h_addr);
servAddr.sin_port=htons(atoi(argv[]));
bzero(&(servAddr.sin_zero),); sockfd=socket(AF_INET,SOCK_STREAM,);
if(sockfd==-)
{
perror("socket 创建失败");
} if(connect(sockfd,(struct sockaddr *)&servAddr,sizeof(struct sockaddr_in))==-)
{
perror("connect 失败");
} //构建一个http请求
sprintf(sendBuf,"GET / HTTP/1.1 \r\nHost: %s \r\nConnection: Close \r\n\r\n",argv[]);
if((sendSize=send(sockfd,sendBuf,BUF_SIZE,))==-)
{
perror("send 失败");
}
//获取http应答信息
memset(recvBuf,,sizeof(recvBuf));
memset(ch,,sizeof(ch));
char pattern[]={};
strcpy(pattern,"http://www.cnblogs.com/[[:alnum:]]*/");
while(recvSize=recv(sockfd,recvBuf,BUF_SIZE,)>)
{
//printf("%s",recvBuf);
strcat(ch,recvBuf);
memset(recvBuf,,sizeof(recvBuf));
}
reptile_regex(ch,pattern); return ;
} //第一个参数是要匹配的字符串,第二个参数是匹配的规则,返回匹配的个数
int reptile_regex(char * buf,char *pattern)
{
size_t nmatch=;//最多匹配100个一次
regmatch_t pm[];//与上面对应
regex_t reg;//正则表达式指针
regcomp(®,pattern,);//编译匹配模式
int z=regexec(®,buf,nmatch,pm,);
if(z==REG_NOMATCH)
{
;//本次没有匹配到
}
else
{
for(int i=;i<&&pm[i].rm_so!=-;++i)
{
for(int j=pm[i].rm_so;j<pm[i].rm_eo;++j)
{
printf("%c",buf[j]);
}
//上面的遍历可以用下面函数代替
//printf("%d=%s\n",i,substr(buf,pm[i].rm_so,pm[i].rm_eo));
printf("\n");
}
}
regfree(®);
return ;
}
本来一开始以为这样就可以了,可是没想到每次都是匹配到第一个而已,后面怎么都匹配不到,还以为是正则写错了,但是就那么几个怎么可能错了。最后找到一篇博客,才知道,一次调用regexec是没有办法全部匹配出来的。要进行多次。哎,怎么这么麻烦呀。
带正则表达式的网页下载程序修改版
将reptile_regex函数修改如下即可实现多次匹配
int reptile_regex(char * buf,char *pattern)
{
size_t nmatch=;//最多匹配100个一次
regmatch_t pm[];//与上面对应
regex_t reg;//正则表达式指针
char * str;
str=buf;
regcomp(®,pattern,);//编译匹配模式
while(regexec(®,str,nmatch,pm,)!=REG_NOMATCH)
{
for(int j=pm[].rm_so;j<pm[].rm_eo;++j)
{
printf("%c",str[j]);
}
//printf("%d=%s\n",i,substr(buf,pm[i].rm_so,pm[i].rm_eo));
printf("\n");
str=str+pm[].rm_eo;
}
regfree(®);
return ;
}
好了,现在可以多次匹配了,但是又出现一个问题了,问题就是会有重复的用户名出现。如何避免呢?一个办法是把用户名保存起来,然后来一个用户名就一个一个进行比较,看是否有相同,如果都没有就加入到用户名组里面去。依次类推。不过一般爬虫爬到的用户名都会比较多,如果这样O(N)的比较效率不是很高,可以通过HASH降低为O(1)。但是设计一个hash函数比较麻烦,为了方便,我就使用一个map来处理,效率还好有O(logN)。
防止重复后的网页下载程序
...
int main(int argc,char *argv[])
{
...
map<string,int> user;//第一个是用户名,第二个保存被加入的次数 ...
reptile_regex(ch,pattern,user);
map<string,int>::iterator it;
for(it=user.begin();it!=user.end();++it)
{
cout<<it->first<<endl;
} return ;
} //第一个参数是要匹配的字符串,第二个参数是匹配的规则,返回匹配的个数
int reptile_regex(char * buf,char *pattern,map<string,int> & user)
{
size_t nmatch=;
regmatch_t pm[];
regex_t reg;//正则表达式指针
char * str;
char ch[];
int i,j;
str=buf;
regcomp(®,pattern,);//编译匹配模式
while(regexec(®,str,nmatch,pm,)!=REG_NOMATCH)
{
//http://www.cnblogs.com/
i=pm[].rm_so+;
for(j=i;j<pm[].rm_eo;++j)
{
//printf("%c",str[j]);
ch[j-i]=str[j];
}
ch[j-i]=;
string st(ch);
user[st]++;
//printf("%s",ch);
//printf("%d=%s\n",i,substr(buf,pm[i].rm_so,pm[i].rm_eo));
//printf("\n");
str=str+pm[].rm_eo;
}
regfree(®);
return ;
}
这样就把所有查询到的用户名都保存在users中了。而且对应的int还保存了查询到的次数。这个还可以在以后用来判断该用户的博客是否经常被人提及到的一个参考值。
参考资料:
正则表达式匹配多个问题: http://blog.163.com/lixiangqiu_9202/blog/static/53575037201412311211291/
本文地址: http://www.cnblogs.com/wunaozai/p/3900169.html