自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Linux下網(wǎng)頁抓取

運維 系統(tǒng)運維
最近一直在學(xué)習(xí)stevens的unix網(wǎng)絡(luò)編程,對于網(wǎng)絡(luò)通信有了一定的認識,所以也想練練手。聊天程序之前用winsock做過,這次不想做重復(fù)的。之前看到一哥們寫過windows下抓取貓撲的帖子,我覺得抓頁面也是一個不錯想法。

 最近一直在學(xué)習(xí)stevens的unix網(wǎng)絡(luò)編程,對于網(wǎng)絡(luò)通信有了一定的認識,所以也想練練手。聊天程序之前用winsock做過,這次不想做重復(fù)的。之前看到一哥們寫過windows下抓取貓撲的帖子,我覺得抓頁面也是一個不錯想法。我也喜歡逛貓撲,有時候也去追追里面寫的文章,貓撲帖子少了一個很重要的功能,就是只看樓主的帖子。貓撲水人很多,容易把樓主的帖子淹沒在大海里面。

        查看了一下貓撲帖子的網(wǎng)頁源代碼,帖子內(nèi)容介于<divclass="box2 js-reply"data-rid="*">和</div>之間,只需要解析這段內(nèi)容,就能得到自己想要的東西。不過里面東西比較多,比較雜,還是先找一個簡單頁面抓取試試。csdn博客相對來說就是個不錯的選擇,***沒廣告,內(nèi)容不算很多,第二,代碼風(fēng)格很好。抓CSDN的頁面無非獲得博主名,文章名字和URL等,如果想獲得更多的信息,可以把博主的排名,評論數(shù)抓取下來。

下面簡單分析一下CSDN博客源代碼。

博主標(biāo)題:

 

  1. <div id="blog_title">  
  2.   
  3.             <h1>  
  4.   
  5.                 <a href="/lanyan822">編程小子的專欄</a></h1>  
  6.   
  7.             <h2>鍥而舍之,朽木不折;鍥而不舍,金石可鏤</h2>  
  8.   
  9.             <div class="clear">  
  10.   
  11.             </div>  
  12.   
  13.         </div>  

文章標(biāo)題和URL:

 

 

  1. <span class="link_title"><a href="/lanyan822/article/details/7549916">  
  2.   
  3.       ubuntu11.10搭建git服務(wù)器  
  4.       </a>  

文章訪問次數(shù),評論次數(shù)等:

 

 

  1. <div class="article_manage">  
  2.   
  3.     <span class="link_postdate">2012-05-14 15:09</span>  
  4.   
  5.     <span class="link_view" title="閱讀次數(shù)"><a href="/lanyan822/article/details/7549916" title="閱讀次數(shù)">閱讀</a>(21)</span>  
  6.   
  7.     <span class="link_comments" title="評論次數(shù)"><a href="/lanyan822/article/details/7549916#comments" title="評論次數(shù)">評論</a>(0)</span>  
  8. </div>  

博客統(tǒng)計信息:

 

 

  1. <ul id="blog_rank">  
  2.   
  3.            <li>訪問:<span>1218次</span></li>  
  4.   
  5.            <li>積分:<span>164分</span></li>  
  6.   
  7.            <li>排名:<span>千里之外</span></li>  
  8.   
  9.        </ul>  
  1. <ul id="blog_statistics">  
  2.   
  3.            <li>原創(chuàng):<span>13篇</span></li>  
  4.   
  5.            <li>轉(zhuǎn)載:<span>2篇</span></li>  
  6.   
  7.            <li>譯文:<span>0篇</span></li>  
  8.   
  9.            <li>評論:<span>1條</span></li>  
  10.   
  11.        </ul>  

 

 從上面貼出的HTML可以看出,所需要的信息都在某一個id下,每個id是唯一的,這對解析是很有利的。我們只需要抓取到網(wǎng)頁,分析相應(yīng)內(nèi)容,得到想要的信息即可。

在確定CSDN博客是可以抓取后,就可以著手抓取。如何抓???簡單來說,就是與CSDN博客服務(wù)器簡歷tcp連接,然后發(fā)送HTTP請求,得到響應(yīng)。頁面抓取過程如下圖所示:

主要流程:

 

  1. 解析域名(csdn.blog.net),得到服務(wù)器IP地址
  2. 與服務(wù)器端建立TCP連接
  3. 發(fā)送HTTP請求
  4. 得到服務(wù)器端響應(yīng),響應(yīng)內(nèi)容里面含有請求頁面源代碼
  5. 解析網(wǎng)頁源代碼,得到所需要信息,如果需要抓取博主所有的文章,需要解析出每篇文章的URL
  6. 統(tǒng)計博主文章數(shù),判斷是否有分頁,如果又分頁,則請求分頁內(nèi)容,獲取分頁的文章URL
  7. 跳轉(zhuǎn)到***步,請求每篇文章
  8. 把文章保存到本地
  9. 根據(jù)需求看是否對文章進行處理  

 

知道流程后,就可以著手編碼。先來看看我目前作出來的效果圖。

 

這里并不只是把文章信息解析出來,也把每篇博客具體內(nèi)容給存到本地了。存在以博主名命名的文件夾下,每篇文章存在以文章命名的html文件中。

 

具體實現(xiàn):

一、解析域名

采用gethostbyname方法。函數(shù)聲明如下:

 

  1. #include<netdb.h>  
  2. struct hostent * gethostbyname(const char *hostname)  
執(zhí)行成功,返回非空指針,失敗返回空指針,并設(shè)置h_errno,可以通過hstrerror方法查看h_errno對應(yīng)的錯誤提示信息。

 

函數(shù)中用到的hostent結(jié)構(gòu)體,如下所示:

 

  1. <pre name="code" class="cpp">struct hostent  
  2. {  
  3.   char *h_name;         /* 查詢主機的規(guī)范名字 */  
  4.   char **h_aliases;     /* 別名 */  
  5.   int h_addrtype;       /* 地址類型  */  
  6.   int h_length;         /* 地址個數(shù)  */  
  7.   char **h_addr_list;       /* 所有的地址 */  
  8. };  
 

二、獲得IP地址后,與CSDN博客服務(wù)器建立TCP連接。

 

解析域名和建立TCP鏈接,我都放在一個自定義函數(shù)buildconnect里面。每次需要建立連接,我只需要調(diào)用這個方法即可。代碼如下:

 

  1. /* 
  2. *功能:獲得CSDN博客IP地址,并與CSDN服務(wù)器建立TCP連接 
  3.     *參數(shù):無 
  4. *返回值:非負描述字-成功,-1-出錯 
  5. */  
  6. int buildConnection() {  
  7.     int sockfd;  
  8.     static struct hostent *host = NULL;  
  9.     static struct sockaddr_in csdn_addr;  
  10.     if (host == NULL) {  
  11.         if ((host = gethostbyname(CSDN_BLOG_URL)) == NULL) {//獲取CSDN博客服務(wù)器IP地址  
  12.             fprintf(stderr, "gethostbyname error:%s\n", hstrerror(h_errno));  
  13.             exit(-1);  
  14.         }  
  15. #ifdef DEBUG  
  16.         printf("csdn ip:%s\n", inet_ntoa(*((struct in_addr *) host->h_addr_list[0])));  
  17. #endif  
  18.         bzero(&csdn_addr, sizeof (csdn_addr));  
  19.         csdn_addr.sin_family = AF_INET;  
  20.         csdn_addr.sin_port = htons(CSDN_BLOG_PORT);  
  21.         csdn_addr.sin_addr = *((struct in_addr *) host->h_addr_list[0]);  
  22.     }  
  23.     sockfd = socket(AF_INET, SOCK_STREAM, 0);  
  24.     if (sockfd == -1) {  
  25.         fprintf(stderr, "socked error:%s\n", strerror(errno));  
  26.         exit(-1);  
  27.     }  
  28.     if (connect(sockfd, (struct sockaddr *) &csdn_addr, sizeof (csdn_addr)) == -1) {  
  29.         fprintf(stderr, "connect error:%s", strerror(errno));  
  30.         exit(-1);  
  31.     }  
  32.     return sockfd;  
  33. }  
不需要每一次都去解析域名,所以把域名存在一個static變量里面。

三、發(fā)送HTTP請求

 

