亚洲国产日韩欧美一区二区三区,精品亚洲国产成人av在线,国产99视频精品免视看7,99国产精品久久久久久久成人热,欧美日韩亚洲国产综合乱

用C#實現(xiàn)網(wǎng)絡爬蟲

Original 2016-11-16 15:49:44 837
abstract:網(wǎng)絡爬蟲在信息檢索與處理中有很大的作用,是收集網(wǎng)絡信息的重要工具。接下來就介紹一下爬蟲的簡單實現(xiàn)。爬蟲的工作流程如下爬蟲自指定的URL地址開始下載網(wǎng)絡資源,直到該地址和所有子地址的指定資源都下載完畢為止。下面開始逐步分析爬蟲的實現(xiàn)。 1. 待下載集合與已下載集合為了保存需要下載的URL,同時防止重復下載,我們需要分別用了兩個集合來存放將要下載的URL和已經(jīng)下載的URL。因為在保存URL的

網(wǎng)絡爬蟲在信息檢索與處理中有很大的作用,是收集網(wǎng)絡信息的重要工具。

接下來就介紹一下爬蟲的簡單實現(xiàn)。

爬蟲的工作流程如下

wKioL1gqt4Xz-xdeAAA2rKk-R6M731.jpg-wh_651x-s_434214206.jpg

爬蟲自指定的URL地址開始下載網(wǎng)絡資源,直到該地址和所有子地址的指定資源都下載完畢為止。

下面開始逐步分析爬蟲的實現(xiàn)。

 

1. 待下載集合與已下載集合

為了保存需要下載的URL,同時防止重復下載,我們需要分別用了兩個集合來存放將要下載的URL和已經(jīng)下載的URL。

因為在保存URL的同時需要保存與URL相關的一些其他信息,如深度,所以這里我采用了Dictionary來存放這些URL。

具體類型是Dictionary<string, int> 其中string是Url字符串,int是該Url相對于基URL的深度。

每次開始時都檢查未下載的集合,如果已經(jīng)為空,說明已經(jīng)下載完畢;如果還有URL,那么就取出第一個URL加入到已下載的集合中,并且下載這個URL的資源。

 

2. HTTP請求和響應

C#已經(jīng)有封裝好的HTTP請求和響應的類HttpWebRequest和HttpWebResponse,所以實現(xiàn)起來方便不少。

為了提高下載的效率,我們可以用多個請求并發(fā)的方式同時下載多個URL的資源,一種簡單的做法是采用異步請求的方法。

控制并發(fā)的數(shù)量可以用如下方法實現(xiàn)

private void DispatchWork()
{
    if (_stop) //判斷是否中止下載
    {
        return;
    }
    for (int i = 0; i < _reqCount; i++)
    {
        if (!_reqsBusy[i]) //判斷此編號的工作實例是否空閑
        {
            RequestResource(i); //讓此工作實例請求資源
        }
    }
}

 由于沒有顯式開新線程,所以用一個工作實例來表示一個邏輯工作線程

1 private bool[] _reqsBusy = null; //每個元素代表一個工作實例是否正在工作
2 private int _reqCount = 4; //工作實例的數(shù)量

 每次一個工作實例完成工作,相應的_reqsBusy就設為false,并調(diào)用DispatchWork,那么DispatchWork就能給空閑的實例分配新任務了。

 

 接下來是發(fā)送請求

private void RequestResource(int index)
 {
     int depth;
     string url = "";
     try
     {
         lock (_locker)
         {
             if (_urlsUnload.Count <= 0) //判斷是否還有未下載的URL
             {
                 _workingSignals.FinishWorking(index); //設置工作實例的狀態(tài)為Finished
                 return;
             }
             _reqsBusy[index] = true;
             _workingSignals.StartWorking(index); //設置工作狀態(tài)為Working
             depth = _urlsUnload.First().Value; //取出第一個未下載的URL
             url = _urlsUnload.First().Key;
             _urlsLoaded.Add(url, depth); //把該URL加入到已下載里
             _urlsUnload.Remove(url); //把該URL從未下載中移除
         }
                 
         HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
         req.Method = _method; //請求方法
         req.Accept = _accept; //接受的內(nèi)容
         req.UserAgent = _userAgent; //用戶代理
         RequestState rs = new RequestState(req, url, depth, index); //回調(diào)方法的參數(shù)
         var result = req.BeginGetResponse(new AsyncCallback(ReceivedResource), rs); //異步請求
         ThreadPool.RegisterWaitForSingleObject(result.AsyncWaitHandle, //注冊超時處理方法
                 TimeoutCallback, rs, _maxTime, true);
     }
     catch (WebException we)
     {
         MessageBox.Show("RequestResource " + we.Message + url + we.Status);
     }
 }

