java 爬取51job招聘信息

2024-01-08 05:20
文章标签 java 信息 爬取 招聘 51job

本文主要是介绍java 爬取51job招聘信息,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

本案例是基于webmagic和jsoup对51job招聘信息的爬取,并将爬取到的数据存入mysql数据库中。

Jsoup

jsoup 是一款Java 的HTML解析器,可直接解析某个URL地址、HTML文本内容。它提供了一套非常省力的API,可通过DOM,CSS以及类似于jQuery的操作方法来取出和操作数据。

jsoup的主要功能如下:

  1. 从一个URL,文件或字符串中解析HTML;
  2. 使用DOM或CSS选择器来查找、取出数据;
  3. 可操作HTML元素、属性、文本;

WebMagic

WebMagic是一个简单灵活的Java爬虫框架。底层基于HttpClient和Jsoup。

WebMagic项目代码分为核心和扩展两部分。核心部分(webmagic-core)是一个精简的、模块化的爬虫实现,而扩展部分则包括一些便利的、实用性的功能。

WebMagic的设计目标是尽量的模块化,并体现爬虫的功能特点。这部分提供非常简单、灵活的API,在基本不改变开发模式的情况下,编写一个爬虫。

扩展部分(webmagic-extension)提供一些便捷的功能,例如注解模式编写爬虫等。同时内置了一些常用的组件,便于爬虫开发。

业务分析

要实现的是爬取https://www.51job.com/上的招聘信息。只爬取java招聘的信息。
在这里插入图片描述


点击职位详情页,我们分析发现详情页还有一些数据需要抓取:
职位、公司名称、工作地点、薪资、发布时间、职位信息、公司联系方式、公司信息
在这里插入图片描述
在这里插入图片描述
根据以上信息,设计数据库表

CREATE TABLE `job_info` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键id',`company_name` varchar(100) DEFAULT NULL COMMENT '公司名称',`company_addr` varchar(200) DEFAULT NULL COMMENT '公司联系方式',`company_info` text COMMENT '公司信息',`job_name` varchar(100) DEFAULT NULL COMMENT '职位名称',`job_addr` varchar(50) DEFAULT NULL COMMENT '工作地点',`job_info` text COMMENT '职位信息',`salary_min` int(10) DEFAULT NULL COMMENT '薪资范围,最小',`salary_max` int(10) DEFAULT NULL COMMENT '薪资范围,最大',`url` varchar(150) DEFAULT NULL COMMENT '招聘信息详情页',`time` varchar(10) DEFAULT NULL COMMENT '职位最近发布时间',PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='招聘信息';

环境搭建

pom.xml 导入jar包坐标

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.0.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>sunyuqi.com</groupId><artifactId>crawlerjob_project</artifactId><version>1.0-SNAPSHOT</version><properties><java.version>9</java.version></properties><dependencies><!--SpringMVC--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--SpringData Jpa--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><!--MySQL连接包--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.11</version></dependency><!--WebMagic核心包--><dependency><groupId>us.codecraft</groupId><artifactId>webmagic-core</artifactId><version>0.7.3</version><exclusions><exclusion><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId></exclusion></exclusions></dependency><!--WebMagic扩展--><dependency><groupId>us.codecraft</groupId><artifactId>webmagic-extension</artifactId><version>0.7.3</version></dependency><!--WebMagic对布隆过滤器的支持--><dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>16.0</version></dependency><!--工具包--><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId></dependency><!--单元测试--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency></dependencies>
</project>

编写application.properties配置文件

#DB Configuration:
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/crawler?useSSL=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=qwe123#JPA Configuration:
spring.jpa.database=MySQL
spring.jpa.show-sql=true

编写实体类

