访问我的博客

前言

年前闲着无聊,研究了一阵子爬虫技术,接触到爬虫框架 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 方法即可获取所有爬虫的状态了。

资源下载

WebMagic之爬虫监控的更多相关文章

  1. java 之webmagic 网络爬虫

    webmagic简介: WebMagic是一个简单灵活的Java爬虫框架.你可以快速开发出一个高效.易维护的爬虫. http://webmagic.io/ 准备工作: Maven依赖(我这里用的Mav ...

  2. 教你用python爬虫监控教务系统,查成绩快人一步!

    教你用python爬虫监控教务系统,查成绩快人一步!这几天考了大大小小几门课,教务系统又没有成绩通知功能,为了急切想知道自己挂了多少门,于是我写下这个脚本. 设计思路:设计思路很简单,首先对已有的成绩 ...

  3. WebMagic 实现爬虫入门教程

    本示例实现某电影网站最新片源名称列表及详情页下载地址的抓取. webmagic是一个开源的Java垂直爬虫框架,目标是简化爬虫的开发流程,让开发者专注于逻辑功能的开发. WebMagic 特点: 完全 ...

  4. 基于webmagic的爬虫项目经验小结

    大概在1个月前,利用webmagic做了一个爬虫项目,下面是该项目的一些个人心得,贴在这里备份: 一.为什么选择webmagic? 说实话,开源的爬虫框架已经很多了,有各种语言(比如:python.j ...

  5. 基于webmagic的爬虫小应用--爬取知乎用户信息

    听到“爬虫”,是不是第一时间想到Python/php ? 多少想玩爬虫的Java学习者就因为语言不通而止步.Java是真的不能做爬虫吗? 当然不是. 只不过python的3行代码能解决的问题,而Jav ...

  6. windows部署SpiderKeeper(爬虫监控)

    最近发现了一个spdierkeeper的库,这个库的主要用途是在于配合这scrpyd管理你的爬虫,支持一键式部署,定时采集任务,启动,暂停等一系列的操作.简单来说将scrapyd的api进行封装,最大 ...

  7. 基于webmagic的爬虫小应用

    以前没有写过爬虫程序,最近两天就研究了一下java的爬虫框架webmagic.然后写了一个demo 写爬虫的基本思想: 1.抓取目标连接 2.根据页面中标签,抓捕你需要的内容 3.保存结果集 以下是实 ...

  8. 用python爬虫监控CSDN博客阅读量

    作为一个博客新人,对自己博客的访问量也是很在意的,刚好在学python爬虫,所以正好利用一下,写一个python程序来监控博客文章访问量 效果 代码会自动爬取文章列表,并且获取标题和访问量,写入exc ...

  9. Python 爬虫监控女神的QQ空间新的说说,实现邮箱发送

    主要实现的功能就是:监控女神的 QQ空间,一旦女神发布新的说说,你的邮箱马上就会收到说说内容,是不是想了解一下 先看看代码运行效果图: PS:只有你有一台云服务器你就可以把程序24h运行起来 直接上代 ...

随机推荐

  1. day28(ajax之js原生代码实现)

    ajax ajax:异步页面无刷新技术 AJAX:异步的 JavaScript And XML. * 使用的是老的技术,用的是新的思想. AJAX的功能:完成页面的局部刷新,不中断用户的体验. XML ...

  2. 关于jdbc连接MySQL数据问题

    1.解压MySQL后配置环境变量 MYSQL_HOME:D:\win7\Program Files(x86)\mysql-5.6.21-win32(mysql根目录) 添加path:%MYSQL_HO ...

  3. xml文件头文件生成策略以及导入约束条件

    约束的作用是对配置文件的一种检验 约束条件分为schema约束和DTD约束,schema约束是还有目录结构,DTD约束没有目录结构 这里暂先介绍schema约束的导入 约束的分类: 1.schema ...

  4. 1.虚拟机中安装ubuntu

    1.VMware安装很简单,全部默认安装即可. 2.安装完VMware之后,打开VMware,点击创建虚拟机 典型安装易出问题,所以这里选择自定义安装 安装过程选项配置如下 处理器数,核数,内存都可以 ...

  5. 关于使用java自动发邮件--找不到smtphost

    今天解决报的第二个异常:Unknown SMTP host: smtp.qq.com;在找了网上的一些资料后,有看到是因为使用了代理服务器,所以无法访问.我试着用了telnet去访问,确实不行.最近都 ...

  6. vs2017常用快捷键

    项目相关的快捷键 Ctrl + Shift + B = 生成项目 Ctrl + Alt + L = 显示 Solution Explorer(解决方案资源管理器) Ctrl + Shift + A = ...

  7. cesiumjs

    官网 https://cesium.com/ https://cesiumjs.org/Cesium/ 论坛 http://cesium.coinidea.com/ 中文网 http://cesium ...

  8. 使用C#写MVC框架(一:核心原理)

    目录: 一.MVC原理解析 二.HttpHandler 1.HttpHandler,IHttpHandler,MvcHandler的说明 2.IHttpHandler解析 3.MvcHandler解析 ...

  9. ASP.NET MVC一次删除多笔记录 V2.0

    前一段时间Insus.NET有写一篇<ASP.NET MVC一次删除多笔记录>http://www.cnblogs.com/insus/p/6241186.html 可以前往去看看. 觉得 ...

  10. python爬虫实践教学

    i春秋作家:Mochazz 一.前言 这篇文章之前是给新人培训时用的,大家觉的挺好理解的,所以就分享出来,与大家一起学习.如果你学过一些python,想用它做些什么又没有方向,不妨试试完成下面几个案例 ...