第7行為了保證多個任務并發(fā)時的同步,加上了互斥鎖。_locker是一個Object類型的成員變量。

第9行判斷未下載集合是否為空,如果為空就把當前工作實例狀態(tài)設為Finished;如果非空則設為Working并取出一個URL開始下載。當所有工作實例都為Finished的時候,說明下載已經(jīng)完成。由于每次下載完一個URL后都調(diào)用DispatchWork,所以可能激活其他的Finished工作實例重新開始工作。

第26行的請求的額外信息在異步請求的回調(diào)方法作為參數(shù)傳入,之后還會提到。

第27行開始異步請求,這里需要傳入一個回調(diào)方法作為響應請求時的處理,同時傳入回調(diào)方法的參數(shù)。

第28行給該異步請求注冊一個超時處理方法TimeoutCallback,最大等待時間是_maxTime,且只處理一次超時,并傳入請求的額外信息作為回調(diào)方法的參數(shù)。

 

RequestState的定義是

class RequestState
{
    private const int BUFFER_SIZE = 131072; //接收數(shù)據(jù)包的空間大小
    private byte[] _data = new byte[BUFFER_SIZE]; //接收數(shù)據(jù)包的buffer
    private StringBuilder _sb = new StringBuilder(); //存放所有接收到的字符

    public HttpWebRequest Req { get; private set; } //請求
    public string Url { get; private set; } //請求的URL
    public int Depth { get; private set; } //此次請求的相對深度
    public int Index { get; private set; } //工作實例的編號
    public Stream ResStream { get; set; } //接收數(shù)據(jù)流
    public StringBuilder Html
    {
        get
        {
            return _sb;
        }
    }

    public byte[] Data
    {
        get
        {
            return _data;
        }
    }

    public int BufferSize
    {
        get
        {
            return BUFFER_SIZE;
        }
    }

    public RequestState(HttpWebRequest req, string url, int depth, int index)
    {
        Req = req;
        Url = url;
        Depth = depth;
        Index = index;
    }
}

TimeoutCallback的定義是

private void TimeoutCallback(object state, bool timedOut)
{
    if (timedOut) //判斷是否是超時
    {
        RequestState rs = state as RequestState;
        if (rs != null)
        {
            rs.Req.Abort(); //撤銷請求
        }
        _reqsBusy[rs.Index] = false; //重置工作狀態(tài)
        DispatchWork(); //分配新任務
    }
}

 接下來就是要處理請求的響應了

private void ReceivedResource(IAsyncResult ar)
{
    RequestState rs = (RequestState)ar.AsyncState; //得到請求時傳入的參數(shù)
    HttpWebRequest req = rs.Req;
    string url = rs.Url;
    try
    {
        HttpWebResponse res = (HttpWebResponse)req.EndGetResponse(ar); //獲取響應
        if (_stop) //判斷是否中止下載
        {
            res.Close();
            req.Abort();
            return;
        }
        if (res != null && res.StatusCode == HttpStatusCode.OK) //判斷是否成功獲取響應
        {
            Stream resStream = res.GetResponseStream(); //得到資源流
            rs.ResStream = resStream;
            var result = resStream.BeginRead(rs.Data, 0, rs.BufferSize, //異步請求讀取數(shù)據(jù)
                new AsyncCallback(ReceivedData), rs);
        }
        else //響應失敗
        {
            res.Close();
            rs.Req.Abort();
            _reqsBusy[rs.Index] = false; //重置工作狀態(tài)
            DispatchWork(); //分配新任務
        }
    }
    catch (WebException we)
    {
        MessageBox.Show("ReceivedResource " + we.Message + url + we.Status);
    }
}

