去年末用 多線程+HtmlAgilityPack.dll 寫了一個抓取「慧聰網」 公司信息的小程序,代碼慘不忍賭。好在能抓到數據,速度也能讓人忍受就好久沒管了。html
最近這段時間把這個小程序發給同事看着玩,沒想到他老感興趣了。而後寫了一個抓「新浪微博」我的資料的小程序,因爲用正則表達式,代碼精簡很多,效率也很高,頓時以爲有種挫敗感啊。node
因而不懂正則的我決定學習下正則,順便學習一下線程池的用法。正則表達式
沒有用正則和線程池以前,個人代碼是這樣的。小程序
//下面這段代碼使用HtmlAgilityPack寫的,因爲對它瞭解不深就寫成下面這個鳥樣子了.....
foreach (HtmlNode node in GetSource.GetHtmlDocument(GetSource.TrimCode(htmlStr)).DocumentNode.ChildNodes[0].ChildNodes) { if (node.InnerHtml.Trim() == "") { continue; }
//一個又一個點..... url = node.ChildNodes[0].ChildNodes[0].ChildNodes[0].ChildNodes[0].ChildNodes[0].Attributes["href"].Value; CompanyInfo company = new CompanyInfo(); company.CompanyName = node.ChildNodes[0].ChildNodes[0].ChildNodes[0].InnerText; SearchInfo(url, company); } //對於多線程,只會這麼寫...
th = new Thread(new ThreadStart(delegate { hc.SearchData(); })); th.Name = "hc360線程"; th.IsBackground = true; th.Start(); //天知道這個線程何時執行完。
我能確認一點的是,上面這段代碼雖慘不忍睹卻能實實在在的把數據找出來。但是,這不是我想要的。cookie
通過學習「正則表達式」和"線程池",且先不說其它的,光代碼看着就舒服了O(∩_∩)O~。多線程
//從網頁代碼提取出總頁數
string pageHtml = TrimOther(new Common.HttpRequestHelper().getHTML(string.Format(SiteUrl, 1))); if (Regex.IsMatch(pageHtml, "(?<=<span class=\"total\">共)\\d+(?=頁</span>)")) { pageHtml = Regex.Match(pageHtml, "(?<=<span class=\"total\">共)\\d+(?=頁</span>)").Value; PageCount = FunLayer.Transform.Int(pageHtml, 0); } //提取公司名稱
user.CompanyName = Regex.Match(htmlStr, "<h1[^>]+?>(?'CompanyName'.+?)</h1>", RegexOptions.Singleline | RegexOptions.IgnoreCase).Groups["CompanyName"].Value.Trim();
比起那些一串點的用法,我更喜歡正則帶來的便利和效率。異步
首先說下正則函數
如下是基礎語法...學習
.匹配除換行符之外的任意字符。(在.net裏用 "RegexOptions.Singleline"能夠改變(.)的含義,讓它能匹配每個字符)ui
\w匹配字母、數字、下劃線和漢字。(試過匹配「漢字」,可就是沒匹配出來。。。求解!!)
\s匹配空白符,它不是「 」。(在獲得網頁源碼後你會發現的)
上面是簡單的「元字符」,更多元字符請百度...或者問大神...
接着介紹「重複」,實際應用中用的很是頻繁。
* 重複匹配零次或更屢次 、+ 重複一次或更屢次。 二者的區別:+ 重複至少1次,* 0次也能夠哦。
? 重複零次或一次 、 {n} 重複n次 、 {n,}重複n到更屢次 (我的理解{1,}和+應該是有一樣效果的)
{n,m}重複 n到m次 ({0,1}和?也應該是等效的)
符號:「|」,我理解成了 運算符「||」,或者的關係。代碼執行順序也和運算符「||」同樣。
C#簡單例子 ,相信各位都能理解。
if("你妹".Equals("你妹")||"牛逼".Equals("很屌")) { Console.WriteLine("你妹都和你妹同樣了,誰管你牛逼是否是很屌"); }
正則裏面:表達式1|表達式2|表達式3,若是表達式1匹配成功了,那麼後面的表達式不會執行,用的時候須要注意左右順序,是從左往右執行。
就介紹一個 [^],例子:[^>] => 匹配除了 > 之外的任意字符,秒懂了吧。其它的反義字符,請百度...
(exp) 匹配exp,並捕獲文本到自動命名的組裏
(?<name>exp) 匹配exp,並捕獲文本到名稱爲name的組裏,也能夠寫成(?'name'exp) 。這個很好用,當你試圖用一條正則表達式匹配出多個不一樣結果,且須要保存起來時,你會發現這個東西很是好。
理解正則的貪婪與懶惰很是重要,由於這會直接影響你想要的。
*? 重複任意次,但儘量少重複
+? 重複1次或更屢次,......(懶得複製了,都是「但儘量少重複」這幾個字。)
?? 重複0次或1次,.....
{n,m}? 重複n到m次,......
{n,}? 重複n次以上,......
對比重複,上面的區別就是「儘量少重複」,那麼儘量少重複是什麼意思呢?
來個例子:
一、adsfasdfad 表達式 a.+ 結果:adsfasdfad
二、adsfasdfad 表達式 a.+? 結果:ad、as、ad (分組結果)
好吧,以上例子可能會有疑問,爲啥2結果不是 「adsf」、「asdf」、「ad」?
簡單粗暴的解釋方式: a.+? 意思是「a開頭除換行之外的任意字符重複1次或更屢次但儘量少重複」(對照意思一個個複製過來)。問題就出在了「但儘量少重複」上,正則匹配的時候是這麼回事:
按照主人寫的正則,程序找到了 a ,後面跟了個 d ,找到了ad,試圖去找後面的s的時候主人說了「要儘量少重複,ad不是已經找到了嘛」,因此d後面的就不找了。
那爲毛後面的第二個a又找到了呢?這就和重複、正則自己有關。重複的是a後面的字符,知足條件本輪匹配則終止,進行一下輪匹配...直到找到最後一個字符(不是絕對的,好比用到「^$\b」等元字符的時候)。
再來個例子,瞬間就秒懂了。
aaasdfsdfsdf .+? 結果:aa,as
我的以爲,相比「但儘量少重複」一詞,「貪婪與懶惰」描述的形象得多。你想,懶惰的方式不就是「 找到了啊,好耶,能夠收工下班了」。貪婪「 地上有1塊錢?無論前面還有沒有錢都要一直走一直走...走到頭」(固然這裏的盡頭,在正則裏是能夠設定的)。
aaasdfsdfsdf a.+d 找到的是 aaasdfsdfsd , 最後的 f 是不會找到的。 (注意區分好源字符和正則字符)
在多線程的程序中,常常會出現兩種狀況:
1. 應用程序中線程把大部分的時間花費在等待狀態,等待某個事件發生,而後給予響應。這通常使用 ThreadPool(線程池)來解決。
2. 線程平時都處於休眠狀態,只是週期性地被喚醒。這通常使用 Timer(定時器)來解決。
ThreadPool 類提供一個由系統維護的線程池(能夠看做一個線程的容器),該容器須要 Windows 2000 以上系統支持,由於其中某些方法調用了只有高版本的Windows 纔有的 API 函數。
將線程安放在線程池裏,需使用 ThreadPool.QueueUserWorkItem() 方法,該方法的原型以下:
// 將一個線程放進線程池,該線程的 Start() 方法將調用 WaitCallback 代理對象表明的函數
public static bool QueueUserWorkItem(WaitCallback);
// 重載的方法以下,參數 object 將傳遞給 WaitCallback 所表明的方法
public static bool QueueUserWorkItem(WaitCallback, object);
以上內容都是從網上抄的....
不用線程池的代碼:
th = new Thread(new ThreadStart(delegate { hc.SearchData(); })); th.Name = "hc360線程"; th.IsBackground = true; th.Start();
不用線程池,隨時均可以開N個線程跑起來。而後你會發現CPU使用率直接飆到99%...
而用了線程池後,你會發現cpu使用率保持在一個穩定的高度,也就是說,系統在協調線程的建立執行。
ThreadPool.QueueUserWorkItem() 詳細解釋:
//
// 摘要: // 將方法排入隊列以便執行,並指定包含該方法所用數據的對象。此方法在有線程池線程變得可用時執行。 //
// 參數: // callBack: // System.Threading.WaitCallback,它表示要執行的方法。 //
// state: // 包含方法所用數據的對象。 //
// 返回結果: // 若是此方法成功排隊,則爲 true;若是未能將該工做項排隊,則引起 System.NotSupportedException。 //
// 異常: // System.NotSupportedException: // 承載公共語言運行時 (CLR) 的宿主不支持此操做。 //
// System.ArgumentNullException: // callBack 爲 null。
也就是說線程池是將線程排隊入池,當達到線程池中運行作多線程時,其它線程均處於等待狀態。
相關:ThreadPool.SetMaxThreads(50, 50); //設置池中同時活動的線程請求數 ,那我有51個線程須要入池怎麼辦?我的理解,此時51個線程都會入池,可是不是全部線程都當即請求執行,超過能夠同時處於活動狀態的線程池的請求數目的線程將保持等待或排隊狀態。
// 摘要: // 設置能夠同時處於活動狀態的線程池的請求數目。全部大於此數目的請求將保持排隊狀態,直到線程池線程變爲可用。 //
// 參數: // workerThreads: // 線程池中輔助線程的最大數目。 //
// completionPortThreads: // 線程池中異步 I/O 線程的最大數目。 //
// 返回結果: // 若是更改爲功,則爲 true;不然爲 false。
廢話一推尚未說抓取數據,對不起標題啊。
對於抓取數據,原理老簡單了。就是從「網頁源碼內獲取你想要的」,不簡單的地方在於要涉及到http請求的方式、是否須要cookie、是否須要注意是代理ip等等。
本文的方向不是深刻講解這一塊,側重講解正則和線程池,抓取到數據是在這兩項技術下的結果。被騙了....
如下貼出部分代碼,複製下來絕對能抓到「慧聰網」數據!
有圖有真相:
/// <summary> /// get獲取網頁源碼(最簡單的方式) /// </summary> /// <param name="url"></param> /// <returns></returns> public string getHTML(string url) { HttpWebRequest httpWebRequest; httpWebRequest = (HttpWebRequest)HttpWebRequest.Create(url); httpWebRequest.ContentType = _contentType; httpWebRequest.Referer = url; httpWebRequest.Accept = _accept; httpWebRequest.UserAgent = _userAgent; httpWebRequest.Method = "Get"; HttpWebResponse httpWebResponse; httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse(); Stream streamresponse = httpWebResponse.GetResponseStream(); StreamReader sr = new StreamReader(streamresponse, Encoding.Default); string html = sr.ReadToEnd(); sr.Close();//關閉流 streamresponse.Close();//關閉流 return html; } /// <summary> /// 去除不少帶「\」的符號,例:回車符 /// </summary> /// <param name="source"></param> /// <returns></returns> public string TrimOther(string source) { source = Regex.Replace(source, "<script[^>]*?>.*?</script>", string.Empty, RegexOptions.IgnoreCase | RegexOptions.Singleline); source = Regex.Replace(source, "<noscript[^>]*?>.*?</noscript>", string.Empty, RegexOptions.IgnoreCase | RegexOptions.Singleline); source = Regex.Replace(source, "<style[^>]*?>.*?</style>", string.Empty, RegexOptions.IgnoreCase | RegexOptions.Singleline); source = Regex.Replace(source, "\n|\t|\r", string.Empty, RegexOptions.IgnoreCase | RegexOptions.Singleline); source = Regex.Replace(source, ">\\s+<", "><", RegexOptions.IgnoreCase | RegexOptions.Singleline); source = Regex.Replace(source, "\\s+<", "<", RegexOptions.IgnoreCase | RegexOptions.Singleline); source = Regex.Replace(source, ">\\s+", ">", RegexOptions.IgnoreCase | RegexOptions.Singleline); return source
.Replace(@"\""", "\"") .Replace(" ", " ")
.Replace(" ",""); }
static void Main(string[] args)
{
//設置同時處於活動狀態的線程池的請求數目
ThreadPool.SetMaxThreads(50, 50); //執行主要函數
HuiCong.Instance.Grab(); //阻止當前線程,等收到信號燈後再放行
HuiCong.Instance.eventX.WaitOne(Timeout.Infinite, true); Console.WriteLine("執行完了"); Console.ReadKey();
} public partial class HuiCong : BaseModel<HuiCong> { //建立信號燈
public ManualResetEvent eventX = new ManualResetEvent(false); protected int iCount = 0; /// <summary>
/// 主函數 /// </summary>
public void Grab() { SiteUrl = "http://s.hc360.com/?mc=enterprise&ee={0}&z=%D6%D0%B9%FA%3A%B1%B1%BE%A9"; string pageHtml = TrimOther(new Common.HttpRequestHelper().getHTML(string.Format(SiteUrl, 1))); if (Regex.IsMatch(pageHtml, "(?<=<span class=\"total\">共)\\d+(?=頁</span>)")) { pageHtml = Regex.Match(pageHtml, "(?<=<span class=\"total\">共)\\d+(?=頁</span>)").Value; PageCount = FunLayer.Transform.Int(pageHtml, 0); } for (int i = 1; i <= PageCount; i++) { //循環每一頁將線程排入線程池 //將函數排入隊列,線程池將自動分配線程執行
ThreadPool.QueueUserWorkItem(new WaitCallback(Search), i); } } /// <summary>
/// 每一頁數據的分析 /// </summary>
/// <param name="index"></param>
private void Search(object index) { string htmlStr = TrimOther(new Common.HttpRequestHelper().getHTML(string.Format(SiteUrl, index))); MatchCollection mc = Regex.Matches(htmlStr, "(?<=<!-- col S -->).+?(?=<!-- bottom_info bm-info -->)", RegexOptions.IgnoreCase | RegexOptions.Singleline); foreach (var item in mc) { if (Regex.IsMatch(item.ToString(), "href=\"(?'link'.*?)\"\\s?onclick", RegexOptions.IgnoreCase | RegexOptions.Singleline)) { string companyLink = Regex.Match(item.ToString(), "href=\"(?'link'.*?)\"\\s?onclick", RegexOptions.IgnoreCase).Groups["link"].Value; GetDeatil(companyLink); } } //多線程並行執行狀況下,執行原子操做(此處是在某一正在進行的線程執行完上面的代碼後執行原子操做,標誌當前線程完成任務。)
Interlocked.Increment(ref iCount); //完成的線程數和頁數一致時發出結束信號,標誌本線程池中線程已所有執行結束。
if (iCount == PageCount) { Console.WriteLine("發出結束信號!"); eventX.Set(); } } /// <summary>
/// 具體某個公司信息的匹配 /// </summary>
/// <param name="url"></param>
private void GetDeatil(string url) { string htmlStr = TrimOther(new Common.HttpRequestHelper().getHTML(url + "shop/show.html")); UserInfo user = new UserInfo(); user.CompanyName = Regex.Match(htmlStr, "<h1[^>]+?>(?'CompanyName'.+?)</h1>", RegexOptions.Singleline | RegexOptions.IgnoreCase).Groups["CompanyName"].Value.Trim(); string pattern = string.Concat( @"<h5\s?[^>]+?>\s?" , @"<li\s?title=""(?'name'[^>]+?)"">\s?" , @"<span\s?title=""(?'ContactName'[^>]+?)"">([^<]+?)</span>\s?" , @"<span\s?title=""(?'ContactPost'[^>]+?)"">([^<]+?)</span>\s?" , @"</li>\s?" , @"</h5>\s?" , @"(?'textlist'(<h5><li\s+title=""[^>]+?"">[^>]+?</li></h5>)+)"); if (Regex.IsMatch(htmlStr, pattern, RegexOptions.IgnoreCase | RegexOptions.Singleline)) { Match mc = Regex.Match(htmlStr, pattern, RegexOptions.IgnoreCase | RegexOptions.Singleline); user.ContactName = mc.Groups["ContactName"].Value; user.ContactPost = mc.Groups["ContactPost"].Value; htmlStr = mc.Groups["textlist"].Value; if (Regex.IsMatch(htmlStr, "title=\".+?\"", RegexOptions.IgnoreCase | RegexOptions.Singleline)) { MatchCollection mcdetail = Regex.Matches(htmlStr, "title=\"(?'text'.+?)\"", RegexOptions.IgnoreCase | RegexOptions.Singleline); foreach (var item in mcdetail) { string itemtext = item.ToString().Replace("title=", "").Replace("\"", "").Trim(); string itemname = itemtext.Substring(0, itemtext.IndexOf(":") + 1); switch (itemname) { case "電話:": user.FixedTelephone = itemtext.Replace(itemname, ""); break; case "手機:": user.Mobile = itemtext.Replace(itemname, ""); break; case "傳真:": user.Fax = itemtext.Replace(itemname, ""); break; default: ; break; } } } Console.WriteLine("=========" + user.CompanyName + "==========="); Console.WriteLine(user.ContactName + "|" + user.ContactPost + "|" + user.Mobile); } } private static object lockObject = new object(); private static volatile HuiCong _instance; public static HuiCong Instance { get { if (_instance == null) { lock (lockObject) { return _instance ?? (_instance = new HuiCong()); } } return _instance; } } }
好吧,感受線程池解釋的很差,還沒理解透徹。再去學習學習。。
今日成語:溫故而知新,能夠爲師矣。
子曰:"溫新,可矣。"
孔子說:"溫習舊知識知道新的理解與體會,能夠憑藉(這一點)成爲老師了。"
故:舊的 而:就 可:能夠 以:憑藉 爲:成爲
↓↓↓掃描下面的二維碼,關注個人公衆號