Java 爬取 51job 数据

一、项目Maven环境配置

相关依赖 jar 包配置

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.2.RELEASE</version>
</parent> <properties>
<java.version>1.8</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>
</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>
</dependencies>

application.properties 配置文件

#DB Configuration:
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/crawler
spring.datasource.username=root
spring.datasource.password=root #JPA Configuration:
spring.jpa.database=MySQL
spring.jpa.show-sql=true

二、相关类

pojo 类

@Entity
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;
... toString() 、 get/set()方法略 }

dao 类

public interface JobInfoDao extends JpaRepository<JobInfo,Long> {}

Service 类

public interface JobInfoService {
/**
* 保存工作信息
*
* @param jobInfo
*/
public void save(JobInfo jobInfo); /**
* 根据条件查询工作信息
*
* @param jobInfo
* @return
*/
public List<JobInfo> findJobInfo(JobInfo jobInfo);
}

ServiceImpl 类

@Service
public class JobInfoServiceImpl implements JobInfoService { @Autowired
private JobInfoDao jobInfoDao; // 查询原有的数据
// 判断数据库是否有已存在的数据
// 如果存在,就执行更新
// 不存在,就执行新增
@Override
@Transactional
public void save(JobInfo jobInfo) {
// 根据查询结果是否为空
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); // 新增或更新方法
}
} @Override
public List<JobInfo> findJobInfo(JobInfo jobInfo) {
// 设置查询条件
Example example = Example.of(jobInfo);
// 执行查询
List list = this.jobInfoDao.findAll(example);
return list;
}
}

功能实现类 Task

@Component
public class JobProcessor implements PageProcessor { private String url = "https://search.51job.com/list/000000,000000,0000,32%252C01,9,99,java,2,1.html?lang=c&stype=&postchannel=0000" +
"&workyear=99&cotype=99&degreefrom=99&jobterm=99&companysize=99&providesalary=99&lonlat=0%2C0&radius=-1&ord_field=0" +
"&confirmdate=9&fromType=&dibiaoid=0&address=&line=&specialarea=00&from=&welfare="; @Override
public void process(Page page) {
// 解析页面,获取招聘信息详情的url地址
List<Selectable> list = page.getHtml().css("div#resultList div.el").nodes();
// 判断获取到的集合是否为空
if (list.size()==0){
// 如果为空,表示这是招聘详情页,解析页面,获取招聘详情信息,保存数据
this.saveJobInfo(page);
}else {
// 如果不为空,表示这是列表页,解析出详情页的url地址,放到任务队列中
for (Selectable selectable : list) {
// 获取到url地址
String JobInfoUrl = selectable.links().toString();
// 把获取到url地址放到任务队列中
page.addTargetRequest(JobInfoUrl);
}
// 获取下一列功能的url
String nextUrl = page.getHtml().css("div.p_in li.bk").nodes().get(1).links().toString();
// 把url放到任务队列中
page.addTargetRequest(nextUrl);
}
} /**
* 解析页面,获取招聘详情信息,保存数据
* @param page
*/
private void saveJobInfo(Page page) {
// 创建招聘详情对象
JobInfo jobInfo = new JobInfo();
// 解析页面
Html html = page.getHtml();
// 获取数据,封装到对象中
// 公司名字
jobInfo.setCompanyName(html.css("div.cn p.cname a", "text").toString());
// 公司地址
String cAddr = Jsoup.parse(html.css("div.cn p.ltype", "text").toString()).text().replace("-","");
cAddr = cAddr.substring(0,6);
jobInfo.setCompanyAddr(cAddr);
// 公司信息
jobInfo.setCompanyInfo(Jsoup.parse(html.css("div.tmsg", "text").toString()).text());
// 工作名字
jobInfo.setJobName(html.css("div.cn h1", "text").toString());
// 工作地址
String jAddr = Jsoup.parse(html.css("div.bmsg").nodes().get(1).toString()).text();
// 部分公司暂没有填写公司详细地址,得非空判断
if (StringUtils.isBlank(jAddr)){
jobInfo.setJobAddr(jobInfo.getCompanyAddr());
}else {
jAddr = jAddr.replace("地图","");
jobInfo.setJobAddr(jAddr);
}
// 工作信息
jobInfo.setJobInfo(Jsoup.parse(html.css("div.job_msg").toString()).text());
// 个人薪水
Integer[] salary = MathSalarys.getSalary(html.css("div.cn strong", "text").toString());
jobInfo.setSalaryMin(salary[0]);
jobInfo.setSalaryMax(salary[1]);
// 发布时间
String time = Jsoup.parse(html.css("div.cn p.msg", "text").toString()).text();
int length = time.lastIndexOf("发布");
jobInfo.setTime(time.substring(length-5,length));
// url地址
jobInfo.setUrl(page.getUrl().toString());
// 把结果保存起来,等待 ResultItem获取 获取
page.putField("jobInfo",jobInfo); } private Site site = Site.me()
.setCharset("gbk") // 设置字符集
.setTimeOut(10*1000) // 设置超时时间
.setRetrySleepTime(3000) // 设置重试时间的间隔
.setRetryTimes(3); // 设置重试次数 @Override
public Site getSite() {
return site;
} @Autowired
private SpringDataPipeline pipeline; // initialDelay:当任务启动后,等等多久执行方法
// fixedDelay:每隔多久执行方法
@Scheduled(initialDelay = 1000,fixedDelay = 10000)
public void process(){
Spider.create(new JobProcessor())
.addUrl(url)
.setScheduler(new QueueScheduler().setDuplicateRemover(new BloomFilterDuplicateRemover(100000)))
.thread(10)
.addPipeline(pipeline)
.run();
} }

