关于java:原创如何快速获取银行联行号的数据

54次阅读

共计 7084 个字符,预计需要花费 18 分钟才能阅读完成。

前言

  通过一段时间的加班,终于是把我的项目熬上线了。本认为能够轻松一点,但往往大失所望,呈现了各种各样的问题。因为做的是 POS 前置交易系统,波及到和商户进件以及交易相干的业务,须要向上游领取机构上送“联行号”,然而因为零碎内的数据不全,经常出现找不到银行或者联行号有误等状况,导致无奈进件。

  为了解决这个问题,我找上游机构要了一份支行信息。好家伙,足足有 14w 条记录。在导入零碎时,发现有一些异样的数据。有些是江西的银行,地区码居然是北京的。通过一段时间排查,发现这样的数据还挺多的。这可愁死我了,原本偷个懒,等客服反馈的时候,呈现一条修一条。

  通过 2 分钟的思考,想到当前每天都要修数据,那不得烦死。于是长痛不如短痛,还不如一次性修了。而后我反手就关上了百度,通过一段时间的漫游。发现上面 3 个网站的支行信息比拟全,筹备用来跟零碎内数据作比照,而后进行修改。

  • http://www.jsons.cn/banknum/
  • http://www.5cm.cn/bank/ 支行编号 /
  • https://www.appgate.cn/branch…

剖析网站

  输出联行号,而后抉择查问形式,点击开始查问就能够。然而呢,后果页面一闪而过,而后被广告页面给笼罩了,这个时候就十分你的手速了。对于这样的,天然是难不倒我。从前端的角度剖析,很显著展现后果的 table 标签被暗藏了,用来显示广告。于是反手就是关上控制台,查看源代码。

通过一顿搜查,终于是找到了详情页的地址。

  通过下面的操作,咱们要想爬到数据,须要做两步操作。先输出联行号进行查问,而后进去详情页,能力取到想要的数据。所以第一步须要先获取查问的接口,于是我又关上了相熟的控制台。

  从上图能够发现这些申请都是在获取广告,并没有发现咱们想要的接口,这个是啥状况,难道凭空变进去的嘛。并不是,次要是因为这个网站不是前后端拆散的,所以这个时候咱们须要从它的源码下手。

<html>
 <body>
  <form id="form1" class="form-horizontal" action="/banknum/" method="post"> 
   <div class="form-group"> 
    <label class="col-sm-2 control-label"> 关键词:</label> 
    <div class="col-sm-10"> 
     <input class="form-control" type="text" id="keyword" name="keyword" value="102453000160"  placeholder="请输出查问关键词,例如:中关村支行" maxlength="50" /> 
    </div> 
   </div> 
   <div class="form-group"> 
    <label class="col-sm-2 control-label"> 搜寻类型:</label> 
    <div class="col-sm-10"> 
     <select class="form-control" id="txtflag" name="txtflag"> 
             <option value="0"> 支行关键词 </option>
          <option value="1" selected=""> 银行联行号 </option>
          <option value="2"> 支行网点地址 </option> 
      </select> 
    </div> 
   </div> 
   <div class="form-group"> 
    <label class="col-sm-2 control-label"> </label> 
    <div class="col-sm-10"> 
     <button type="submit" class="btn btn-success"> 开始查问 </button> 
     <a href="/banknum/" class="btn btn-danger"> 清空输入框 </a> 
    </div> 
   </div> 
  </form>
 </body>
</html>

通过剖析代码能够得出:

  • 申请地址:http://www.jsons.cn/banknum/
  • 申请形式:POST
  • 申请参数:

    • keyword: 联行号
    • txtflag :1

咱们能够用 PostMan 来验证一下接口是否无效,验证后果如下图所示:

  剩下的两个网站绝对比较简单,只须要更改相应的联行号,进行申请就能够获取到相应的数据,所以这里不过多赘述。

爬虫编写

  通过下面的剖析了,曾经取到了咱们想要的接口,堪称是万事俱备,只欠代码了。爬取原理很简略,就是解析 HTML 元素,而后获取到相应的属性值保留下来就好了。因为应用 Java 进行开发,所以选用 Jsoup 来实现这个工作。

