网络爬虫(专门抓取图片)

xmfdsh我真是兴趣多多,怎么老是静不下心来搞定一方面的技术,再学点其他的东西,循序渐进,好吧,我又研究网络爬虫去了,这是一个简单版的,参考了网上很多资料,C#来编写,专门抓取图片,能够抓取一些需要cookie的网站,所以功能上还是挺完善的,xmfdsh只研究了三天,因此还有大把需要改进的地方,日后再 慢慢改进,在本文后面附上整个项目,在此献给喜欢研究C#的朋友们,让我慢慢地道来:

#region 访问数据 + Request(int index)
        /// <summary>
        /// 访问数据
        /// </summary>
        private void Request(int index)
        {
            try
            {
                int depth;
                string url = "";
                //lock锁住Dictionary,因为Dictionary多线程会出错
                lock (_locker)
                {
                    //查看是否还存在未下载的链接
                    if (UnDownLoad.Count <= 0)
                    {
                        _workingSignals.FinishWorking(index);
                        return;
                    }
                    //否则的话,把该虫子标记为在工作
                    _reqsBusy[index] = true;
                    depth = UnDownLoad.First().Value;
                    url = UnDownLoad.First().Key;


                    IsDownLoad.Add(url, depth);//将URL 加入到已经下载集合里
                    UnDownLoad.Remove(url);
                }
                //网络协议的东西,不懂网上搜一下,(HttpWebRequest)的使用
                //这个需要一点日子理解,xmfdsh不是一下子就弄懂
                HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
                request.Method = "GET";
                request.Accept = "text/html";
                request.UserAgent = "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0)";
                request.CookieContainer = cookies;//cookie 尝试

                RequestState rs = new RequestState(request, url, depth, index);
                //回调函数,如果接受到数据的处理方法
                var result = request.BeginGetResponse(new AsyncCallback(Received), rs);
                //也是回调函数,超时的回调函数
                ThreadPool.RegisterWaitForSingleObject(result.AsyncWaitHandle, TimeOut, rs, MAXTIME, true);
            }
            catch (Exception ex)
            {
                _reqsBusy[index] = false;
                DispatchWork();
            }

        } 
        #endregion

首先上来的是请求某个连接的方法,里面有两个回调函数,说明这里的编程时异步的,这个异步编程个人觉得需要花点时间去了解和学习,这个不是一朝一夕的,个人觉得C#比较难的地方就在一些接口,工具类,还有异步事件等等。

#region 超时的方法 + TimeOut(object state, bool timedOut)
        /// <summary>
        /// 超时的方法
        /// </summary>
        /// <param name="state"></param>
        /// <param name="timedOut"></param>
        private void TimeOut(object state, bool timedOut)
        {
            //判断是否超时
            if (timedOut)
            {
                RequestState rs = state as RequestState;
                if (rs != null)
                {
                    //撤销internet请求
                    rs.Request.Abort();
                }
                DispatchWork();
            }
        } 
#endregion

这个是超时的方法,当超时的时候,默认撤销internet的请求,并回滚,所以这个链接下的东西就没有了,当然超时后,继续请求下一个链接的资源

#region 获取数据 异步 + Received(IAsyncResult ar)
        /// <summary>
        /// 获取数据 异步
        /// </summary>
        /// <param name="ar"></param>
        private void Received(IAsyncResult ar)
        {

            try
            {
                //得到请求进来的参数
                RequestState rs = (RequestState)ar.AsyncState;
                HttpWebRequest request = rs.Request;
                string url = rs.Url;

                //获取响应 
                HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(ar);
                if (response != null && response.StatusCode == HttpStatusCode.OK)//成功获取响应
                {
                    //得到资源流
                    Stream responseStream = response.GetResponseStream();
                    rs.ResponseStream = responseStream;
                    //处理读取数据的异步方法 ReceivedData
                    var result = responseStream.BeginRead(rs.Data, 0, rs.BufferSize, new AsyncCallback(ReceivedData), rs);
                }
                //响应失败
                else
                {
                    response.Close();
                    rs.Request.Abort();
                    _reqsBusy[rs.Index] = false;
                    DispatchWork();
                }
            }
            catch (Exception ex)
            {
                RequestState rs = (RequestState)ar.AsyncState;
                _reqsBusy[rs.Index] = false;
                DispatchWork();
                
            }
        } 
        #endregion

