likes
comments
collection
share

【干货】我用Java抓取了贝壳上深圳楼盘的信息

作者站长头像
站长
· 阅读数 50

点赞再看,养成习惯👏👏

Hello 大家好,我是l拉不拉米,今天又更文了,带来了一份很干的干货。

前言

说起爬虫抓取网页数据,相信大家的第一反应都是 python,确实 python 天生就适合做这件事,但是很多有多年经验的 Java 开发都不一定知道,其实 Java 也是可以做爬虫的。最出名的就是 Jsoup 网页提取框架。

结缘

很多年前,自己做了一个贵金属资讯类的网站,需要实时展示最新的各类型各交易所的金价、银价等,当时有提供这类数据的第三方 API 接口的服务商,需要付费,后面就百度到 Jsoup 可以抓取网页的数据,然后就找到一个大的网站照着爬了数据,自己再出表格展示在页面,省了一笔钱。

昨晚,心血来潮,想着可以写一篇文章,于是去官网复习了下,决定对贝壳找房下手了😏

Jsoup食用指南

Jsoup 是真的简单,简单到不想介绍开发流程,直接看官网就行了,十分钟搞定其API

官网有入门指南和例子,大家自己去看吧。

官网地址:jsoup.org/

干货

干货部分我会用项目实战的方式,详细讲解,相信通过这个实战的例子,大家都能轻松掌握该技术。

1、准备要抓取的网页

贝壳找房-深圳站-新房:sz.fang.ke.com/loupan/pg

2、新建Maven项目

【干货】我用Java抓取了贝壳上深圳楼盘的信息

3、Pom文件添加依赖

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>30.0-jre</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.18</version>
</dependency>
<dependency>
    <groupId>org.jsoup</groupId>
    <artifactId>jsoup</artifactId>
    <version>1.14.3</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>2.2.10</version>
</dependency>

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方法执行业务代码

这里要注意几个点:

  1. 贝壳找房对于短时间同一个ip的频繁访问会触发人机验证,所以我们每次分页执行需要让线程休眠一段时间。
  2. Jsoup是针对html元素进行抓取,如果贝壳网的网页有变动,该程序可能不能正常抓取数据。
  3. 该程序没有对楼盘的详细信息抓取,有兴趣的同学可以根据抓取到的详情页url做二次开发。
  4. 如果你要抓取的网站需要登录信息等特殊请求参数,Jsoup也是支持设置的,具体参考官网API。
@SneakyThrows
public static void main(String[] args) {
    AtomicInteger pageIndex = new AtomicInteger(1);
    int pageSize = 10;
    List<House> 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<House> 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、成果展示

【干货】我用Java抓取了贝壳上深圳楼盘的信息

【干货】我用Java抓取了贝壳上深圳楼盘的信息

有兴趣的小伙伴可以自己试试!

源码:GitHub

原创不易,请多多点赞,感谢万分!🙏🙏