计算机系统应用教程网站

网站首页 > 技术文章 正文

实战通过联行号爬取支行信息

btikc 2025-02-27 14:22:23 技术文章 4 ℃ 0 评论

前言

??经过一段时间的加班,终于是把项目熬上线了。本以为可以轻松一点,但往往事与愿违,出现了各种各样的问题。由于做的是POS前置交易系统,涉及到和商户进件以及交易相关的业务,需要向上游支付机构上送“联行号”,但是由于系统内的数据不全,经常出现找不到银行或者联行号有误等情况,导致无法进件。

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

??经过2分钟的思考,想到以后每天都要修数据,那不得烦死。于是长痛不如短痛,还不如一次性修了。然后我反手就打开了百度,经过一段时间的遨游。发现下面3个网站的支行信息比较全,准备用来跟系统内数据作对比,然后进行修正。

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

分析网站

??输入联行号,然后选择查询方式,点击开始查询就可以。但是呢,结果页面一闪而过,然后被广告页面给覆盖了,这个时候就非常你的手速了。对于这样的,自然是难不倒我。从前端的角度分析,很明显展示结果的table标签被隐藏了,用来显示广告。于是反手就是打开控制台,查看源代码。

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

??通过上面的操作,我们要想爬到数据,需要做两步操作。先输入联行号进行查询,然后进去详情页,才能取到想要的数据。所以第一步需要先获取查询的接口,于是我又打开了熟悉的控制台。

??从上图可以发现这些请求都是在获取广告,并没有发现我们想要的接口,这个是啥情况,难道凭空变出来的嘛。并不是,主要是因为这个网站不是前后端分离的,所以这个时候我们需要从它的源码下手。


?
???
????
?????关键词:?
?????
??????
????
? ???
? ???? ?????搜索类型:? ????? ?????? ???????支行关键词 ???????银行联行号 ???????支行网点地址? ??????? ????
? ???
? ???? ?????? ????? ??????开始查询? ?????清空输入框? ????? ???? ?? ?

通过分析代码可以得出:

我们可以用PostMan来验证一下接口是否有效,验证结果如下图所示:

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

爬虫编写

??经过上面的分析了,已经取到了我们想要的接口,可谓是万事俱备,只欠代码了。爬取原理很简单,就是解析HTML元素,然后获取到相应的属性值保存下来就好了。由于使用Java进行开发,所以选用「Jsoup」来完成这个工作。



??org.jsoup
??jsoup
??1.13.1

??由于单个网站的数据可能不全,所以我们需要逐个进行抓取。先抓取第一个,如果抓取不到,则抓取下一个网站,这样依次进行下去。这样的业务场景,我们可以使用变种的责任链设计模式来进行代码的编写。

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?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()?

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);
????}

}

演示

爬取成功

爬取失败的情况

代码地址

总结

?? 这个爬虫的难点主要是在于Jsons.cn。因为数据接口被隐藏在代码里面,所以想取到需要花费一些时间。并且请求地址和页面地址一致,只是请求方式不一样,容易被误导。比较下来其他的两个就比较简单,直接替换联行号就可以了,还有就是这个三个网站也没啥反扒的机制,所以很轻松的就拿到了数据。

往期回顾

结尾

??如果觉得对你有帮助,可以多多评论,多多点赞哦,也可以到我的主页看看,说不定有你喜欢的文章,也可以随手点个关注哦,谢谢。

??我是不一样的科技宅,每天进步一点点,体验不一样的生活。我们下期见!

Tags:

猜你喜欢

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表