这个是收到了网站响应的时候做的事情,当然用到了异步来请求的话,数据也只能异步的读取,因此有了ReceivedData方法来接受数据,并处理,如果出错或者获取相应失败,相应的,回滚,并把该爬虫虫子的工作状态重新置为准备状态,为下一个链接做好准备。

        #region 异步操作读写 + ReceivedData(IAsyncResult ar)
        /// <summary>
        /// 异步操作读写
        /// </summary>
        /// <param name="ar">异步操作状态</param>
        private void ReceivedData(IAsyncResult ar)
        {

            //获取异步状态参数
            RequestState rs = (RequestState)ar.AsyncState;
            HttpWebRequest request = rs.Request;
            System.Net.HttpWebResponse responseImg = request.GetResponse() as System.Net.HttpWebResponse;
            Stream responseStream = rs.ResponseStream;
            
            string url = rs.Url;
            int depth = rs.Depth;
            string html = "";
            int index = rs.Index;
            int read = 1;
            try
            {
                //如果改链接为图片来的,需要保存此图片
                if (url.Contains(".jpg") || url.Contains(".png"))
                {
                    read = responseStream.EndRead(ar);
                    if (read > 0)
                    {

                        MemoryStream ms = new System.IO.MemoryStream(rs.Data, 0, read);
                        BinaryReader reader = new BinaryReader(ms);

                        byte[] buffer = new byte[32 * 1024];
                        while ((read = reader.Read(buffer, 0, buffer.Length)) > 0)
                        {
                            rs.memoryStream.Write(buffer, 0, read);

                        }
                        //递归 再次请求数据
                        var result = responseStream.BeginRead(rs.Data, 0, rs.BufferSize, new AsyncCallback(ReceivedData), rs);
                        return;
                    }
                }
                else
                {
                    read = responseStream.EndRead(ar);
                    if (read > 0)
                    {
                        //创建内存流
                        MemoryStream ms = new MemoryStream(rs.Data, 0, read);
                        StreamReader reader = new StreamReader(ms, Encoding.GetEncoding("gb2312"));
                        string str = reader.ReadToEnd();
                        //添加到末尾
                        rs.Html.Append(str);
                        //递归 再次请求数据
                        var result = responseStream.BeginRead(rs.Data, 0, rs.BufferSize, new AsyncCallback(ReceivedData), rs);
                        return;
                    }

                }
                if (url.Contains(".jpg") || url.Contains(".png"))
                {
                    //images = rs.Images;
                    SaveContents(rs.memoryStream.GetBuffer(), url);
                }
                else
                {
                    html = rs.Html.ToString();
                    //保存
                    SaveContents(html, url);
                    //获取页面的链接
                }
            }
            catch (Exception ex)
            {
                
                _reqsBusy[rs.Index] = false;
                DispatchWork();
            }
            List<string> links = GetLinks(html,url);
            //得到过滤后的链接,并保存到未下载集合
            AddUrls(links, depth + 1);
            _reqsBusy[index] = false;
            DispatchWork();
        }
        #endregion

这个便是对数据的处理,这里就是重点的,其实也不难,判断是否为图片,如果为图片,保存此图片,因为目前网络爬虫做的还不够高级的时候爬图片是比较实际也比较好玩的(还不赶紧找找哪些网站有好多妹子图片),如果不是图片,我们认为它为普通html页面,便读取其中html代码,如果有发现有链接http或者href便加入到未下载链接中。当然读到的链接我们对一些js或者一些css等做了限制(不去读取这类东西)。

private void SaveContents(byte[] images, string url)
        {
            
            if (images.Count() < 1024*30)
                return;
            if (url.Contains(".jpg"))
            {
                File.WriteAllBytes(@"d:网络爬虫图片" + _index++ + ".jpg", images);
                Console.WriteLine("图片保存成功" + url);
            }
            else
            {
                File.WriteAllBytes(@"d:网络爬虫图片" + _index++ + ".png", images);
                Console.WriteLine("图片保存成功" + url);
            }

        } 
