哈尔滨海月数据恢复,技术亚洲领先 设为首页加入收藏RSS订阅
中国区:哈尔滨(总部)郑州福建深圳沈阳合肥大连包头淄博上海广州
    南京杭州嘉兴武汉济南青岛新疆太原
亚太区:印度韩国泰国新加坡马来西亚澳大利亚
Linux
Linux下网页抓取

  来源:【51cto.com】

  ***近一直在学习stevens的unix网络编程,对于网络通信有了一定的认识,所以也想练练手。聊天程序之前用winsock做过,这次不想做重复的。之前看到一哥们写过windows下抓取猫扑的帖子,我觉得抓页面也是一个不错想法。我也喜欢逛猫扑,有时候也去追追里面写的文章,猫扑帖子少了一个很重要的功能,就是只看楼主的帖子。猫扑水人很多,容易把楼主的帖子淹没在大海里面。

  查看了一下猫扑帖子的网页源代码,帖子内容介于

之间,只需要解析这段内容,就能得到自己想要的东西。不过里面东西比较多,比较杂,还是先找一个简单页面抓取试试。csdn博客相对来说就是个不错的选择,******没广告,内容不算很多,第二,代码风格很好。抓CSDN的页面无非获得博主名,文章名字和URL等,如果想获得更多的信息,可以把博主的排名,评论数抓取下来。

 

  自定义的结构体:

  [cpp] view plaincopyprint?

  struct BloggerInfo

  {

  int visits;//访问次数

  int integral;//积分

  int ranking;//排名

  int artical_original;//原创文章数

  int artical_reproduce;//转载文章数

  int artical_translation;//翻译文章数

  int comments;//评论

  };

  struct ArticleInfo

  {

  char articleName[SMALLLEN];//文章标题

  char URL[SMALLLEN];//URL

  char createDate[25];//创建时间

  int visits;//访问时间

  int comments;//评论次数

  struct ArticleInfo *next;//下一篇文章地址

  };

  struct Articles

  {

  int page;//页数

  struct Articles * pageNext;//下一页所在地址

  struct ArticleInfo *firstArticle;//该页******篇文章地址

  struct ArticleInfo *currentArticle;//插入文章时使用,表示插入时的***后一篇文章

  };

  下面简单分析一下CSDN博客源代码。

  博主标题:

  [html] view plaincopyprint?

  


 

  


  编程小子的专栏

  

锲而舍之,朽木不折;锲而不舍,金石可镂


  


  


  


  文章标题和URL:

  [html] view plaincopyprint?

  

  ubuntu11.10搭建git服务器

  

  文章访问次数,评论次数等:

  [html] view plaincopyprint?

  


  

  阅读(21)

  评论(0)

  


  博客统计信息:

  [html] view plaincopyprint?

  


     

      

  • 访问:1218次

  •   

  • 积分:164分

  •   

  • 排名:千里之外

  •   


 

  [html] view plaincopyprint?

  


      

  • 原创:13篇

  •  

      

  • 转载:2篇

  •   

  • 译文:0篇

  •   

  • 评论:1条

  •   


  从上面贴出的HTML可以看出,所需要的信息都在某一个id下,每个id是******的,这对解析是很有利的。我们只需要抓取到网页,分析相应内容,得到想要的信息即可。

  在确定CSDN博客是可以抓取后,就可以着手抓取。如何抓取?简单来说,就是与CSDN博客服务器简历tcp连接,然后发送HTTP请求,得到响应。页面抓取过程如下图所示:

 

 

  主要流程:

  解析域名(csdn.blog.net),得到服务器IP地址

  与服务器端建立TCP连接

  发送HTTP请求

  得到服务器端响应,响应内容里面含有请求页面源代码

  解析网页源代码,得到所需要信息,如果需要抓取博主所有的文章,需要解析出每篇文章的URL

  统计博主文章数,判断是否有分页,如果又分页,则请求分页内容,获取分页的文章URL

  跳转到******步,请求每篇文章

  把文章保存到本地

  根据需求看是否对文章进行处理

  知道流程后,就可以着手编码。先来看看我目前作出来的效果图。


 

 


 

  这里并不只是把文章信息解析出来,也把每篇博客具体内容给存到本地了。存在以博主名命名的文件夹下,每篇文章存在以文章命名的html文件中。

 

  具体实现:

  一、解析域名

  采用gethostbyname方法。函数声明如下:

  [cpp] view plaincopyprint?

  #include

  struct hostent * gethostbyname(const char *hostname)

  执行成功,返回非空指针,失败返回空指针,并设置h_errno,可以通过hstrerror方法查看h_errno对应的错误提示信息。

  函数中用到的hostent结构体,如下所示:

  [cpp] view plaincopyprint?

  

