[C#/WinForm] 無名關站備份相簿(下載實體照片)的懶人程式分享

[C#/WinForm] 無名關站備份相簿(下載實體照片)的懶人程式分享

自己寫的這支程式主要搭配2013.9.2開始,無名小站公告終止服務的相簿備份圖片下載

載點在文章最後面

 

前言

無名小站要關站了~

http://www.wretch.cc/

image

若照著官網說明,自己的無名資料備份完會出現一個超連結

image

從超連結下載完.zip壓縮檔,但是

解壓完不會有實體照片,只有一堆相簿編號的.txt檔,裡面只有超連結Orz…

image

回到官網FAQ查看

image

還需要另外找軟體,下載實體檔案圖片,而且官網介紹的軟體,麻煩的是還要一個一個.txt檔打開做匯入動作

那相簿如果有100多本,不就開.txt開100次匯到手軟O_O?

 

所以…自己來客製化好了…

程式目標只要讓使用者輸入相簿的detail.html檔所在檔案總管路徑

程式就自動下載檔案到detail.html相同的目錄下,並依相簿名稱做資料夾分類,減少使用者的操作,免安裝綠色版(不過由於使用.net 4技術開發,XP系統可能要先裝.net framework 4才能執行)

因為我只有備份相簿的需求,影片、MP3的檔案此程式不支援,所以開放Source Code,看有需要的人就自行改寫吧

邏輯說明都在註解裡,匆匆忙忙花一個早上寫完,架構有點亂,程式品質不保證XD

 

程式碼實作

UI設計

image

後置程式碼


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using HtmlAgilityPack;
using System.Net;
using System.Security.Policy;
using System.Threading;

namespace WretchAlbumBackupDownload
{
    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();
            btnGo.Enabled = true;
            btnStop.Enabled = false;
        }
        //抓圖Thread
        public Thread mainThread = null;


        /// <summary>
        /// 開始下載
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnGo_Click(object sender, EventArgs e)
        {
            mainThread = new Thread(new ParameterizedThreadStart((o) =>
            {
                try
                {
                    //detail.html檔路徑
                    string detailHtmlPath = txtDetailHtmlPath.Text.Trim();

                    if (File.Exists(detailHtmlPath))
                    { //detail.html檔有存在的話
                        btnGo.Invoke(new Action(() => {
                            btnGo.Enabled = false;
                        }));
                        btnStop.Invoke(new Action(() => {
                            btnStop.Enabled = true;
                        }));
                        

                        //detail.html的資料夾位置
                        string detailHtmlDir = Path.GetDirectoryName(detailHtmlPath);

                        //打開detail.html解析有哪些.txt檔
                        HtmlAgilityPack.HtmlDocument htmlDoc = new HtmlAgilityPack.HtmlDocument();

                        using (FileStream fs = new FileStream(detailHtmlPath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
                        {
                            //撈html資料進HtmlDocument物件
                            htmlDoc.Load(fs, Encoding.UTF8);
                            IEnumerable<HtmlNode> tableNodes = htmlDoc.DocumentNode.Descendants("table");
                            //儲存相簿名稱及其圖片的超連結
                            Dictionary<string, List<string>> albums = new Dictionary<string, List<string>>();
                            foreach (HtmlNode table in tableNodes)
                            {

                                //取得相簿名稱
                                string albumTitle = table.SelectSingleNode("tr[2]/td[1]").InnerText;
                                //圖片超連結儲存
                                List<string> picUrls = new List<string>();
                                //取得有.txt檔名
                                string txtFileName = table.SelectSingleNode("tr[6]/td[1]").InnerText;
                                //txt檔的路徑
                                string txtFilePath = Path.Combine(detailHtmlDir, txtFileName);
                                //檢查該.txt存在
                                if (File.Exists(txtFilePath))
                                {
                                    //打開.txt檔
                                    using (FileStream fsTxt = new FileStream(txtFilePath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
                                    {

                                        using (StreamReader sr = new StreamReader(fsTxt))
                                        {
                                            //暫存圖片Url的變數
                                            string picUrl = string.Empty;
                                            //一列一列地讀取照片Url
                                            while ((picUrl = sr.ReadLine()) != null)
                                            {
                                                if (picUrl.StartsWith("http"))
                                                {
                                                    picUrls.Add(picUrl);
                                                }
                                                
                                            }
                                            //相簿名稱防呆重覆
                                            if (albums.Keys.Contains(albumTitle))
                                            {
                                                albumTitle = albumTitle +"_"+ Guid.NewGuid().ToString();//取不重覆的名稱
                                            }
                                            //加入一本相簿
                                            albums.Add(albumTitle, picUrls);

                                        }
                                    }//End using
                                }//End if
                            }//End foreach

                            //有相簿資料待下載
                            if (albums.Count > 0)
                            {
                                //走訪每本相簿
                                foreach (KeyValuePair<string, List<string>> album in albums)
                                {
                                    //將相簿名稱置換成檔案總管可接受的名稱
                                    string albumTitle = this.ReplaceUrlToOK(album.Key);
                                    //相簿預計存放的位置
                                    string albumDirPath = Path.Combine(detailHtmlDir, albumTitle);
                                    if (!Directory.Exists(albumDirPath))
                                    {//建立相簿資料夾
                                        Directory.CreateDirectory(albumDirPath);
                                    }
                                    //下載此相簿的圖片
                                    foreach (string picHttpUrl in album.Value)
                                    {
                                        if (!string.IsNullOrEmpty(picHttpUrl))
                                        {
                                            this.DonwloadOnePic(picHttpUrl, albumDirPath);
                                            txtLog.Invoke(new Action(() =>
                                            {
                                                txtLog.AppendText("下載完圖片:" + picHttpUrl + "。保存目錄:" + albumDirPath);
                                            }));
                                        }
                                     
                                    }

                                }
                            }



                            //下載完成
                            txtLog.Invoke(new Action(()=>{
                                txtLog.AppendText(Environment.NewLine+"檔案已全部下載完成" + Environment.NewLine);
                                }));
                            btnGo.Invoke(new Action(() => {
                                btnGo.Enabled = true;
                            
                            }));
                            btnStop.Invoke(new Action(() => {
                                btnStop.Enabled = false;
                            }));
                            
                        }//End using

                    }//End if
                }
                catch (Exception ex)
                {
                      
                    txtLog.Invoke(new Action( ()=>{
                        txtLog.AppendText(  ex.Message + Environment.NewLine);
                    }  ));
                
                    this.AbortMainThread();
                }
            }));
            //啟動執行緒
            mainThread.Start();

          
        }

       

        /// <summary>
        /// 中止下載
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnStop_Click(object sender, EventArgs e)
        {
            try
            {
                //中止執行緒
                this.AbortMainThread();

                btnGo.Enabled = true;
                btnStop.Enabled = false;
            }
            catch (Exception ex)
            {

               // txtLog.AppendText("程式發生錯誤:" + ex.Message + Environment.NewLine);
            }
        }
        /// <summary>
        /// 中止執行緒
        /// </summary>
        private void AbortMainThread()
        {
            if (mainThread != null)
            {
                try
                {

                }
                finally
                {
                    mainThread.Abort();

                }


            }

        }


        /// <summary>
        /// 存放目錄用
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        private  string ReplaceUrlToOK(string url)
        {
            url = url.Replace("|", "").Replace("\"", "”");
            url = url.Replace("`", "‘");//.Replace("-", "-");
            //url = url.Replace("=", "=")

            url = url.Replace("[", "〔");
            url = url.Replace("]", "〕").Replace("\\", "\");
            url = url.Replace(";", ";").Replace("'", "’");
            url = url.Replace(",", ",");//.Replace(".", ".");
            url = url.Replace("/", "/").Replace("~", "~");
            url = url.Replace("!", "!").Replace("@", "@");
            url = url.Replace("#", "#").Replace("$", "$");
            url = url.Replace("%", "%").Replace("^", "^");
            url = url.Replace("&", "&").Replace("*", "*");
            // url = url.Replace("(", "(").Replace(")", ")");
            url = url.Replace("_", "_").Replace("+", "+");
            url = url.Replace(":", ":").Replace("\"", "”");
            url = url.Replace("<", "<").Replace(">", ">");
            url = url.Replace("?", "?");
            return url;
        }
        /// <summary>
        /// 下載一張圖片
        /// </summary>
        /// <param name="PicUrl">只支援Http開頭的Url或檔名,相對路徑不算</param>
        /// <param name="SaveLocalPath"></param>
        /// <returns></returns>
        private   void DonwloadOnePic(  string picHttpUrl, string albumDirPath)
        {
            string picFileName =Path.GetFileName(picHttpUrl).Split('?')[0];

            //預計存放圖片位置
            string picFilePath = Path.Combine(albumDirPath, picFileName);
            //解決檔名重覆
            if (File.Exists(picFilePath))
            {
                string newFileName = Path.GetFileNameWithoutExtension(picFileName) + "(" + Guid.NewGuid().ToString() + ")" + Path.GetExtension(picFileName);
                //新的存放圖檔路徑名稱
                picFilePath = Path.Combine(albumDirPath, newFileName);
            }
             

            try
            {
                int maxRetryCount = 5;//下載重試次數最多5次
                int retryCount = 1;
                do
                {
                    //下載
                    long[] totalSize = this.DownloadFile_WebRequest(picHttpUrl, picFilePath);

                    Thread.Sleep(100);

                    //驗證檔案是否下載完全
                    if (totalSize[0] != totalSize[1])
                    {//下載不完全
                        if (retryCount >= maxRetryCount)
                        {
                            txtLog.Invoke(new Action(() => {
                                txtLog.AppendText("✕下載檔案:" + picHttpUrl + " 已下載" + maxRetryCount + "次仍不完全,存放位置:" + picFilePath);
                            
                            }));
                            
                            break;//離開迴圈
                        }
                        else
                        {
                            retryCount++;
                        }

                    }
                    else
                    {//下載完全

                        break;//離開迴圈
                    }

                } while (true);

 



            }
            catch (Exception ex)
            {
                txtLog.Invoke(new Action(() => {
                    txtLog.AppendText("下載一張圖片發生例外(" + picHttpUrl + "。保存位置:" + picFilePath + "):" + ex.Message);
                }));
                 

            }


        }


        /// <summary>
        /// 使用HttpWebRequest 下載檔案
        /// </summary>
        /// <param name="httpUrlHaveFileSpot"></param>
        /// <param name="httpFileUrl"></param>
        /// <param name="localFilePath"></param>
        /// <returns></returns>
        private long[] DownloadFile_WebRequest( string httpFileUrl,
                               string localFilePath)
        {
            //檔案總下載大小
            long bytesProcessed = 0;
            //Web上紀錄的檔案大小
            long WebContentLength = 0;
           
            Stream remoteStream = null;
            Stream localStream = null;
            HttpWebResponse response = null;

          
            try
            {
              

                HttpWebRequest request = (HttpWebRequest)WebRequest.Create(httpFileUrl);
                if (request != null)
                {
                    request.Method = "GET";
                    request.Accept = "text/html, application/xhtml+xml, */*";
                    request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
                    request.Headers.Add("Accept-Language:zh-TW");
                    request.UserAgent = "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)";
                    request.KeepAlive = true;
                    request.Credentials = System.Net.CredentialCache.DefaultCredentials;
                
                    //發出request
                    response = (HttpWebResponse)request.GetResponse();
                    if (response != null)
                    {
                         
                        remoteStream = response.GetResponseStream();
                         

                        //取得Web檔案大小
                        long.TryParse(response.Headers.Get("Content-Length"), out WebContentLength);

                       
                        localStream = new FileStream(localFilePath, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite);
                         
                        byte[] buffer = new byte[1024];
                        int bytesRead;

                        while ((bytesRead=remoteStream.Read(buffer, 0, buffer.Length))>0)
                        {
                            //一點一點地抓下圖片
                            localStream.Write(buffer, 0, bytesRead);

                            //累加下載檔案大小
                            bytesProcessed += bytesRead;
                        }
                        
                            

                        
                      
                    }
                }
            }
            catch (Exception e)
            {
                txtLog.Invoke(new Action(() => {
                    txtLog.AppendText("下載一張圖片發生例外(httpFileUrl:" + httpFileUrl + "。保存位置:" + localFilePath + "):" + e.Message + Environment.NewLine);
                }));

                
               
            }
            finally
            {
                if (response != null) response.Close();
                if (remoteStream != null) remoteStream.Close();
                if (localStream != null) localStream.Close();
            }
             
            return new long[] { bytesProcessed, WebContentLength };
        }
    }
}

 

 

 

 

 

程式使用方法&注意事項

1.首先從無名官網先進行資料備份,並下載解壓備份檔,再進到裡面的album資料夾,可以找到detail.html檔及一堆.txt檔

image

那堆.txt檔裡就是實體照片的超連結,detail.html檔和.txt檔要維持預設在同一個資料夾內

2.然後把detail.html檔的完整路徑輸入到程式裡,按下「開始下載」※執行WretchAlbumBackupDownload.exe這個程式

image

3. 等檔案下載全部完成後,再回到剛剛的album資料夾,即可看到實體檔案

image

image

 

 

結語

操作應該很簡單,程式有Bug請再留言通知囉~

開發人員下載 專案檔

一般使用者下載 執行檔

2013.9.19 程式更新:

相簿無照片超連結時,就不下載該相簿的圖片

2013.9.23 程式更新:

相簿名稱重覆的話,自動命名相簿名稱