<!-- HTML 解析器 -->
<dependency>
  <groupId>org.jsoup</groupId>
  <artifactId>jsoup</artifactId>
  <version>1.13.1</version>
</dependency>

  因为单个网站的数据可能不全,所以咱们须要一一进行抓取。先抓取第一个,如果抓取不到,则抓取下一个网站,这样顺次进行上来。这样的业务场景,咱们能够应用变种的责任链设计模式来进行代码的编写。

BankBranchVO 支行信息

@Data
@Builder
public class BankBranchVO {

    /**
     * 支行名称
     */
    private String bankName;

    /**
     * 联行号
     */
    private String bankCode;

    /**
     * 省份
     */
    private String provName;

    /**
     * 市
     */
    private String cityName;

}

BankBranchSpider 抽象类

public abstract class BankBranchSpider {

    /**
     * 下一个爬虫
     */
    private BankBranchSpider nextSpider;

    /**
     * 解析支行信息
     *
     * @param bankBranchCode 支行联行号
     * @return 支行信息
     */
    protected abstract BankBranchVO parse(String bankBranchCode);

    /**
     * 设置下一个爬虫
     *
     * @param nextSpider 下一个爬虫
     */
    public void setNextSpider(BankBranchSpider nextSpider) {this.nextSpider = nextSpider;}

    /**
     * 应用下一个爬虫
     * 依据爬取的后果进行断定是否应用下一个网站进行爬取
     *
     * @param vo 支行信息
     * @return true 或者 false
     */
    protected abstract boolean useNextSpider(BankBranchVO vo);

    /**
     * 查问支行信息
     *
     * @param bankBranchCode 支行联行号
     * @return 支行信息
     */
    public BankBranchVO search(String bankBranchCode) {BankBranchVO vo = parse(bankBranchCode);
        while (useNextSpider(vo) && this.nextSpider != null) {vo = nextSpider.search(bankBranchCode);
        }
        if (vo == null) {throw new SpiderException("无奈获取支行信息:" + bankBranchCode);
        }
        return vo;
    }

}

  针对不同的网站解析形式不太一样,简言之就是获取 HTML 标签的属性值,对于这步能够有很多种形式实现,上面贴出我的实现形式,仅供参考。

JsonCnSpider

@Slf4j
public class JsonCnSpider extends BankBranchSpider {

    /**
     * 爬取 URL
     */
    private static final String URL = "http://www.jsons.cn/banknum/";


    @Override
    protected BankBranchVO parse(String bankBranchCode) {

        try {log.info("json.cn- 支行信息查问:{}", bankBranchCode);

            // 设置申请参数
            Map<String, String> map = new HashMap<>(2);
            map.put("keyword", bankBranchCode);
            map.put("txtflag", "1");

            // 查问支行信息
            Document doc = Jsoup.connect(URL).data(map).post();


            Elements td = doc.selectFirst("tbody")
                    .selectFirst("tr")
                    .select("td");

            if (td.size() < 3) {return null;}

            // 获取详情 url
            String detailUrl = td.get(3)
                    .selectFirst("a")
                    .attr("href");

            if (StringUtil.isBlank(detailUrl)) {return null;}

            log.info("json.cn- 支行详情 - 联行号:{}, 详情页:{}", bankBranchCode, detailUrl);

            // 获取详细信息
            Elements footers = Jsoup.connect(detailUrl).get().select("blockquote").select("footer");

            String bankName = footers.get(1).childNode(2).toString();
            String bankCode = footers.get(2).childNode(2).toString();
            String provName = footers.get(3).childNode(2).toString();
            String cityName = footers.get(4).childNode(2).toString();

            return BankBranchVO.builder()
                    .bankName(bankName)
                    .bankCode(bankCode)
                    .provName(provName)
                    .cityName(cityName)
                    .build();} catch (IOException e) {log.error("json.cn- 支行信息查问失败:{}, 失败起因:{}", bankBranchCode, e.getLocalizedMessage());
            return null;
        }
    }

    @Override
    protected boolean useNextSpider(BankBranchVO vo) {return vo == null;}

}

FiveCmSpider

@Slf4j
public class FiveCmSpider extends BankBranchSpider {