struct hostent
																												


  {

  char *h_name; /* 查询主机的规范名字 */

  char **h_aliases; /* 别名 */

  int h_addrtype; /* 地址类型 */

  int h_length; /* 地址个数 */

  char **h_addr_list; /* 所有的地址 */

  };

  二、获得IP地址后,与CSDN博客服务器建立TCP连接。

  解析域名和建立TCP链接,我都放在一个自定义函数buildconnect里面。每次需要建立连接,我只需要调用这个方法即可。代码如下:

  [cpp] view plaincopyprint?

  /*

  *功能:获得CSDN博客IP地址,并与CSDN服务器建立TCP连接

  *参数:无

  *返回值:非负描述字-成功,-1-出错

  */

  int buildConnection() {

  int sockfd;

  static struct hostent *host = NULL;

  static struct sockaddr_in csdn_addr;

  if (host == NULL) {

  if ((host = gethostbyname(CSDN_BLOG_URL)) == NULL) {//获取CSDN博客服务器IP地址

  fprintf(stderr, "gethostbyname error:%s\n", hstrerror(h_errno));

  exit(-1);

  }

  #ifdef DEBUG

  printf("csdn ip:%s\n", inet_ntoa(*((struct in_addr *) host->h_addr_list[0])));

  #endif

  bzero(&csdn_addr, sizeof (csdn_addr));

  csdn_addr.sin_family = AF_INET;

  csdn_addr.sin_port = htons(CSDN_BLOG_PORT);

  csdn_addr.sin_addr = *((struct in_addr *) host->h_addr_list[0]);

  }

  sockfd = socket(AF_INET, SOCK_STREAM, 0);

  if (sockfd == -1) {

  fprintf(stderr, "socked error:%s\n", strerror(errno));

  exit(-1);

  }

  if (connect(sockfd, (struct sockaddr *) &csdn_addr, sizeof (csdn_addr)) == -1) {

  fprintf(stderr, "connect error:%s", strerror(errno));

  exit(-1);

  }

  return sockfd;

  }

  不需要每一次都去解析域名,所以把域名存在一个static变量里面。

  三、发送HTTP请求

  HTTP请求格式如下所示:

  [cpp] view plaincopyprint?

  "GET /lanyan822 HTTP/1.1\r\n

  Accept:*/*\r\n

  Accept-Language:zh-cn\r\n

  User-Agent: Mozilla/4.0 (compatible;MSIE 5.01;Windows NT 5.0)\r\n

  Host: blog.csdn.net:80\r\n

  Connection: Close\r\n

  \r\n

  说明:GET:表明是一个GET请求,还有POST请求(你可以模拟登陆,发送用户名和密码到服务端。不过现在CSDN登陆需要一个随机码验证。这个不好办)/lanyan822表示请求的页面,HTTP1.1表示使用的版本。\r\n表示结束。

  Accept:表示浏览器接受的MIME类型

  Accept-Language:表示浏览器接受的语言类型

  User-Agent:指浏览器的名字。呵呵,因为是模拟浏览器发请求,所以这里是假的

  Host:服务器的域名和端口

  Connection:用来告诉服务器是否可以维持固定的HTTP连接。HTTP/1.1使用Keep-Alive为默认值,这样,当浏览器需要多个文件时(比如一个HTML文件和相关的图形文件),不需要每次都建立连接。这里我每次请求页面后,我都选择关闭。

  这里需要注意的是:HTTP请求格式,千万不能在里面多写空格什么的。我之前一直请求页面失败就是因为里面多了空格。***后以\r\n结束。

  [cpp] view plaincopyprint?

  /*

  *功能:发送HTTP请求,HTTP请求格式一定要正确,且不能有多余的空格.

  *参数:sockfd:套接字,requestParam:http请求路径

  *返回值:写入套接口的字节数-成功,-1:失败

  */

  int sendRequest(int sockfd, const char *requestParam) {

  char request[BUFFERLEN];

  int ret;

  bzero(request, sizeof (request));

  sprintf(request, "GET %s HTTP/1.1\r\n Accept:*/*\r\n Accept-Language:zh-cn\r\n"

  "User-Agent: Mozilla/4.0 (compatible;MSIE 5.01;Windows NT 5.0)\r\n"

  "Host: %s\r\n"

  "Connection: Close\r\n"

  "\r\n", requestParam, CSDN_BLOG_URL);

  #ifdef DEBUG

  printf("请求HTTP格式:%s\n", request);

  #endif

  ret = write(sockfd, request, sizeof (request));

  #ifdef DEBUG

  printf("send %d data to server\n", ret);

  #endif

  return ret;

  }

  四、接受服务端响应,并存储请求页面

  HTTP响应包括响应头和所请求页面的源代码。

  HTTP响应头如下所示:

  [cpp] view plaincopyprint?

  HTTP/1.1 200 OK

  Server: nginx/0.7.68

  Date: Wed, 16 May 2012 06:28:28 GMT

  Content-Type: text/html; charset=utf-8

  Connection: close

  Vary: Accept-Encoding

  X-Powered-By: ASP.NET

  Set-Cookie: uuid=344c2ad0-b060-448b-b75f-2c9dd308e5a5; expires=Thu, 17-May-2012 06:24:49 GMT; path=/

  Set-Cookie: avh=yKfd8EgMOqw1YuvAzcgrbQ%3d%3d; expires=Wed, 16-May-2012 06:29:49 GMT; path=/

  Cache-Control: private

  Content-Length: 18202

  响应头部也是以\r\n结束。所以可以通过\r\n\r\n来判断响应头部的结束位置。

  实现源码:

  [cpp] view plaincopyprint?

  /*

  *功能:将服务端返回的html内容存入filePath中.这里使用了select函数.

  *参数:sockfd:套接字,filePath:文件存储路径

  *返回值:读入套接字字节数-成功,-1-失败,-2请求页面返回状态值非200

  */

  int saveRequestHtml(int sockfd, const char *filePath) {

  int headerTag, ret, fileFd = -1,contentLen,count=0;

  char receiveBuf[BUFFERLEN];

  fd_set rset;

  struct timeval timeout;

  memset(&timeout, 0, sizeof (timeout));

  timeout.tv_sec = 60;

  timeout.tv_usec = 0;

  char *first, *last,*ok_loc,*pContentLenStart,*pContentLenEnd;

  while (TRUE) {

  FD_SET(sockfd, &rset);

  ret = select(sockfd + 1, &rset, NULL, NULL, &timeout);

  if (ret == 0) {

  fprintf(stderr, "select time out:%s\n", strerror(errno));

  return ret;

  } else

  if (ret == -1) {

  fprintf(stderr, "select error :%s\n", strerror(errno));

  return ret;

  }

  headerTag = 0;

  if (FD_ISSET(sockfd, &rset)) {

  while (ret = read(sockfd, receiveBuf, BUFFERLEN - 1)) {

  if (headerTag == 0) {

  if (access(filePath, F_OK) == 0) {

  if (remove(filePath) == -1)

  fprintf(stderr, "remove error:%s\n", strerror(errno));

  } else {

  #ifdef DEBUG

  printf("%s not exist\n", filePath);

  #endif

  }

  receiveBuf[ret] = '\0';

  first = strstr(receiveBuf, "\r\n\r\n");//服务端返回消息头部和网页html内容.消息头部也是以\r\n\r\n结尾.

  if (first != 0) {

  last = first + strlen("\r\n\r\n");

  ok_loc=strstr(receiveBuf,"OK");//如果请求成功,状态码是200,并且有OK

  if(ok_loc!=0)

  {

  #ifdef DEBUG

  printf("页面请求成功\n");

  #endif

  fileFd = open(filePath, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR);

  if (fileFd == -1) {

  fprintf(stderr, "open error:%s\n", strerror(errno));

  return -1;

  }

  pContentLenStart=strstr(receiveBuf,CONTENT_LENGTH);//这里是为了获取HTTP响应头content-length大小。

  if(pContentLenStart!=0)

  {

  pContentLenEnd=strstr(pContentLenStart+strlen(CONTENT_LENGTH),"\r\n");

  if(pContentLenEnd!=0)

  {

  contentLen=myatoi(pContentLenStart,pContentLenEnd);

  #ifdef DEBUG

  printf("content-length:%d\n",contentLen);

  #endif

  count+= write(fileFd, last, ret - (last - receiveBuf));

  headerTag = 1;

  }else

  return -1;

  }else

  {

  return -1;

  }

  }else

  {

  return -2;//页面请求失败。

  }

  }

  #ifdef DEBUG

  printf("%s\n", receiveBuf);

  #endif

  } else {

  count+= write(fileFd, receiveBuf, ret);

  }

  }

  close(fileFd);

  }

  break;

  }

  if(count!=contentLen)

  {

  printf("接受长度与HTTP响应头长度不一致\n");

  return -1;

  }

  return count;

  }

  五、解析网页源代码,得到所需要信息

  我主要解析了博客的文章名,文章URL,访问次数,排名,积分,原创文章数,转载文章数,翻译文章数,评论数。

  源代码解析是按照所需要的信息在源代码中出现的顺序依次解析,先出现文章名,接着是文章的评论,发表日期等信息,接着解析博主的积分,等级等,***后解析博主发表的文章数。

  解析用的***多的是strstr函数。

  [cpp] view plaincopyprint?

  #include

  char *strstr (char *haystack, const char *needle);

  函数功能:查找needle在haystack中******次出现的地址,查找成功,返回******次出现的地址,查找失败返回0.类似于c++ string的find_first_of函数。

  信息解析出来,需要存储下来。主要是存在自定义的数据结构里面。每一页(***多50篇文章)存储在struct Articles结构体里面,文章信息则存入struct ArticleInfo里面。页面存储结构如下图所示:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 
客户服务 +more
上门服务
服务承诺
异地恢复
收费标准
付款方式
疑难解答
服务特色 +more
·免费检测
·免费提供3天备份
·专业工程师提供服务
·免费清洁送修的介质
·数据恢复前报价,客户确认后工程师开始数据恢复
·数据恢复不成功不收费
·与客户签订保密协议,对客户的数
 据严格保密,整个恢复过程不会对
 客户的原盘有任何的写操作,以确
 保原盘的数据完全

·免费参观恢复全过程

·工程师在线免费咨询

·专业工程师提供服务
服务器数据恢复 点击这里给我发消

息
数据库修复
点击这里给我发

消息
硬盘数据恢复
点击这里给我发消

息