博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
笨笨图片批量下载器 V0.3 beta[C# | WinForm | 正则表达式 | HttpWebRequest | Async异步编程] new...
阅读量:6312 次
发布时间:2019-06-22

本文共 7517 字,大约阅读时间需要 25 分钟。

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.NowRandom在遇到异步多线程应该会出现脏读吧?!

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

2.     接下来贴部分关键代码和讲解,这里只讲异步的代码,源码注释也比较多:

ExpandedBlockStart.gif
/// <summary>
        
/// 开始异步分析下载
        
/// </summary>
        
/// <param name="url"></param>
        
/// <param name="savePath"></param>
        
private
 
void
 AsyncAnalyzeAndDownload(
string
 url, 
string
 savePath)
ExpandedBlockStart.gif        
{
            
this.uriString = url;
            
this.savePath = savePath;
ContractedSubBlock.gif            
初始化全局变量
ContractedSubBlock.gif            
分析计时开始
            
using (WebClient wClient = new WebClient())
ExpandedSubBlockStart.gif            
{
                AutoResetEvent waiter 
= new AutoResetEvent(false);
                
//为异步结果返回传参
                wClient.QueryString.Add("url", uriString);
                wClient.QueryString.Add(
"IsInclueCssImages", _IsInclueCssImages.ToString());
                wClient.Credentials 
= CredentialCache.DefaultCredentials;
                wClient.DownloadDataCompleted 
+= new DownloadDataCompletedEventHandler(AsyncURIAnalyze);
                wClient.DownloadDataAsync(
new Uri(uriString), waiter);
                
//waiter.WaitOne();     //阻止当前线程,直到收到信号
            }
        }

          说明:这里是异步分析和下载的入口,传入网址、保存路径、是否分析在CSS文件内的图片参数。

 ----------------------------------------------------------------------------------------------------------

///
 
<summary>
        
///
 异步分析指定网址返回的数据
        
///
 
</summary>
        
///
 
<param name="sender"></param>
        
///
 
<param name="e"></param>
        
protected
 
void
 AsyncURIAnalyze(Object sender, DownloadDataCompletedEventArgs e)
        {
            AutoResetEvent waiter 
=
 (AutoResetEvent)e.UserState;
            WebClient nWC 
=
 sender 
as
 WebClient;
            
bool
 IsMatchCss 
=
 
true
;
            
bool
 IsInclueCssImages 
=
 Convert.ToBoolean(nWC.QueryString[
"
IsInclueCssImages
"
]);
            
try
            {
                
if
 (
!
e.Cancelled 
&&
 e.Error 
==
 
null
)
                {
                    
string
 dnDir 
=
 
string
.Empty;
                    
string
 domainName 
=
 
string
.Empty;
                    
string
 uri 
=
 nWC.QueryString[
"
url
"
];
                    
if
 (
!
uri.StartsWith(
"
http://
"
&&
 
!
uri.StartsWith(
"
https://
"
))
                        uri 
=
 
string
.Concat(
"
http://
"
, uri);
                    
//
获得域名 
http://www.sina.com
                    domainName 
=
 GetDomain(uri);
                    
//
获得域名最深层目录 
http://www.sina.com/mail/
                    dnDir 
=
 GetFullPath(domainName, uri);
                    
//
获取数据
                    
string
 pageData 
=
 Encoding.UTF8.GetString(e.Result);
                    
//
匹配全路径
                    AnalyzeContent(Regex.Matches(pageData, ImagePattern), domainName, dnDir);
                    
//
是否下载页面包含CSS文件内的图片
                    
if
 (IsInclueCssImages)
                    {
                        
//
匹配CSS文件 
//
[\w=/]*((\.css){1})
                        MatchCollection mc 
=
 Regex.Matches(pageData, CssPattern, RegexOptions.IgnoreCase);
                        
for
 (
int
 i 
=
 
0
, j 
=
 mc.Count; i 
<
 j; i
++
)
                        {
                            
string
 item 
=
 mc[i].Value;
                            
//
短路径处理
                            
if
 (
!
item.StartsWith(
"
http://
"
&&
 
!
item.StartsWith(
"
https://
"
))
                                item 
=
 (item[
0
==
 
'
/
'
 
?
 domainName : dnDir) 
+
 item;
                            
using
 (WebClient wClient 
=
 
new
 WebClient())
                            {
                                AutoResetEvent are 
=
 
new
 AutoResetEvent(
false
);
                                wClient.QueryString.Add(
"
url
"
, item);
                                wClient.QueryString.Add(
"
IsOver
"
, i 
==
 j 
-
 
1
 
?
 
"
1
"
 : 
"
0
"
);
                                wClient.QueryString.Add(
"
IsInclueCssImages
"
"
false
"
);
                                wClient.Credentials 
=
 CredentialCache.DefaultCredentials;
                                wClient.DownloadDataCompleted 
+=
 
new
 DownloadDataCompletedEventHandler(AsyncURIAnalyze);
                                wClient.DownloadDataAsync(
new
 Uri(item), are);
                            }
                        }
                        
if
 (mc.Count 
==
 
0
)
                            IsMatchCss 
=
 
false
;
                    }
                }
            }
            
finally
            {
                
//
waiter.Set();
                
if
 ((IsInclueCssImages 
&&
 
!
IsMatchCss) 
||
 (
!
IsInclueCssImages 
&&
 
string
.IsNullOrEmpty(nWC.QueryString[
"
IsOver
"
])) 
||
 (
!
string
.IsNullOrEmpty(nWC.QueryString[
"
IsOver
"
]) 
&&
 
"
1
"
 
==
 nWC.QueryString[
"
IsOver
"
]))
                {
                    
lock
 (slock)
                    {
                        
#region
 分析计时结束
                        QueryPerformanceCounter(
ref
 count1);
                        count 
=
 count1 
-
 count;
                        
                        toolStripStatusLabel1.Text 
=
 
"
分析完毕!
"
;
                        toolStripStatusLabel2.Text 
=
 
string
.Format(
"
 | 分析耗时:{0:#.####}秒
"
, (
double
)(count) 
/
 (
double
)freq);
                        
#endregion
                        
//
分析完毕
                        isAnalyzeComplete 
=
 
true
;
                    }
                    Application.DoEvents();
                }
            }
        }

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

------------------------------------------------------------------------------------------------------

///
 
<summary>
        
///
 分析链接
        
///
 
</summary>
        
///
 
<param name="mc"></param>
        
///
 
<param name="domainName"></param>
        
///
 
<param name="dnDir"></param>
        
private
 
void
 AnalyzeContent(MatchCollection mc, 
string
 domainName, 
string
 dnDir)
        {
            List
<
string
>
 urlList 
=
 
new
 List
<
string
>
();
            
foreach
 (Match mt 
in
 mc)
            {
                
string
 item 
=
 mt.Value;
                
/*
  处理图片正则表达式的缺陷,即图片必须带域名,如:
                 *      当前正则表达式匹配http://www.icoxxx.com
                 *      匹配结果为http://www.ico
                 
*/
                
if
 (item.Length 
>
 
8
 
&&
 item.IndexOf(
'
/
'
9
==
 
-
1
)
                    
continue
;
                
//
短路径处理
                
if
 (
!
item.StartsWith(
"
http://
"
&&
 
!
item.StartsWith(
"
https://
"
))
                    item 
=
 (item[
0
==
 
'
/
'
 
?
 domainName : dnDir) 
+
 item;
                
//
处理../
                
if
 (item.IndexOf(
"
../
"
!=
 
-
1
)
                {
                    List
<
string
>
 urls 
=
 
new
 List
<
string
>
();
                    urls.AddRange(item.Split(
'
/
'
));
                    
for
 (
int
 i 
=
 
0
; i 
<
 urls.Count; i
++
)
                        
if
 (
"
..
"
.Equals(urls[i]))
                        {
                            urls.RemoveRange(i 
-
 
1
2
);
                            i 
-=
 
2
;
                        }
                    item 
=
 Join(
"
/
"
, urls);
                }
                
if
 (
!
urlList.Contains(item))
                {
                    urlList.Add(item);
                    
lock
 (slock)
                    {
                        imgUrlList.Add(item);
                    }
                    
//
实时显示分析结果
                    AddlbShowItem(item);
                    
//
边分析边下载
                    HttpWebRequest hwr 
=
 WebRequest.Create(item) 
as
 HttpWebRequest;
                    hwr.AllowWriteStreamBuffering 
=
 
false
;
                    
//
hwr.ReadWriteTimeout = 5 * 1000; 
//
默认超时30秒
                    IAsyncResult res 
=
 hwr.BeginGetResponse(
new
 AsyncCallback(AsyncDownLoad), hwr);
                    
//
加入线程池控制
                    ThreadPool.RegisterWaitForSingleObject(res.AsyncWaitHandle, 
new
 WaitOrTimerCallback(timeoutCallback), hwr, Timeout, 
true
);
                }
            }
        }

          说明:这块代码是分析图片链接的代码,由于正则表达式缺陷,加入了一些处理;加入了线程池控制,说到这里,关于异步线程池的控制几乎没找到中文资料,就在Google Code Seach里面搜索到了以下代码片段:

IAsyncResult res 
=
 hwr.BeginGetResponse(
new
 AsyncCallback(AsyncDownLoad), hwr);
                    
//
加入线程池控制
                    ThreadPool.RegisterWaitForSingleObject(res.AsyncWaitHandle, 
new
 WaitOrTimerCallback(timeoutCallback), hwr, Timeout, 
true
);

          具体如何测试是否加入连接池也没有深入的研究了,所以前面提到新加功能时后面打了一个问号,望资深人士帮忙分析下:)

-------------------------------------------------------------------------------------------------------------

///
 
<summary>
        
///
 异步接受数据
        
///
 
</summary>
        
///
 
<param name="asyncResult"></param>
        
public
 
void
 AsyncDownLoad(IAsyncResult asyncResult)
        {
            
#region
 下载计时开始
            
lock
 (slock)
            {
                
if
 (cfreq 
==
 
0L
)
                {
                    ccount 
=
 
0L
                    QueryPerformanceFrequency(
ref
 cfreq);
                    QueryPerformanceCounter(
ref
 ccount);
                }
            }
            
#endregion
            
            WebRequest request 
=
 (WebRequest)asyncResult.AsyncState;
            
string
 url 
=
 request.RequestUri.ToString();
            
int
 indexItem 
=
 
this
.lbShow.Items.IndexOf(url);
            
//
从未下载的列表中删除已经下载的图片
            
lock
 (slock)
            {
                imgUrlList.Remove(url);
            }
            
            
try
            {
                WebResponse response 
=
 request.EndGetResponse(asyncResult);
                
long
 cLength 
=
 response.ContentLength;
                
if
 (cLength 
>
 
0
 
&&
 cLength 
<=
 FileSize)
                {
                    
using
 (Stream stream 
=
 response.GetResponseStream())
                    {
                        Image img 
=
 Image.FromStream(stream);
                        img.Save(
string
.Concat(savePath, 
"
/
"
, GetFileName(url)));
                        img.Dispose();
                        stream.Close();
                    }
                    
//
allDone.Set();
                    
//
成功下载
                    
if
 (indexItem 
>=
 
0
 
&&
 indexItem 
<=
 
this
.lbShow.Items.Count)
                        SetlbShowItem(indexItem, 
"
√  
"
);
                }
                
else
 
if
 (cLength 
==
 
0L
 
&&
 indexItem 
>=
 
0
)
                    SetlbShowItem(indexItem, 
"
×  
"
);
                
else
 
if
 (indexItem 
>=
 
0
)
                    SetlbShowItem(indexItem, 
"
!  
"
);    
//
表示图片过大或过小
            }
            
catch
 (Exception)
            {
                
if
 (indexItem 
>=
 
0
)
                    SetlbShowItem(indexItem, 
"
×  
"
);
            }
        }

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

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

结束

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

本文转自博客园农民伯伯的博客,原文链接:,如需转载请自行联系原博主。

你可能感兴趣的文章
nvm管理node.js版本(Windows系统)
查看>>
『高级篇』docker之kubernetes理解认证、授权(37)
查看>>
Apple Watch学习之路 数据存储
查看>>
SpringBoot记录HTTP请求日志
查看>>
利用WebSocket和EventSource实现服务端推送
查看>>
Java架构-spring+springmvc+Interceptor+jwt+redis实现sso单点登录
查看>>
异步之三:Async 函数的使用及简单实现
查看>>
没有后端也能快速开发H5应用,Vue + OkayApi最佳CP开发!
查看>>
以太坊构建DApps系列教程(四):Story DAO的白名单和测试
查看>>
java B2B2C 多级分销多租户电子商城系统-服务容错保护(Hystrix断路器)
查看>>
智慧能源管控中心系统建设开发搭建公司
查看>>
[ARKit]5-加载自定义几何体
查看>>
HTML 常用的标签和属性
查看>>
『Go 内置库第一季:json』
查看>>
React Hooks入门: 基础
查看>>
iOS swift2.3 迁移到3.0 遇到的一些问题
查看>>
禁用chrome自动密码填充到用户编辑界面相关字段
查看>>
guzzle使用遇到的问题记录
查看>>
不同编程语言在发生stackoverflow之前支持的调用栈最大嵌套层数
查看>>
Pyro概率编程语言成为LF DL最新的项目
查看>>