C# 制作的WEB图片批量下载器

介绍图片批量下载器V0.3版本的升级特性,包括图片重命名、异步线程池控制、图片大小限制等功能改进,并分享开发过程中的心得与挑战。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

    从【笨笨图片批量抓取下载 V0.2 beta】到【笨笨图片批量下载器 V0.3 beta】时间将近2个月,不是说这个升级版本开发了这么久,实在是懒,呵呵: )再加有时候工作忙、学习,多的时间就不愿意动了,现在都感觉辜负了上一版N多朋友的支持了,不过这将近一个星期时间我按计划完成了这个小软件版的升级开发,并且依然和上两个版本一样保持源代码 开源,文章最后有下载地址,以下是这个版本相比上个版本的特点:

    1.    加入图片是否重命名。

    2.    加入 异步 线程池控制(? 随后解释)。

    3.    加入图片大小限制。

    4.    加入支持指定网址内 CSS文件内的图片下载。

    5.    加入了正则表达式即时 配置更改(应变正则表达式缺陷)。

    6.     优化部分代码。

    7.    修改部分统计错误。

感谢

    1.    感谢热心回帖并提供建议的部分网友:Stoneq、liyundong、寻梦E.net、Caspar Jiong 、laoda、lexus 等

    2.    感谢Google Code Seach,在我找不到任何我能看懂的中英文资料时(尤其是异步线程池控制),她提供了我参考代码!!

    3.    感谢女朋友在精神上的莫大鼓舞!

正文

    1.    和以往一样,先来一张图,然后看图说话:)

            

          说明:界面上V0.3与V0.2差不多,剔除了图片类型复选框,如果你需要下载指定类型的图片的话,可以从 配置->设置正则表达式图片链接分析 里面直接修改匹配图片的正则表达式,最末端就是图片的文件类型了,如:(jpg|jpeg|png|ico|bmp|gif);状态栏增加了一个实际下载图片数量,可以实时的显示当前下载图片的张数;多了一个[重命名]和[下载页面包含CSS文件内的图片],后者就不用说明了,但是前者需要说明一下:这个地方是费了我不少时间了,我原先的重命名方案是DateTime.Now.ToString("yyyyMMddHHmmssfff"),后来又加上了new Random().Next(100)和lock,都不对,显示下载图片的数量和文件夹里面的图片数量不符合,并且选择重命名和不选择重命名文件夹实际图片数相差较大,几张到几十张不等,最后改用了System.Guid.NewGuid()至此基本正确符合,所有猜想,DateTime.Now和Random在遇到异步多线程应该会出现脏读吧?!

          栏目里面的配置,开始想的时候觉得有那么点画蛇添足的味道,但是觉得有促进和各位朋友正则表达式交流的作用而保留下来了,所以期待牛人给予友情提示:)

    2.    接下来贴部分关键代码和讲解,这里只讲异步的代码,源码注释也比较多:
  1. /**//// <summary>
  2.         /// 开始异步分析下载
  3.         /// </summary>
  4.         /// <param name="url"></param>
  5.         /// <param name="savePath"></param>
  6.         private void AsyncAnalyzeAndDownload(string url, string savePath)
  7.         {
  8.             this.uriString = url;
  9.             this.savePath = savePath;

  10.             初始化全局变量#region 初始化全局变量

  11.             ccount = 0L;
  12.             ccount1 = 0L;
  13.             cfreq = 0L;

  14.             okcount = 0;

  15.             uriString = string.Empty;
  16.             savePath = string.Empty;

  17.             isAnalyzeComplete = false;

  18.             imgUrlList = new List<string>();

  19.             #endregion

  20.             分析计时开始#region 分析计时开始

  21.             count = 0L;
  22.             count1 = 0L;
  23.             freq = 0L;

  24.             QueryPerformanceFrequency(ref freq);
  25.             QueryPerformanceCounter(ref count);

  26.             #endregion

  27.             using (WebClient wClient = new WebClient())
  28.             {
  29.                 AutoResetEvent waiter = new AutoResetEvent(false);
  30.                 //为异步结果返回传参
  31.                 wClient.QueryString.Add("url", uriString);
  32.                 wClient.QueryString.Add("IsInclueCssImages", _IsInclueCssImages.ToString());
  33.                 wClient.Credentials = CredentialCache.DefaultCredentials;
  34.                 wClient.DownloadDataCompleted += new DownloadDataCompletedEventHandler(AsyncURIAnalyze);
  35.                 wClient.DownloadDataAsync(new Uri(uriString), waiter);
  36.                 //waiter.WaitOne();    //阻止当前线程,直到收到信号
  37.             }
  38.         }
