我們經常瀏覽網頁,有時候看到一些精美的圖片,想下載下來保存,但是太多,如果一張一張的下載,那太費時了;如果你喜歡看書,看小說,那么瀏覽小說網站是常有的事,但是有時候我們不能聯網,比如農村老家,如果還想看,我們有沒有想過一次性保存到手機里。網站上的小說都是一章一個頁面,難道要我們一次一個章節復制粘貼保存?這個事我真的干了,為了一本小說,我連續花了三個小時,目不轉睛地盯着電腦復制粘貼。還有抓取別人的網站一些有用數據,為我們服務,比如說找工作的事,買彩票的事等等。
現在我要來實現一個下載他趣網站上性感漂亮女性圖片,發現這個網站也是通過別人博客得知的。那些圖片很不錯,養眼,所以對這個網站感興趣了,也就有動力了。
今天寫這個博客就是為了記錄一次學習爬蟲過程,分享自己遇到一些問題,以及解決問題的方法經驗。
常規思路是,首先打開我們要爬取的網站,然后點擊審查網頁元素,分析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