背景:實驗室大數據分析須要獲得社交網站的數據,首選固然是新浪。數據包括指定關鍵詞、話題、位置的微博的內容。字段包括:圖片、時間、用戶、位置信息。php
思路分析:要爬新浪的數據主要有2種方法:html
1.微博開發者平臺提供的微博API,資源包括微博內容、評論、用戶、關係、話題等信息。同時,你也能夠申請高級接口、商業接口得到更多權限,你要去註冊申請成爲開發者得到OAuth2受權以及這個使用參考,審覈大約要1周。也能夠直接使用別人分享的APPKEY。python
優勢是簡單,有專門的問答社區,代碼更新方便,能夠得到real time的數據,對熱門話題比較開放,返回的文檔主要是json有不少現成的庫類能夠解析。 缺點是,數據受API規定,沒有關鍵詞檢索,對不嗯呢該有訪問次數限制,畢竟人家主要是服務微博的第三方應用,這個缺陷能夠用更換上面的appkey來解決。
git
2.傳統html爬蟲,即發送http請求返回html源文件,再解析,提取裏面的下級URL或者其餘咱們要的數據。C#.JAVA python都有相關的網頁工具,c#主要是WebClient,或者HttpRequest/HttpResponse,也可使用WebBrowser/page/htmldocument模擬瀏覽器操做,Java可使用htmluint包。python對web處理的強大支持,使得網上的不少爬蟲都是用python寫的。github
實現方法web
因爲實驗室要採集的量比較大,就先選擇第二種試試看。首先分析一下微博的網頁。首先是按關鍵字搜索微博,可使用高級搜索進行過濾,選着原創和含圖片選項,過濾掉轉發的重複內容和不含圖片的微博。問題是改版後的微博搜索只能顯示第一頁,後面內容須要登錄才能看的。因此咱們的爬蟲程序首要經過登錄這一關。微博的驗證碼能夠手動取消,位置以下圖:正則表達式
1.登錄json
我知道的登錄的方法有3種。c#
1,模擬登錄過程。C#地webBrowser封裝了操控瀏覽器的方法,根據ID獲取HTML上的控件填充表單,點擊登錄等,很是的人性化操做,可是須要識別驗證碼的代碼。瀏覽器
2,模擬登錄POST,要用到抓包工具,好比burpsuite。這可能須要瞭解JS加密方法,以及驗證碼,具體網上找找。
3。第三種比較簡單,模擬登錄後狀態。到瀏覽器找與s.weibo.com鏈接的heards,提取COOKIE字段加入到代碼做爲heard一塊兒發送request。具體是打開網站-》F12-》NetWor選項卡-》Documents / Scripts-》點個文件看看-》heards的Hosst是否是s.weibo.com,是的話把裏面的cookie複製下來。注意cookie閣段時間會變化。因爲只要登錄一次就能取數據,爲了節省時間我就用了第三種方法。
2.得到html源文件
要得到源文件首先要向服務器發送訪問請求。新浪微博搜索的URL是:http://s.weibo.com/wb/xiada&xsort=time 。仔細看會發現是像php命令風格的url:
1.其中s.weibo.com是Host。 /wb/是指搜微博,綜合搜索的話是/weibo/,找人的話是/user/,圖片是/pic/。注意這裏的搜的據我觀察好像是用戶使用發微博功能現拍的單圖內容,因此內容少不少。
2.接着xiada就是搜索的關鍵詞了,若是是中文關鍵詞,這個字段就是經URLEncode編碼的字符串。
3.Xsort=time 是搜索結果按時間排序,
4.page=2,就是頁數了,沒有寫就是第一頁啦。能夠經過更換page=多少來實現翻頁。
5.還有一些其餘命令就不列舉了,這些命令的可選值 均可以按"高級搜索" 選擇你要的篩選條件再看看URL是什麼樣的就知道啦。
你能夠作個界面讓用戶(也就是本身。。)來設置要搜什麼樣的微博。用HttpRequest 加上cookie 訪問你經過上面命令生成的url,返回的就是網頁源代碼了。
3.解析html獲取數據
最關鍵的步驟就是怎麼提取你的文檔,假如你用c#的WebBrowser 那麼恭喜你能夠直接用Document類把html裝進去,經過getItemById()之類的方法,根據標籤頭,直接提取標籤內容,很是之方便。我是用正則表達式去源代碼字符串裏匹配我要的數據(圖片連接,下層連接)。正則表達式C#參考這個,總結的夠清楚。
分析完這同樣還要找下一頁的超連接,在網頁源碼裏搜"next",會發現只有一個,page next前面的herf="..."就是下一頁的連接,能夠根據這個來判斷是否是最後一頁。更快的方法是直接修改上面的命令page=n. 獲得next page的url以後重複步驟2,3直到完成。
須要注意的是微博的反爬蟲機制,若是你短期內請求30次左右,無論你登錄與否,會被警告而後讓你輸入驗證。解決方法要麼推送到軟件上手動輸入,要麼用瀏覽器手動輸入,要麼加個驗證碼識別模塊。自動驗證碼識別這個能夠用htmlunit實現,我還在研究中。
4.源碼
這個程序適合入門學習,不少特性(存儲策略,網絡優化,性能)都沒考慮進去,我這裏貼出主要代碼。主要部分實現細節能夠參考http://www.cnblogs.com/Jiajun/archive/2012/06/16/2552103.html 這篇博客,思路大體同樣,註釋很詳細,就是有點繁雜了,我借鑑了一點,很是之感謝。
另外有更多需求的同窗能夠去研究一下這個項目,在gihub有不少watch and fork,有API讓你使用,並且有不少在爬到的數據能夠下載,值得深刻學習一下源碼。
1 namespace IMT.weiboSpider 2 { 3 class Spider 4 { 5 #region field
6 public List<WB_Feed> feedList { get; set; } 7 private readonly object _locker = new object(); 8 private ThreadManager mThreadManager;//workManager.dowork_handler=
9 public delegate void ContentsSavedHandler(string name); 10 public delegate void DownloadFinishHandler(int count); 11 public event ContentsSavedHandler ContentsSaved = null; 12 public event DownloadFinishHandler DownloadFinish = null; 13 public string _savePath; 14 public string _url; 15 public string _cookie; 16 public string _next_url; 17 public string _html; 18 public int _count_current_URL; 19 #endregion
20
21
22 /// <summary>
23 /// init 24 /// </summary>
25 public Spider() 26 { 27 feedList = new System.Collections.Generic.List<WB_Feed>(); 28 mThreadManager = new ThreadManager(4); 29 mThreadManager.dowork_handler += new ThreadManager.Do_a_Work_Handler(GetIMGDownload); 30 } 31 /// <summary>
32 /// start working 33 /// </summary>
34 /// <param name="cookies"></param>
35 public void Start(string cookies) 36 { 37 _cookie = cookies; 38 Random rad=new Random(); 39 _html=GetHtmlDownload(_url, _cookie); 40 _count_current_URL = GetHtmlPrased(_html); 41 _next_url = GetNextHtmlUrl(_html); 42 ContentsSaved.Invoke("url:[" + _url + "]IMG counts:" + _count_current_URL); 43 while (!string.IsNullOrEmpty(_next_url)) 44 { 45 Thread.Sleep(rad.Next(1000,3000)); 46 _html = GetHtmlDownload(_next_url, _cookie); 47 _count_current_URL = GetHtmlPrased(_html); 48 if (_count_current_URL < 2) 49 { 50 MessageBox.Show("須要手動刷新帳號輸入驗證碼,刷新後點再擊肯定"); 51 continue; 52 } 53 ContentsSaved.Invoke("In The url:[" + _next_url + "]IMG counts:" + _count_current_URL); 54 _next_url = GetNextHtmlUrl(_html); 55
56 } 57
58
59 DialogResult dlresult = MessageBox.Show("fund image:" + feedList.Count, "ALL search result was prased and saveD in ./URLCollection.txt.\nContinue download?", MessageBoxButtons.YesNo); 60 switch (dlresult) 61 { 62 case DialogResult.Yes: 63 { 64 SaveURLBuffer(_savePath + "URLCollection.txt"); 65 mThreadManager.DispatchWork(); 66 break; 67 } 68 case DialogResult.No: 69 { 70 SaveURLBuffer(_savePath + "URLCollection.txt"); 71 break; 72 } 73 } 74 //增長顯示狀態欄
75 } 76
77 /// <summary>
78 /// stop work 79 /// </summary>
80 public void Abort() 81 { 82 if (mThreadManager != null) 83 { 84 mThreadManager.StopWorking(); 85 } 86 } 87
88 /// <summary>
89 /// save the imgages url to local 90 /// </summary>
91 /// <param name="filepath"></param>
92 public void SaveURLBuffer(string filepath) 93 { 94 FileStream fs = new FileStream(filepath, FileMode.Create, FileAccess.ReadWrite); 95 StreamWriter sw = new StreamWriter(fs); 96 foreach (var i in feedList) 97 sw.WriteLine(i.imgSrc); 98 sw.Flush(); 99 sw.Close(); 100 fs.Close(); 101 } 102
103 #region 網頁請求方法
104
105 /// <summary>
106 /// downloaad image in (index) work thread 107 /// </summary>
108 /// <param name="index"></param>
109 private void GetIMGDownload(int index) 110 { 111 string imgUrl = ""; 112 try
113 { 114 lock (_locker)// lock feedlist access
115 { 116 if (feedList.Count <= 0) 117 { 118 mThreadManager.FinishWoking(index); 119 if (mThreadManager.IsAllFinished()) 120 DownloadFinish(index); 121 return; 122 } 123 imgUrl = feedList.First().imgSrc; 124 feedList.RemoveAt(0); 125 } 126 string fileName = imgUrl.Substring(imgUrl.LastIndexOf("/") + 1); 127 WebClient wbc = new WebClient(); 128 wbc.DownloadFileCompleted += new AsyncCompletedEventHandler(DownloadFileCallback); 129 wbc.DownloadFileAsync(new Uri(imgUrl), _savePath + fileName,fileName+"in task:"+index); 130 } 131 catch (WebException we) 132 { 133 System.Windows.Forms.MessageBox.Show("RequestImageFAIL" + we.Message + imgUrl + we.Status); 134 } 135 } 136
137 /// <summary>
138 /// call this when the img save finished 139 /// </summary>
140 /// <param name="sender"></param>
141 /// <param name="e"></param>
142 private void DownloadFileCallback(object sender, AsyncCompletedEventArgs e) 143 { 144 if (e.Error != null) 145 { //下載成功 146 // WebClient wbc=sender as WebClient; 147 // wbc.Dispose();
148 if(ContentsSaved!=null) 149 ContentsSaved((string)sender); 150 string msg=sender as string; 151 mThreadManager.FinishWoking((int)msg[msg.Length-1]); 152 mThreadManager.DispatchWork(); 153 } 154 throw new NotImplementedException();//下載完成回調;
155 } 156
157 /// <summary>
158 /// get response html string 159 /// </summary>
160 /// <param name="wbUrl"></param>
161 /// <param name="heardCookie"></param>
162 /// <returns></returns>
163 public string GetHtmlDownload(string wbUrl, string heardCookie) 164 { 165 string url = wbUrl; 166 string Cookie = heardCookie; 167 string html = null; 168 WebClient client = new WebClient(); 169 client.Encoding = System.Text.ASCIIEncoding.UTF8; 170 client.Headers.Add("Cookie", Cookie); 171
172 Stream data = client.OpenRead(url); 173
174 StreamReader reader = new StreamReader(data); 175 html = reader.ReadToEnd(); 176 client.Dispose(); 177 return html; 178 } 179
180 /// <summary>
181 /// prase html string return the picture url number in this page 182 /// </summary>
183 /// <param name="html"></param>
184 /// <returns></returns>
185 public int GetHtmlPrased(string html) 186 { 187 string _html = html; 188 string result; 189 int count=0; 190 Regex regex = new Regex(@"(?<=http)[^""]+(?=jpg)"); 191 MatchCollection theMatches = regex.Matches(_html); 192 foreach (Match thematch in theMatches) 193 { 194 if (thematch.Length != 0) 195 { 196 result = "http" + thematch.Value.Replace("\\", "") + "jpg"; 197
198 //TO DO : 定義匹配規則查找相同的微博。
199
200 feedList.Add(new WB_Feed(result)); 201 count++; 202 } 203 } 204 return count; 205 } 206
207 /// <summary>
208 /// form the url commond to get next page 209 /// </summary>
210 /// <param name="url"></param>
211 /// <param name="num"></param>
212 /// <returns></returns>
213 public string GetNextHtmlUrlFromPageNUM(string url,int num) 214 { 215 string nextPage; 216 string preUrl = url; 217 int pageIdex = preUrl.IndexOf("page")+5; 218 nextPage=preUrl.Remove(pageIdex, 1); 219 nextPage = nextPage.Insert(pageIdex, "" + num); 220 return nextPage; 221 } 222 /// <summary>
223 /// prase html string to get the next page url 224 /// </summary>
225 /// <param name="html"></param>
226 /// <returns></returns>
227 public string GetNextHtmlUrl(string html) 228 { 229 string nextPage; 230 string s_domain; 231 string _html = html; 232
233 int nextIndex = _html.LastIndexOf("page next");//find last to be fast
234 if (nextIndex < 0) 235 { 236
237 MessageBox.Show("there is not nextpage"); 238 return null; 239 } 240 //MessageBox.Show("find next in=" + nextIndex);
241 int herfIndex = _html.LastIndexOf("href=", nextIndex); 242 nextPage = _html.Substring(herfIndex + 5 + 2, nextIndex - herfIndex); 243 nextPage= nextPage.Substring(0, nextPage.IndexOf(@"""") - 1); 244 nextPage= nextPage.Replace("\\", ""); 245 //$CONFIG['s_domain'] = 'http://s.weibo.com';
246 int domainIndex=html.IndexOf("'s_domain'"); 247 domainIndex = html.IndexOf('=', domainIndex)+3; 248 int domainLength=html.IndexOf(";",domainIndex)-1-domainIndex; 249 s_domain = html.Substring(domainIndex, domainLength); 250
251 nextPage = s_domain + nextPage; 252 return nextPage; 253 } 254 #endregion
255
256
257
258 /// <summary>
259 /// work group manageer of downloading images with defult 4 work thread; 260 /// </summary>
261 private class ThreadManager 262 { 263 private bool[] _reqBusy = null; //每一個元素表明一個工做實例是否正在工做
264 private int _reqCount = 4; //工做實例的數量
265 private bool _stop = true; 266 public delegate void Do_a_Work_Handler(int index); 267 public Do_a_Work_Handler dowork_handler; 268 public ThreadManager(int threadCount) 269 { 270 _reqCount = threadCount; 271 _reqBusy = new bool[threadCount]; 272 for (int i=0;i<threadCount;i++) 273 { 274 _reqBusy[i] = false; 275 } 276 _stop = false; 277 } 278 public void StartWorking(int index) 279 { 280 _reqBusy[index] = true; 281 dowork_handler.Invoke(index);/////invoke requeset resource
282 } 283 public void FinishWoking(int index) 284 { 285 _reqBusy[index] = false; 286 } 287 public bool IsAllFinished() 288 { 289 bool done = true; 290 foreach (var i in _reqBusy) 291 done = i & done; 292 return done; 293 } 294 public void WaitALLFinished() 295 { 296 while (!IsAllFinished()) 297 Thread.Sleep(1000); 298 } 299 public void StopWorking() 300 { 301 _stop = true; 302 for (int i = 0; i < _reqCount; i++) 303 _reqBusy[i] = false; 304 } 305 public void DispatchWork() 306 { 307 if (_stop) 308 return; 309 for (int i = 0; i < _reqCount; i++)////判斷i編號的工做實例是否空閒
310 if (!_reqBusy[i]) 311 StartWorking(i); 312 } 313 } 314 } 315
316 /// <summary>
317 /// the class of weibo data you can add more field for get more detail in codes 318 /// </summary>
319 public class WB_Feed 320 { 321 public WB_Feed(string img) 322 { 323 imgSrc = img; 324 } 325 public string imgSrc { get; set; } 326
327 } 328
329 }