一次爬蟲實踐學習(C#)


     我們經常瀏覽網頁,有時候看到一些精美的圖片,想下載下來保存,但是太多,如果一張一張的下載,那太費時了;如果你喜歡看書,看小說,那么瀏覽小說網站是常有的事,但是有時候我們不能聯網,比如農村老家,如果還想看,我們有沒有想過一次性保存到手機里。網站上的小說都是一章一個頁面,難道要我們一次一個章節復制粘貼保存?這個事我真的干了,為了一本小說,我連續花了三個小時,目不轉睛地盯着電腦復制粘貼。還有抓取別人的網站一些有用數據,為我們服務,比如說找工作的事,買彩票的事等等。


    現在我要來實現一個下載他趣網站上性感漂亮女性圖片,發現這個網站也是通過別人博客得知的。那些圖片很不錯,養眼,所以對這個網站感興趣了,也就有動力了。
  

     今天寫這個博客就是為了記錄一次學習爬蟲過程,分享自己遇到一些問題,以及解決問題的方法經驗。

    

    常規思路是,首先打開我們要爬取的網站,然后點擊審查網頁元素,分析HTML結構如下圖

然后就開始寫代碼,初看這肯定要從HTML元素中獲取圖片鏈接了。於是我還下載了Jumony 一個提取網頁元素的幫助類。

 url = "http://www.taqu.cn/community/";
            HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url);
            using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
            {
                // 請求成功的狀態碼:200
                if (response.StatusCode == HttpStatusCode.OK)
                {
                    using (Stream stream = response.GetResponseStream())
                    {
                        using (StreamReader reader = new StreamReader(stream))
                        {
                            string html = reader.ReadToEnd();

                            var parser = new JumonyParser();
                            var doc = parser.Parse(html);
                            var lists = doc.Find("#subject_list > ul > li");
                            foreach (var li in lists)
                            {
                                var img = li.FindFirst("img");
                                var src =img.Attribute("src").Value();
                                DownloadImage(src);
                            }
                            //Console.WriteLine(html);
                        }
                    }
                }
                else
                {
                    Console.WriteLine("服務器返回錯誤:{0}", response.StatusCode);
                }
            }
        

 

沒有想到string html = reader.ReadToEnd(); 獲取的是一張算作空白頁,根本就沒有我審查元素看到的內容,沒有我想要的元素,那要怎么辦?難道錯了?不行呀,於是我又通過這個觀察如下圖,有圖片的請求,也有內容。——原來是ajax請求呀!

 

 

再仔細看,不是有請求么,而且有返回數據

 

點擊他趣girl

 

返回數據

 

 在Headers這一欄里我們可以發現請求頭部一些信息,可以請求之后響應一些信息。主要看請求的。

 

 分析得知,在代碼里我們要發送請求需要填寫哪些內容並注意事項了。代碼如下,參考別人的,后面會說明。

  public static string GetContent(string method, string url, string postData = "", CookieContainer cookie = null)
        {
            HttpWebResponse response = null;
            HttpWebRequest request = null;
            if (cookie == null)
                cookie = new CookieContainer();
            // 准備請求...
            try
            {
                // 設置參數
                request = WebRequest.Create(url) as HttpWebRequest;
                request.CookieContainer = cookie;
                request.AllowAutoRedirect = true;
                request.Method = method.ToUpper();
                request.ContentType = "application/json, text/javascript, */*; q=0.01";
                string userAgent = string.Format("Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36)");//這個根據電腦不同而改變
                request.UserAgent = userAgent;

                if (method.ToUpper() == "POST")
                {
                    request.ContentLength = postData.Length;
                    if (!string.IsNullOrEmpty(postData))
                    {
                        byte[] data = Encoding.Default.GetBytes(postData);
                        request.ContentLength = data.Length;
                        using (Stream outstream = request.GetRequestStream())
                        {
                            outstream.Write(data, 0, data.Length);
                        }
                    }
                }
                //發送請求並獲取相應回應數據
                response = request.GetResponse() as HttpWebResponse;
                //直到request.GetResponse()程序才開始向目標網頁發送Post請求
                using (Stream instream = response.GetResponseStream())
                {
                    using (StreamReader sr = new StreamReader(instream, Encoding.Default))
                    {

                        //返回結果網頁(html)代碼
                        string content = sr.ReadToEnd();
                        return UnicodeToGB(content);
                    }
                }
            }
            catch (Exception ex)
            {
                string err = ex.Message;
                return err;
            }
            finally
            {
                if (response != null)
                    response.Close();
            }
        }

