自己動手寫工具:百度圖片批量下載器


開篇:在某些場景下,我們想要對百度圖片搜出來的東東進行保存,但是一個一個得下載保存不僅耗時而且費勁,有木有一種方法能夠簡化我們的工作量呢,讓我們在離線模式下也能爽爽地瀏覽大量的美圖呢?於是,我們想到了使用網絡抓取去幫我們去下載圖片,並且保存到我們設定的文件夾中,現在我們就來看看如何來設計開發一個這樣的圖片批量下載器。

一、關於網絡抓取與爬蟲

  網絡蜘蛛的主要作用是從Internet上不停地下載網絡資源。它的基本實現思想就是通過一個或多個入口網址來獲取更多的URL,然后通過對這些URL所指向的網絡資源下載並分析后,再獲得這些網絡資源中包含的URL,以此類推,直到再沒有可下的URL為止。

  網絡蜘蛛的實現的一般步湊可以分為以下幾步:

  (1) 指定一個(或多個)入口網址{ 如http://www.xx.com),並將這個網址加入到下載隊列中(這時下載隊列中只有一個或多個入口網址)}。
    (2) 負責下載網絡資源的線程從下載隊列中取得一個或多個URL,並將這些URL所指向的網絡資源下載到本地{ 在下載之前,一般應該判斷一下這個URL是否已經被下載過,如果被下載過,則忽略這個URL }。如果下載隊列中沒有URL,並且所有的下載線程都處於休眠狀態,說明已經下載完了由入口網址所引出的所有網絡資源。這時網絡蜘蛛會提示下載完成,並停止下載。
    (3)分析這些下載到本地的未分析過的網絡資源{ 一般為html代碼 },並獲得其中的URL{ 如標簽<a>中href屬性的值 }。
    (4)將第3步獲得的URL加入到下載隊列中,然后重新執行第2步。

二、關於圖片批量下載器

2.1 手工下載工作量大

  在平常的使用中,我們經常會去百度圖片搜索圖片,然后保存到本地進行瀏覽或二次使用。但是,如果我們需要使用很多個同一題材的圖片的時候,單個地手工去一張一張的下載保存效率就會顯得很低下。這時候,我們不由得想找一個方法,讓計算機幫我們去做這件事兒!

  但是,想破頭顱都沒想到辦法。於是,我們打開F12開發者工具,發現了這么一個AJAX請求,有點意思:

  查看這個AJAX請求的HTTP報文信息,發現它返回了一大串的JSON數據,將其復制到JSON在線查看器(http://www.bejson.com/jsonview2/)中查看,原來所有的圖片列表信息都在這個JSON中被返回到瀏覽器端。

2.2 批量下載爽爽看圖

  (1)看到了上面的那個請求,我們的心中大概就有譜了。在此,我們先來對剛剛那個AJAX請求的地址來分析一下:

Request URL:http://image.baidu.com/i?tn=resultjsonavatarnew&ie=utf-8&word=%E5%AE%8B%E6%99%BA%E5%AD%9D&cg=star&pn=60&rn=60&z=&itg=0&fr=&width=&height=&lm=-1&ic=0&s=0
Request Method:GET
Status Code:200 OK

  ①這個AJAX請求首先是通過GET方式傳遞的,所有的參數都是通過QueryString的方式跟在URL地址后,也就是所有的參數都在后邊跟着,包括我們輸入的搜索詞,每頁的頁容量(大小),當前是第幾頁等參數;

  ②再來看看這個請求地址后面的參數,找出我們所需要的幾個重要參數。其中,word是搜索的關鍵詞,只是后邊經過了URL編碼,rn是頁容量(或者說是頁大小,即一頁有多少張圖片,可以看出默認是60張圖片),而pn則代表了是一共請求的圖片數量,可以通過pn/rn得到當前是第幾頁,例如這里pn=60,rn=60,那么請求的是第一頁。

  (2)現在我們來梳理一下我們這個下載器的工作流程:

  (3)下面我們來看看我們的實現后的圖片下載器的樣子如何:

三、關鍵代碼實現

3.1 聲明一個異步委托去執行圖片下載操作,與UI線程分開防止界面卡死

            // 聲明一個異步委托去處理圖片下載操作
            Action downloadAction = new Action(() =>
            {
                ProcessDownload(keyword);
            });
            // 聲明一個下載完成后的回調函數
            AsyncCallback callBack = new AsyncCallback(asyncResult =>
            {
                downloadAction.EndInvoke(asyncResult);
                progressBar.BeginInvoke(new Action(() =>
                {
                    progressBar.Value = progressBar.Maximum;
                }));
                txtLogs.BeginInvoke(new Action(() =>
                {
                    txtLogs.AppendText("下載圖片操作結束!" + Environment.NewLine);
                }));
                btnStart.BeginInvoke(new Action(() =>
                {
                    btnStart.Enabled = true;
                }));
            });
            // 執行該異步委托
            IAsyncResult result = downloadAction.BeginInvoke(callBack, null);
            // 主線程繼續干自己的事兒
            txtLogs.AppendText("正在下載圖片中..." + Environment.NewLine);

  使用異步委托,關鍵在於設置其回調函數,這里在回調函數中結束線程操作,並通過UI控件的BeginInvoke實現安全地跨線程調用(類似於使用委托來操作)。

3.2 使用WebRequest向指定服務器端發出Http請求

        private void ProcessDownload(string keyword)
        {
            int pageCount = (int)numPageCount.Value;
            sumCount = pageCount * 60;
            for (int i = 0; i < pageCount; i++)
            {
                HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create("http://image.baidu.com/i?tn=resultjsonavatarnew&ie=utf-8&word=" + Uri.EscapeDataString(keyword) + "&pn=" + pageCount * 60 + "&cg=girl&rn=60&itg=0&lm=-1&ic=0&s=0");
                using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
                {
                    if (response.StatusCode == HttpStatusCode.OK)
                    {
                        using (Stream stream = response.GetResponseStream())
                        {
                            try
                            {
                                // 下載指定頁的所有圖片
                                DownloadPage(stream);
                            }
                            catch (Exception ex)
                            {
                                // 跨線程訪問UI線程的txtLogs
                                txtLogs.BeginInvoke(new Action(() =>
                                    {
                                        txtLogs.AppendText(ex.Message + Environment.NewLine);
                                    }));
                            }
                        }
                    }
                    else
                    {
                        MessageBox.Show("獲取第" + pageCount + "頁失敗:" + response.StatusCode);
                    }
                }
            }
        }

  這里使用了try..catch將下載時碰到的異常信息填充到了TextBox文本框中。

3.3 使用第三方JSON組件解析JSON數據

        private void DownloadPage(Stream stream)
        {
            using (StreamReader reader = new StreamReader(stream))
            {
                string jsonData = reader.ReadToEnd();
                // 解析JSON,分析JSON
                JObject objectRoot = JsonConvert.DeserializeObject(jsonData) as JObject;
                JArray imgsArray = objectRoot["imgs"] as JArray;
                for (int i = 0; i < imgsArray.Count; i++)
                {
                    JObject img = imgsArray[i] as JObject;
                    string objUrl = (string)img["objURL"];
                    //txtLogs.AppendText(objUrl + Environment.NewLine); // 測試獲取圖片路徑
                    try
                    {
                        // 下載具體的某一張圖片
                        DownloadImage(objUrl);
                        // 更新進度條
                        progressBar.BeginInvoke(new Action(() =>
                            {
                                progressBar.Value = i * 100 / sumCount;
                            }));
                        // 更新文本框
                        txtLogs.BeginInvoke(new Action(() =>
                            {
                                txtLogs.AppendText("已下載:" + objUrl + Environment.NewLine);
                            }));
                    }
                    catch (Exception ex)
                    {
                        // 跨線程訪問UI線程的txtLogs控件
                        txtLogs.BeginInvoke(new Action(() =>
                            {
                                txtLogs.AppendText("【異常:" + ex.Message + "" + Environment.NewLine);
                            }));
                    }
                }
            }
        }

  這里使用的是Newtonsoft.Json組件,在返回的JSON數據中,找到imgs集合,對其進行遍歷,找出其中的objURL並一一地進行下載到本地。

3.4 偽造URLRerfer並使用FileStream將其保存到本地

        private void DownloadImage(string objUrl)
        {
            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);
                        }
                    }
                }
                else
                {
                    throw new Exception("下載" + objUrl + "失敗,錯誤碼:" + response.StatusCode);
                }
            }
        }

  這里通過在客戶端偽造URLRerfer讓服務器端誤以為是自己的站內請求(偽造我們的請求不是騙它流量的),然后通過FileStream將返回的圖片響應流保存到指定的文件夾中。

