前言
從【笨笨圖檔批量抓取下載下傳 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. 接下來貼部分關鍵代碼和講解,這裡隻講異步的代碼,源碼注釋也比較多:
/// <summary>
/// 開始異步分析下載下傳
/// </summary>
/// <param name="url"></param>
/// <param name="savepath"></param>
private void asyncanalyzeanddownload(string url, string savepath)
{
this.uristring = url;
this.savepath = savepath;
初始化全局變量
分析計時開始
using (webclient wclient = new webclient())
{
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檔案内的圖檔參數。
----------------------------------------------------------------------------------------------------------
/// 異步分析指定網址傳回的資料
/// <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);
------------------------------------------------------------------------------------------------------
/// 分析連結
/// <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);
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);
具體如何測試是否加入連接配接池也沒有深入的研究了,是以前面提到新加功能時後面打了一個問号,望資深人士幫忙分析下:)
-------------------------------------------------------------------------------------------------------------
/// 異步接受資料
/// <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);
//從未下載下傳的清單中删除已經下載下傳的圖檔
imgurllist.remove(url);
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)
說明:這部分代碼是異步下載下傳的代碼,加入了人性化了多符号标示檔案的狀态。
代碼注釋後可講解的就不多了,比較難的就是異步中同步資料控制,得控制好了并且盡量少,否則對性能能較大影響,大夥有興趣的話看代碼吧:)
結束
感謝能閱讀到此處的網友,希望能多多交流關于異步、同步、正規表達式方面的經驗,不吝賜教!在下載下傳中可能出現其他意外情況,導緻圖檔已經下載下傳完畢但是下載下傳圖檔的按鈕不可用,狀态欄顯示正在下載下傳...,持續時間超過1分鐘,那麼可能是哪裡出了bug,呵呵!可以強制關閉然後重新啟動程式下載下傳,或者确認已經下載下傳完畢,狀态沒法恢複可以從工具->初始化來重置一下。
補充
做這個小軟體隻有兩個目的:
1. 學習交流
2. 希望能幫得到大家
希望和大家多多交流使用到的相關技術,以期能積累更多這方面的經驗,謝謝大家支援!!
轉載:http://www.cnblogs.com/over140/archive/2008/10/31/1322786.html