#region 提取页面链接 + List<string> GetLinks(string html)
        /// <summary>
        /// 提取页面链接
        /// </summary>
        /// <param name="html"></param>
        /// <returns></returns>
        private List<string> GetLinks(string html,string url)
        {
            //匹配http链接
            const string pattern2 = @"http://([w-]+.)+[w-]+(/[w- ./?%&=]*)?";
            Regex r2 = new Regex(pattern2, RegexOptions.IgnoreCase);
            //获得匹配结果
            MatchCollection m2 = r2.Matches(html);
            List<string> links = new List<string>();
            for (int i = 0; i < m2.Count; i++)
            {
                //这个原因是w3school的网址,但里面的东西明显不是我们想要的
                if (m2[i].ToString().Contains("www.w3.org"))
                    continue;
                links.Add(m2[i].ToString());
            }
            //匹配href里面的链接,并加到主网址上(学网站的你懂的)
            const string pattern = @"href=([""'])?(?<href>[^'""]+)1[^>]*";
            Regex r = new Regex(pattern, RegexOptions.IgnoreCase);
            //获得匹配结果
            MatchCollection m = r.Matches(html);
           // List<string> links = new List<string>();
            for (int i = 0; i < m.Count; i++)
            {
                string href1 = m[i].ToString().Replace("href=", "");
                href1 = href1.Replace(""", "");
                //找到符合的,添加到主网址(一开始输入的网址)里面去
                string href = RootUrl + href1;
                if (m[i].ToString().Contains("www.w3.org"))
                    continue;
                links.Add(href);
            }
            return links;
        } 
        #endregion

提取页面链接的方法,当读到发现这个为html代码的时候,继续解读里面的代码,找到里面的网址链接,也正是这样才有网络爬虫的功能(不然只能提取本页面就没意思了),其中http链接要提取就理所当然,href里面的话是因为。。。。(学网站你们懂的,不好解释)里面放的几乎都是图片,文章,因此才有了上面要处理href这类代码。

#region 添加url到 UnDownLoad 集合 + AddUrls(List<string> urls, int depth)
        /// <summary>
        /// 添加url到 UnDownLoad 集合
        /// </summary>
        /// <param name="urls"></param>
        /// <param name="depth"></param>
        private void AddUrls(List<string> urls, int depth)
        {
            lock (_locker)
            {
                if (depth >= MAXDEPTH)
                {
                    //深度过大
                    return;
                }
                foreach (string url in urls)
                {
                    string cleanUrl = url.Trim();
                    int end = cleanUrl.IndexOf(' ');
                    if (end > 0)
                    {
                        cleanUrl = cleanUrl.Substring(0, end);
                    }
                    if (UrlAvailable(cleanUrl))
                    {
                        UnDownLoad.Add(cleanUrl, depth);

                    }
                }
            }
        } 
        #endregion
#region 开始捕获 + DispatchWork()
        /// <summary>
        /// 开始捕获
        /// </summary>
        private void DispatchWork()
        {

            for (int i = 0; i < _reqCount; i++)
            {
                if (!_reqsBusy[i])
                {
                    Request(i);
                    Thread.Sleep(1000); 
                }
            }
        } 
        #endregion

这个函数就是让那些虫子工作的,其中_reqCount的值是一开始弄上去的,其实形象理解就是你放出虫子的个数,这个程序里面我默认放的20,随时可以修改。说到某些网站需要cookie的话是通过一开始先访问输入的网址,当然也是用HttpWebRequest帮助类,cookies = request.CookieContainer; //保存cookies,在后面访问后续网址的时候就加上去就行了request.CookieContainer = cookies;//cookie 尝试。应用了cookie才能访问的网站的话,根网页是不需要的,也就好比百度图片的网址http://image.baidu.com/是不需要的,但如果很唐突的访问里面的图片的话就要附上cookie了,所以这个问题也解决,xmfdsh发现这个程序还是有一些网站不能去抓图,抓到一定数量就停了,具体原因不知道,后面再慢慢改进

附上源码:http://files.cnblogs.com/xmfdsh/%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB.rar

原文地址:https://www.cnblogs.com/xmfdsh/p/3697047.html