简易小爬虫项目

最近疏于学习,干脆就把去年跟同学做的职潮人小程序中的爬取职位这部分的代码拿出来,加以改进瞎搞一波吧。

代码Github地址: https://github.com/zhouhuanghua/project

之前的可以爬取拉勾BOSS智联还有一些校招网站的,现在只拿拉勾的做个典型吧。

(#说起来也可惜,前后端都基本做完了,网站备案也弄好了,then不知道为啥突然就没了兴趣......宣告失败#)

简易小爬虫项目_第1张图片简易小爬虫项目_第2张图片简易小爬虫项目_第3张图片

说回主题,这次做完的效果是这样的 (#说好的不再搞前端的,哎

简易小爬虫项目_第4张图片

简易小爬虫项目_第5张图片

看一下重点吧,这个是架构图(镇楼图)

 

简易小爬虫项目_第6张图片

那么,问题来了

1, 为什么需要RabbitMQ?

答:看图中的几个服务小伙子,各自的责任是分离的,我只干我的你也不能影响我,所以拿到结果直接丢到MQ上面(强行解释了MQ发挥着异步+解耦的作用)。

2, 为什么使用binlog处理增量数据?

答:项目结构里,爬虫系统和查询平台分在了不同的模块,可以独立运行,我不想它们之间共用一些东西。

简易小爬虫项目_第7张图片

代码这里只讲一下里面用到的责任链设计模式。其它的话可以去GitHub克隆下来看(点击这里前往)。

定义:使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系, 将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。之前的博客(点击这里前往)里面有更详细的介绍。

常见的有两种写法,也就是外部控制链路流向和内部控制链路流向。

外部控制的比较简单,就是把所有处理对象收集起来遍历,根据每个处理的返回值确定是否继续和终止。伪代码如下

List handlerList = ...;
for (Handler h : handlerList) {
    R result = h.process(xxx);
    if (result xxx) {
        break;
    }
}

内部的就复杂一点,有点递归的意思,就是由处理者决定是否将请求交给下一个处理者处理。

首先定义处理者接口,抽象方法是crawl

public interface IStrategy {

    default boolean isNormalPage(Document document) {
        return Objects.nonNull(document.selectFirst("div[class=job-name]"));
    }

    void crawl(String url, ObjectWrapper docWrapper, CrawlStrategyChain strategyChain);
}

然后添加两个处理者实现类,Jsoup和Chrome

public class JsoupStrategy implements IStrategy {

    private final Map COMMON_HEADER_MAP = MapUtils.buildMap(
            "User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36",
            "Accept", "application/json, text/plain, */*",
            "Cookie", "token");

    @Override
    public void crawl(String url, ObjectWrapper docWrapper, CrawlStrategyChain strategyChain) {
        try {
            Document document = Jsoup.connect(url).headers(COMMON_HEADER_MAP).get();
            if (isNormalPage(document)) {
                docWrapper.setObj(document);
                return;
            }
        } catch (Throwable t) {
            log.warn("Jsoup加载网页[url={}]异常,t={}", url, ThrowableUtils.getStackTrace(t));
        }
        strategyChain.doCrawl(url, docWrapper);
    }
}
public class ChromeStrategy implements IStrategy {

    @Override
    public void crawl(String url, ObjectWrapper docWrapper, CrawlStrategyChain strategyChain) {
        WebDriver webDriver = BrowserDriverFactory.openChromeBrowser();
        try {
            webDriver.get(url);
            Document document = Jsoup.parse(webDriver.getPageSource());
            if (isNormalPage(document)) {
                docWrapper.setObj(document);
                return;
            }
        } catch (Throwable t) {
            log.warn("Chrome加载网页[url={}]异常,t={}", url, ThrowableUtils.getStackTrace(t));
        } finally {
            OptionalOperationUtils.consumeIfNonNull(webDriver, WebDriver::quit);
        }
        strategyChain.doCrawl(url, docWrapper);
    }
}

接着,定义一个链调用对象

public final class CrawlStrategyChain {
    private int pos = 0;
    private int n;
    private IStrategy[] strategies;

    private CrawlStrategyChain() {
    }

    public static CrawlStrategyChain build(IStrategy[] strategies) {
        CrawlStrategyChain instance = new CrawlStrategyChain();
        instance.strategies = Objects.requireNonNull(strategies, "CrawlStrategyChain构造参数strategies不能为空!");
        instance.n = strategies.length;
        return instance;
    }

    public void doCrawl(String url, ObjectWrapper docWrapper) {
        if (pos < n) {
            strategies[pos++].crawl(url, docWrapper, this);
        }
    }
}

最后,看下是怎么使用的

简易小爬虫项目_第8张图片

聪明的你看明白了没有呀?不明白的话没关系,赶紧去下载代码本地跑几遍吧!

你可能感兴趣的:(中间件,设计模式)