java抓取网页内容( 点赞再看,养成习惯(一)网页提取框架)
优采云 发布时间: 2021-12-21 17:06java抓取网页内容(
点赞再看,养成习惯(一)网页提取框架)
喜欢再看,养成习惯
大家好,我叫Labrami,今天更新了文章,带来了很干的干货。
前言
说起爬虫爬取网页数据,相信大家的第一反应都是python。Python 确实很适合这一点,但是许多具有多年经验的 Java 开发人员不一定知道它。其实Java也可以作为爬虫使用。最著名的是Jsoup网页提取框架。
结婚了
多年前,我做了一个贵金属信息类型网站。我需要实时显示各个交易所的最新黄金和白银价格。当时有第三方API接口服务商提供此类数据。你需要付钱。后来百度去Jsoup抓取网页的数据,然后找了个大网站根据它抓取数据,然后自己把表格展示在页面上,省了一大笔钱。
昨晚心血来潮,想写一篇文章的文章,于是去官网看了一下,决定给优采云找个房间
Jsoup食用指南
Jsoup真的很简单,简单到不想介绍开发过程,直接看官网,十分钟搞定它的API。
官网有介绍指南和例子,大家自己去看看吧。
官网地址:/
干货
对于干货部分,我会用实战的方式来详细讲解。相信通过这个实战例子,大家可以轻松掌握技术。
1、准备要爬取的网页
优采云找房子-深圳站-新房:/loupan/pg
2、新的Maven项目
3、pom 文件添加依赖
com.google.guava
guava
30.0-jre
org.projectlombok
lombok
1.18.18
org.jsoup
jsoup
1.14.3
com.alibaba
easyexcel
2.2.10
复制代码
4、创建一个Pojo类将数据映射到excel表
因为从网页抓取的地址最后会保存在excel文件中,我们使用阿里巴巴的EasyExcel,所以需要在pom文件中引入依赖,并且需要创建一个pojo类来映射导出的文件。
@Data
@Accessors(chain = true)
public class House {
@ExcelProperty("楼盘名称")
private String title;
@ExcelProperty("访问网页")
private String detailPageUrl;
@ExcelProperty("楼盘图片")
private String imageUrl;
@ExcelProperty("所在地址")
private String address;
@ExcelProperty("户型")
private String houseType;
@ExcelProperty("房产类型")
private String propertyType;
@ExcelProperty("状态")
private String status;
@ExcelProperty("建筑面积")
private String buildingArea;
@ExcelProperty("总价")
private String totalPrice;
@ExcelProperty("单价(元/㎡(均价))")
private String singlePrice;
@ExcelProperty("标签")
private String tag;
}
复制代码
5、Main方法执行业务代码
这里有几点需要注意:
优采云短时间频繁访问同一个ip会触发人机验证,所以每次分页执行我们都需要让线程休眠一段时间。Jsoup 抓取 html 元素。如果优采云的网页有改动,程序可能无法正常抓取数据。程序不抓取楼盘详细信息,感兴趣的同学可以根据抓取到的详情页url进行二次开发。如果你要抓取的网站需要登录信息等特殊请求参数,Jsoup也支持设置,具体请参考官网API。
@SneakyThrows
public static void main(String[] args) {
AtomicInteger pageIndex = new AtomicInteger(1);
int pageSize = 10;
List dataList = Lists.newArrayList();
// 贝壳找房深圳区域网址
String beikeUrl = "https://sz.fang.ke.com";
// 贝壳找房深圳市楼盘展示页地址
String loupanUrl = "https://sz.fang.ke.com/loupan/pg";
// 用Jsoup抓取该地址完整网页信息
Document doc = Jsoup.connect(loupanUrl + pageIndex.get()).get();
// 网页标题
String pageTitle = doc.title();
// 分页容器
Element pageContainer = doc.select("div.page-box").first();
if (pageContainer == null) {
return;
}
// 楼盘总数
int totalCount = Integer.parseInt(pageContainer.attr("data-total-count"));
// 分页执行
for (int i = 0; i < totalCount / pageSize; i++) {
log.info("running get data, the current page is {}", pageIndex.get());
// 贝壳网有人机认证,不能短时间频繁访问,每次翻页都让线程休眠10s
Thread.sleep(10000);
doc = Jsoup.connect(loupanUrl + pageIndex.getAndIncrement()).get();
// 获取楼盘列表的ul元素
Element list = doc.select("ul.resblock-list-wrapper").first();
if (list == null) {
continue;
}
// 获取楼盘列表的li元素
Elements elements = list.select("li.resblock-list");
elements.forEach(el -> {
// 楼盘介绍
Element introduce = el.child(0);
// 详情页面
String detailPageUrl = beikeUrl + introduce.attr("href");
// 楼盘图片
String imageUrl = introduce.select("img").attr("data-original");
// 楼盘详情
Element childDesc = el.select("div.resblock-desc-wrapper").first();
Element childName = childDesc.child(0);
// 楼盘名称
String title = childName.child(0).text();
// 楼盘在售状态
String status = childName.child(1).text();
// 产权类型
String propertyType = childName.child(2).text();
// 楼盘所在地址
String address = childDesc.child(1).text();
// 房间属性
Element room = childDesc.child(2);
// 户型
String houseType = "";
// 户型集合
Elements houseTypeSpans = room.getElementsByTag("span");
if (CollectionUtils.isNotEmpty(houseTypeSpans)) {
// 剔除文案:【户型:】
houseTypeSpans.remove(0);
// 剔除文案:【建面:xxx】
houseTypeSpans.remove(houseTypeSpans.size() - 1);
houseType = StringUtil.join(houseTypeSpans.stream().map(Element::text).collect(Collectors.toList()), "/");
}
// 建筑面积
String buildingArea = room.select("span.area").text();
// div - 标签
Element descTag = childDesc.select("div.resblock-tag").first();
Elements tagSpans = descTag.getElementsByTag("span");
String tag = "";
if (CollectionUtils.isNotEmpty(tagSpans)) {
tag = StringUtil.join(tagSpans.stream().map(Element::text).collect(Collectors.toList()), " ");
}
// div - 价格
Element descPrice = childDesc.select("div.resblock-price").first();
String singlePrice = descPrice.select("span.number").text();
String totalPrice = descPrice.select("div.second").text();
dataList.add(new House().setTitle(title)
.setDetailPageUrl(detailPageUrl)
.setImageUrl(imageUrl)
.setSinglePrice(singlePrice)
.setTotalPrice(totalPrice)
.setStatus(status)
.setPropertyType(propertyType)
.setAddress(address)
.setHouseType(houseType)
.setBuildingArea(buildingArea)
.setTag(tag)
);
});
}
if (CollectionUtils.isEmpty(dataList)) {
log.info("dataList is empty returned.");
return;
}
log.info("dataList prepare finished, size = {}", dataList.size());
// 调用导出逻辑,将数据导出到excel文件
export(pageTitle, dataList);
}
复制代码
6、EasyExcel 导出逻辑
/**
* 将爬取的数据写入到excel中
* @param pageTitle
* @param dataList
*/
private static void export(String pageTitle, List dataList) {
WriteCellStyle headWriteCellStyle = new WriteCellStyle();
//设置头居中
headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
//内容策略
WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
//设置 水平居中
contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.LEFT);
HorizontalCellStyleStrategy horizontalCellStyleStrategy = new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);
// 这里需要设置不关闭流
EasyExcelFactory.write("D:\深圳楼盘汇总.xlsx", House.class).autoCloseStream(Boolean.FALSE).registerWriteHandler(horizontalCellStyleStrategy).sheet(pageTitle).doWrite(dataList);
}
复制代码
7、成就展示
有兴趣的朋友可以自己尝试一下!
源代码:GitHub
原创不容易,请多多点赞,非常感谢!