复制代码
说明:这里是异步分析和下载的入口,传入网址、保存路径、是否分析在CSS文件内的图片参数。

----------------------------------------------------------------------------------------------------------
  1. /// <summary>
  2.         /// 异步分析指定网址返回的数据
  3.         /// </summary>
  4.         /// <param name="sender"></param>
  5.         /// <param name="e"></param>
  6.         protected void AsyncURIAnalyze(Object sender, DownloadDataCompletedEventArgs e)
  7.         {
  8.             AutoResetEvent waiter = (AutoResetEvent)e.UserState;
  9.             WebClient nWC = sender as WebClient;
  10.             bool IsMatchCss = true;
  11.             bool IsInclueCssImages = Convert.ToBoolean(nWC.QueryString["IsInclueCssImages"]);
  12.             try
  13.             {
  14.                 if (!e.Cancelled && e.Error == null)
  15.                 {
  16.                     string dnDir = string.Empty;
  17.                     string domainName = string.Empty;
  18.                     string uri = nWC.QueryString["url"];

  19.                     if (!uri.StartsWith("http://") && !uri.StartsWith("https://"))
  20.                         uri = string.Concat("http://", uri);

  21.                     //获得域名 [url]http://www.sina.com[/url]
  22.                     domainName = GetDomain(uri);

  23.                     //获得域名最深层目录 [url]http://www.sina.com/mail/[/url]
  24.                     dnDir = GetFullPath(domainName, uri);

  25.                     //获取数据
  26.                     string pageData = Encoding.UTF8.GetString(e.Result);

  27.                     //匹配全路径
  28.                     AnalyzeContent(Regex.Matches(pageData, ImagePattern), domainName, dnDir);

  29.                     //是否下载页面包含CSS文件内的图片
  30.                     if (IsInclueCssImages)
  31.                     {
  32.                         //匹配CSS文件 //[\w=/]*((\.css){1})
  33.                         MatchCollection mc = Regex.Matches(pageData, CssPattern, RegexOptions.IgnoreCase);
  34.                         for (int i = 0, j = mc.Count; i < j; i++)
  35.                         {
  36.                             string item = mc.Value;

  37.                             //短路径处理
  38.                             if (!item.StartsWith("http://") && !item.StartsWith("https://"))
  39.                                 item = (item[0] == '/' ? domainName : dnDir) + item;

  40.                             using (WebClient wClient = new WebClient())
  41.                             {
  42.                                 AutoResetEvent are = new AutoResetEvent(false);
  43.                                 wClient.QueryString.Add("url", item);
  44.                                 wClient.QueryString.Add("IsOver", i == j - 1 ? "1" : "0");
  45.                                 wClient.QueryString.Add("IsInclueCssImages", "false");
  46.                                 wClient.Credentials = CredentialCache.DefaultCredentials;
  47.                                 wClient.DownloadDataCompleted += new DownloadDataCompletedEventHandler(AsyncURIAnalyze);
  48.                                 wClient.DownloadDataAsync(new Uri(item), are);
  49.                             }
  50.                         }
  51.                         if (mc.Count == 0)
  52.                             IsMatchCss = false;
  53.                     }
  54.                 }
  55.             }
  56.             finally
  57.             {
  58.                 //waiter.Set();

  59.                 if ((IsInclueCssImages && !IsMatchCss) || (!IsInclueCssImages && string.IsNullOrEmpty(nWC.QueryString["IsOver"])) || (!string.IsNullOrEmpty(nWC.QueryString["IsOver"]) && "1" == nWC.QueryString["IsOver"]))
  60.                 {
  61.                     lock (slock)
  62.                     {
  63.                         #region 分析计时结束

  64.                         QueryPerformanceCounter(ref count1);
  65.                         count = count1 - count;
  66.                         
  67.                         toolStripStatusLabel1.Text = "分析完毕!";
  68.                         toolStripStatusLabel2.Text = string.Format(" | 分析耗时:{0:#.####}秒", (double)(count) / (double)freq);

  69.                         #endregion

  70.                         //分析完毕
  71.                         isAnalyzeComplete = true;
  72.                     }
  73.                     Application.DoEvents();
  74.                 }
  75.             }
  76.         }