參考:http://www.cnblogs.com/shoufengwei/p/5654491.html

請求方法是有了,但是返回的數據好像不是漢字哦,是Unicode編碼過的數據,於是有了轉碼這一步

 public static string UnicodeToGB(string text)
        {
            System.Text.RegularExpressions.MatchCollection mc = System.Text.RegularExpressions.Regex.Matches(text, "\\\\u([\\w]{4})");
            if (mc != null && mc.Count > 0)
            {
                foreach (System.Text.RegularExpressions.Match m2 in mc)
                {
                    string v = m2.Value;
                    string word = v.Substring(2);
                    byte[] codes = new byte[2];
                    int code = Convert.ToInt32(word.Substring(0, 2), 16);
                    int code2 = Convert.ToInt32(word.Substring(2), 16);
                    codes[0] = (byte)code2;
                    codes[1] = (byte)code;
                    text = text.Replace(v, Encoding.Unicode.GetString(codes));
                }
            }
            else
            {

            }
            return text;
        }

參考:http://www.cnblogs.com/guardianf/archive/2012/08/21/2649147.html

但是,但是數據是json,我要怎么獲取其中的圖片請求鏈接呢?我首先想到的事json轉datatable 然后循環依次獲得。想法沒有錯,但是怎么寫,我又不會。只有網上找了,一搜真的不少,滿懷喜悅之情,復制粘貼,運行,試了不少,不是這里報錯,就是轉化不成功,一下子失望透頂了。怎么辦,怎么辦。想到網上一些現成方法,應該只適合作者的項目,沒有通用性,一下子就輕松多了。再找找看,說不定有的。

這里舉一個報錯的轉化方法

/// <summary>
        /// json轉換為DataTable
        /// </summary>
        /// <param name="json">需要轉化的json格式字符串</param>
        /// <returns></returns>
        public DataTable updateInfo(string json)
        {
            System.Web.Script.Serialization.JavaScriptSerializer jss =new System.Web.Script.Serialization.JavaScriptSerializer();
            object[] obj = (object[])jss.DeserializeObject(json);
            Dictionary<string, object> dic;
            DataRow dr;
            DataTable dt = getDataTable();
            foreach (object _obj in obj)
            {
                dr = dt.NewRow();
                dt.Rows.Add(dr);
                dic = (Dictionary<string, object>)_obj;
                dr["id"] = dic["id"];
                dr["img_url"] = dic["img_url"];
                dr["title"] = dic["title"];

            }
            return dt;
        }

 再次改造成功的json轉換為DataTable

        /// <summary>
        /// json轉換為DataTable
        /// </summary>
        /// <param name="json">需要轉化的json格式字符串</param>
        /// <returns></returns>
        public DataTable updateInfo(string json)
        {
            System.Web.Script.Serialization.JavaScriptSerializer jss = new System.Web.Script.Serialization.JavaScriptSerializer();
           // KeyValuePair<string, object> obj = (KeyValuePair<string, object>)jss.DeserializeObject(json);
            var obj = (object)jss.DeserializeObject(json);

            System.Collections.Generic.Dictionary<string, object> xm = (System.Collections.Generic.Dictionary<string, object>)obj;
            DataTable dt = getDataTable();
            DataRow dr;
            Dictionary<string, object> dic;
            object  test="";
            foreach (var x in xm)
            {
                if (x.Key == "info")
                {
                    System.Collections.Generic.Dictionary<string, object> xm3 = (System.Collections.Generic.Dictionary<string, object>)x.Value;

                    foreach (var y in xm3)
                    {
                        if (y.Key == "data") 
                        {
                            var  xml = (object [])y.Value;

                            foreach (object _obj in xml)
                            {
                                dr = dt.NewRow();
                                dt.Rows.Add(dr);
                                dic = (Dictionary<string, object>)_obj;
                                dr["id"] = dic["id"];
                                dr["img_url"] = dic["img_url"];
                                dr["title"] = dic["title"];

                            }
                           
                        }
                    }
                }
            }
            return dt;
        }

 