四、個人開發小結

4.1 運行結果演示

  這里我們批量下載一頁(60張)的美女圖片到指定的文件夾中,看看下載器是否真的幫助我們下載了圖片:

  (1)程序的運行過程:

  (2)下載后的圖片文件夾:

4.2 更改搜索名詞

  這里我們將“美女”改為了“宋智孝”后,發現下載器未能成功下載圖片。經過分析,原來百度圖片搜索中,每個搜索詞所生成的AJAX請求都不同,因此本下載器目前不具有通用性,也就是說每次更換搜索詞都需要改代碼,主要是改HttpWebRequest那的URL地址。

  (1)更改URL處的代碼:

  (2)程序的運行過程:

  (3)下載的圖片文件:

4.3 不是小結的小結

  本次我們實現了一個小工具,它可以幫我們下載我們想要搜索的圖片到執行的圖片文件夾中,讓我們可以離線爽爽地看美圖。設計開發這樣一個工具,最重要的莫過於:分析Http報文、解析返回數據、線程創建與同步、異步操作、文件流、進度條的更新(跨線程的調用)等等,本次開發中都多多少少涉及到了其中的一些東東。當然,不足之處還有很多,例如工具的通用性不足,每次更換搜索詞都需要更改代碼,可配置型不高等等。這里提供一個我的代碼實現DEMO,有興趣的朋友也可以自行修改並進行擴展。

參考資料

  (1)楊中科,《自己動手寫美女圖片下載器》:http://www.rupeng.com/Courses/Index/14

  (2)冰封的心,《C#2.0實現抓取網絡資源的網絡蜘蛛》:http://www.cnblogs.com/yibinboy/articles/1236356.html

附件下載

  MyPictureDownloader v1.0:https://github.com/EdisonChou/EDC.MyPictureDownloader.Sample  

 


免責聲明!

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



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