    /**
     * 爬取 URL
     */
    private static final String URL = "http://www.5cm.cn/bank/%s/";

    @Override
    protected BankBranchVO parse(String bankBranchCode) {log.info("5cm.cn- 查问支行信息:{}", bankBranchCode);

        try {Document doc = Jsoup.connect(String.format(URL, bankBranchCode)).get();
            Elements tr = doc.select("tr");

            Elements td = tr.get(0).select("td");
            if ("".equals(td.get(1).text())) {return null;}

            String bankName = doc.select("h1").get(0).text();
            String provName = td.get(1).text();
            String cityName = td.get(3).text();

            return BankBranchVO.builder()
                    .bankName(bankName)
                    .bankCode(bankBranchCode)
                    .provName(provName)
                    .cityName(cityName)
                    .build();} catch (IOException e) {log.error("5cm.cn- 支行信息查问失败:{}, 失败起因:{}", bankBranchCode, e.getLocalizedMessage());
            return null;
        }
    }

    @Override
    protected boolean useNextSpider(BankBranchVO vo) {return vo == null;}

}

AppGateSpider

@Slf4j
public class AppGateSpider extends BankBranchSpider {

    /**
     * 爬取 URL
     */
    private static final String URL = "https://www.appgate.cn/branch/bankBranchDetail/";

    @Override
    protected BankBranchVO parse(String bankBranchCode) {
        try {log.info("appgate.cn- 查问支行信息:{}", bankBranchCode);

            Document doc = Jsoup.connect(URL + bankBranchCode).get();
            Elements tr = doc.select("tr");

            String bankName = tr.get(1).select("td").get(1).text();

            if(Boolean.FALSE.equals(StringUtils.hasText(bankName))){return null;}

            String provName = tr.get(2).select("td").get(1).text();
            String cityName = tr.get(3).select("td").get(1).text();

            return BankBranchVO.builder()
                    .bankName(bankName)
                    .bankCode(bankBranchCode)
                    .provName(provName)
                    .cityName(cityName)
                    .build();} catch (IOException e) {log.error("appgate.cn- 支行信息查问失败:{}, 失败起因:{}", bankBranchCode, e.getLocalizedMessage());
            return null;
        }
    }

    @Override
    protected boolean useNextSpider(BankBranchVO vo) {return vo == null;}
}

初始化爬虫

@Component
public class BankBranchSpiderBean {

    @Bean
    public BankBranchSpider bankBranchSpider() {JsonCnSpider jsonCnSpider = new JsonCnSpider();
        FiveCmSpider fiveCmSpider = new FiveCmSpider();
        AppGateSpider appGateSpider = new AppGateSpider();
        jsonCnSpider.setNextSpider(fiveCmSpider);
        fiveCmSpider.setNextSpider(appGateSpider);
        return jsonCnSpider;
    }
}

爬取接口

@RestController
@AllArgsConstructor
@RequestMapping("/bank/branch")
public class BankBranchController {

    private final BankBranchSpider bankBranchSpider;

    /**
     * 查问支行信息
     *
     * @param bankBranchCode 支行联行号
     * @return 支行信息
     */
    @GetMapping("/search/{bankBranchCode}")
    public BankBranchVO search(@PathVariable("bankBranchCode") String bankBranchCode) {return bankBranchSpider.search(bankBranchCode);
    }

}

演示

爬取胜利

爬取失败的状况

代码地址

  • https://gitee.com/huangxunhui…

总结

   这个爬虫的难点次要是在于 Jsons.cn。因为数据接口被暗藏在代码外面,所以想取到须要破费一些工夫。并且申请地址和页面地址统一,只是申请形式不一样,容易被误导。比拟下来其余的两个就比较简单,间接替换联行号就能够了,还有就是这个三个网站也没啥反扒的机制,所以很轻松的就拿到了数据。

往期回顾

  • 实战省市区三级联动数据爬取

结尾

  如果感觉对你有帮忙,能够多多评论,多多点赞哦,也能够到我的主页看看,说不定有你喜爱的文章,也能够顺手点个关注哦,谢谢。

  我是不一样的科技宅,每天提高一点点,体验不一样的生存。咱们下期见!

正文完
 0