这里面用到了一个 统计工资的工具类 MathSalary

public class MathSalary {

    /**
* 获取薪水范围
*
* @param salaryStr
* @return
*/
public static Integer[] getSalary(String salaryStr) {
//声明存放薪水范围的数组
Integer[] salary = new Integer[2]; //"500/天"
//0.8-1.2万/月
//5-8千/月
//5-6万/年
String date = salaryStr.substring(salaryStr.length() - 1, salaryStr.length());
//如果是按天,则直接乘以240进行计算
if (!"月".equals(date) && !"年".equals(date)) {
salaryStr = salaryStr.substring(0, salaryStr.length() - 2);
salary[0] = salary[1] = str2Num(salaryStr, 240);
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)) {
//如果是万,薪水乘以10000
salary = str2Num(salaryStr, 10000);
} else {
//否则乘以1000
salary = str2Num(salaryStr, 1000);
} //判断时间是否是月
if ("月".equals(date)) {
//如果是月,薪水乘以12
salary = str2Num(salary.toString(), 12);
} return salary;
} private static int str2Num(String salaryStr, int num) {
try {
// 把字符串转为小数,必须用Number接受,否则会有精度丢失的问题
Number result = Float.parseFloat(salaryStr) * num;
return result.intValue();
} catch (Exception e) {
}
return 0;
} }

导出数据到数据库相关类 Pipeline

@Component
public class SpringDataPipeline implements Pipeline { @Autowired
private JobInfoService jobInfoService; @Override
public void process(ResultItems resultItems, Task task) {
// 获取我们封装好的招聘详情对象
JobInfo jobInfo = resultItems.get("jobInfo");
// 判断我们的数据是否不为空
if (jobInfo != null){
// 不为空就保存到数据库中
this.jobInfoService.save(jobInfo);
}
}
}

引导类 Application

@SpringBootApplication
@EnableScheduling// 开启定时任务
public class Application { public static void main(String[] args) { SpringApplication.run(Application.class,args); } }

结果展示:


整理了以下,可能会出现以下问题,可自行修改

//  String index out of range: -1: 存在部分字符串越界问题,应该是截取那里除了问题
// Data too long for column 'job_addr' at row 1: 数据库的字符集出错,将数据库数据类型换成了longtext 长度不用设置
// failed: connect timed out: 有可能是网络问题,网络不畅通会有超时的现象
// could not execute statement: 数据库中有字段不允许为空,而我们提交的数据中却没有提交该字段的值,就会造成这个异常。

最新文章

  1. T-SQL 拆分使用指定分隔符的字符串(split string)
  2. iOS提交AppStore后申请加急审核
  3. 3D游戏常用技巧Normal Mapping (法线贴图)原理解析——基础篇
  4. CentOS7—HAProxy安装与配置
  5. java中abstract详解
  6. qt5.5实现 记事本程序
  7. [工作记录] Android OpenSL ES: references &amp; AAC related
  8. Linux基础知识(二)
  9. windows7旗舰版系统自带组件IIS搭建ftp
  10. multipleOutputs Hadoop
  11. JavaScript(第四天)【运算符】
  12. 17python-BS编程
  13. java中int和String之间的转换
  14. HP服务器设置iLO步凑
  15. 转载一篇关于toString和valueOf
  16. C语言一闪而过
  17. 简述HttpSession的作用、使用方法,可用代码说明
  18. informatica powercenter学习笔记(三)
  19. SIFT 、Hog 、LBP 了解
  20. Spring实战之切面编程

热门文章

  1. 在localStorage中存储对象数组并读取
  2. Flink的状态管理与恢复机制
  3. JAVAWEB开发批量删除,SSM的几种情况
  4. Promise.race()
  5. 使用栅格系统开发响应式页面——logo+nav实例
  6. k8s笔记0528-基于KUBERNETES构建企业容器云手动部署集群记录-5
  7. tomcat配置启动不了
  8. netty系列之:搭建HTTP上传文件服务器
  9. 20210819 Emotional Flutter,Medium Counting,Huge Counting,字符消除2
  10. React Native踩坑日记 —— tailwind-rn