• WebMagic之爬虫监控


    访问我的博客

    前言

    年前闲着无聊,研究了一阵子爬虫技术,接触到爬虫框架 WebMagic,感觉很好用。
    在之后的工作中,接手了新站与第三方接口对接的工作,主要的工作是去抓取对方接口的内容;初始的时候,之前负责该工作的同事,是手动使用多线程去抓取,在应用的过程当中暴露了不少问题。比如对于接口内容超级多的时候,虽然使用了多线程,但是抓取的效率很低,而且也没有实现增量抓取,每次都需要去全量抓取,跑一次基本需要好几天-.-;小说是连载的情况下,这种问题是亟需解决的。

    趁着熟悉了新兵器 WebMagic, 果断在项目中进行引入,解决以上问题。功能上线后,替换了原有的多线程抓取,目前已经十分稳定, 基本上配置好任务之后,就无需再人工干预了。

    以下,正文是基于我学习 WebMagic 时练手项目,功能和在公司开发的差不多,只不过我本地开发的是去抓取盗版网站的内容。

    项目预览

    1. 菜单管理

    2. 爬虫任务管理

    3. 实现了爬虫的状态监控,以及可视化启停

    初入手兵器-基本使用

    1. 爬虫套路分析
      先看官方文档的总体架构图

      大部分模块WebMagic已经提供了默认实现。
      一般来说,对于编写一个爬虫,PageProcessor是需要编写的部分,而Spider则是创建和控制爬虫的入口。

      得益于 WebMagic 框架的良好封装,对于框架的使用者来说,所需要编写的代码几乎只有爬虫的逻辑代码,而对于怎么爬,维护任务队列的事情,WebMagic 都可以替我们做好。开始我们的爬虫之旅吧!

    2. 引入依赖
      本文中所使用到的项目是基于 Maven 的 SSM 项目,在 pom.xml 中引入 WebMagic 的依赖。

      <dependency>
      	<groupId>us.codecraft</groupId>
      	<artifactId>webmagic-core</artifactId>
      	<version>0.7.3</version>
      </dependency>
      <dependency>
      	<groupId>us.codecraft</groupId>
      	<artifactId>webmagic-extension</artifactId>
      	<version>0.7.3</version>
      </dependency>
      
    3. 基本类图
      先将对应的处理类进行抽象出来,方便统一处理。

    4. 每个爬虫都有其对应的配置信息

      Site 是 抓取网站的相关配置,包括编码、抓取间隔、重试次数

    5. 对应的实现类重写 process 方法,在方法中实现对应的爬虫逻辑处理

    6. 启动爬虫

    7. 爬虫的使用就简单带过,具体可以将本文与官方文档结合使用,官方文档的示例只是基于 main 方法。

    爬虫监控

    1. 扩展源码
      为了实现项目预览的效果,实现爬虫的状态监控,需要对爬虫进行扩展。因为官网提供的方式功能不足以达到在页面展示的效果。

      添加监控非常简单,获取一个 SpiderMonitor 的单例 SpiderMonitor.instance(),并将你想要监控的 Spider 注册进去即可。你可以注册多个 Spider 到 SpiderMonitor 中。

      查看 SpiderMonitor 源代码后,如果调用的是 获取一个 SpiderMonitor 的单例 SpiderMonitor 的 注册方法,发现 WebMagic 将每只爬虫的状态对象 SpiderStatusMXBean 全部添加到一个 List 集合当中去,这样就难以区分具体是哪一只爬虫的状态,所以我们需要对 SpiderMonitor 进行扩展。

      将 SpiderMonitor 中的
      private List<SpiderStatusMXBean> spiderStatuses = new ArrayList<SpiderStatusMXBean>();
      修改为 Map 集合,key 选择 Spider 的 UUID 作为唯一区分爬虫的标记。

      @Experimental
      public class MySpiderMonitor {
      
      	private static MySpiderMonitor INSTANCE = new MySpiderMonitor();
      
      	private AtomicBoolean started = new AtomicBoolean(false);
      
      	private Logger logger = LoggerFactory.getLogger(getClass());
      
      	private MBeanServer mbeanServer;
      
      	private String jmxServerName;
      
      	private Map<String,MySpiderStatus> spiderStatuses = new HashMap<String,MySpiderStatus>();
      
      	protected MySpiderMonitor() {
      		jmxServerName = "WebMagic";
      		mbeanServer = ManagementFactory.getPlatformMBeanServer();
      	}
          public Map<String,MySpiderStatus> getSpiderStatuses()
          {
              return spiderStatuses;
          }
      
      	/**
      	 * Register spider for monitor.
      	 *
      	 * @param spiders spiders
      	 * @return this
      	 */
      	public synchronized MySpiderMonitor register(Spider... spiders) throws JMException {
      		for (Spider spider : spiders) {
      			MyMonitorSpiderListener monitorSpiderListener = new MyMonitorSpiderListener();
      			if (spider.getSpiderListeners() == null) {
      				List<SpiderListener> spiderListeners = new ArrayList<SpiderListener>();
      				spiderListeners.add(monitorSpiderListener);
      				spider.setSpiderListeners(spiderListeners);
      			} else {
      				spider.getSpiderListeners().add(monitorSpiderListener);
      			}
                  MySpiderStatus spiderStatusMBean = getSpiderStatusMBean(spider, monitorSpiderListener);
      			registerMBean(spiderStatusMBean);
      			spiderStatuses.put(spider.getUUID(),spiderStatusMBean);
      		}
      		return this;
      	}
      
      	protected MySpiderStatus getSpiderStatusMBean(Spider spider, MyMonitorSpiderListener monitorSpiderListener) {
      		return new MySpiderStatus(spider, monitorSpiderListener);
      	}
      
      	public static MySpiderMonitor instance() {
      		return INSTANCE;
      	}
      
      	public class MyMonitorSpiderListener implements SpiderListener {
      
      		private final AtomicInteger successCount = new AtomicInteger(0);
      
      		private final AtomicInteger errorCount = new AtomicInteger(0);
      
      		private List<String> errorUrls = Collections.synchronizedList(new ArrayList<String>());
      
      		@Override
      		public void onSuccess(Request request) {
      			successCount.incrementAndGet();
      		}
      
      		@Override
      		public void onError(Request request) {
      			errorUrls.add(request.getUrl());
      			errorCount.incrementAndGet();
      		}
      
      		public AtomicInteger getSuccessCount() {
      			return successCount;
      		}
      
      		public AtomicInteger getErrorCount() {
      			return errorCount;
      		}
      
      		public List<String> getErrorUrls() {
      			return errorUrls;
      		}
      	}
      
      	protected void registerMBean(SpiderStatusMXBean spiderStatus) throws MalformedObjectNameException, InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException {
      		ObjectName objName = new ObjectName(jmxServerName + ":name=" + spiderStatus.getName());
              if(mbeanServer.isRegistered(objName)==false)
              {
                  mbeanServer.registerMBean(spiderStatus, objName);
              }
      	}
      
      }
      

      需要注意的是,SpiderMonitor 中使用的 SpiderStatus 也需要进行一同扩展。

      public class MySpiderStatus implements SpiderStatusMXBean {
      
          protected final Spider spider;
      
          protected Logger logger = LoggerFactory.getLogger(getClass());
      
          protected final MySpiderMonitor.MyMonitorSpiderListener monitorSpiderListener;
      
          public MySpiderStatus(Spider spider, MySpiderMonitor.MyMonitorSpiderListener monitorSpiderListener) {
              this.spider = spider;
              this.monitorSpiderListener = monitorSpiderListener;
          }
      
          public Spider getSpider()
          {
              return this.spider;
          }
      
          public String getName() {
              return spider.getUUID();
          }
      
          public int getLeftPageCount() {
              if (spider.getScheduler() instanceof MonitorableScheduler) {
                  return ((MonitorableScheduler) spider.getScheduler()).getLeftRequestsCount(spider);
              }
              logger.warn("Get leftPageCount fail, try to use a Scheduler implement MonitorableScheduler for monitor count!");
              return -1;
          }
      
          public int getTotalPageCount() {
              if (spider.getScheduler() instanceof MonitorableScheduler) {
                  return ((MonitorableScheduler) spider.getScheduler()).getTotalRequestsCount(spider);
              }
              logger.warn("Get totalPageCount fail, try to use a Scheduler implement MonitorableScheduler for monitor count!");
              return -1;
          }
      
          @Override
          public int getSuccessPageCount() {
              return monitorSpiderListener.getSuccessCount().get();
          }
      
          @Override
          public int getErrorPageCount() {
              return monitorSpiderListener.getErrorCount().get();
          }
      
          public List<String> getErrorPages() {
              return monitorSpiderListener.getErrorUrls();
          }
      
          @Override
          public String getStatus() {
              return spider.getStatus().name();
          }
      
          @Override
          public int getThread() {
              return spider.getThreadAlive();
          }
      
          public void start() {
              spider.start();
          }
      
          public void stop() {
              spider.stop();
          }
      
          @Override
          public Date getStartTime() {
              return spider.getStartTime();
          }
      
          @Override
          public int getPagePerSecond() {
              int runSeconds = (int) (System.currentTimeMillis() - getStartTime().getTime()) / 1000;
              return getSuccessPageCount() / runSeconds;
          }
      
      }
      
    2. 重写爬虫启动处代码

      @Service
      public class WebMagicService {
          
          @Resource
          private ApplicationContext context;
          @Resource
          private TaskService taskService;
          
          public void run(TaskDTO taskDTO, boolean runAsync) throws Exception {
              MySpiderMonitor spiderMonitor = MySpiderMonitor.instance();
              String ruleJson = taskDTO.getTaskRuleJson();
              WebMagicConfig config = JSONObject.parseObject(ruleJson, WebMagicConfig.class);
              SpiderConfig spiderConfig = config.getSpider();
              AbstractPageProcess pageProcess = context.getBean(spiderConfig.getProcesser(), AbstractPageProcess.class);
          
              pageProcess.init(config);
              pageProcess.setUuid(taskDTO.getSpiderUUID());
          
              Spider spider = Spider.create(pageProcess).thread(spiderConfig.getThread());
              spider.setUUID(taskDTO.getSpiderUUID());
          
              List<String> pipelines = spiderConfig.getPipeline();
              for (String pipeline : pipelines) {
                  Pipeline bean = context.getBean(pipeline, Pipeline.class);
                  if (bean != null) {
                      spider.addPipeline(bean);
                  }
              }
              // 设置Downloader
              // 设置Scheduler
          
              // 注册爬虫
              spiderMonitor.register(spider);
              spider.addUrl(spiderConfig.getStartUrl());
          
              if (runAsync) {
                  spider.runAsync();
              } else {
                  spider.run();
              }
          }
          
          /**
           * 爬虫状态监控
           * @return
           */
          public List<TaskDTO> runTaskList() {
          
              MySpiderMonitor spiderMonitor = MySpiderMonitor.instance();
              Map<String, MySpiderStatus> spiderStatuses = spiderMonitor.getSpiderStatuses();
          
              List<TaskDTO> taskDTOList = taskService.findAll();
              for (TaskDTO taskDTO : taskDTOList) {
                  MySpiderStatus spiderStatus = spiderStatuses.get(taskDTO.getSpiderUUID());
                  if (spiderStatus == null) {
                      taskDTO.setRunState(Spider.Status.Stopped.name());
                  } else {
                      taskDTO.setRunState(spiderStatus.getStatus());
                  }
              }
          
              return taskDTOList;
          }
          
          public TaskDTO stop(TaskDTO taskDTO) {
              MySpiderMonitor spiderMonitor = MySpiderMonitor.instance();
              Map<String, MySpiderStatus> spiderStatuses = spiderMonitor.getSpiderStatuses();
              MySpiderStatus spiderStatus = spiderStatuses.get(taskDTO.getSpiderUUID());
          
              if (spiderStatus != null) {
                  spiderStatus.stop();
                  spiderStatus.getSpider().close();
              }
          
              return taskDTO;
          }
      }
      

      创建爬虫时,将爬虫注册到 MySpiderMonitor 中,之后通过 getSpiderStatuses 方法即可获取所有爬虫的状态了。

    资源下载

  • 相关阅读:
    Using a custom AxisRenderer object
    进度条
    flex 自定义tooltip
    深入理解JAVA虚拟机 垃圾收集器和内存分配策略
    深入理解JAVA虚拟机 自动内存管理机制
    oracle pl/sql 程序设计 历史笔记整理
    oracle sql 高级编程 历史笔记整理
    JAVA并发编程的艺术 Java并发容器和框架
    JAVA并发编程的艺术 JMM内存模型
    Java并发编程实战 第16章 Java内存模型
  • 原文地址:https://www.cnblogs.com/vcmq/p/9484404.html
Copyright © 2020-2023  润新知