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: 数据库中有字段不允许为空,而我们提交的数据中却没有提交该字段的值,就会造成这个异常。

Java 爬取 51job 数据 WebMagic实现的更多相关文章

  1. Java爬取51job保存到MySQL并进行分析

    大二下实训课结业作业,想着就爬个工作信息,原本是要用python的,后面想想就用java试试看, java就自学了一个月左右,想要锻炼一下自己面向对象的思想等等的, 然后网上转了一圈,拉钩什么的是动态 ...

  2. Java爬取同花顺股票数据(附源码)

    最近有小伙伴问我能不能抓取同花顺的数据,最近股票行情还不错,想把数据抓下来自己分析分析.我大A股,大家都知道的,一个概念火了,相应的股票就都大涨. 如果能及时获取股票涨跌信息,那就能在刚开始火起来的时 ...

  3. python之爬取网页数据总结(一)

    今天尝试使用python,爬取网页数据.因为python是新安装好的,所以要正常运行爬取数据的代码需要提前安装插件.分别为requests    Beautifulsoup4   lxml  三个插件 ...

  4. 利用linux curl爬取网站数据

    看到一个看球网站的以下截图红色框数据,想爬取下来,通常爬取网站数据一般都会从java或者python爬取,但本人这两个都不会,只会shell脚本,于是硬着头皮试一下用shell爬取,方法很笨重,但旨在 ...

  5. 使用webdriver+urllib爬取网页数据(模拟登陆,过验证码)

    urilib是python的标准库,当我们使用Python爬取网页数据时,往往用的是urllib模块,通过调用urllib模块的urlopen(url)方法返回网页对象,并使用read()方法获得ur ...

  6. Java爬取校内论坛新帖

    Java爬取校内论坛新帖 为了保持消息灵通,博主没事会上上校内论坛看看新帖,作为爬虫爱好者,博主萌生了写个爬虫自动下载的想法. 嗯,这次就选Java. 第三方库准备 Jsoup Jsoup是一款比较好 ...

  7. Python的scrapy之爬取51job网站的职位

    今天老师讲解了Python中的爬虫框架--scrapy,然后带领我们做了一个小爬虫--爬取51job网的职位信息,并且保存到数据库中 用的是Python3.6  pycharm编辑器 爬虫主体: im ...

  8. Node.js爬取豆瓣数据

    一直自以为自己vue还可以,一直自以为webpack还可以,今天在慕课逛node的时候,才发现,自己还差的很远.众所周知,vue-cli基于webpack,而webpack基于node,对node不了 ...

  9. MinerHtmlThread.java 爬取页面线程

    MinerHtmlThread.java 爬取页面线程 package com.iteye.injavawetrust.miner; import org.apache.commons.logging ...

随机推荐

  1. 十六:使用JDBC对数据库进行CRUD

    一.statement对象介绍 Jdbc中的statement对象用于向数据库发送SQL语句,想完成对数据库的增删改查,只需要通过这个对象向数据库发送增删改查语句即可. Statement对象的exe ...

  2. swagger2 注解说明文档

    @Api:用于类上,说明该类的作用.可以标记一个Controller类做为swagger 文档资源 @Api(value = "xxx", description = " ...

  3. LeetCode入门指南 之 二叉树

    二叉树的遍历 递归: void traverse (TreeNode root) { if (root == null) { return null; } //前序遍历位置 traverse(root ...

  4. kubebuilder实战之七:webhook

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  5. Django——Paginator分页功能练习

    1.路由urls.py from django.contrib import admin from django.urls import path from app01.views import in ...

  6. Tars | 第7篇 TarsJava Subset最终代码的测试方案设计

    目录 前言 1. SubsetConf配置项的结构 1.1 SubsetConf 1.2 RatioConfig 1.3 KeyConfig 1.4 KeyRoute 1.5 SubsetConf的结 ...

  7. Typeora 图床设置

    Typeora 文章中的图片 使用 Github 作为图床. 使用 PicGo 上传图片到 Github 并获取图片链接. 设置 Typeora 的上传服务. 一.Github 作为图床 创建 Rep ...

  8. linux traceroute追踪路由路径

    TraceRoute的工作原理 1.TraceRoute的工作原理:      traceroute 有使用两种:使用ICMP的和使用UDP的.Microsoft      使用ICMP,所以win9 ...

  9. 真香!原来 CLI 开发可以这么简单

    CLI(命令行工具,Command Line Interface)大家都非常熟悉了,比如 create-react-app 等.我们今天介绍一个 CLI 工具的开发框架,可以帮助我们快速构建 CLI ...

  10. 利用k8s yaml配置文件起一个http能够让外部访问

    1.首先建一个http的Deployment apiVersion: apps/v1 #版本信息 kind: Deployment #文件类型 metadata: #Deployment资源的元数据信 ...