經歷大半天尋找,實踐運行總算有一個轉化成功的了。

  #region 將json轉換為DataTable
        /// <summary>
        /// 將json轉換為DataTable 參考 http://www.jb51.net/article/61990.htm
        /// </summary>
        /// <param name="strJson">得到的json</param>
        /// <returns></returns>
        private DataTable JsonToDataTable(string strJson)
        {
            //轉換json格式
            strJson = strJson.Replace(",\"", "*\"").Replace("\":", "\"#").ToString();
            //取出表名   
            var rg = new Regex(@"(?<={)[^:]+(?=:\[)", RegexOptions.IgnoreCase);
            string strName = rg.Match(strJson).Value;
            DataTable tb = null;
            //去除表名   
            strJson = strJson.Substring(strJson.IndexOf("[") + 1);
            strJson = strJson.Substring(0, strJson.IndexOf("]"));
            //獲取數據   
            rg = new Regex(@"(?<={)[^}]+(?=})");
            MatchCollection mc = rg.Matches(strJson);
            for (int i = 0; i < mc.Count; i++)
            {
                string strRow = mc[i].Value;
                string[] strRows = strRow.Split('*');
                //創建表   
                if (tb == null)
                {
                    tb = new DataTable();
                    tb.TableName = strName;
                    foreach (string str in strRows)
                    {
                        var dc = new DataColumn();
                        string[] strCell = str.Split('#');
                        if (strCell[0].Substring(0, 1) == "\"")
                        {
                            int a = strCell[0].Length;
                            dc.ColumnName = strCell[0].Substring(1, a - 2);
                        }
                        else
                        {
                            dc.ColumnName = strCell[0];
                        }
                        tb.Columns.Add(dc);
                    }
                    tb.AcceptChanges();
                }
                //增加內容   
                DataRow dr = tb.NewRow();
                for (int r = 0; r < strRows.Length; r++)
                {
                    dr[r] = strRows[r].Split('#')[1].Trim().Replace("", ",").Replace("", ":").Replace("\"", "");
                }
                tb.Rows.Add(dr);
                tb.AcceptChanges();
            }
            return tb;
        }
        #endregion

 

現在連接也有了,最后一般是下載了,我們來分析單個鏈接。

沒有了cookie,我們只要一個一個鏈接請求下載就可以了。於是我有了兩個方法,肯定不止這兩個了。

方法一,模仿上面那個有cookie的

 

 public void  GetContent2(string url, string destFileName, string method)
        {
            HttpWebResponse response = null;
            HttpWebRequest request = null;
            try
            {
               
                // 設置參數
                //request.Host = "183.60.137.147:80";
              //  request.Address[""] = new Uri("");
                
                request = WebRequest.Create(url) as HttpWebRequest;
                request.AllowAutoRedirect = true;
                request.Host = "forumimg01.touchcdn.com";
                request.Method = method.ToUpper();
                request.Headers["Accept-Encoding"] = "gzip, deflate, sdch";
                request.ContentType = "image/webp,image/*,*/*;q=0.8";
                string userAgent = string.Format("Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36)");//這個根據電腦不同而改變
                request.UserAgent = userAgent;

                //發送請求並獲取相應回應數據
                response = request.GetResponse() as HttpWebResponse;
                //直到request.GetResponse()程序才開始向目標網頁發送Post請求
                using (Stream instream = response.GetResponseStream())
                {
                   
                        //返回結果網頁(html)代碼
                        using (FileStream fileStream = new FileStream(destFileName, FileMode.Create))
                        {
                            instream.CopyTo(fileStream);//stream 寫入到fileStream中
                        }
                  
                }
            }
            catch (Exception ex)
            {
                string err = ex.Message; 
            }
            finally
            {
                if (response != null)
                    response.Close();
            }
        }

 

