前言
从【笨笨图片批量抓取下载 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 beta[C# | WinForm | 正则表达式 | HttpWebRequest | Async异步编程] new](http://img.e-com-net.com/image/product/4436f7008a684ed3ae79afe114faea6d.jpg)
说明:界面上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. 接下来贴部分关键代码和讲解,这里只讲异步的代码,源码注释也比较多:
/**/
/// <summary>
/// 开始异步分析下载
/// </summary>
/// <param name="url"></param>
/// <param name="savePath"></param>
private
void
AsyncAnalyzeAndDownload(
string
url,
string
savePath)
![笨笨图片批量下载器 V0.3 beta[C# | WinForm | 正则表达式 | HttpWebRequest | Async异步编程] new](http://img.e-com-net.com/image/product/8bf85857a9e14d23a651e7c8ef856bcc.gif)
{
this.uriString = url;
this.savePath = savePath;
![笨笨图片批量下载器 V0.3 beta[C# | WinForm | 正则表达式 | HttpWebRequest | Async异步编程] new](http://img.e-com-net.com/image/product/b5ccfbfdc70d443ba19cbdc6a5005b86.gif)
![笨笨图片批量下载器 V0.3 beta[C# | WinForm | 正则表达式 | HttpWebRequest | Async异步编程] new](http://img.e-com-net.com/image/product/561925b32f8f4c9d9561af4d649930ae.gif)
初始化全局变量#region 初始化全局变量
![笨笨图片批量下载器 V0.3 beta[C# | WinForm | 正则表达式 | HttpWebRequest | Async异步编程] new](http://img.e-com-net.com/image/product/b5ccfbfdc70d443ba19cbdc6a5005b86.gif)
ccount = 0L;
ccount1 = 0L;
cfreq = 0L;
![笨笨图片批量下载器 V0.3 beta[C# | WinForm | 正则表达式 | HttpWebRequest | Async异步编程] new](http://img.e-com-net.com/image/product/b5ccfbfdc70d443ba19cbdc6a5005b86.gif)
okcount = 0;
![笨笨图片批量下载器 V0.3 beta[C# | WinForm | 正则表达式 | HttpWebRequest | Async异步编程] new](http://img.e-com-net.com/image/product/b5ccfbfdc70d443ba19cbdc6a5005b86.gif)
uriString = string.Empty;
savePath = string.Empty;
![笨笨图片批量下载器 V0.3 beta[C# | WinForm | 正则表达式 | HttpWebRequest | Async异步编程] new](http://img.e-com-net.com/image/product/b5ccfbfdc70d443ba19cbdc6a5005b86.gif)
isAnalyzeComplete = false;
![笨笨图片批量下载器 V0.3 beta[C# | WinForm | 正则表达式 | HttpWebRequest | Async异步编程] new](http://img.e-com-net.com/image/product/b5ccfbfdc70d443ba19cbdc6a5005b86.gif)
imgUrlList = new List<string>();
![笨笨图片批量下载器 V0.3 beta[C# | WinForm | 正则表达式 | HttpWebRequest | Async异步编程] new](http://img.e-com-net.com/image/product/b5ccfbfdc70d443ba19cbdc6a5005b86.gif)
#endregion
![笨笨图片批量下载器 V0.3 beta[C# | WinForm | 正则表达式 | HttpWebRequest | Async异步编程] new](http://img.e-com-net.com/image/product/b5ccfbfdc70d443ba19cbdc6a5005b86.gif)
![笨笨图片批量下载器 V0.3 beta[C# | WinForm | 正则表达式 | HttpWebRequest | Async异步编程] new](http://img.e-com-net.com/image/product/561925b32f8f4c9d9561af4d649930ae.gif)
分析计时开始#region 分析计时开始
![笨笨图片批量下载器 V0.3 beta[C# | WinForm | 正则表达式 | HttpWebRequest | Async异步编程] new](http://img.e-com-net.com/image/product/b5ccfbfdc70d443ba19cbdc6a5005b86.gif)
count = 0L;
count1 = 0L;
freq = 0L;
![笨笨图片批量下载器 V0.3 beta[C# | WinForm | 正则表达式 | HttpWebRequest | Async异步编程] new](http://img.e-com-net.com/image/product/b5ccfbfdc70d443ba19cbdc6a5005b86.gif)
QueryPerformanceFrequency(ref freq);
QueryPerformanceCounter(ref count);
![笨笨图片批量下载器 V0.3 beta[C# | WinForm | 正则表达式 | HttpWebRequest | Async异步编程] new](http://img.e-com-net.com/image/product/b5ccfbfdc70d443ba19cbdc6a5005b86.gif)
#endregion
![笨笨图片批量下载器 V0.3 beta[C# | WinForm | 正则表达式 | HttpWebRequest | Async异步编程] new](http://img.e-com-net.com/image/product/b5ccfbfdc70d443ba19cbdc6a5005b86.gif)
using (WebClient wClient = new WebClient())
![笨笨图片批量下载器 V0.3 beta[C# | WinForm | 正则表达式 | HttpWebRequest | Async异步编程] new](http://img.e-com-net.com/image/product/437570a32a8e47669b1b8f34e2a85bac.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,呵呵!可以强制关闭然后重新启动程序下载,或者确认已经下载完毕,状态没法恢复可以从工具->初始化来重置一下。
补充
感谢 的批评,确实下载回来执行有问题,原因是因为写文章的时候临时改了几个参数忘了编译测试了,已经更新了下载文件,不过查看源代码直接调试也行!
做这个小软件只有两个目的:
1. 学习交流
2. 希望能帮得到大家
希望和大家多多交流使用到的相关技术,以期能积累更多这方面的经验,谢谢大家支持!!
下载
执行程序
源代码
更新(2009-4-15)
笨笨图片下载器.exe
笨笨图片下载器 源代码