HTTP請求格式如下所示:

 

  1. "GET /lanyan822 HTTP/1.1\r\n  
  2. Accept:*/*\r\n  
  3. Accept-Language:zh-cn\r\n  
  4. User-Agent: Mozilla/4.0 (compatible;MSIE 5.01;Windows NT 5.0)\r\n  
  5. Host: blog.csdn.net:80\r\n  
  6. Connection: Close\r\n  
  7. \r\n  
說明:GET:表明是一個GET請求,還有POST請求(你可以模擬登陸,發(fā)送用戶名和密碼到服務(wù)端。不過現(xiàn)在CSDN登陸需要一個隨機碼驗證。這個不好辦)/lanyan822表示請求的頁面,HTTP1.1表示使用的版本。\r\n表示結(jié)束。
Accept:表示瀏覽器接受的MIME類型
Accept-Language:表示瀏覽器接受的語言類型
User-Agent:指瀏覽器的名字。呵呵,因為是模擬瀏覽器發(fā)請求,所以這里是假的
Host:服務(wù)器的域名和端口
Connection:用來告訴服務(wù)器是否可以維持固定的HTTP連接。HTTP/1.1使用Keep-Alive為默認值,這樣,當(dāng)瀏覽器需要多個文件時(比如一個HTML文件和相關(guān)的圖形文件),不需要每次都建立連接。這里我每次請求頁面后,我都選擇關(guān)閉。
這里需要注意的是:HTTP請求格式,千萬不能在里面多寫空格什么的。我之前一直請求頁面失敗就是因為里面多了空格。***以\r\n結(jié)束。
  1. /* 
  2. *功能:發(fā)送HTTP請求,HTTP請求格式一定要正確,且不能有多余的空格. 
  3. *參數(shù):sockfd:套接字,requestParam:http請求路徑 
  4. *返回值:寫入套接口的字節(jié)數(shù)-成功,-1:失敗 
  5. */  
  6. int sendRequest(int sockfd, const char *requestParam) {  
  7.     char request[BUFFERLEN];  
  8.     int ret;  
  9.     bzero(request, sizeof (request));  
  10.     sprintf(request, "GET %s HTTP/1.1\r\n Accept:*/*\r\n Accept-Language:zh-cn\r\n"  
  11.             "User-Agent: Mozilla/4.0 (compatible;MSIE 5.01;Windows NT 5.0)\r\n"  
  12.             "Host: %s\r\n"  
  13.             "Connection: Close\r\n"  
  14.             "\r\n", requestParam, CSDN_BLOG_URL);  
  15. #ifdef DEBUG  
  16.     printf("請求HTTP格式:%s\n", request);  
  17. #endif  
  18.     ret = write(sockfd, request, sizeof (request));  
  19. #ifdef DEBUG  
  20.     printf("send %d data to server\n", ret);  
  21. #endif  
  22.     return ret;  
  23. }  

四、接受服務(wù)端響應(yīng),并存儲請求頁面

 

HTTP響應(yīng)包括響應(yīng)頭和所請求頁面的源代碼。

HTTP響應(yīng)頭如下所示:

 

  1. HTTP/1.1 200 OK  
  2. Server: nginx/0.7.68  
  3. Date: Wed, 16 May 2012 06:28:28 GMT  
  4. Content-Type: text/html; charset=utf-8  
  5. Connection: close  
  6. Vary: Accept-Encoding  
  7. X-Powered-By: ASP.NET  
  8. Set-Cookie: uuid=344c2ad0-b060-448b-b75f-2c9dd308e5a5; expires=Thu, 17-May-2012 06:24:49 GMT; path=/  
  9. Set-Cookie: avh=yKfd8EgMOqw1YuvAzcgrbQ%3d%3d; expires=Wed, 16-May-2012 06:29:49 GMT; path=/  
  10. Cache-Control: private  
  11. Content-Length: 18202  
響應(yīng)頭部也是以\r\n結(jié)束。所以可以通過\r\n\r\n來判斷響應(yīng)頭部的結(jié)束位置。

 

