JobHarvest——校招并发爬虫系统(更新中)

Github项目地址:https://github.com/LeoCai/JobHarvester

相关文章:

JobHarvest——利用springAOP进行运行性能测评(更新中)

JobHarvest——虚拟机性能监控实例

项目背景

就业季到了,现在的校招从7月份就差不多陆续开始了,但很多信息经常受限于地域,学校,很多学生会苦于找不到安全合理的渠道进行内推,网申等。现在比较靠谱的内推消息一般来自于各大名校的官方bbs,比如北邮人论坛。但是信息好乱,老是在bbs中看来看去,又很有可能遗漏掉信息。于是我决定做一个爬虫,专门自动收集各个论坛的就业信息。

摸索

从没写过爬虫,但对网页html有所了解,大概知道通过分析html结点得到自己所需的结点内容即可,先查阅资料怎么写爬虫的基本方法。开始简单地试着用urlconnetion,结果发现无法加载动态网页。然后查阅资料,发现很多人用webdriver,这个库本来是用来自动化测试的,它可以控制浏览器进行一些动作。现在基本的思路和工具有了,技术上是可行的,接下来就是设计。由于各大高校bbs的格式都是不同的,我们需要适配不同bbs,所以扩展性对系统非常重要。在设计的过程中,我发现尽管格式不同,很多步骤可以抽象出来,比如初始化浏览器,找到目标的列表,抽取目标信息,下一页等等。这种场景就非常适合模板模式

版本1设计:

功能:

  1. 各大高校并行收集job信息
  2. 对job信息进行过滤(包括想要,不想要)
  3. 支持扩展爬虫,只需继承抽象类并实现少量函数(定位列表标签等)
  4. 利用类加载器加载配置文件中不同高校的爬虫。
  5. 抽取job信息中的格式,解析成统一的Bean
  6. 结果以简单地html页面展示。

目前涉及到的技术点:

  1. 主要包括利用spring解耦;
  2. mybatis处理数据库对象映射;
  3. 线程池并发收集;
  4. 类加载器动态加载爬虫。

系统架构设计:


主要类设计:

程序入口两个:
  • 一个是CrawlerWriter,用于爬虫和写数据库
  • 一个是CrawlerRead,用于读取数据库和显示
还有几个重要的类介绍如下:
  1. CrawlerService:利用线程池并发收集不同高校的就业信息。
  2. MyCrawler:爬虫抽象类,模板模式,扩展爬虫必须继承的父类,包含了单爬虫爬取的算法
  3. JobInfoService:就业信息与数据库交互的服务
类图依赖关系如图所示:

主要类图

若要扩展爬虫只需三步:
  1. 只需继承MyCrawler
  2. 实现部分抽象方法,包括定位行,列,信息的索引,下一页等。
    public class NYUCrawler extends MyCrawler {
    
        public NYUCrawler(String url) {
            super(url);
            setSource("南邮bbs");
        }
    
        @Override protected JobInfo getInfoDTO(WebElement we) {
    
            JobInfoIndex jobInfoIndex = new JobInfoIndex();
            jobInfoIndex.setHrefIdnex(1);
            jobInfoIndex.setTimeIndex(2);
            jobInfoIndex.setTitleIndex(1);
            jobInfoIndex.setHotIndex(4);
            JobDateParser jobDateParser = new JobDateParser() {
    
                public Date parse(String dateStr) {
                    final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
                    final SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                    Date date = new Date();
                    if (dateStr.contains("-")) {
                        try {
                            date = sdf.parse(dateStr);
                        } catch (ParseException e) {
                            e.printStackTrace();
                        }
                    } else {
                        try {
                            date = sdf2.parse(JobDateUtils.getTodayDateStr() + " " + dateStr);
                        } catch (ParseException e) {
                            e.printStackTrace();
                        }
                    }
                    return date;
                }
            };
            return JobInfoExtractUtils.simpleExtract(we, jobInfoIndex, jobDateParser);
        }
    
        protected List<WebElement> getCuCaoTarget() {
            return driver.findElements(By.tagName("tr"));
        }
    
        protected void nextPage() {
            WebElement el = driver.findElement(By.linkText(">>"));
            el.click();
        }
    }

  3. 在school.properties中配置对应的类名与网站的链接。
    NJU = http://bbs.nju.edu.cn/board?board=JobExpress
    NJUCS = http://bbs.nju.edu.cn/board?board=D_Computer
    SJU = https://bbs.sjtu.edu.cn/bbsdoc?board=JobInfo
    NYU = http://bbs.cloud.icybee.cn/board/Job

