使用Jsoup技术获取`阿里拍卖`中法院拍卖的所有拍卖品

前言

最近在学习过程中接到一个任务,要求爬取阿里拍卖中法院拍卖的所有拍卖品。用了点时间完成了任务,并分享出来作为经验供学习、交流。若文中有任何不妥之处请提出。

最终效果

效果演示

爬取所有记录

  • 控制台打印记录
    爬取所有内容
  • 数据库记录
    在这里插入图片描述

根据条件爬取

  • 控制台打印记录
    根据条件爬取内容
  • 数据库记录
    在这里插入图片描述

项目仓库

项目前准备

技术选型

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

想了解Jsoup相关内容,请移步:https://jsoup.org/

开发工具

  • IDEA 2018.2
  • MYSQL 5.7
  • Chrome

用到的jar包

  • easy-poi:3.0.3
  • mysql-connector-java:5.1.40
  • jsoup:1.8.3
  • fastjson:1.2.47

爬取所有数据

页面分析

  • 首先我们通过http://sf.taobao.com/court_list.htm?spm=a213w.3065169.sfhead2014.3.3e1f1a333zcUBm,进入要爬取的入口页,入口页的结构如下图所示:
    在这里插入图片描述
    页面的机构是以省份为区分,在省下包含市,市包含法院,而法院中的链接市我们要获取信息的下一个入口。为了实现目的,爬虫的处理逻辑如下:
1.进入入口页,获取所有省份的DOM
2.对第一个省份继续进行处理,获取市级列表
3.对第一个市进行处理,获取法院列表和链接地址
4.进入第一个法院,获得拍卖品列表
5.进入商品详情页获取需要的信息
6.重复5,知道所有拍卖品爬完
7.重复4-6,直至所有法院爬完
8.重复3-7,直至所有市爬完
9.重复2-8,直至所有省份爬完
10.结束

第一步:获取所有省份列表

  • 首先用Chrome浏览器检查爬虫入口页面元素,这样能够清晰直观看出页面的布局,如下图所示
    爬虫入口页面元素
    图中,1是省份的div,包裹在一个class="provinces clearfix"div中,在1中还包含了2,3,4这些元素,这些都是我们接下来需要的内容。
    图中,2是省份的名称,包裹在一个class="province"div中,通过查看多个省份的内容可以发现,这个模型中只会有省份名称这个中文,所以取出这个信息时可以通过正则表达式对中文进行匹配,从而取出值。
    图中,3是省份下的市列表,包裹在一个class="province"div中。每个市在一个单独的class="city"div中。图中的4,市该市下的法院列表,法院列表中每个法院的链接才是我们需要的内容,通过访问链接才能进入该法院的拍卖品列表,每个法院的链接规则如下:
    在这里插入图片描述
    获取也很简答,只用获取a标签中的href属性即可,但访问时为了比较明显,要在前面加个http:
    点击链接,进入拍卖品列表,如下:
    在这里插入图片描述
    都这样的结构也很简单,第一想法时使用上述相同的方法直接一条一条爬取就行。但如果这样你会发现,并爬不到数据,后来才发现,所有的数据并不是在页面请求时就直接加载的,所有的数据会以json的格式放在一个script标签中,如下图所示:
    在这里插入图片描述
    这样就更简单了,直接通过Json解析,然后取出详情链接就可以了。但需要注意的是,数据只是这一页中的,事实上数据可能有很多页。所以这时的想法就是看下每页的请求路径有什么规律,通过对比就发现翻页时的路径类似于http://sf.taobao.com/court_item.htm?spm=a213w.7398554.pagination.1.5edd2fc2Vn735Z&user_id=2364124517&auction_start_seg=-1&page=2,而页码对应的就是page的值,所以我们只需要从页面上获取总计页数,通过循环就可以得到每页的数据。
    在这里插入图片描述
    方法很简单,只需要获取到class="page-total"的em标签的text内容即可
    接下来就是详情页,详情页我们只抓三个部分的内容
    在这里插入图片描述
    其中标题和变卖价的规则如下:
    在这里插入图片描述
    在这里插入图片描述
    这里根据之前的规则就可以取出值了。接下来通过代码来实现这一过程

代码实现

本文前面已经附带了github的地址,原项目使用gradle构建,需要的朋友可以参考。所以本部分以关键部分代码讲解为主。

进入主页