方法二,請求整個網頁的

 

 private void DownloadImage2(string objUrl, string destFileName)
        {
           // string destFileName = Path.Combine(destDir, Path.GetFileName(objUrl));
            HttpWebRequest request =
                (HttpWebRequest)HttpWebRequest.Create(objUrl);
            // 欺騙服務器判斷URLReferer
            // request.Referer = "http://image.baidu.com";
            using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
            {
                if (response.StatusCode == HttpStatusCode.OK)
                {
                    using (Stream stream = response.GetResponseStream())
                    {
                        using (FileStream fileStream = new FileStream(destFileName, FileMode.Create))
                        {
                            stream.CopyTo(fileStream);//stream 寫入到fileStream中
                        }
                    }
                }
                else
                {
                    throw new Exception("下載" + objUrl + "失敗,錯誤碼:" + response.StatusCode);
                }
            }
        }

 

 下載保存路徑方法

   private void DownloadImage(string id, string objUrl)
        {
            destDir = txtSavePath.Text.Trim();
            if (string.IsNullOrEmpty(destDir))
            {
                MessageBox.Show("請選擇要保存的文件夾!");
                return;
            }
            if (!destDir.EndsWith("\\"))
            {
                destDir += "\\";
            }
            
            string destFileName = Path.Combine(destDir, Path.GetFileName(objUrl));

          //  string urlimg = objUrl + "?imageView/2/w/380/interface/1";這個下載下來是小圖,所以不用它才是大圖
            DownloadImage2(objUrl, destFileName);
           // GetContent2(objUrl, destFileName, "GET");
          
        }

 

點擊開始執行

   private void btnStart_Click(object sender, EventArgs e)
        { 
          //1到20頁
            btnStart.Enabled = false;
            for(int i=0 ;i<20;i++){
                getbasedata(i + 1);
            }
        }

調用請求方法

  public void getbasedata(int num)//num是頁碼
        {
            string url = string.Format("http://www.taqu.cn/ajax/Forum/getFindListByTab?tab_id=119&limit=10&score=1468547984&page={0}", num);
            string method = "GET";

            DataTable dt = JsonToDataTable(GetContent(method, url));
            if (dt.Rows.Count > 0)
            {
                foreach (DataRow dr in dt.Rows)
                {
                    //string urlimg = dr["img_url"].ToString()+ "?imageView/2/w/380/interface/1";
                    DownloadImage(dr["id"].ToString(), dr["img_url"].ToString().Replace(@"\", ""));//這里@符號阻止轉義
                    //因為json轉化為datatable連接變成這樣的了——http:\/\/forumimg01.touchcdn.com\/index\/3e637fe0142008158574c24a278d7804.jpg
                    //正確的是——http://forumimg01.touchcdn.com/index/3e637fe0142008158574c24a278d7804.jpg 所以需要替換
                }
            }
        }

 

 

 忘了 這個全局的變量 private string destDir; // 目標文件夾

運行的效果

 

 

 主要和關鍵代碼都放上來了。

窗體代碼去這里下載 博客最底端  http://www.cnblogs.com/edisonchou/p/4175190.html

能想到的,都寫上了,其實還可以更完善,比如加上IP代理,,一些數據保存到數據庫,不過這個工程太大了。這也不是我要寫這篇文章的目的,我只要這些圖片,這些樂趣。

原來認真做一件事,真的可以獲得意想不到的的樂趣,也打開了自己興趣之門。

原來我也可以做到,不要臨淵羡魚,自己動手一干!

參考:

http://www.cnblogs.com/edisonchou/p/4175190.html

http://www.cnblogs.com/haigege/p/5492177.html

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM