爬虫实战

/ Webmagic / 没有评论 / 49浏览

通过爬虫爬取资源、提供服务,本着与 @阿龙哥 练习爬虫技术、web技术的宗旨,不做任何非法盈利。

项目介绍

项目

项目地址

gitee

项目介绍

基于webmagic爬取小说

软件架构

安装教程

使用

说明

腾讯云1核1g,5个线程(sleep 1s)爬取书籍信息(不包含章节)11小时20w本书籍。

爬虫总结

如何爬

选取一个列表页作为入口,下一页添加到继续爬取url,本页信息根据规则解析出对应的内容

如何防止被封

定期更新资源

需要定期更新资源,这时需要调度,让爬虫定时爬取。

/**
 * 爬虫任务
 *
 * @author 奔波儿灞
 * @since 1.0
 */
@Component
public class CrawlTask {

    private static final Logger LOG = LoggerFactory.getLogger(CrawlTask.class);

    @Autowired
    private CrawlService crawlService;

    /**
     * 调度
     */
    @Autowired
    private QueueScheduler scheduler;

    /**
     * 调度
     */
    @Autowired
    private QueueScheduler bookScheduler;

    /**
     * 启动5分钟后,每隔5分钟开始爬取
     */
    @Scheduled(initialDelay = 1000 * 60 * 5, fixedDelay = 1000 * 60 * 5)
    public void crawl() {
        LOG.info("begin crawl...");
        crawlService.crawl();
        cleanup();
        LOG.info("stop crawl...");
    }

    /**
     * 清理
     */
    private void cleanup() {
        DuplicateRemover remover = scheduler.getDuplicateRemover();
        LOG.info("clear scheduler, total request num: {}", remover.getTotalRequestsCount(null));
        remover.resetDuplicateCheck(null);
    }


    /**
     * 启动1分钟后,每隔一周开始爬取
     */
    @Scheduled(initialDelay = 1000 * 60, fixedDelay = 1000 * 60 * 60 * 24 * 7)
    public void crawlBook() {
        LOG.info("begin crawl...");
        crawlService.crawlBook();
        bookSchedulerCleanup();
        LOG.info("stop crawl...");
    }

    /**
     * 清理
     */
    private void bookSchedulerCleanup() {
        DuplicateRemover remover = bookScheduler.getDuplicateRemover();
        LOG.info("clear book scheduler, total request num: {}", remover.getTotalRequestsCount(null));
        remover.resetDuplicateCheck(null);
    }

}

在webmagic的使用中,发现每次new一个spider都会创建线程池以及相应的组件,运行完成后清理相关资源,无法复用线程池等资源。因此,需要定制spider。主要是组件初始化一次,退出后不销毁。

/**
 * 退出爬虫后不销毁资源,供重复利用
 *
 * @author 奔波儿灞
 * @since 1.0
 */
public class BatterSpider extends Spider {

    /**
     * 组件是否初始化过
     */
    private boolean init;

    /**
     * create a spider with pageProcessor.
     *
     * @param pageProcessor pageProcessor
     */
    private BatterSpider(PageProcessor pageProcessor) {
        super(pageProcessor);
    }

    /**
     * create a spider with pageProcessor.
     *
     * @param pageProcessor pageProcessor
     * @return new spider
     * @see PageProcessor
     */
    public static Spider create(PageProcessor pageProcessor) {
        return new BatterSpider(pageProcessor);
    }

    /**
     * 保证初始化一次组件
     */
    @Override
    protected void initComponent() {
        if (init) {
            // 初始化过只添加url到scheduler
            if (startRequests != null) {
                for (Request request : startRequests) {
                    addRequest(request);
                }
                startRequests.clear();
            }
        } else {
            super.initComponent();
            init = true;
        }
    }

    /**
     * 运行完后不销毁组件
     */
    @Override
    public void close() {
    }
}

spring boot自带Schedule默认单线程,任务无法并发

需要自定义任务线程池

/**
  * 自定义爬虫线程池
  * 特点:队列容量与最大线程池相同,充分利用线程,防止队列阻塞大量的请求
  *
  * @param properties 配置
  * @return ThreadPoolExecutor
  */
@Bean
public ExecutorService spiderExecutor(SpiderProperties properties) {
    BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(properties.getPoolSize());
    return new ThreadPoolExecutor(properties.getPoolSize(), properties.getPoolSize(),
            properties.getKeepAliveSeconds(), TimeUnit.SECONDS, workQueue,
            new CustomizableThreadFactory(properties.getThreadNamePrefix()),
            new ThreadPoolExecutor.CallerRunsPolicy());
}

webmagic相应状态码问题

webmagic默认认为返回内容即成功,正常[200, 500)内的请求认为客户端成功,需要定制Downloader组件

/**
 * 扩展下载器,添加状态码判断
 *
 * @author 奔波儿灞
 * @since 1.0
 */
public class CustomHttpClientDownloader extends HttpClientDownloader {

    private static final Logger LOG = LoggerFactory.getLogger(CustomHttpClientDownloader.class);

    @Override
    protected Page handleResponse(Request request, String charset, HttpResponse httpResponse, Task task) throws IOException {
        // 调用父类处理响应
        Page page = super.handleResponse(request, charset, httpResponse, task);
        int code = page.getStatusCode();
        // 状态码判断
        if (HttpStatus.SC_OK <= code && code < HttpStatus.SC_INTERNAL_SERVER_ERROR) {
            return page;
        } else {
            LOG.warn("下载[{}]错误, 响应码: {}, 不在给定的范围内[{}-{})", request.getUrl(), code, HttpStatus.SC_OK, HttpStatus.SC_INTERNAL_SERVER_ERROR);
            page.setDownloadSuccess(false);
        }
        return page;
    }

}

总结

webmagic是一个优秀的爬虫框架,设计优雅,性能优秀,支持分布式(默认redis,可以扩展)。

虽然,每个爬虫需求都不一样,但是利用webmagic可以方便的定制、二次开发,节省了成本。