Document document = Jsoup.connect(ConValues.SF_URL_ENTRANCE).timeout(5000).get();

这一步比较简单,需要注意的是最好加上timeout()方法设置超时时间,不然可能会出现java.net.SocketTimeoutException:Read timed out异常。通过这一步就可以得到文档对象,来完成下列操作。

解析页面

获得所有省的文档模型
Elements select = document.select("div[class=provinces clearfix]");
循环,获取各省的信息
Elements elements = element.select("div[class=province"); Matcher valueByReg = DataUtil.getValueByReg(ConValues.ZHONG_WEN_REG, elements.toString());
if(valueByReg.find()){
    //省
   province = valueByReg.group(0);
 }
获得市列表
 Elements citys = var1.select("dl[class=city]");
获得总页数和翻页时的路径规则
 Elements var4 = var3.select("a");
 for(Element var5:var4){
        if(var5.hasAttr("rel")){
               basePageUrl = "http:"+var5.attr("href").trim();
               break;
        }
    }
 String pageText = var3.select("span[class=page-skip]").select("em[class=page-total]").text();
 if(pageText.length()>0){
         totalPage = Integer.parseInt(pageText);
 }
进入拍卖品列表页并取出值
//对每一页进行处理
String pageUrl = basePageUrl.substring(0,basePageUrl.length()-1)+i;
Document document2 = Jsoup.connect(pageUrl).timeout(5000).get();
 //获得该页所有商品信息,该页的所有商品信息是以json的格式存放在<script>标签中的
 String oriData = document2.getElementById("sf-item-list-data").toString();
 //接下来对数据进行处理
 //1.找到“>”标签的位置
 int start = oriData.indexOf(">");
 //2.找到"</"标签的位置
 int end = oriData.indexOf("</");
 //截取出值
 String data = oriData.substring(start+1, end);
解析json数据获得详情路径,并提取数据
JSONArray data1 = (JSONArray) JsonUtil.convertJsonStrToMap(data).get("data");
Iterator<Object> iterator = data1.iterator();
while(iterator.hasNext()){
    JSONObject next = (JSONObject)iterator.next();
    //需要记录的url
    String detailUrl = "http:"+next.get("itemUrl");
    //获取标题
    Document document3 = Jsoup.connect(detailUrl).timeout(5000).get();
    String detailTitle = document3.select("div[class=pm-main clearfix]").select("h1").text();
    //获取变卖价
    String detailPrice = document3.select("span[class=J_Price]").first().text();
    System.out.println("抓取信息:");
    System.out.println(province+"  "+city+"  "+countryName+"  "+detailTitle+"  "+detailPrice+"   "+detailUrl);   
}

持久化数据库

定义POJO
public class AuctionItem {
    private String province;//省份
    private String city;//城市
    private String countryName;//法院
    private String detailTitle;//标题
    private String detailPrice;//变卖价
    private String detailUrl;//资源路径

  //getter and setter
  .............
}
构造数据库操作工厂类
public class DbOpFactory {
    // JDBC 驱动名及数据库 URL
    static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
    static final String DB_URL = "jdbc:mysql://localhost:3306/spider-data";
    private static DbOpFactory instance;
    public static DbOpFactory getInstance(){
        if(instance==null){
            return new DbOpFactory();
        }else{
            return instance;
        }
    }

    private DbOpFactory(){
        init();
    }
    // 数据库的用户名与密码,需要根据自己的设置
    static final String USER = "root";
    static final String PASS = "xda265856";
    Connection conn = null;
    Statement stmt = null;
    ResultSet rs = null;

