xmfdsh我真是興趣多多,怎么老是靜不下心來搞定一方面的技術,再學點其他的東西,循序漸進,好吧,我又研究網絡爬蟲去了,這是一個簡單版的,參考了網上很多資料,C#來編寫,專門抓取圖片,能夠抓取一些需要cookie的網站,所以功能上還是挺完善的,xmfdsh只研究了三天,因此還有大把需要改進的地方,日后再 慢慢改進,在本文后面附上整個項目,在此獻給喜歡研究C#的朋友們,讓我慢慢地道來:
#region 訪問數據 + Request(int index) /// <summary> /// 訪問數據 /// </summary> private void Request(int index) { try { int depth; string url = ""; //lock鎖住Dictionary,因為Dictionary多線程會出錯 lock (_locker) { //查看是否還存在未下載的鏈接 if (UnDownLoad.Count <= 0) { _workingSignals.FinishWorking(index); return; } //否則的話,把該蟲子標記為在工作 _reqsBusy[index] = true; depth = UnDownLoad.First().Value; url = UnDownLoad.First().Key; IsDownLoad.Add(url, depth);//將URL 加入到已經下載集合里 UnDownLoad.Remove(url); } //網絡協議的東西,不懂網上搜一下,(HttpWebRequest)的使用 //這個需要一點日子理解,xmfdsh不是一下子就弄懂 HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); request.Method = "GET"; request.Accept = "text/html"; request.UserAgent = "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0)"; request.CookieContainer = cookies;//cookie 嘗試 RequestState rs = new RequestState(request, url, depth, index); //回調函數,如果接受到數據的處理方法 var result = request.BeginGetResponse(new AsyncCallback(Received), rs); //也是回調函數,超時的回調函數 ThreadPool.RegisterWaitForSingleObject(result.AsyncWaitHandle, TimeOut, rs, MAXTIME, true); } catch (Exception ex) { _reqsBusy[index] = false; DispatchWork(); } } #endregion
首先上來的是請求某個連接的方法,里面有兩個回調函數,說明這里的編程時異步的,這個異步編程個人覺得需要花點時間去了解和學習,這個不是一朝一夕的,個人覺得C#比較難的地方就在一些接口,工具類,還有異步事件等等。
#region 超時的方法 + TimeOut(object state, bool timedOut) /// <summary> /// 超時的方法 /// </summary> /// <param name="state"></param> /// <param name="timedOut"></param> private void TimeOut(object state, bool timedOut) { //判斷是否超時 if (timedOut) { RequestState rs = state as RequestState; if (rs != null) { //撤銷internet請求 rs.Request.Abort(); } DispatchWork(); } } #endregion
這個是超時的方法,當超時的時候,默認撤銷internet的請求,並回滾,所以這個鏈接下的東西就沒有了,當然超時后,繼續請求下一個鏈接的資源
#region 獲取數據 異步 + Received(IAsyncResult ar) /// <summary> /// 獲取數據 異步 /// </summary> /// <param name="ar"></param> private void Received(IAsyncResult ar) { try { //得到請求進來的參數 RequestState rs = (RequestState)ar.AsyncState; HttpWebRequest request = rs.Request; string url = rs.Url; //獲取響應 HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(ar); if (response != null && response.StatusCode == HttpStatusCode.OK)//成功獲取響應 { //得到資源流 Stream responseStream = response.GetResponseStream(); rs.ResponseStream = responseStream; //處理讀取數據的異步方法 ReceivedData var result = responseStream.BeginRead(rs.Data, 0, rs.BufferSize, new AsyncCallback(ReceivedData), rs); } //響應失敗 else { response.Close(); rs.Request.Abort(); _reqsBusy[rs.Index] = false; DispatchWork(); } } catch (Exception ex) { RequestState rs = (RequestState)ar.AsyncState; _reqsBusy[rs.Index] = false; DispatchWork(); } } #endregion
這個是收到了網站響應的時候做的事情,當然用到了異步來請求的話,數據也只能異步的讀取,因此有了ReceivedData方法來接受數據,並處理,如果出錯或者獲取相應失敗,相應的,回滾,並把該爬蟲蟲子的工作狀態重新置為准備狀態,為下一個鏈接做好准備。
#region 異步操作讀寫 + ReceivedData(IAsyncResult ar) /// <summary> /// 異步操作讀寫 /// </summary> /// <param name="ar">異步操作狀態</param> private void ReceivedData(IAsyncResult ar) { //獲取異步狀態參數 RequestState rs = (RequestState)ar.AsyncState; HttpWebRequest request = rs.Request; System.Net.HttpWebResponse responseImg = request.GetResponse() as System.Net.HttpWebResponse; Stream responseStream = rs.ResponseStream; string url = rs.Url; int depth = rs.Depth; string html = ""; int index = rs.Index; int read = 1; try { //如果改鏈接為圖片來的,需要保存此圖片 if (url.Contains(".jpg") || url.Contains(".png")) { read = responseStream.EndRead(ar); if (read > 0) { MemoryStream ms = new System.IO.MemoryStream(rs.Data, 0, read); BinaryReader reader = new BinaryReader(ms); byte[] buffer = new byte[32 * 1024]; while ((read = reader.Read(buffer, 0, buffer.Length)) > 0) { rs.memoryStream.Write(buffer, 0, read); } //遞歸 再次請求數據 var result = responseStream.BeginRead(rs.Data, 0, rs.BufferSize, new AsyncCallback(ReceivedData), rs); return; } } else { read = responseStream.EndRead(ar); if (read > 0) { //創建內存流 MemoryStream ms = new MemoryStream(rs.Data, 0, read); StreamReader reader = new StreamReader(ms, Encoding.GetEncoding("gb2312")); string str = reader.ReadToEnd(); //添加到末尾 rs.Html.Append(str); //遞歸 再次請求數據 var result = responseStream.BeginRead(rs.Data, 0, rs.BufferSize, new AsyncCallback(ReceivedData), rs); return; } } if (url.Contains(".jpg") || url.Contains(".png")) { //images = rs.Images; SaveContents(rs.memoryStream.GetBuffer(), url); } else { html = rs.Html.ToString(); //保存 SaveContents(html, url); //獲取頁面的鏈接 } } catch (Exception ex) { _reqsBusy[rs.Index] = false; DispatchWork(); } List<string> links = GetLinks(html,url); //得到過濾后的鏈接,並保存到未下載集合 AddUrls(links, depth + 1); _reqsBusy[index] = false; DispatchWork(); } #endregion
這個便是對數據的處理,這里就是重點的,其實也不難,判斷是否為圖片,如果為圖片,保存此圖片,因為目前網絡爬蟲做的還不夠高級的時候爬圖片是比較實際也比較好玩的(還不趕緊找找哪些網站有好多妹子圖片),如果不是圖片,我們認為它為普通html頁面,便讀取其中html代碼,如果有發現有鏈接http或者href便加入到未下載鏈接中。當然讀到的鏈接我們對一些js或者一些css等做了限制(不去讀取這類東西)。
private void SaveContents(byte[] images, string url) { if (images.Count() < 1024*30) return; if (url.Contains(".jpg")) { File.WriteAllBytes(@"d:\網絡爬蟲圖片\" + _index++ + ".jpg", images); Console.WriteLine("圖片保存成功" + url); } else { File.WriteAllBytes(@"d:\網絡爬蟲圖片\" + _index++ + ".png", images); Console.WriteLine("圖片保存成功" + url); } }
#region 提取頁面鏈接 + List<string> GetLinks(string html) /// <summary> /// 提取頁面鏈接 /// </summary> /// <param name="html"></param> /// <returns></returns> private List<string> GetLinks(string html,string url) { //匹配http鏈接 const string pattern2 = @"http://([\w-]+\.)+[\w-]+(/[\w- ./?%&=]*)?"; Regex r2 = new Regex(pattern2, RegexOptions.IgnoreCase); //獲得匹配結果 MatchCollection m2 = r2.Matches(html); List<string> links = new List<string>(); for (int i = 0; i < m2.Count; i++) { //這個原因是w3school的網址,但里面的東西明顯不是我們想要的 if (m2[i].ToString().Contains("www.w3.org")) continue; links.Add(m2[i].ToString()); } //匹配href里面的鏈接,並加到主網址上(學網站的你懂的) const string pattern = @"href=([""'])?(?<href>[^'""]+)\1[^>]*"; Regex r = new Regex(pattern, RegexOptions.IgnoreCase); //獲得匹配結果 MatchCollection m = r.Matches(html); // List<string> links = new List<string>(); for (int i = 0; i < m.Count; i++) { string href1 = m[i].ToString().Replace("href=", ""); href1 = href1.Replace("\"", ""); //找到符合的,添加到主網址(一開始輸入的網址)里面去 string href = RootUrl + href1; if (m[i].ToString().Contains("www.w3.org")) continue; links.Add(href); } return links; } #endregion
提取頁面鏈接的方法,當讀到發現這個為html代碼的時候,繼續解讀里面的代碼,找到里面的網址鏈接,也正是這樣才有網絡爬蟲的功能(不然只能提取本頁面就沒意思了),其中http鏈接要提取就理所當然,href里面的話是因為。。。。(學網站你們懂的,不好解釋)里面放的幾乎都是圖片,文章,因此才有了上面要處理href這類代碼。
#region 添加url到 UnDownLoad 集合 + AddUrls(List<string> urls, int depth) /// <summary> /// 添加url到 UnDownLoad 集合 /// </summary> /// <param name="urls"></param> /// <param name="depth"></param> private void AddUrls(List<string> urls, int depth) { lock (_locker) { if (depth >= MAXDEPTH) { //深度過大 return; } foreach (string url in urls) { string cleanUrl = url.Trim(); int end = cleanUrl.IndexOf(' '); if (end > 0) { cleanUrl = cleanUrl.Substring(0, end); } if (UrlAvailable(cleanUrl)) { UnDownLoad.Add(cleanUrl, depth); } } } } #endregion
#region 開始捕獲 + DispatchWork() /// <summary> /// 開始捕獲 /// </summary> private void DispatchWork() { for (int i = 0; i < _reqCount; i++) { if (!_reqsBusy[i]) { Request(i); Thread.Sleep(1000); } } } #endregion
這個函數就是讓那些蟲子工作的,其中_reqCount的值是一開始弄上去的,其實形象理解就是你放出蟲子的個數,這個程序里面我默認放的20,隨時可以修改。說到某些網站需要cookie的話是通過一開始先訪問輸入的網址,當然也是用HttpWebRequest幫助類,cookies = request.CookieContainer; //保存cookies,在后面訪問后續網址的時候就加上去就行了request.CookieContainer = cookies;//cookie 嘗試。應用了cookie才能訪問的網站的話,根網頁是不需要的,也就好比百度圖片的網址http://image.baidu.com/是不需要的,但如果很唐突的訪問里面的圖片的話就要附上cookie了,所以這個問題也解決,xmfdsh發現這個程序還是有一些網站不能去抓圖,抓到一定數量就停了,具體原因不知道,后面再慢慢改進
附上源碼:http://files.cnblogs.com/xmfdsh/%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB.rar