實現(xiàn)源碼:

 

  1. /* 
  2. *功能:將服務(wù)端返回的html內(nèi)容存入filePath中.這里使用了select函數(shù). 
  3. *參數(shù):sockfd:套接字,filePath:文件存儲路徑 
  4. *返回值:讀入套接字字節(jié)數(shù)-成功,-1-失敗,-2請求頁面返回狀態(tài)值非200 
  5. */  
  6. int saveRequestHtml(int sockfd, const char *filePath) {  
  7.     int headerTag, ret, fileFd = -1,contentLen,count=0;  
  8.     char receiveBuf[BUFFERLEN];  
  9.     fd_set rset;  
  10.     struct timeval timeout;  
  11.     memset(&timeout, 0, sizeof (timeout));  
  12.     timeout.tv_sec = 60;  
  13.     timeout.tv_usec = 0;  
  14.     char *first, *last,*ok_loc,*pContentLenStart,*pContentLenEnd;  
  15.     while (TRUE) {  
  16.         FD_SET(sockfd, &rset);  
  17.         ret = select(sockfd + 1, &rset, NULL, NULL, &timeout);  
  18.         if (ret == 0) {  
  19.             fprintf(stderr, "select time out:%s\n", strerror(errno));  
  20.             return ret;  
  21.         } else  
  22.             if (ret == -1) {  
  23.             fprintf(stderr, "select error :%s\n", strerror(errno));  
  24.             return ret;  
  25.         }  
  26.         headerTag = 0;  
  27.         if (FD_ISSET(sockfd, &rset)) {  
  28.   
  29.   
  30.             while (ret = read(sockfd, receiveBuf, BUFFERLEN - 1)) {  
  31.                 if (headerTag == 0) {  
  32.                     if (access(filePath, F_OK) == 0) {  
  33.                         if (remove(filePath) == -1)  
  34.                             fprintf(stderr, "remove error:%s\n", strerror(errno));  
  35.                     } else {  
  36. #ifdef DEBUG  
  37.                         printf("%s not exist\n", filePath);  
  38. #endif  
  39.                     }                  
  40.                     receiveBuf[ret] = '\0';  
  41.                     first = strstr(receiveBuf, "\r\n\r\n");//服務(wù)端返回消息頭部和網(wǎng)頁html內(nèi)容.消息頭部也是以\r\n\r\n結(jié)尾.  
  42.                     if (first != 0) {          
  43.                         last = first + strlen("\r\n\r\n");  
  44.                         ok_loc=strstr(receiveBuf,"OK");//如果請求成功,狀態(tài)碼是200,并且有OK  
  45.                         if(ok_loc!=0)  
  46.                         {  
  47. #ifdef DEBUG  
  48.                             printf("頁面請求成功\n");  
  49. #endif  
  50.                             fileFd = open(filePath, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR);  
  51.                             if (fileFd == -1) {  
  52.                                 fprintf(stderr, "open error:%s\n", strerror(errno));  
  53.                                 return -1;  
  54.                             }  
  55.                             pContentLenStart=strstr(receiveBuf,CONTENT_LENGTH);//這里是為了獲取HTTP響應(yīng)頭content-length大小。  
  56.                             if(pContentLenStart!=0)  
  57.                             {  
  58.                                 pContentLenEnd=strstr(pContentLenStart+strlen(CONTENT_LENGTH),"\r\n");  
  59.                                 if(pContentLenEnd!=0)  
  60.                                 {  
  61.                                     contentLen=myatoi(pContentLenStart,pContentLenEnd);  
  62. #ifdef DEBUG  
  63.                                     printf("content-length:%d\n",contentLen);  
  64. #endif  
  65.                                     count+= write(fileFd, last, ret - (last - receiveBuf));  
  66.                                     headerTag = 1;  
  67.                                 }else  
  68.                                     return -1;  
  69.                             }else  
  70.                             {  
  71.                                 return -1;  
  72.                             }  
  73.                         }else  
  74.                         {  
  75.                             return -2;//頁面請求失敗。  
  76.                         }  
  77.                          
  78.                     }  
  79. #ifdef DEBUG  
  80.                     printf("%s\n", receiveBuf);  
  81. #endif  
  82.                 } else {  
  83.                    count+= write(fileFd, receiveBuf, ret);  
  84.                 }  
  85.             }  
  86.             close(fileFd);  
  87.         }  
  88.         break;  
  89.     }  
  90.     if(count!=contentLen)  
  91.     {  
  92.         printf("接受長度與HTTP響應(yīng)頭長度不一致\n");  
  93.         return -1;  
  94.     }  
  95.     return count;  
  96. }  

五、解析網(wǎng)頁源代碼,得到所需要信息