无需显示调用,程序会利用类加载器加载配置文件中的爬虫类。

爬虫的抽象类如下:


部分模块详解:

初始化:
只需在配置文件中配置类名和url,系统会利用 类加载器自动加载所有的爬虫类并放到一个 hashmap中。
/**
     * 使用类加载器加载各个爬虫类
     * 在school.properties中配置对于url
     * 读取学校链接匹配地址,命名规范[School]+[Crawer]
     */
    @PostConstruct public void init() {
        Properties properties = new Properties();
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        try {
            properties.load(classLoader.getResourceAsStream("school.properties"));
        } catch (IOException e) {
            e.printStackTrace();
        }

        Set<Object> keys = properties.keySet();
        for (Object key : keys) {
            try {
                Class<MyCrawler> crawer = (Class<MyCrawler>) classLoader.loadClass(
                        "com.leocai.bbscraw.crawlers." + key + "Crawler");
                MyCrawler c = crawer.getConstructor(String.class).newInstance(properties.getProperty((String) key));
                c.setJobInfoService(jobInfoService);
                map.put((String) key, c);
            } catch (ClassNotFoundException e) {
                logger.error(e.getMessage(), e);
            } catch (InstantiationException e) {
                logger.error(e.getMessage(), e);
            } catch (IllegalAccessException e) {
                logger.error(e.getMessage(), e);
            } catch (NoSuchMethodException e) {
                logger.error(e.getMessage(), e);
            } catch (InvocationTargetException e) {
                logger.error(e.getMessage(), e);
            }
            properties.get(key);
        }
    }

单个爬虫算法初稿很简单如下,设计成一个模板模式

红色均为抽象函数,不同的爬虫需要重写。

  1. 初始化:包括打开浏览器,禁用图片,css
  2. 进行连接
  3. 循环页数:
  4. 获得粗糙的目标(定位table)
  5. 定位行
  6. 过滤想要和不想要的信息
  7. 抽取jobinfo模型
  8. 下一页

/**
     * 模板模式算法
     * 粗糙定位目标位置:比如table
     *      过滤想要和不想要的信息
     *      定位行位置
     *      抽取封装JobInfo信息
     *      调用服务进行处理(此处可异步)
     * 下一页
     *
     * @return
     */
    public String start() {
        init();
        driver.get(url);
        StringBuilder sb = new StringBuilder("");
        for (int i = 0; i < pageNum; i++) {
            List<WebElement> wes = getCuCaoTarget();
            for (WebElement we : wes) {
                String text = we.getText();
                if (!AttentionUtils.isAttentioned(text) || AttentionUtils.isWithOut(text)) continue;
                JobInfo infoDTO = getInfoDTO(we);
                infoDTO.setSource(source);
                jobInfoService.produceJobInfo(infoDTO);
            }
            nextPage();
        }
        return sb.toString();
    }


主函数

  1. 系统遍历爬虫map;
  2. 然后利用线程池提交任务;
  3. 利用future并行执行。

/**
     * 利用线程池并行收集各个高校的信息
     * 利用future进行收集
     * 若未启用mysql,将得到的数据写入到html中
     */
    public void asynStart() {
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        Set<String> set = map.keySet();
        List<Future<String>> fts = new ArrayList<Future<String>>(5);
        for (final String key : set) {
            Future<String> ft = executorService.submit(new Callable<String>() {

                public String call() throws Exception {
                    String rs = map.get(key).start();
                    map.get(key).close();
                    return rs;
                }
            });
            fts.add(ft);
        }
        for (Future<String> f : fts) {
            try {
                f.get();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
        if (!isDBEnabled()) {
            List<JobInfo> jobInfoList = jobInfoService.getJobInfosFromMemory();
            HtmlUtils.writeHtml(jobInfoList, jobInfoService);
        }
    }



版本一实现展示:



第二期需求:

  1. 支持断点爬虫(记录上次的位置),仅仅更新最新的信息(已完成)
  2. redis缓存(已完成)
  3. 已阅处理

相关文章:

JobHarvest——利用springAOP进行运行性能测评(更新中)

JobHarvest——虚拟机性能监控实例


发布了35 篇原创文章 · 获赞 61 · 访问量 16万+

猜你喜欢

转载自blog.csdn.net/cql342624757/article/details/52060104