package job.domain;import javax.persistence.*;@Entity
@Table(name = "job_info")
public class JobInfo {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private String companyName;private String companyAddr;private String companyInfo;private String jobName;private String jobAddr;private String jobInfo;private Integer salaryMin;private Integer salaryMax;private String url;private String time;public Long getId() {return id;}public void setId(Long id) {this.id = id;}public String getCompanyName() {return companyName;}public void setCompanyName(String companyName) {this.companyName = companyName;}public String getCompanyAddr() {return companyAddr;}public void setCompanyAddr(String companyAddr) {this.companyAddr = companyAddr;}public String getCompanyInfo() {return companyInfo;}public void setCompanyInfo(String companyInfo) {this.companyInfo = companyInfo;}public String getJobName() {return jobName;}public void setJobName(String jobName) {this.jobName = jobName;}public String getJobAddr() {return jobAddr;}public void setJobAddr(String jobAddr) {this.jobAddr = jobAddr;}public String getJobInfo() {return jobInfo;}public void setJobInfo(String jobInfo) {this.jobInfo = jobInfo;}public Integer getSalaryMin() {return salaryMin;}public void setSalaryMin(Integer salaryMin) {this.salaryMin = salaryMin;}public Integer getSalaryMax() {return salaryMax;}public void setSalaryMax(Integer salaryMax) {this.salaryMax = salaryMax;}public String getUrl() {return url;}public void setUrl(String url) {this.url = url;}public String getTime() {return time;}public void setTime(String time) {this.time = time;}@Overridepublic String toString() {return "JobInfo{" +"id=" + id +", companyName='" + companyName + '\'' +", companyAddr='" + companyAddr + '\'' +", companyInfo='" + companyInfo + '\'' +", jobName='" + jobName + '\'' +", jobAddr='" + jobAddr + '\'' +", jobInfo='" + jobInfo + '\'' +", salaryMin=" + salaryMin +", salaryMax=" + salaryMax +", url='" + url + '\'' +", time='" + time + '\'' +'}';}
}

编写Dao

package job.dao;import job.domain.JobInfo;
import org.springframework.data.jpa.repository.JpaRepository;public interface JobInfoDao extends JpaRepository<JobInfo, Long> {
}

编写Service

Service接口

package job.service;import job.domain.JobInfo;
import org.springframework.data.domain.Page;import java.util.List;public interface JobInfoService {/*** 保存工作信息** @param jobInfo*/public void save(JobInfo jobInfo);/*** 根据条件查询工作信息** @param jobInfo* @return*/public List<JobInfo> findJobInfo(JobInfo jobInfo);/*** 分页查询数据* @param page* @param rows* @return*/Page<JobInfo> findJobInfoByPage(int page, int rows);
}

Service实现类

package job.service.impl;import job.dao.JobInfoDao;
import job.domain.JobInfo;
import job.service.JobInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import java.util.List;@Service
public class JobInfoServiceImpl implements JobInfoService {@Autowiredprivate JobInfoDao jobInfoDao;@Override@Transactionalpublic void save(JobInfo jobInfo) {//根据url和发布时间查询数据JobInfo param = new JobInfo();param.setUrl(jobInfo.getUrl());param.setTime(jobInfo.getTime());//执行查询List<JobInfo> list = this.findJobInfo(param);//判断查询结果是否为空if (list.size() == 0) {//如果查询结果为空,表示招聘信息数据不存在,或者已经更新了,需要新增或者更新数据库this.jobInfoDao.saveAndFlush(jobInfo);}}@Overridepublic List<JobInfo> findJobInfo(JobInfo jobInfo) {//设置查询条件Example example = Example.of(jobInfo);//执行查询List list = this.jobInfoDao.findAll(example);return list;}@Overridepublic Page<JobInfo> findJobInfoByPage(int page, int rows) {Page<JobInfo> JobInfos = this.jobInfoDao.findAll(PageRequest.of(page - 1, rows));return JobInfos;}
}

编写引导类

package job;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;@SpringBootApplication
@EnableScheduling//开启定时任务
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}
}

自定义Pipeline

SpringDataPipeline