    public void init() {
        // 注册 JDBC 驱动
        try {
            Class.forName("com.mysql.jdbc.Driver");
            // 打开链接
            System.out.println("连接数据库...");
            conn = DriverManager.getConnection(DB_URL,USER,PASS);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
      /**
     * 持久化拍卖信息
     * @param auctionItem
     */
    public void insertAuction(AuctionItem auctionItem){
        try{
            // 执行查询
           // System.out.println(" 实例化Statement对象...");
            stmt = conn.createStatement();
            String sql;
            sql = String.format("INSERT INTO TB_AUCTION_ITEM(AUCTION_PROVINCE,AUCTION_CITY,AUCTION_COUNTRY_NAME,AUCTION_DETAIL_TITLE,AUCTION_PRICE,AUCTION_DETAIL_URL) VALUES('%s','%s','%s','%s','%s','%s')"
                    ,auctionItem.getProvince(),auctionItem.getCity(),auctionItem.getCountryName(),auctionItem.getDetailTitle(),auctionItem.getDetailPrice(),auctionItem.getDetailUrl());
            stmt.executeUpdate(sql);
        }catch(SQLException se){
            // 处理 JDBC 错误
            se.printStackTrace();
        }catch(Exception e){
            // 处理 Class.forName 错误
            e.printStackTrace();
        }

    public void close(){
        // 完成后关闭
        try {
            stmt.close();
            conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }

    }
}
执行插入
DbOpFactory instance = DbOpFactory.getInstance();
instance.init();
........
AuctionItem auctionItem = new AuctionItem();
auctionItem.setProvince(province);
auctionItem.setCity(city);
auctionItem.setCountryName(countryName);
auctionItem.setDetailTitle(detailTitle);
auctionItem.setDetailPrice(detailPrice);
auctionItem.setDetailUrl(detailUrl);
instance.insertAuction(auctionItem);
System.out.println("存入数据库成功");
........
instance.close();

至此爬虫完成,上述代码有不详尽的地方请到github上查看源码。

根据搜索条件爬取数据

根据条件的爬虫时根据搜索栏中输入的关键字进行查询,这里将需要查询的信息放到EXCEL表格中,方便获取。

分析

总体思路于爬取所有的内容时相同的。

  • 在搜索框中输入关键字
    在这里插入图片描述
  • 搜索结果页面的地址如下 在这里插入图片描述
    值为http://sf.taobao.com/list/0.htm?auction_start_seg=-1&q=%CE%E2%BD%AD%CB%C9%C1%EA%D5%F2%BD%AD%D8%C7%C2%B78%BA%C5A16-1&page=1,令人费解的是%CE%E2%BD%AD%CB%C9%C1%EA%D5%F2%BD%AD%D8%C7%C2%B78%BA%C5A16-1这串字符串。猜测应该是搜索的关键字但具体是什么就不得而知。通过了解发现这是URL字符转义(想了解更多,请自行百度),所以我们通过下面的方法可以实现对URL的字符转义功能
 /**
     * url字符转码
*/
 public static String getURLEncode(String urlValue){
     String urlEncode= null;

     try {
         urlEncode = java.net.URLEncoder.encode(urlValue, "gb2312");
     } catch (UnsupportedEncodingException e) {
         e.printStackTrace();
     }
     return urlEncode;
 }

我们通过对路径的拆分、拼接,即可得到完整的路径

 //查询地址前面部分
public static String SEARCH_ADDRESS_PREFIX = "http://sf.taobao.com/item_list.htm?q=";
//查询地址后面部分
public static String SEARCH_ADDRESS_SUFFIX = "&spm=a213w.3064813.9001.1";

从EXCEL中获得查询关键字

这里使用Easy-poi获取excel中的内容

  • 定义pojo
public class SearchAttribute {
    @Excel(name = "序号")
    private String id;
    @Excel(name = "地址")
    private String address;
    @Excel(name = "所有人")
    private String owner;
    @Excel(name = "产证号1")
    private String certNum1;
    @Excel(name = "产证号2")
    private String certNum2;
    @Excel(name = "产证号3")
    private String certNum3;
  //getter and setter
  ...................
}
  • 获取信息
public class PoiUtils {


    public static <T> List<T> importExcel(String filePath, Integer titleRows, Integer headerRows, Class<T> pojoClass) throws  Exception {
        ImportParams params = new ImportParams();
        params.setTitleRows(titleRows);
        params.setHeadRows(headerRows);
        List<T> list = null;
        try {
            list = ExcelImportUtil.importExcel(new File(filePath), pojoClass, params);
        } catch (NoSuchElementException e) {
            throw new Exception("模板不能为空");
        } catch (Exception e) {
            e.printStackTrace();
            throw new Exception(e.getMessage());
        }
        return list;
    }
}
//得到Excel中的信息
List<SearchAttribute> allAttributes = PoiUtils.importExcel("file/paimai.xlsx", 0, 1, SearchAttribute.class);

剩下的内容和爬取所有内容相同在此不做赘述

总结

  • 总体上来说功能实现了,但还有很多细节需要优化
  • 如果有任何问题欢迎留言探讨
  • 8
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值