Socket网络编程--网络爬虫(3)
上一小节我们实现了从博客园的首页获取一些用户的用户名,并保存起来。接下来的这一小节我将对每个用户名构建一个用户的博客主页,然后从这个主页获取所有能获取到的网页,网页的格式现在是http://www.cnblogs.com/yourname/p/xxxxxxxx.html以前是http://www.cnblogs.com/youurname/archive/xxxxxxx.html
我的做法是把所有用户名处理后得到的一个个url放到一个队列里去,然后每次在这个队列中拿一个url进行解析查找看有没有新的用户。如果有那么把新的用户加入到map中,结束后就从队列中再拿一个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>//正则表达式
#include <map>
#include <string>
#include <iostream> using namespace std;
#define BUF_SIZE 512 struct URL
{
char host[];
char url[];//除去域名后的url
};
int reptile_regex(char * buf,char *pattern,map<string,int> & user);
int createSocket(char *hostname,int port);
int closeSocket(int sockfd);
int sendHttpRequest(int sockfd,struct URL url);
int recvHttpRespond(int sockfd,char *ch); int main(int argc,char *argv[])
{
int sockfd;
char ch[];//100k
char pattern[]={};
struct URL url;
string str;
map<string,int> user;//第一个是用户名,第二个保存被加入的次数 strcpy(url.host,"www.cnblogs.com");
strcpy(url.url,"/");
sockfd=createSocket(url.host,);
sendHttpRequest(sockfd,url);
recvHttpRespond(sockfd,ch);
strcpy(pattern,"http://www.cnblogs.com/[[:alnum:]\\-\\_]*");
reptile_regex(ch,pattern,user);
map<string,int>::iterator it;
for(it=user.begin();it!=user.end();++it)
{
cout<<it->first<<endl;
} closeSocket(sockfd);
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)
{
i=pm[].rm_so+;
for(j=i;j<pm[].rm_eo;++j)
{
ch[j-i]=str[j];
}
ch[j-i]=;
string st(ch);
user[st]++;
//printf("%d=%s\n",i,substr(buf,pm[i].rm_so,pm[i].rm_eo));
str=str+pm[].rm_eo;
}
regfree(®);
return ;
} int closeSocket(int sockfd)
{
close(sockfd);
return ;
} int createSocket(char *hostname,int port)
{
struct sockaddr_in servAddr;
struct hostent * host;
int sockfd;
host=gethostbyname(hostname);
if(host==NULL)
{
perror("dns 解析失败");
}
servAddr.sin_family=AF_INET;
servAddr.sin_addr=*((struct in_addr *)host->h_addr);
servAddr.sin_port=htons(port);
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 失败");
}
return sockfd;
} int sendHttpRequest(int sockfd,struct URL url)
{
char sendBuf[BUF_SIZE];
int sendSize;
//构建一个http请求
sprintf(sendBuf,"GET %s HTTP/1.1 \r\nHost: %s \r\nConnection: Close \r\n\r\n",url.url,url.host);
if((sendSize=send(sockfd,sendBuf,BUF_SIZE,))==-)
{
perror("send 失败");
}
return ;
} int recvHttpRespond(int sockfd,char *ch)
{
char recvBuf[BUF_SIZE];
int recvSize;
//获取http应答信息
memset(recvBuf,,sizeof(recvBuf));
memset(ch,,sizeof(ch));
while(recvSize=recv(sockfd,recvBuf,BUF_SIZE,)>)
{
strcat(ch,recvBuf);
memset(recvBuf,,sizeof(recvBuf));
}
return ;
}
接下来要做的是创建两个队列,一个保存新进来的用户,一个保存新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); int main(int argc,char *argv[])
{
int sockfd;
char ch[];//100k
char pattern_user[]={};
char pattern_url[]={};
struct URL url;
string str;
map<string,int> user;//第一个是用户名,第二个保存被加入的次数
queue<struct URL> qurl;
queue<string> qstr; strcpy(url.host,"www.cnblogs.com");
strcpy(url.url,"/");
//
sockfd=createSocket(url.host,);
//初始化用户名
sendHttpRequest(sockfd,url);
recvHttpRespond(sockfd,ch);
//printf("%s\n",ch);
strcpy(pattern_user,"http://www.cnblogs.com/[[:alnum:][:cntrl:]\\-\\_]*");
reptile_regex_url(ch,pattern_user,user,qstr);
map<string,int>::iterator it;
for(it=user.begin();it!=user.end();++it)
{
qstr.push(it->first);
strcpy(url.host,"www.cnblogs.com");
strcpy(url.url,"/");
strcat(url.url,it->first.c_str());
strcat(url.url,"/");
qurl.push(url);
}
//一开始以为是只要创建一次socket然后每次都可以进行send&recv的。但是后来测试好像不行,每次都要进行一次socket的创建
closeSocket(sockfd); while()
{
while(!qurl.empty())
{
url=qurl.front();
qurl.pop();
cout<<"现在正在判断:";
cout<<url.host<<url.url<<endl;
//将获取到的地址进行再次获取用户名
strcpy(url.host,"www.cnblogs.com");
strcpy(url.url,"/");
sockfd=createSocket(url.host,);
sendHttpRequest(sockfd,url);
recvHttpRespond(sockfd,ch);
//printf("\n\n\n%s\n",ch);
strcpy(pattern_user,"http://www.cnblogs.com/[[:alnum:][:cntrl:]\\-\\_]*");
reptile_regex_url(ch,pattern_user,user,qstr);
closeSocket(sockfd);
}
while(!qstr.empty())
{
qstr.pop();
}
}
sendHttpRequest(sockfd,url);
recvHttpRespond(sockfd,ch);
strcpy(pattern_url,"http://www.cnblogs.com/[[:alnum:]\\-\\_]*/[[:alnum:]\\-\\_/]*\\.html");
//reptile_regex(ch,pattern_url,qurl); return ;
} //第一个参数是要匹配的字符串,第二个参数是匹配的规则
int reptile_regex_url(char * buf,char *pattern,map<string,int> & user,queue<string> & qstr)
{
size_t nmatch=;
regmatch_t pm[];
regex_t reg;//正则表达式指针
char * str;
char ch[];
int i,j,k;
str=buf;
regcomp(®,pattern,REG_EXTENDED);//编译匹配模式
while(regexec(®,str,nmatch,pm,)!=REG_NOMATCH)
{
i=pm[].rm_so+;
k=;
memset(ch,,sizeof(ch));
for(j=i;j<pm[].rm_eo;++j)//这里修改****
{
if(str[j]!=0x01) //ctrl-v ctrl-a
{
ch[k++]=str[j];
}
}
string st(ch);
if(user[st]==)
{
cout<<"新加入的用户名:"<<st<<endl;
qstr.push(st);
}
user[st]++;
str=str+pm[].rm_eo;
}
regfree(®);
return ;
} int closeSocket(int sockfd)
{
close(sockfd);
return ;
} int createSocket(char *hostname,int port)
{
...
return sockfd;
} int sendHttpRequest(int sockfd,struct URL url)
{
...
return ;
} int recvHttpRespond(int sockfd,char *ch)
{
...
return ;
}
我们从博客园的首页中可以看到最新博客有200页之多。每一页的格式为http://www.cnblogs.com/sitehome/p/1 到 http://www.cnblogs.com/sitehome/p/200 所以我们可以根据这个格式进行获取用户名,一般也是这种方式获取的比较多。
int main(int argc,char *argv[])
{
int sockfd;
char ch[];//100k
char pattern_user[]={};
char pattern_url[]={};
struct URL url;
string str;
map<string,int> user;//第一个是用户名,第二个保存被加入的次数
queue<struct URL> qurl;
queue<string> qstr; //http://www.cnblogs.com/sitehome/p/1 - 200 //最新博客的200篇
//初始化用户名
for(int i=;i<=;++i)
{
strcpy(url.host,"www.cnblogs.com");
strcpy(url.url,"/sitehome/p/");
char pch[];
sprintf(pch,"%d",i);
strcat(url.url,pch);
strcat(url.url,"/");
cout<<"当前正在判断:"<<url.host<<url.url<<endl;
sockfd=createSocket(url.host,);
sendHttpRequest(sockfd,url);
recvHttpRespond(sockfd,ch);
strcpy(pattern_user,"http://www.cnblogs.com/[[:alnum:][:cntrl:]\\-\\_]*");
reptile_regex_url(ch,pattern_user,user,qstr); closeSocket(sockfd);
}
map<string,int>::iterator it;
for(it=user.begin();it!=user.end();++it)
{
qstr.push(it->first);
strcpy(url.host,"www.cnblogs.com");
strcpy(url.url,"/");
strcat(url.url,it->first.c_str());
strcat(url.url,"/");
qurl.push(url);
}
//一开始以为是只要创建一次socket然后每次都可以进行send&recv的。但是后来测试好像不行,每次都要进行一次socket的创建 ... ...
return ;
}
获取到的用户名如下
这一小节到这里就结束了,可以获取用户名了,不过虽然有200页,不过获取来还是很快的。下一节我将对这些用户的关注和粉丝进行用户名的再次提取。然后得到新的用户名,然后再次提取,就这样一直下去。理论上有在博客园活跃过的人都可以爬取的到,想想都激动。(这个是理论,我没敢试,怕管理员找我谈人生和理想。)
本文地址: http://www.cnblogs.com/wunaozai/p/3900454.html
Socket网络编程--网络爬虫(3)的更多相关文章
- [转] - Linux网络编程 -- 网络知识介绍
(一)Linux网络编程--网络知识介绍 Linux网络编程--网络知识介绍客户端和服务端 网络程序和普通的程序有一个最大的区别是网络程序是由两个部分组成的--客户端和服务器端. 客户 ...
- JAVA基础知识之网络编程——-网络基础(Java的http get和post请求,多线程下载)
本文主要介绍java.net下为网络编程提供的一些基础包,InetAddress代表一个IP协议对象,可以用来获取IP地址,Host name之类的信息.URL和URLConnect可以用来访问web ...
- UNIX网络编程——网络IPC:套接字
UNIX网络编程——网络IPC:套接字 Contents 套接字接口 套接字描述符 寻址 字节序 地址格式 地址查询 绑定地址 建立连接 数据传输 套接字选项 带外数据 UNIX域套接字 使用套接字的 ...
- Socket网络编程--网络爬虫(1)
我们这个系列准备讲一下--网络爬虫.网络爬虫是搜索引擎系统中十分重要的组成部分,它负责从互联网中搜集网页,采集信息,这些网页信息用于建立索引从而为搜索引擎提供支持,它决定着整个引擎系统的内容是否丰富, ...
- 网络编程—网络基础概览、socket,TCP/UDP协议
网络基础概览 socket概览 socket模块—TCP/UDP的实现 TCP/UDP总结 网络基础概览 osi七层协议各层主要的协议 # 物理层传输电信号1010101010 # 数据链路层,以太网 ...
- linux网络编程--网络编程的基本函数介绍与使用【转】
本文转载自:http://blog.csdn.net/yusiguyuan/article/details/17538499 我们深谙信息交流的价值,那网络中进程之间如何通信,如我们每天打开浏览器浏览 ...
- python网络编程——网络IO模型
1 网络IO模型介绍 服务器端编程经常需要构造高性能的IO模型,常见的IO模型有四种: (1)同步阻塞IO(Blocking IO):即传统的IO模型. (2)同步非阻塞IO(Non-bl ...
- JAVA基础知识之网络编程——-网络通信模型(IO模型)
<Unix网络编程:卷1>中介绍了5中I/O模型,JAVA作为运行在宿主机上的程序,底层也遵循这5中I/O模型规则.这5中I/O模型分别是: 阻塞式IO 非阻塞式IO I/O复用 信号驱动 ...
- UNIX网络编程——网络I/O模型
在学习UNIX网络编程的时候.一開始分不清 同步 和 异步,所以还是总结一下,理清下他们的差别比較好. IO分类 IO依据对IO的调度方式可分为堵塞IO.非堵塞IO.IO复用.信号驱动IO.异步IO. ...
- Socket网络编程--网络爬虫(2)
上一小节,我们实现了下载一个网页.接下来的一步就是使用提取有用的信息.如何提取呢?一个比较好用和常见的方法就是使用正则表达式来提取的.想一想我们要做个什么样的网络爬虫好呢?我记得以前好像博客园里面有人 ...
随机推荐
- Pytorch 基础
Pytorch 1.0.0 学习笔记: Pytorch 的学习可以参考:Welcome to PyTorch Tutorials Pytorch 是什么? 快速上手 Pytorch! Tensors( ...
- Linux上的压缩与归档
非常非常非常简要的描述而已. 压缩 压缩的简要原理是通过一些算法,拿CPU的计算时间去换磁盘上存储的空间.同时还可节省网络传输中的带宽. 对于文本文件的压缩效果比较好,对二进制程序.图片等文件的压缩效 ...
- R2 day2
简单写一下吧 emmmm,来晚了1h,没赶上,所以没交.......(捂脸 T1 开始读错题了诶 开烤1.2h后 发现是个傻逼题.... 排序一下,维护前缀最左,右端点 随机数据我跑的比他们都慢... ...
- CentOS 7 NAT软路由
☼ NAT 转发软路由 开启 NAT 转发之后,只要本机可以上网,不论是单网卡还是多网卡,局域网内的其他机器可以将默认网关设置为已开启 NAT 转发的服务器 IP ,即可实现上网. 信任所有连接,并且 ...
- CSS3 Flex布局整理(三)-项目属性
一.Flex布局中 Flex Item属性控制,可以指定显示顺序.剩余空间的放大,缩小.交叉轴的排列 1.order:定义项目的排列顺序,数值越小,排列越靠前,默认为0.类似z-index 2.fle ...
- opencv error: undefined reference to `png_set_expand_gray_1_2_4_to_8@PNG16_0'
问题1:/usr/bin/ld: warning: libpng16.so.16, needed by /home/andrei/anaconda/lib/libopencv_highgui.so, ...
- C#中Split用法【转】
https://www.cnblogs.com/webenh/p/6570801.html 1.用字符串分隔: using System.Text.RegularExpressions;string ...
- android: 在android studio中使用retrolambda的步骤
找了各种说明,包括retrolambda官方文档都没有试成功 最后在这个链接中找到答案:http://blog.csdn.net/qq_26819733/article/details/5222565 ...
- android ndk-build 编译静态库libxx.a 以及Android studio openssl 静态库配置(cmake)
android ndk-build 编译静态库libxx.a 需求场景: 目前有安卓编码好的现在的openssl的两个.a,我们需要调用openssl的函数,并把功能再封装成.a; 这样使用时,在an ...
- MDX Cookbook 06 - GENERATE 循环遍历
有时候需要从集合中取出特定的成员但是又不能执行遍历操作,这个时候就可以使用 GENERATE 函数来解决这个问题. 根据地区查询每年的销售额 - SELECT NON EMPTY { , NON EM ...