整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          教你用C#實現:簡單又高效的“爬蟲”程序

          教你用C#實現:簡單又高效的“爬蟲”程序

          起爬蟲似乎都成了python的專屬了,我不知道其他人怎么樣?至少我平時對爬蟲的應用場景很小,所以一直沒有關注它。近期因為一個小項目——一個客戶的網站忘了續費,結果被服務商索取高額服務費(好在網站還能打開),剛好客戶遇到我們,我們當場從0開始開發了一個爬蟲,一下午就把內容爬下來了。


          一、原理(用大白話告訴你原理)

          網頁最終呈現在瀏覽器中,都是以html編碼呈現的,html是什么都不懂的話,這篇文章都不懂的話,這篇文章可能不適合你,但我還是愿意給你掃掃盲;html就是把文字或圖片進行定位排版的一種描述性語言。比如:加粗<b>加粗</b>,比如下劃線<i>這是下劃線</i>基本所有內容都是由類似這樣的標簽包裹起來的,下面看一組html的編碼(你到任何一個網站點右鍵‘查看源文件’都能看到它的編碼文件)。


          html源文件


          通過上圖可以觀察到,我們需要取的內容都在html標簽內包含,再仔細觀察這個html標簽,發現兩個特點:

          1、新聞的標題在:<a>新聞標題</a>(a標簽內包含)

          2、新聞的下級鏈接:<a href="http://下級內容鏈接"> (a標簽的href內包含)

          這里你需要記住兩個行業術語,1這種形態:標簽內包含的叫“內容”,2這種形態:叫“屬性”,一個完整的標簽是由內容和屬性構成,屬性不是必須的,如果內容你也不寫的話,就會什么也不顯示,就成了無意義標簽。

          大量的標簽構成了html網頁,標簽也有父子歸屬關系,就好比數學上的:大括號管中括號,中括號管小括號一樣。

          掃盲到此為止……

          根據上面的html顯示,我們需要用爬蟲剔骨卸肉一般,將a標簽中的標題文字,和下級鏈接取出。

          二、關鍵技術——“解析”

          本文開始說到python很擅長做爬蟲,那是python語言的愛好者圈子里,貢獻了很多解析html代碼的小工具(行業內把各種工具類的代碼片段叫:類庫),比如這個類庫的作用是:給它喂一篇html文檔,你簡單描述一下要求,(例如:要求取出所有a標簽的文字),這個類庫就可以高效的根據你的描述,把整個源碼文檔中的a標簽的文字剔出來。這個“剔”的過程,行業內把它叫“解析”。

          解析文檔無論在什么語言中,都是一件非常細致且耗時的工作,“類庫”只是某種語言的愛好者,把某個常用功能剝離出來,可以在不同的程序之間移植而已。

          比如:上面介紹的就是html解析類庫,python的這個語言并不是天生就會做什么,而是它的社區很活躍,被愛好者共享的類庫很多而已。

          但是話又說回來了,其他老牌語言也不見得類庫就少,比如:C#或java各種工具類庫也是鋪天蓋地,今天介紹的就是c#下的一款優秀的html解析類庫——“HtmlAgilityPack”。

          它的用法和jqurey非常像,如果你想取出下面鏈接地址和文字,那么就這么描述就行:



          實現方法

          //定義一個根節點
          Dom_list="//table[@id='content_one_4']";
          //查詢指定容器下的組節點
           HtmlNode documentNode=doc.DocumentNode.SelectSingleNode(Dom_list);
           HtmlNodeCollection linklist=documentNode.SelectNodes(".//a");
          
          //把這一組a標簽循環取出
          foreach (var item in linklist)
          {
                //【核心】獲取鏈接文字和鏈接的地址
                string link_title=item.InnerHtml;
                string link_url=item.Attributes["href"].Value;
          
                 //檢測重復記錄后寫入數據庫:根據情況自己完成入庫操作
          }

          這么簡簡單單的幾句代碼就完成了爬蟲的核心工作,數據進了你本地庫,你愛怎么變花樣就怎么變。

          如果你把爬蟲的原理搞明白了,制作起來還是很容易的,但容易的前提是你用了別人的類庫,如果讓你從零開始,去分析解析一篇html文檔,那就非常復雜。除了用常見的正則表達式去匹配各種規則,還要有大量的容錯機制,確實不是一般程序員能快速搞定的事。

          python之所以目前很受歡迎,主要得益于它的類庫豐富,把程序開發活脫脫變成了套殼,小白上手很容易;但話又說回來,如果一個項目要你徒手從零完成,那無論用什么語言,工作量其實也都差不多了。

          下面是我做的一個桌面爬蟲應用程序,之所以用做成程序而不是網頁,主要是考慮到這是個工具,開箱即用,web再方便也需要部署環境,在這一點上一個exe的綠色軟件,還是有相當大的優勢的。


          我的爬蟲軟件


          軟件使用很簡單:對目標網站進行簡單進行配置以后,先點擊【采集目錄】就可以獲得所有的內容頁的地址存入“工作進程”中(例如:獲取了1000條內容地址),然后點擊【采集內容】,程序就依次從從“工作進程”中的地址采集內容。

          多線程采集,網速夠快幾分鐘就采集完成了,如果你想做個爬蟲,就試試吧。

          近有個概念得很火,網絡爬蟲,但是基本都是用什么python或者JAVA寫,貌似很少看到用c++寫的,我在網上找了一個,看到其實還是很簡單的算法

          算法講解:1.遍歷資源網站

               2.獲取html信息

               3.然后解析網址和圖片url下載。

               4.遞歸調用搜索網址

          BFS是最重要的處理:


          先是獲取網頁響應,保存到文本里面,然后找到其中的圖片鏈接HTMLParse,

          下載所有圖片DownLoadImg。

          //廣度遍歷  
          void BFS( const string & url ){  
          	char * response;  
          	int bytes;  
          	// 獲取網頁的相應,放入response中。  
          	if( !GetHttpResponse( url, response, bytes ) ){  
          		cout << "The url is wrong! ignore." << endl;  
          		return;  
          	}  
          	string httpResponse=response;  
          	free( response );  
          	string filename=ToFileName( url );  
          	ofstream ofile( "./html/"+filename );  
          	if( ofile.is_open() ){  
          		// 保存該網頁的文本內容  
          		ofile << httpResponse << endl;  
          		ofile.close();  
          	}  
          	vector<string> imgurls;  
          	//解析該網頁的所有圖片鏈接,放入imgurls里面  
          	HTMLParse( httpResponse,  imgurls, url );  
          
          	//下載所有的圖片資源  
          	DownLoadImg( imgurls, url );  
          }  
          


          然后附上代碼:

          #include "stdafx.h"


          //#include <Windows.h>

          #include <string>

          #include <iostream>

          #include <fstream>

          #include <vector>

          #include "winsock2.h"

          #include <time.h>

          #include <queue>

          #include <hash_set>


          #pragma comment(lib, "ws2_32.lib")

          using namespace std;


          #define DEFAULT_PAGE_BUF_SIZE 1048576


          queue<string> hrefUrl;

          hash_set<string> visitedUrl;

          hash_set<string> visitedImg;

          int depth=0;

          int g_ImgCnt=1;


          //解析URL,解析出主機名,資源名

          bool ParseURL( const string & url, string & host, string & resource){

          if ( strlen(url.c_str()) > 2000 ) {

          return false;

          }


          const char * pos=strstr( url.c_str(), "http://" );

          if( pos==NULL ) pos=url.c_str();

          else pos +=strlen("http://");

          if( strstr( pos, "/")==0 )

          return false;

          char pHost[100];

          char pResource[2000];

          sscanf( pos, "%[^/]%s", pHost, pResource );

          host=pHost;

          resource=pResource;

          return true;

          }


          //使用Get請求,得到響應

          bool GetHttpResponse( const string & url, char * &response, int &bytesRead ){

          string host, resource;

          if(!ParseURL( url, host, resource )){

          cout << "Can not parse the url"<<endl;

          return false;

          }


          //建立socket

          struct hostent * hp=gethostbyname( host.c_str() );

          if( hp==NULL ){

          cout<< "Can not find host address"<<endl;

          return false;

          }


          SOCKET sock=socket( AF_INET, SOCK_STREAM, IPPROTO_TCP);

          if( sock==-1 || sock==-2 ){

          cout << "Can not create sock."<<endl;

          return false;

          }


          //建立服務器地址

          SOCKADDR_IN sa;

          sa.sin_family=AF_INET;

          sa.sin_port=htons( 80 );

          //char addr[5];

          //memcpy( addr, hp->h_addr, 4 );

          //sa.sin_addr.s_addr=inet_addr(hp->h_addr);

          memcpy( &sa.sin_addr, hp->h_addr, 4 );


          //建立連接

          if( 0!=connect( sock, (SOCKADDR*)&sa, sizeof(sa) ) ){

          cout << "Can not connect: "<< url <<endl;

          closesocket(sock);

          return false;

          };


          //準備發送數據

          string request="GET " + resource + " HTTP/1.1\r\nHost:" + host + "\r\nConnection:Close\r\n\r\n";


          //發送數據

          if( SOCKET_ERROR==send( sock, request.c_str(), request.size(), 0 ) ){

          cout << "send error" <<endl;

          closesocket( sock );

          return false;

          }


          //接收數據

          int m_nContentLength=DEFAULT_PAGE_BUF_SIZE;

          char *pageBuf=(char *)malloc(m_nContentLength);

          memset(pageBuf, 0, m_nContentLength);


          bytesRead=0;

          int ret=1;

          cout <<"Read: ";

          while(ret > 0){

          ret=recv(sock, pageBuf + bytesRead, m_nContentLength - bytesRead, 0);


          if(ret > 0)

          {

          bytesRead +=ret;

          }


          if( m_nContentLength - bytesRead<100){

          cout << "\nRealloc memorry"<<endl;

          m_nContentLength *=2;

          pageBuf=(char*)realloc( pageBuf, m_nContentLength); //重新分配內存

          }

          cout << ret <<" ";

          }

          cout <<endl;


          pageBuf[bytesRead]='> pageBuf[bytesRead]='\0'; <';

          response=pageBuf;

          closesocket( sock );

          return true;

          //cout<< response <<endl;

          }


          //提取所有的URL以及圖片URL

          void HTMLParse ( string & htmlResponse, vector<string> & imgurls, const string & host ){

          //找所有連接,加入queue中

          const char *p=htmlResponse.c_str();

          char *tag="href=\"";

          const char *pos=strstr( p, tag );

          ofstream ofile("url.txt", ios::app);

          while( pos ){

          pos +=strlen(tag);

          const char * nextQ=strstr( pos, "\"" );

          if( nextQ ){

          char * url=new char[ nextQ-pos+1 ];

          //char url[100]; //固定大小的會發生緩沖區溢出的危險

          sscanf( pos, "%[^\"]", url);

          string surl=url; // 轉換成string類型,可以自動釋放內存

          if( visitedUrl.find( surl )==visitedUrl.end() ){

          visitedUrl.insert( surl );

          ofile << surl<<endl;

          hrefUrl.push( surl );

          }

          pos=strstr(pos, tag );

          delete [] url; // 釋放掉申請的內存

          }

          }

          ofile << endl << endl;

          ofile.close();


          tag="<img ";

          const char* att1="src=\"";

          const char* att2="lazy-src=\"";

          const char *pos0=strstr( p, tag );

          while( pos0 ){

          pos0 +=strlen( tag );

          const char* pos2=strstr( pos0, att2 );

          if( !pos2 || pos2 > strstr( pos0, ">") ) {

          pos=strstr( pos0, att1);

          if(!pos) {

          pos0=strstr(att1, tag );

          continue;

          } else {

          pos=pos + strlen(att1);

          }

          }

          else {

          pos=pos2 + strlen(att2);

          }


          const char * nextQ=strstr( pos, "\"");

          if( nextQ ){

          char * url=new char[nextQ-pos+1];

          sscanf( pos, "%[^\"]", url);

          cout << url<<endl;

          string imgUrl=url;

          if( visitedImg.find( imgUrl )==visitedImg.end() ){

          visitedImg.insert( imgUrl );

          imgurls.push_back( imgUrl );

          }

          pos0=strstr(pos0, tag );

          delete [] url;

          }

          }

          cout << "end of Parse this html"<<endl;

          }


          //把URL轉化為文件名

          string ToFileName( const string &url ){

          string fileName;

          fileName.resize( url.size());

          int k=0;

          for( int i=0; i<(int)url.size(); i++){

          char ch=url[i];

          if( ch!='\'&&ch!='/'&&ch!=':'&&ch!='*'&&ch!='?'&&ch!='"'&&ch!='<'&&ch!='>'&&ch!='|')

          fileName[k++]=ch;

          }

          return fileName.substr(0,k) + ".txt";

          }


          //下載圖片到img文件夾

          void DownLoadImg( vector<string> & imgurls, const string &url ){


          //生成保存該url下圖片的文件夾

          string foldname=ToFileName( url );

          foldname="./img/"+foldname;

          if(!CreateDirectory( foldname.c_str(),NULL ))

          cout << "Can not create directory:"<< foldname<<endl;

          char *image;

          int byteRead;

          for( int i=0; i<imgurls.size(); i++){

          //判斷是否為圖片,bmp,jgp,jpeg,gif

          string str=imgurls[i];

          int pos=str.find_last_of(".");

          if( pos==string::npos )

          continue;

          else{

          string ext=str.substr( pos+1, str.size()-pos-1 );

          if( ext!="bmp"&& ext!="jpg" && ext!="jpeg"&& ext!="gif"&&ext!="png")

          continue;

          }

          //下載其中的內容

          if( GetHttpResponse(imgurls[i], image, byteRead)){

          if ( strlen(image)==0 ) {

          continue;

          }

          const char *p=image;

          const char * pos=strstr(p,"\r\n\r\n")+strlen("\r\n\r\n");

          int index=imgurls[i].find_last_of("/");

          if( index!=string::npos ){

          string imgname=imgurls[i].substr( index , imgurls[i].size() );

          ofstream ofile( foldname+imgname, ios::binary );

          if( !ofile.is_open() )

          continue;

          cout <<g_ImgCnt++<< foldname+imgname<<endl;

          ofile.write( pos, byteRead- (pos-p) );

          ofile.close();

          }

          free(image);

          }

          }

          }


          //廣度遍歷

          void BFS( const string & url ){

          char * response;

          int bytes;

          // 獲取網頁的相應,放入response中。

          if( !GetHttpResponse( url, response, bytes ) ){

          cout << "The url is wrong! ignore." << endl;

          return;

          }

          string httpResponse=response;

          free( response );

          string filename=ToFileName( url );

          ofstream ofile( "./html/"+filename );

          if( ofile.is_open() ){

          // 保存該網頁的文本內容

          ofile << httpResponse << endl;

          ofile.close();

          }

          vector<string> imgurls;

          //解析該網頁的所有圖片鏈接,放入imgurls里面

          HTMLParse( httpResponse, imgurls, url );


          //下載所有的圖片資源

          DownLoadImg( imgurls, url );

          }


          void main()

          {

          //初始化socket,用于tcp網絡連接

          WSADATA wsaData;

          if( WSAStartup(MAKEWORD(2,2), &wsaData) !=0 ){

          return;

          }


          // 創建文件夾,保存圖片和網頁文本文件

          CreateDirectory( "./img",0);

          CreateDirectory("./html",0);

          //string urlStart="http://hao.360.cn/meinvdaohang.html";


          // 遍歷的起始地址

          string urlStart="http://desk.zol.com.cn/bizhi/7018_87137_2.html";

          //string urlStart="http://item.taobao.com/item.htm?spm=a230r.1.14.19.sBBNbz&id=36366887850&ns=1#detail";


          // 使用廣度遍歷

          // 提取網頁中的超鏈接放入hrefUrl中,提取圖片鏈接,下載圖片。

          BFS( urlStart );


          // 訪問過的網址保存起來

          visitedUrl.insert( urlStart );


          while( hrefUrl.size()!=0 ){

          string url=hrefUrl.front(); // 從隊列的最開始取出一個網址

          cout << url << endl;

          BFS( url ); // 遍歷提取出來的那個網頁,找它里面的超鏈接網頁放入hrefUrl,下載它里面的文本,圖片

          hrefUrl.pop(); // 遍歷完之后,刪除這個網址

          }

          WSACleanup();

          return;

          } <br><br><br>

          學習網絡爬蟲要掌握哪些技術?#

          挑戰升級!老板又為我設定了新的任務目標:利用C#開發一款超能爬蟲,它必須深入挖掘我們網站的每一個細節,不論是主體內容、相關鏈接,甚至是細微至頁面標題,都需快速精準獲取,全程保持高效穩定的運行狀態。

          這不僅是一個任務,還是一次創新能力的試煉。那天,我坐在電腦前,心里琢磨著,這得怎么開始呢?

          好吧,我得先熟悉我們網站的結構,就像了解一個新朋友一樣。我打開了瀏覽器,一頁一頁地翻,仔細觀察那些HTML代碼,找到了BODY、鏈接和標題的蛛絲馬跡。

          根據我們網站結構,我開始了針對這個任務的計劃:

          需求分析:

          抓取頁面內容(BODY):需要從目標網頁中提取出HTML的主體部分,這通常包括文字、圖片、視頻等內容。

          抓取網站鏈接:我需要識別網頁中的超鏈接(如<a>標簽),并提取出它們的href屬性,以獲取其他相關頁面的鏈接。

          抓取頁面標題:頁面標題通常位于<title>標簽內,我要能夠提取并存儲這些標題信息。

          技術選型:

          由于使用C#作為開發語言,我可以使用HttpClient來實現HTTP請求和響應處理。利用解析庫(這次用AngleSharp)來解析網頁內容,提取所需信息。

          主要實現步驟:

          初始化:設置爬蟲的基本配置,如并發請求數、超時時間等。網頁抓取:通過HTTP請求獲取目標網頁的HTML內容。內容解析:使用HTML解析庫提取頁面內容、鏈接和標題。數據演示(實際是要保存的):將提取的信息展示到控制臺中顯示,以便查看、分析和處理。錯誤處理:實現適當的錯誤處理機制,如展示異常記錄等。

          測試與驗證:

          對爬蟲程序進行單元測試,確保每個功能模塊都能正常工作。進行集成測試,確保整個爬蟲流程的流暢性和穩定性。在實際網站上進行驗證,確保抓取到的數據準確無誤。

          維護與更新:

          定期檢查爬蟲程序的運行狀態,及時處理可能出現的問題。根據網站結構和內容的變化,適時更新爬蟲程序以適應新的抓取需求。當然,原來的爬蟲程序需要改進,功能需要增強。

          根據這個計劃,我初步修改了原來的代碼,把要求的三個功能都完成了,看效果:


          關鍵實現代碼:

                  // 發送HTTP GET請求并獲取網頁內容
                  public async Task<string> GetWebPageContentAsync(string url)
                  {
                      try
                      {
                          HttpResponseMessage response=await _httpClient.GetAsync(url);
                          response.EnsureSuccessStatusCode();
                          string content=await response.Content.ReadAsStringAsync();
                          return content;
                      }
                      catch (HttpRequestException e)
                      {
                          Console.WriteLine($"從URL {url} 獲取內容時出錯: {e.Message}");
                          return null;
                      }
                  }
                  // 解析網頁內容,提取<body>、鏈接和標題
                  public async Task CrawlAndParseAsync(string url)
                  {
                      string content=await GetWebPageContentAsync(url);
                      if (content !=null)
                      {
                          var document=_htmlParser.ParseDocument(content);
          
                          // 獲取并打印<body>的內容
                          var bodyContent=document.Body.InnerHtml;
                          Console.WriteLine($"網頁<body>內容:\n{bodyContent}\n");
          
                          // 提取并打印所有鏈接
                          foreach (var link in document.QuerySelectorAll("a[href]"))
                          {
                              var href=link.GetAttribute("href");
                              Console.WriteLine($"找到的鏈接: {href}");
                          }
          
                          // 提取并打印頁面標題
                          var title=document.Title;
                          Console.WriteLine($"頁面標題: {title}");
                      }
                  }

          如有需要完整代碼的朋友,請關注我并留言。


          主站蜘蛛池模板: 国偷自产视频一区二区久| 国产精品一区二区三区99| 四虎永久在线精品免费一区二区 | 中文字幕在线视频一区| 日本免费一区二区三区四区五六区| 国产麻豆精品一区二区三区| 久久精品一区二区| 精品无码人妻一区二区三区| 日韩精品无码免费一区二区三区| 亚洲综合一区二区精品导航| 免费日本一区二区| 国产经典一区二区三区蜜芽| 无码人妻一区二区三区av| 国产精品一区二区三区久久| 色一情一乱一伦一区二区三区日本 | 亚洲一区二区三区亚瑟| 国产精品久久久久久麻豆一区| 3D动漫精品啪啪一区二区下载| 亚洲国产韩国一区二区| 精品综合一区二区三区| 国产精品成人一区二区三区| 国产SUV精品一区二区88| 亚洲国模精品一区| 毛片一区二区三区无码| 相泽亚洲一区中文字幕| 欧美日韩精品一区二区在线观看| 亚洲日本一区二区一本一道| 国产成人精品第一区二区| 夜夜嗨AV一区二区三区| 亚洲爆乳精品无码一区二区| 国产在线观看一区二区三区精品| 在线免费观看一区二区三区| 色窝窝无码一区二区三区| 精品国产亚洲一区二区在线观看 | 蜜桃传媒一区二区亚洲AV| 久久国产午夜一区二区福利| 亚洲无圣光一区二区| 狠狠色综合一区二区| 亚洲av无码片区一区二区三区| 99久久精品费精品国产一区二区| 国内精自品线一区91|