第19行這里采用了異步的方法來讀數(shù)據(jù)流是因為我們之前采用了異步的方式請求,不然的話不能夠正常的接收數(shù)據(jù)。

該異步讀取的方式是按包來讀取的,所以一旦接收到一個包就會調(diào)用傳入的回調(diào)方法ReceivedData,然后在該方法中處理收到的數(shù)據(jù)。

該方法同時傳入了接收數(shù)據(jù)的空間rs.Data和空間的大小rs.BufferSize。

 

接下來是接收數(shù)據(jù)和處理

private void ReceivedData(IAsyncResult ar)
{
    RequestState rs = (RequestState)ar.AsyncState; //獲取參數(shù)
    HttpWebRequest req = rs.Req;
    Stream resStream = rs.ResStream;
    string url = rs.Url;
    int depth = rs.Depth;
    string html = null;
    int index = rs.Index;
    int read = 0;

    try
    {
        read = resStream.EndRead(ar); //獲得數(shù)據(jù)讀取結(jié)果
        if (_stop)//判斷是否中止下載
        {
            rs.ResStream.Close();
            req.Abort();
            return;
        }
        if (read > 0)
        {
            MemoryStream ms = new MemoryStream(rs.Data, 0, read); //利用獲得的數(shù)據(jù)創(chuàng)建內(nèi)存流
            StreamReader reader = new StreamReader(ms, _encoding);
            string str = reader.ReadToEnd(); //讀取所有字符
            rs.Html.Append(str); // 添加到之前的末尾
            var result = resStream.BeginRead(rs.Data, 0, rs.BufferSize, //再次異步請求讀取數(shù)據(jù)
                new AsyncCallback(ReceivedData), rs);
            return;
        }
        html = rs.Html.ToString();
        SaveContents(html, url); //保存到本地
        string[] links = GetLinks(html); //獲取頁面中的鏈接
        AddUrls(links, depth + 1); //過濾鏈接并添加到未下載集合中

        _reqsBusy[index] = false; //重置工作狀態(tài)
        DispatchWork(); //分配新任務
    }
    catch (WebException we)
    {
        MessageBox.Show("ReceivedData Web " + we.Message + url + we.Status);
    }
}

第14行獲得了讀取的數(shù)據(jù)大小read,如果read>0說明數(shù)據(jù)可能還沒有讀完,所以在27行繼續(xù)請求讀下一個數(shù)據(jù)包;

如果read<=0說明所有數(shù)據(jù)已經(jīng)接收完畢,這時rs.Html中存放了完整的HTML數(shù)據(jù),就可以進行下一步的處理了。

第26行把這一次得到的字符串拼接在之前保存的字符串的后面,最后就能得到完整的HTML字符串。

 

然后說一下判斷所有任務完成的處理

private void StartDownload()
{
    _checkTimer = new Timer(new TimerCallback(CheckFinish), null, 0, 300);
    DispatchWork();
}

private void CheckFinish(object param)
{
    if (_workingSignals.IsFinished()) //檢查是否所有工作實例都為Finished
    {
        _checkTimer.Dispose(); //停止定時器
        _checkTimer = null;
        if (DownloadFinish != null && _ui != null) //判斷是否注冊了完成事件
        {
            _ui.Dispatcher.Invoke(DownloadFinish, _index); //調(diào)用事件
        }
    }
}

第3行創(chuàng)建了一個定時器,每過300ms調(diào)用一次CheckFinish來判斷是否完成任務。
第15行提供了一個完成任務時的事件,可以給客戶程序注冊。_index里存放了當前下載URL的個數(shù)。

該事件的定義是

public delegate void DownloadFinishHandler(int count);

/// <summary>
/// 全部鏈接下載分析完畢后觸發(fā)
/// </summary>
public event DownloadFinishHandler DownloadFinish = null;
 
 
GJM :于 2016-11-16 轉(zhuǎn)載自 http://www.cnblogs.com/Jiajun/archive/2012/06/17/2552458.html   如影響作者版權問題 請聯(lián)系我 993056011@163.com


Release Notes

Popular Entries