多线程带智能采集策略的采集系统

简介:

  去年年底的时候曾经发过一个数据采集器网页数据采集器,那是专门针对某一个网站来进行采集的,如果需要采集新的网站内容,就需要修改代码并重新编译。

    昨晚完成了一个带智能策略的采集系统。其实,这个策略的方案三年前就想好了,那时候打算用VB做,做了一半就搁置了。现在用C#才终于把这个方案实现了。

    整个方案大概是这样的:

    需要建立一个AC数据库,MSSQL也行,有四个表:PageType用于记录页面的种类,比如列表页和详细页两类;Url表用于记录要采集的网址,另外还有一个字段TypeID标明该网址属于哪一种页面类型,比如是列表页还是详细页;Rule表记录着各种规则,主要有三个字段,FromTypeID源页类型,ToTypeID目的页类型,Pattern规则;CjPage用于存储采集到的网页内容,还包含网址和页面种类。

    采集策略的核心就在于规则库Rule。

    工作过程大概这样:
1,采集线程从Url表抽取一个网址,并马上在表中将其删除,为了防止冲突,这个过程需要用多线程同步解决;
2,用WebClient请求该网址的页面内容;
3,取得内容后,给线程池的线程来分析处理,本线程回到1,继续去Url表取下一个网址;
4,线程池在有空闲线程时,会调用分析函数ParsePage去处理上次获得的页面内容;
5,先到Rule中取所有FromTypeID为当前网址TypeID;
6,如果没有取到任何规则Rule,则将本页内容写入到CjPage中;
7,如果取到规则,那么遍历规则,为每条规则执行ParseUrl方法;
8,ParseUrl根据规则的Pattern匹配到页面内容中的所有网址,并记录到Url中,规则的ToTypeID就是Url的TypeID。

    至此,整个流程就完成了。下面举一个实际例子来说明一下:
    我要截取动网开发者网络的所有ASP文章http://www.cndw.com/tech/asp/
    首先,在页面类型库中加入列表页和详细页两行,再把http://www.cndw.com/tech/asp/写入到Url中,页面类型是列表页;
    其次,在Rule中加入两条规则:
        一,从列表页取得详细页的网址FromTypeID=1  ToTypeID=2,Pattern是· <a href="([^>]*)" target=_blank>,这条规则将会识别列表页上的所有详细页的链接,并记入到Url中,TypeID是详细页;
        二,从列表页取得列表页的网址FromTypeID=1  ToTypeID=1,Pattern是<a href='([^>]*)'>下一页<\/a>,这条规则将会取得当前列表页上的下一页的链接,并记入到Url中,TypeID还是列表页。
    采集器工作时,如果采集的是详细页的内容,将会直接写入到CjPage中,因为没有FromTypeID=2的规则;而采集的是列表页的内容时,就要做两件事了,因为有两条FromTypeID=1的规则,一件事是识别当前列表页中所有文章的链接并存入Url,另一件事是识别下一列表页链接并存入Url。
    由于规则具有递归性,使得采集器能递归采集到所有的文章。

    下面是一些核心源码(没有公开的都是一些数据层的添删改查的代码):

以下是代码片段:

using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Threading;
using CJData;
using System.Text.RegularExpressions;
using NLog;

namespace CJ
{
    /// <summary>
    /// 写日志委托
    /// </summary>
    /// <param name="log"></param>
    public delegate void WriteLogCallBack(String log);
    /// <summary>
    /// 采集
    /// </summary>
    public class CaiJi
    {
        private WebClient _wc;

        public WebClient Wc
        {
            get
            {
                if (_wc == null) _wc = new WebClient();
                return _wc;
            }
        }
        private Thread thread;

        public String Name = "";
        public event WriteLogCallBack OnWriteLog;

        /// <summary>
        /// 开始工作
        /// </summary>
        public void Start()
        {
            if (thread != null) return;
            thread = new Thread(new ThreadStart(Work));
            thread.Start();
        }
        /// <summary>
        /// 停止工作
        /// </summary>
        public void Stop()
        {
            if (thread != null) thread.Abort();
            thread = null;
        }

        private void Work()
        {
            int times = 0;
            while (times < 100)
            {
                Url url = Url.SelectOne();
                try
                {
                    if (url != null)
                    {
                        String page = Wc.DownloadString(url.UrlAddress);
                        if (!String.IsNullOrEmpty(page))
                        {
                            OnWriteLog(Name + " 成功抓取:" + url.UrlAddress);
                            times = 0;
                            ThreadPool.QueueUserWorkItem(new WaitCallback(ParsePage), new Object[] { url, page });
                        }
                    }
                    else
                    {
                        //OnWriteLog(Name + " 没有工作,休息半秒");
                        times++;
                        //没有工作,休息半秒
                        Thread.Sleep(500);
                    }
                }
                catch (ThreadAbortException e)
                {
                    OnWriteLog(Name + " 外部终止");
                    break;
                }
                catch (Exception e)
                {
                    times++;
                    OnWriteLog(Name + " 赚取" + url.UrlAddress + "出错,休息半秒。" + e.Message);
                    Trace.WriteLine(url.UrlAddress);
                    //出错,休息半秒
                    Thread.Sleep(500);
                }
            }
            OnWriteLog(Name + " 完成!");
        }

        private void ParsePage(Object state)
        {
            Object[] objs = (Object[])state;
            Url url = objs[0] as Url;
            String page = (String)objs[1];
            IList<Rule> rs = Rule.SelectAll(Rule._.FromTypeID, url.TypeID);
            //if (url.PageType.TypeName == "详细页")
            if (rs == null || rs.Count < 1)
            {
                CjPage cp = new CjPage();
                cp.CjTime = DateTime.Now;
                cp.Content = page;
                cp.Url = url.UrlAddress;
                cp.TypeID = url.TypeID;
                cp.Insert();
            }
            else
            {
                foreach (Rule r in rs)
                {
                    ParseUrl(url, r, page);
                }
            }
        }
        private void ParseUrl(Url u, Rule r, String page)
        {
            Regex reg = new Regex(r.Pattern, RegexOptions.Compiled | RegexOptions.IgnoreCase);
            MatchCollection ms = reg.Matches(page);
            foreach (Match m in ms)
            {
                Url url = new Url();
                url.TypeID = r.ToTypeID;
                url.UrlAddress = m.Groups[1].Value;
                if (!url.UrlAddress.StartsWith("http://"))
                {
                    if (url.UrlAddress.Substring(0, 1) == "/")
                    {
                        url.UrlAddress = u.UrlAddress.Substring(0, u.UrlAddress.IndexOf("/", 8)) + url.UrlAddress;
                    }
                    else
                    {
                        if (u.UrlAddress.Substring(u.UrlAddress.Length - 1) == "/")
                            url.UrlAddress = u.UrlAddress + url.UrlAddress;
                        else
                            if (u.UrlAddress.LastIndexOf("/") < u.UrlAddress.LastIndexOf("."))
                                url.UrlAddress = u.UrlAddress.Substring(0, u.UrlAddress.LastIndexOf("/") + 1) + url.UrlAddress;
                            else
                                url.UrlAddress = u.UrlAddress + "/" + url.UrlAddress;
                    }
                }
                url.Insert();
            }
        }
    }
}

以下是代码片段:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Net;

namespace CJ
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        CaiJi[] cjs;
        private void button1_Click(object sender, EventArgs e)
        {
            Button btn = sender as Button;
            if (btn.Text == "停止")
            {
                foreach (CaiJi cj in cjs)
                {
                    if (cj != null) cj.Stop();
                }
                cjs = null;
                btn.Text = "开始";
                return;
            }

            richTextBox1.Text = "";
            btn.Text = "停止";

            int k = 100;
            if (!int.TryParse(textBox1.Text, out k)) k = 100;
            cjs = new CaiJi[k];
            for (int i = 0; i < cjs.Length; i++)
            {
                cjs[i] = new CaiJi();
                cjs[i].Name = "线程" + i.ToString("00");
                cjs[i].OnWriteLog += new WriteLogCallBack(cj_OnWriteLog);
            }
            foreach (CaiJi cj in cjs)
            {
                cj.Start();
            }
        }

        void cj_OnWriteLog(string log)
        {
            if (richTextBox1.InvokeRequired)
            {
                richTextBox1.Invoke(new WriteLogCallBack(cj_OnWriteLog), new object[] { log });
            }
            else
            {
                if (richTextBox1.Lines.Length > 3000) richTextBox1.Text = "";
                richTextBox1.Text = log + Environment.NewLine + richTextBox1.Text;
            }
        }
    }
}


我不相信神话,我只相信汗水!我不相信命运,我只相信双手!

本文转自大石头博客园博客,原文链接:http://www.cnblogs.com/nnhy/archive/2007/08/18/860656.html,如需转载请自行联系原作者
目录
相关文章
|
监控 Java 测试技术
Java并发编程最佳实践:设计高性能的多线程系统
Java并发编程最佳实践:设计高性能的多线程系统
227 1
|
数据采集 XML JavaScript
C# 中 ScrapySharp 的多线程下载策略
C# 中 ScrapySharp 的多线程下载策略
|
12月前
|
算法 安全 Java
Java线程调度揭秘:从算法到策略,让你面试稳赢!
在社招面试中,关于线程调度和同步的相关问题常常让人感到棘手。今天,我们将深入解析Java中的线程调度算法、调度策略,探讨线程调度器、时间分片的工作原理,并带你了解常见的线程同步方法。让我们一起破解这些面试难题,提升你的Java并发编程技能!
484 16
|
Java
线程池内部机制:线程的保活与回收策略
【10月更文挑战第24天】 线程池是现代并发编程中管理线程资源的一种高效机制。它不仅能够复用线程,减少创建和销毁线程的开销,还能有效控制并发线程的数量,提高系统资源的利用率。本文将深入探讨线程池中线程的保活和回收机制,帮助你更好地理解和使用线程池。
616 2
|
调度 Android开发 开发者
构建高效Android应用:探究Kotlin多线程优化策略
【10月更文挑战第11天】本文探讨了如何在Kotlin中实现高效的多线程方案,特别是在Android应用开发中。通过介绍Kotlin协程的基础知识、异步数据加载的实际案例,以及合理使用不同调度器的方法,帮助开发者提升应用性能和用户体验。
230 4
|
监控 安全 算法
线程死循环确实是多线程编程中的一个常见问题,它可能导致应用程序性能下降,甚至使整个系统变得不稳定。
线程死循环是多线程编程中常见的问题,可能导致性能下降或系统不稳定。通过代码审查、静态分析、日志监控、设置超时、使用锁机制、测试、选择线程安全的数据结构、限制线程数、使用现代并发库及培训,可有效预防和解决死循环问题。
366 1
|
监控 安全 算法
线程死循环是多线程编程中的常见问题,可能导致应用性能下降甚至系统不稳定。
【10月更文挑战第6天】线程死循环是多线程编程中的常见问题,可能导致应用性能下降甚至系统不稳定。为了解决这一问题,可以通过代码审查、静态分析、添加日志监控、设置超时机制、使用锁和同步机制、进行全面测试、选用线程安全的数据结构、限制线程数量、利用现代并发库,并对团队进行培训等方法来预防和减少死循环的发生。尽管如此,多线程编程的复杂性仍需要持续监控和维护以确保系统稳定。
246 3
|
安全 Java 程序员
Java编程中实现线程安全的策略
【8月更文挑战第31天】在多线程环境下,保证数据一致性和程序的正确运行是每个程序员的挑战。本文将通过浅显易懂的语言和实际代码示例,带你了解并掌握在Java编程中确保线程安全的几种策略。让我们一起探索如何用同步机制、锁和原子变量等工具来保护我们的数据,就像保护自己的眼睛一样重要。
|
监控 负载均衡 算法
线程数突增!领导说再这么写就GC掉我:深入理解与优化策略
【8月更文挑战第29天】在软件开发的世界里,性能优化总是开发者们绕不开的话题。特别是当面对“线程数突增”这样的紧急情况时,更是考验着我们的技术功底和问题解决能力。今天,我们就来深入探讨这一话题,分享一些工作学习中积累的技术干货,帮助大家避免被“GC”(垃圾回收,也常用来幽默地表示“被炒鱿鱼”)的尴尬。
205 2
|
前端开发 JavaScript 大数据
React与Web Workers:开启前端多线程时代的钥匙——深入探索计算密集型任务的优化策略与最佳实践
【8月更文挑战第31天】随着Web应用复杂性的提升,单线程JavaScript已难以胜任高计算量任务。Web Workers通过多线程编程解决了这一问题,使耗时任务独立运行而不阻塞主线程。结合React的组件化与虚拟DOM优势,可将大数据处理等任务交由Web Workers完成,确保UI流畅。最佳实践包括定义清晰接口、加强错误处理及合理评估任务特性。这一结合不仅提升了用户体验,更为前端开发带来多线程时代的全新可能。
447 1

热门文章

最新文章