我主要解析了博客的文章名,文章URL,訪問次數(shù),排名,積分,原創(chuàng)文章數(shù),轉(zhuǎn)載文章數(shù),翻譯文章數(shù),評論數(shù)。
源代碼解析是按照所需要的信息在源代碼中出現(xiàn)的順序依次解析,先出現(xiàn)文章名,接著是文章的評論,發(fā)表日期等信息,接著解析博主的積分,等級等,***解析博主發(fā)表的文章數(shù)。
解析用的最多的是strstr函數(shù)。
  1. #include<string.h>  
  2. char *strstr (char *haystack, const char *needle);  
函數(shù)功能:查找needle在haystack中***次出現(xiàn)的地址,查找成功,返回***次出現(xiàn)的地址,查找失敗返回0.類似于c++ string的find_first_of函數(shù)。
 
信息解析出來,需要存儲下來。主要是存在自定義的數(shù)據(jù)結(jié)構(gòu)里面。每一頁(最多50篇文章)存儲在struct Articles結(jié)構(gòu)體里面,文章信息則存入struct ArticleInfo里面。頁面存儲結(jié)構(gòu)如下圖所示:

自定義的結(jié)構(gòu)體:
 

 

  1. struct BloggerInfo  
  2. {  
  3.     int visits;//訪問次數(shù)  
  4.     int integral;//積分  
  5.     int ranking;//排名  
  6.     int artical_original;//原創(chuàng)文章數(shù)  
  7.     int artical_reproduce;//轉(zhuǎn)載文章數(shù)  
  8.     int artical_translation;//翻譯文章數(shù)  
  9.     int comments;//評論  
  10. };  
  11.   
  12. struct ArticleInfo  
  13. {  
  14.     char articleName[SMALLLEN];//文章標(biāo)題  
  15.     char URL[SMALLLEN];//URL  
  16.     char createDate[25];//創(chuàng)建時間  
  17.     int visits;//訪問時間  
  18.     int comments;//評論次數(shù)  
  19.     struct ArticleInfo *next;//下一篇文章地址  
  20. };  
  21.   
  22. struct Articles  
  23. {  
  24.     int page;//頁數(shù)  
  25.     struct Articles * pageNext;//下一頁所在地址  
  26.     struct ArticleInfo *firstArticle;//該頁***篇文章地址  
  27.     struct ArticleInfo *currentArticle;//插入文章時使用,表示插入時的***一篇文章  
  28. };  

【編輯推薦】

  1. Chkdsk大躍進:Win8磁盤檢測時間大大縮短
  2. Linux下使用mke2fsk格式化分區(qū)的方法
  3. Ubuntu 11.10 利用終端環(huán)境備份還原
責(zé)任編輯:趙寧寧
相關(guān)推薦

2013-11-13 14:00:31

網(wǎng)頁設(shè)計設(shè)計

2021-11-24 17:22:06

網(wǎng)絡(luò)抓取網(wǎng)絡(luò)爬蟲數(shù)據(jù)收集

2023-03-09 15:55:17

JavaScriptURLCSS

2009-07-31 10:34:41

ASP.NET抓取網(wǎng)頁

2009-12-02 15:50:41

PHP抓取網(wǎng)頁內(nèi)容

2010-03-03 15:39:50

Python抓取網(wǎng)頁內(nèi)

2025-04-03 02:35:00

GoogleGemini工具

2020-12-04 06:39:25

爬蟲網(wǎng)頁

2010-03-04 11:22:59

Python抓取網(wǎng)頁圖

2009-09-07 14:00:57

C#抓取網(wǎng)頁

2010-07-16 11:16:40

Perl抓取網(wǎng)頁

2019-01-31 09:02:56

網(wǎng)頁抓取設(shè)計模式數(shù)據(jù)

2017-04-29 10:37:23

2017-05-16 13:55:57

2010-01-18 14:41:33

VB.NET抓取網(wǎng)頁

2022-08-12 08:00:00

編碼語言開發(fā)工具

2020-11-11 10:58:59

Scrapy

2024-05-23 08:15:03

.NET網(wǎng)頁數(shù)據(jù)

2009-04-28 10:03:35

PHPcURL抓取網(wǎng)頁

2019-11-19 15:43:07

人工智能軟件技術(shù)
點贊
收藏

51CTO技術(shù)棧公眾號