复制代码
说明:这部分代码是程序的主体部分之一,如果需要分析CSS文件内的图片,则采用递归调用本方法,AnalyzeContent方法为分析图片链接核心代码,这里发现比较有意思的是可以为下次接受的数据传参,即自己传给自己,这样对于判断是否分析完毕有很大便利性,代码如:wClient.QueryString.Add("url", item);

------------------------------------------------------------------------------------------------------
  1. /// <summary>
  2.         /// 分析链接
  3.         /// </summary>
  4.         /// <param name="mc"></param>
  5.         /// <param name="domainName"></param>
  6.         /// <param name="dnDir"></param>
  7.         private void AnalyzeContent(MatchCollection mc, string domainName, string dnDir)
  8.         {
  9.             List<string> urlList = new List<string>();
  10.             foreach (Match mt in mc)
  11.             {
  12.                 string item = mt.Value;

  13.                 /*  处理图片正则表达式的缺陷,即图片必须带域名,如:
  14.                 *      当前正则表达式匹配[url]http://www.icoxxx.com[/url]
  15.                 *      匹配结果为[url]http://www.ico[/url]
  16.                 */
  17.                 if (item.Length > 8 && item.IndexOf('/', 9) == -1)
  18.                     continue;

  19.                 //短路径处理
  20.                 if (!item.StartsWith("http://") && !item.StartsWith("https://"))
  21.                     item = (item[0] == '/' ? domainName : dnDir) + item;

  22.                 //处理../
  23.                 if (item.IndexOf("../") != -1)
  24.                 {
  25.                     List<string> urls = new List<string>();
  26.                     urls.AddRange(item.Split('/'));
  27.                     for (int i = 0; i < urls.Count; i++)
  28.                         if ("..".Equals(urls))
  29.                         {
  30.                             urls.RemoveRange(i - 1, 2);
  31.                             i -= 2;
  32.                         }
  33.                     item = Join("/", urls);
  34.                 }

  35.                 if (!urlList.Contains(item))
  36.                 {
  37.                     urlList.Add(item);

  38.                     lock (slock)
  39.                     {
  40.                         imgUrlList.Add(item);
  41.                     }

  42.                     //实时显示分析结果
  43.                     AddlbShowItem(item);

  44.                     //边分析边下载
  45.                     HttpWebRequest hwr = WebRequest.Create(item) as HttpWebRequest;
  46.                     hwr.AllowWriteStreamBuffering = false;
  47.                     //hwr.ReadWriteTimeout = 5 * 1000; //默认超时30秒
  48.                     IAsyncResult res = hwr.BeginGetResponse(new AsyncCallback(AsyncDownLoad), hwr);
  49.                     //加入线程池控制
  50.                     ThreadPool.RegisterWaitForSingleObject(res.AsyncWaitHandle, new WaitOrTimerCallback(timeoutCallback), hwr, Timeout, true);
  51.                 }
  52.             }
  53.         }
复制代码
说明:这块代码是分析图片链接的代码,由于正则表达式缺陷,加入了一些处理;加入了线程池控制,说到这里,关于异步线程池的控制几乎没找到中文资料,就在Google Code Seach里面 搜索到了以下代码片段:

IAsyncResult res = hwr.BeginGetResponse(new AsyncCallback(AsyncDownLoad), hwr);
                    //加入线程池控制
                    ThreadPool.RegisterWaitForSingleObject(res.AsyncWaitHandle, new WaitOrTimerCallback(timeoutCallback), hwr, Timeout, true);
          具体如何 测试是否加入连接池也没有深入的研究了,所以前面提到新加功能时后面打了一个问号,望资深人士帮忙分析下:)

-------------------------------------------------------------------------------------------------------------
  1. /// <summary>
  2.         /// 异步接受数据
  3.         /// </summary>
  4.         /// <param name="asyncResult"></param>
  5.         public void AsyncDownLoad(IAsyncResult asyncResult)
  6.         {
  7.             #region 下载计时开始

  8.             lock (slock)
  9.             {
  10.                 if (cfreq == 0L)
  11.                 {
  12.                     ccount = 0L; 
  13.                     QueryPerformanceFrequency(ref cfreq);
  14.                     QueryPerformanceCounter(ref ccount);
  15.                 }
  16.             }

  17.             #endregion
  18.             
  19.             WebRequest request = (WebRequest)asyncResult.AsyncState;
  20.             string url = request.RequestUri.ToString();
  21.             int indexItem = this.lbShow.Items.IndexOf(url);

  22.             //从未下载的列表中删除已经下载的图片
  23.             lock (slock)
  24.             {
  25.                 imgUrlList.Remove(url);
  26.             }
  27.             
  28.             try
  29.             {
  30.                 WebResponse response = request.EndGetResponse(asyncResult);
  31.                 long cLength = response.ContentLength;

  32.                 if (cLength > 0 && cLength <= FileSize)
  33.                 {
  34.                     using (Stream stream = response.GetResponseStream())
  35.                     {
  36.                         Image img = Image.FromStream(stream);
  37.                         img.Save(string.Concat(savePath, "/", GetFileName(url)));
  38.                         img.Dispose();
  39.                         stream.Close();
  40.                     }
  41.                     //allDone.Set();

  42.                     //成功下载
  43.                     if (indexItem >= 0 && indexItem <= this.lbShow.Items.Count)
  44.                         SetlbShowItem(indexItem, "√  ");
  45.                 }
  46.                 else if (cLength == 0L && indexItem >= 0)
  47.                     SetlbShowItem(indexItem, "×  ");
  48.                 else if (indexItem >= 0)
  49.                     SetlbShowItem(indexItem, "!  ");    //表示图片过大或过小
  50.             }
  51.             catch (Exception)
  52.             {
  53.                 if (indexItem >= 0)
  54.                     SetlbShowItem(indexItem, "×  ");
  55.             }

  56.         }
复制代码
说明:这部分代码是异步下载的代码,加入了人性化了多符号标示文件的状态。

          代码注释后可讲解的就不多了,比较难的就是异步中 同步数据控制,得控制好了并且尽量少,否则对 性能能较大影响,大伙有兴趣的话看代码吧:)

结束

          感谢能阅读到此处的网友,希望能多多交流关于异步、同步、正则表达式方面的经验,不吝赐教!在下载中可能出现其他意外情况,导致图片已经下载完毕但是下载图片的按钮不可用,状态栏显示正在下载...,持续时间超过1分钟,那么可能是哪里出了BUG,呵呵!可以强制关闭然后重新启动程序下载,或者确认已经下载完毕,状态没法恢复可以从 工具->初始化来重置一下。

补充

    感谢 Jon.Hong 的批评,确实下载回来执行有问题,原因是因为写文章的时候临时改了几个参数忘了编译测试了,已经更新了下载文件,不过查看源代码直接调试也行!

    做这个小软件只有两个目的:

          1.    学习交流

          2.    希望能帮得到大家

    希望和大家多多交流使用到的相关技术,以期能积累更多这方面的经验,谢谢大家支持!!

下载

执行程序及源代码: [ 评价与收藏 ]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值