多线程采集器小结

       这两天因为要从网上采集数据,就改造了下之前写的那个采集器(之前那个是单个线程的,效率非常低,昨天改为多线程的了。),使用了ThreadPool(线程池)。感觉线程池还是相当的好用的。但同时也还有好多问题没有搞清楚。虽然数据时顺利采下来了,但在接下来的日子里还是要好好把没弄清楚的搞清楚。今天就暂时先做下粗略的小结。

      采集器的界面如下,做的非常简单。

数据采集页面

 

      采集器的原理说起来也非常的简单,首先根据Uri读取页面的HTML源代码,读取好页面之后,使用正则匹配,将你需要的内容扣出来写入数据库就OK了。

      读取网页的HTML源代码有好几种方式,可以使用不同的类,如WebClient,HttpRequest等等,我这次使用的是WebClient。具体代码如下:

        /// <summary>
        /// 获取页面的HTML代码
        /// </summary>
        /// <param name="uri">页面所在的网址</param>
        /// <returns>页面的HTML源代码</returns>
        public string GetHTMLCode(Uri uri) {
            WebClient webclient = new WebClient();
            webclient.Headers.Add("user-agent", "mozilla/5.0 (windows; u; windows nt 5.2; zh-cn; rv:1.9.1.3) gecko/20090824 firefox/3.5.3");
            try
            {
                Stream data = webclient.OpenRead(@uri);  //将HTML源码读入data
                StreamReader reader = new StreamReader(data, Encoding.Default);  //以默认编码从data中读入到reader
                string s = reader.ReadToEnd();  //读取完整的HTML源码到s中
                data.Close();
                reader.Close();
                return s;
            }
            catch (WebException ex)
            {
                return "";
            }
            catch (ArgumentNullException ex) {
                return "";
            }
        }

      通过调用这个方法,就可以实现将HTML源码写入字符串s中。下一步就是通过正则来筛选数据,代码如下:

        /// <summary>
        /// 通过正则截取需要的信息
        /// </summary>
        /// <param name="HTMLCode">页面的HTML源代码</param>
        /// <returns>ArrayList</returns>
        public ArrayList RegFetch(string HTMLCode) {
            try
            {
                ArrayList alist = new ArrayList();
                Regex r = new Regex("<li><a href=/"(.*?)/">(.*?)</a>", RegexOptions.Compiled);
                Match m = r.Match(HTMLCode);
                while (m.Success)
                {
                    for (int i = 1; i <= 2; i++)
                    {
                        Group g = m.Groups[i];
                        CaptureCollection cc = g.Captures;
                        for (int j = 0; j < cc.Count; j++)
                        {
                            Capture c = cc[j];
                            alist.Add(c);
                        }
                    }
                    m = m.NextMatch();
                }
                return alist;
            }
            catch {
                return null;
            }
        }

      通过调用这个匹配方法,就可以把超链接的地址及文字存入ArrayList中,下一步只需要将这些收集是数据入库即可(具体代码省略了)。

      做完以上这几步,我们其实已经实现了数据的采集,只不过现在这个采集还只是主线程在做,即单线程的采集。效率之低可想而知了。所以下面需要对代码稍加改动,把单线程转为多线程。这里我使用ThreadPool线程池,这样比较省事,直接把任务丢给线程池,由线程池自动分配任务给空闲的线程。代码如下:

        /// <summary>
        /// 点击开始采集按钮
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btn_autostart_Click(object sender, EventArgs e)
        {
            try
            {
                ThreadPool.SetMaxThreads(100, 100);  //设置最大线程数
                foreach (var i in lbox_urllist.Items)
                {
                    ThreadPool.QueueUserWorkItem(new WaitCallback(AutoStartCollection), i);//线程池指定线程执行AutoStartCollection方法
                }
            }
            catch (ApplicationException ex)
            {
                MessageBox.Show(ex.Message);
            }
            catch (OutOfMemoryException ex)
            {
                MessageBox.Show(ex.Message);
            }
            catch (ArgumentNullException ex)
            {
                MessageBox.Show(ex.Message);
            }
        }
        /// <summary>
        /// AutoStart按钮点击
        /// </summary>
        /// <param name="Object"></param>
        private void AutoStartCollection(object Object)
        {
            Uri uri = new Uri(Object.ToString());
            string HTMLCode = cdobj.GetHTMLCode(uri);
            ArrayList alist = cdobj.RegFetch(HTMLCode);
            bool flag = cdobj.AutoSaveData(alist);
            if (flag)
            {
                this.Invoke(new Action(delegate()
                {
                    this.lbox_success.Items.Add(uri);
                    this.lb_success.Text = "( " + this.lbox_success.Items.Count.ToString() + "/" + this.lbox_urllist.Items.Count.ToString() + " )";
                }));
            }
            else
            {
                this.Invoke(new Action(delegate()
                {
                    this.lbox_failure.Items.Add(uri);
                    this.lb_failure.Text = "( " + this.lbox_failure.Items.Count.ToString() + "/" + this.lbox_urllist.Items.Count.ToString() + " )";
                }));
            }
        }

      这样就变成多线程的采集器了,当时在测试的时候发现了一个问题,我原本想没采完一张网页,那个成功列表的Listbox里面就要加一条。但是当时一运行就报错,后来经过周哥提醒(在此表示感谢)发现是少了InVoke的调用。因为这涉及到不同线程间的访问,.NET本身是不允许执行线程去访问其他线程创建的控件的,所以需要调用this.Invoke(new Action(delegate(){...}));这样的委托。而且特别强调一点,该方法只适用于.NET 3.5 如果是.NET 2.0的话就需要先定义委托,然后通过委托来调用。.NET 3.5中直接简化了。

      当时还有一个问题搞的不是很清楚,那就是多线程的线程最多能开多少?我在网上查了下,有些人说最多只能开64个,估计不止。如果有高手清楚的,还请赐教。最后还想实现一个功能就是在执行采集的时候,底部能循环显示进度条。我的思路是开始执行的时候主线程开始调用显示进度条的方法,当线程池内的所有线程均完成任务时通知主线程停止调用。不知道思路对不对,到现在都没做出来。

 

 

PS. 数据库连接的是MySql,当时是用的参数传递的方式。用惯了SQL Server的参数传递方式,这次也理所当然的写成了@paramname,然后发现参数明明已经赋值却一直没法写入数据库。郁闷了好久,在网上搜了搜,才发现,Mysql用的是跟Java那样的,用?来表示,所以应该写成?paramname。非常郁闷!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值