package job.task;import job.domain.JobInfo;
import job.service.JobInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import us.codecraft.webmagic.ResultItems;
import us.codecraft.webmagic.Task;
import us.codecraft.webmagic.pipeline.Pipeline;@Component
public class SpringDataPipeline  implements Pipeline {@Autowiredprivate JobInfoService jobInfoService;@Overridepublic void process(ResultItems resultItems, Task task) {//获取封装好的招聘详情对象JobInfo jobInfo = resultItems.get("jobInfo");//判断数据是否不为空if (jobInfo != null) {//如果不为空把数据保存到数据库中this.jobInfoService.save(jobInfo);}}
}

计算薪水工具类

MathSalary.java

package job.task;public class MathSalary {/*** 获取薪水范围* @param salaryStr* @return*/public static Integer[] getSalary(String salaryStr) {if (salaryStr==null || "".equals(salaryStr)){return new Integer[]{0,0};}Integer[] salary = new Integer[2];String date = salaryStr.substring(salaryStr.length() - 1, salaryStr.length());if (!"月".equals(date) && !"年".equals(date)) {salaryStr = salaryStr.substring(0, salaryStr.length() - 2);salary[0] = salary[1] = str2Num(salaryStr, 365);return salary;}String unit = salaryStr.substring(salaryStr.length() - 3, salaryStr.length() - 2);String[] salarys = salaryStr.substring(0, salaryStr.length() - 3).split("-");salary[0] = mathSalary(date, unit, salarys[0]);salary[1] = mathSalary(date, unit, salarys[1]);return salary;}private static Integer mathSalary(String date, String unit, String salaryStr) {Integer salary = 0;if ("万".equals(unit)) {salary = str2Num(salaryStr, 10000);} else {salary = str2Num(salaryStr, 1000);}if ("月".equals(date)) {salary = str2Num(salary.toString(), 12);}return salary;}private static int str2Num(String salaryStr, int num) {try {Number result = Float.parseFloat(salaryStr) * num;return result.intValue();} catch (Exception e) {}return 0;}
}

编写爬虫

JobProcessor.java

package job.task;import job.domain.JobInfo;
import org.jsoup.Jsoup;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import us.codecraft.webmagic.Page;
import us.codecraft.webmagic.Site;
import us.codecraft.webmagic.Spider;
import us.codecraft.webmagic.processor.PageProcessor;
import us.codecraft.webmagic.scheduler.BloomFilterDuplicateRemover;
import us.codecraft.webmagic.scheduler.QueueScheduler;
import us.codecraft.webmagic.selector.Html;
import us.codecraft.webmagic.selector.Selectable;import java.util.List;@Component
public class JobProcessor implements PageProcessor {private int yema=2;private String url = "https://search.51job.com/list/000000,000000,0000,00,9,99,java,2,1.html?lang=c&postchannel=0000&workyear=99&cotype=99&degreefrom=99&jobterm=99&companysize=99&ord_field=0&dibiaoid=0&line=&welfare=";@Overridepublic void process(Page page) {//解析页面,获取招聘信息详情的url地址List<Selectable> list = page.getHtml().regex("\"job_href\":\"(.*?)\"").nodes();//判断获取到的集合是否为空if (list.size() == 0) {// 如果为空,表示这是招聘详情页,解析页面,获取招聘详情信息,保存数据this.saveJobInfo(page);} else {//如果不为空,表示这是列表页,解析出详情页的url地址,放到任务队列中for (Selectable selectable : list) {//获取url地址String jobInfoUrl = selectable.toString();jobInfoUrl = jobInfoUrl.replace("\\","");//把获取到的url地址放到任务队列中page.addTargetRequest(jobInfoUrl);}if (this.yema<100){//下一页的urlString next_url = "https://search.51job.com/list/000000,000000,0000,00,9,99,java,2,"+this.yema+".html?lang=c&postchannel=0000&workyear=99&cotype=99&degreefrom=99&jobterm=99&companysize=99&ord_field=0&dibiaoid=0&line=&welfare=";//把url放到任务队列中page.addTargetRequest(next_url);this.yema = this.yema+1;}}}//解析页面,获取招聘详情信息,保存数据private void saveJobInfo(Page page) {//创建招聘详情对象JobInfo jobInfo  = new JobInfo();//解析页面Html html = page.getHtml();//获取数据,封装到对象中jobInfo.setCompanyName(html.css("p.cname > a","text").toString());String tmp = html.css("p.ltype","title").toString();String[] strings = tmp.split("  |  ");jobInfo.setCompanyAddr(strings[0]);jobInfo.setCompanyInfo(Jsoup.parse(html.css("div.tmsg").toString()).text());jobInfo.setJobName(html.css("div.in > div.cn > h1","text").toString());jobInfo.setJobAddr(html.css("div.bmsg > p.fp","text").toString());jobInfo.setJobInfo(Jsoup.parse(html.css("div.job_msg").toString()).text());jobInfo.setUrl(page.getUrl().toString());//获取薪资Integer[] salary = MathSalary.getSalary(html.css("div.cn strong", "text").toString());jobInfo.setSalaryMin(salary[0]);jobInfo.setSalaryMax(salary[1]);//获取发布时间String time = html.css("p.ltype","title").regex("\\d\\d-\\d\\d发布").toString();jobInfo.setTime(time.substring(0,time.length()-2));//把结果保存起来page.putField("jobInfo",jobInfo);}private Site site = Site.me().setCharset("gbk")//设置编码.setTimeOut(10 * 1000)//设置超时时间.setRetrySleepTime(3000)//设置重试的间隔时间.setRetryTimes(3);//设置重试的次数@Overridepublic Site getSite() {return site;}@Autowiredprivate SpringDataPipeline springDataPipeline;//initialDelay当任务启动后,等等多久执行方法//fixedDelay每个多久执行方法@Scheduled(initialDelay = 1000, fixedDelay = 1000 * 1000)public void process() {Spider.create(new JobProcessor()).addUrl(url).setScheduler(new QueueScheduler().setDuplicateRemover(new BloomFilterDuplicateRemover(100000))).thread(10).addPipeline(this.springDataPipeline).run();}
}

运行引导类即可爬取数据。
爬取的部分数据如下:
在这里插入图片描述
欢迎关注。

这篇关于java 爬取51job招聘信息的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/582449

相关文章

java如何解压zip压缩包

《java如何解压zip压缩包》:本文主要介绍java如何解压zip压缩包问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Java解压zip压缩包实例代码结果如下总结java解压zip压缩包坐在旁边的小伙伴问我怎么用 java 将服务器上的压缩文件解压出来,

SpringBoot中SM2公钥加密、私钥解密的实现示例详解

《SpringBoot中SM2公钥加密、私钥解密的实现示例详解》本文介绍了如何在SpringBoot项目中实现SM2公钥加密和私钥解密的功能,通过使用Hutool库和BouncyCastle依赖,简化... 目录一、前言1、加密信息(示例)2、加密结果(示例)二、实现代码1、yml文件配置2、创建SM2工具

Spring WebFlux 与 WebClient 使用指南及最佳实践

《SpringWebFlux与WebClient使用指南及最佳实践》WebClient是SpringWebFlux模块提供的非阻塞、响应式HTTP客户端,基于ProjectReactor实现,... 目录Spring WebFlux 与 WebClient 使用指南1. WebClient 概述2. 核心依

Spring Boot @RestControllerAdvice全局异常处理最佳实践

《SpringBoot@RestControllerAdvice全局异常处理最佳实践》本文详解SpringBoot中通过@RestControllerAdvice实现全局异常处理,强调代码复用、统... 目录前言一、为什么要使用全局异常处理?二、核心注解解析1. @RestControllerAdvice2

Spring IoC 容器的使用详解(最新整理)

《SpringIoC容器的使用详解(最新整理)》文章介绍了Spring框架中的应用分层思想与IoC容器原理,通过分层解耦业务逻辑、数据访问等模块,IoC容器利用@Component注解管理Bean... 目录1. 应用分层2. IoC 的介绍3. IoC 容器的使用3.1. bean 的存储3.2. 方法注

Spring事务传播机制最佳实践

《Spring事务传播机制最佳实践》Spring的事务传播机制为我们提供了优雅的解决方案,本文将带您深入理解这一机制,掌握不同场景下的最佳实践,感兴趣的朋友一起看看吧... 目录1. 什么是事务传播行为2. Spring支持的七种事务传播行为2.1 REQUIRED(默认)2.2 SUPPORTS2

怎样通过分析GC日志来定位Java进程的内存问题

《怎样通过分析GC日志来定位Java进程的内存问题》:本文主要介绍怎样通过分析GC日志来定位Java进程的内存问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、GC 日志基础配置1. 启用详细 GC 日志2. 不同收集器的日志格式二、关键指标与分析维度1.

Java进程异常故障定位及排查过程

《Java进程异常故障定位及排查过程》:本文主要介绍Java进程异常故障定位及排查过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、故障发现与初步判断1. 监控系统告警2. 日志初步分析二、核心排查工具与步骤1. 进程状态检查2. CPU 飙升问题3. 内存

java中新生代和老生代的关系说明

《java中新生代和老生代的关系说明》:本文主要介绍java中新生代和老生代的关系说明,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、内存区域划分新生代老年代二、对象生命周期与晋升流程三、新生代与老年代的协作机制1. 跨代引用处理2. 动态年龄判定3. 空间分

Java设计模式---迭代器模式(Iterator)解读

《Java设计模式---迭代器模式(Iterator)解读》:本文主要介绍Java设计模式---迭代器模式(Iterator),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,... 目录1、迭代器(Iterator)1.1、结构1.2、常用方法1.3、本质1、解耦集合与遍历逻辑2、统一