2022年 11月 16日

【爬虫案例】python爬取XX银行在每个地区的数量

任务如下:

爬取XX银行在全国各地的数量,该银行在XX省XX区XX市的数量。

(该网址没有robots.txt规则,若有需遵守规则)

只以两种银行为例,仅供学习使用。

A银行是从后端返回给前端是数据是json格式,也就是最常见的前后端分离模式。

做过后端项目的应该知道,这里返回的json数据就是DTO(数据传输对象)数据,当前端要我们返回某些数据的时候,我们不可能把所有数据(例如敏感数据或无关数据)全部都传输到前端,这样很容易就泄露大量的信息。于是就有了DTO,它封装了一部分可对外公开的数据,同时也可以节省流量。

我们能够获取到这个DTO,就能往下继续分析如何利用并爬取这类信息。

而B银行则是以htm结尾的静态页面,也就是不通过服务器编译解释直接送出给浏览器读取的静态网页,它没有返回数据,这类处理起来比较麻烦,后面会说。

 

 

 A银行

分析

先从A银行开始说。思路如下:随便输入地区,可以发现返回了一部分搜索结果。 

观察网址发现并没有变化。

来到F12网络那块,可以看到发送请求的地址,并且有明确的各种id。

试了几次之后,可以确定如下:

p=110000&c=110100&b=110101&q=&t=1&z=0&i=0

p是省 c是市 b是区县  z是业务类型  t是营业网点   q是关键字  i是搜索结果的页数

预览中也是正常的json数据

我们复制请求网址到浏览器,可以发现正常返回的json格式数据,这意味着我们可以通过python模拟访问这样的网址,从而获取到该请求返回的json数据,通过进一步筛选得到我们想要的数据。 

实现

 先写出获取页面的代码,使用json库转换为python的dict字典对象

  1. import requests #网络请求
  2. from bs4 import BeautifulSoup #页面解析
  3. import json
  4. from jsonpath import jsonpath
  5. url = "https://app.abchina.com/branch/common/BranchService.svc/Branch?p=110000&c=110100&b=110101&q=&t=1&z=0&i=0"
  6. # p是省 c是市 b是区县 z是业务类型 t是营业网点 q是关键字 i是搜索结果的页数
  7. headers = {"user-agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36"}
  8. req = requests.get(url=url,headers=headers) #访问的地址,
  9. req.encoding='utf-8'
  10. txt = req.text#txt是str类型
  11. # print(txt+"\n")
  12. txt1 = json.loads(txt)
  13. print(txt1)#loads转成字典类型

在一级目录下,能看到一个叫TotalCount的属性,和BranchSearchRests里的数据进行比对,可以知道这个就是当前区县所拥有的A银行数量,也就是北京市东城区的A银行数量。我们要的就是这个,但还不够,我们还需要其他的属性,用于我们后面的处理。 

打开BranchSearchRests,可以看到几个主要的数据,BroughId,CityId,ProvinceId,Name, 也就是我上面说的,对应到请求网址中的,p是省 c是市 b是区县,我们需要得到对应的Id,对他们逐个进行循环,就能够得到每一个区县的准确请求网址。对这些网址进行模拟访问,就能够得到每个区县的A银行数量。

 因为没有用过jsonpath,所以查了一下用法,参考Python3中JsonPath用法 – 漂泊的小虎 – 博客园

于是我们可以先输出一下看看是否能获取到准确数据,以Name和Address为例

  1. list_name = jsonpath(txt1, '$..BranchSearchRests[*].BranchBank.Name')
  2. print(list_name)
  3. list_adress = jsonpath(txt1, '$..BranchSearchRests[*].BranchBank.Address')
  4. print(list_adress)

 

回到F12网络页面,能发现初始页面还有其他其他的请求

 里面是关于的Id和名称,我们需要它出现在循环里面,所以将它拿下来。

  1. url2="https://app.abchina.com/branch/common/BranchService.svc/District"
  2. headers={"user-agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36"}
  3. req=requests.get(url=url2,headers=headers) #访问的地址,
  4. req.encoding='utf-8'
  5. txt=req.text#txt是双引号
  6. txt1 = json.loads(txt)
  7. # print(txt1)
  8. list_province_id = jsonpath(txt1, '$..Id')
  9. list_province_name = jsonpath(txt1, '$..Name')
  10. print(list_province_id)
  11. print(list_province_name)
  12. print(type(list_province_id[0]))

 此时,list_province_id列表里是str类型,为方便后面测试,先改成int型,需要的时候再转换成str。转换方法为新建一个列表,遍历原列表并强转然后赋给新列表,

  1. list_num_province_id = list(int(x) for x in list_province_id)
  2. print(type(list_num_province_id[0]))

 

这样我们就可以开始对省市进行循环了,如下

  1. url = "https://app.abchina.com/branch/common/BranchService.svc/District"
  2. try:
  3. headers = {
  4. "user-agent":
  5. "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36"
  6. }
  7. r = requests.get(url, headers)
  8. r.encoding = 'utf-8'
  9. txt = r.text #txt是双引号
  10. txt1 = json.loads(txt)
  11. # print(txt1)
  12. list_province_id = jsonpath(txt1, '$..Id')
  13. list_num_province_id = list(int(x) for x in list_province_id)
  14. # 省市列表
  15. print(list_num_province_id)
  16. #对省市循环
  17. for province_id in list_num_province_id:
  18. # 这里用来测试,遍历到13000时候就先停止
  19. if (province_id > 130000):
  20. break
  21. # 拼接成地市的链接
  22. url1 = "https://app.abchina.com/branch/common/BranchService.svc/District/" + str(
  23. province_id)
  24. print(url1)
  25. r = requests.get(url1, headers)
  26. r.encoding = 'utf-8'
  27. txt = r.text #txt是双引号
  28. txt1= json.loads(txt)
  29. print(txt1)
  30. except:
  31. print("爬取失败")

 可以看到,对省市的链接进行访问,可以得到地市的id与name,那么继续使用地市id就可以获得区县的id与name。同样,对地市使用jsonpath获取id,转换成int,做循环。

  1. url = "https://app.abchina.com/branch/common/BranchService.svc/District"
  2. # try:
  3. headers = {
  4. "user-agent":
  5. "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36"
  6. }
  7. r = requests.get(url, headers)
  8. r.encoding = 'utf-8'
  9. txt = r.text #txt是双引号
  10. txt1 = json.loads(txt)
  11. # print(txt1)
  12. list_province_id = jsonpath(txt1, '$..Id')
  13. list_num_province_id = list(int(x) for x in list_province_id)
  14. # 省市列表
  15. print(list_num_province_id)
  16. #对省市循环
  17. for province_id in list_num_province_id:
  18. # 这里用来测试,遍历到13000时候就先停止
  19. if (province_id > 130000):
  20. break
  21. url1 = "https://app.abchina.com/branch/common/BranchService.svc/District/" + str(
  22. province_id)
  23. # print(url1)
  24. r = requests.get(url1, headers)
  25. r.encoding = 'utf-8'
  26. txt = r.text #txt是双引号
  27. txt1= json.loads(txt)
  28. print(txt1)
  29. list_city_id = jsonpath(txt1, '$..Id')
  30. list_num_city_id = list(int(x) for x in list_city_id)
  31. # 对地市循环
  32. for district_id in list_num_city_id:
  33. url2 = "https://app.abchina.com/branch/common/BranchService.svc/District/any/" + str(
  34. district_id)
  35. print(url2)
  36. r = requests.get(url2, headers)
  37. r.encoding = 'utf-8'
  38. txt = r.text #txt是双引号
  39. txt1 = json.loads(txt)
  40. print(txt1)

 地址和获取到的数据都正常,继续使用jsonpath选取id和name:
list_district_id = jsonpath(txt1, ‘$..Id‘)
list_district_name = jsonpath(txt1, ‘$..Name‘),
然后循环内进行拼接
url3 = “https://app.abchina.com/branch/common/BranchService.svc/Branch?p=” +
          str(province_id) + “&c=” + str(district_id) + “&b=” + str(item2[0]) + “&q=&t=1&z=0&i=0”

这里的循环使用zip()函数zip()函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的对象,这样做的好处是节约了不少的内存。

  1. import requests #网络请求
  2. from bs4 import BeautifulSoup #页面解析
  3. import pandas as pd #保存数据
  4. import time
  5. import json
  6. from jsonpath import jsonpath
  7. # import urllib
  8. url = "https://app.abchina.com/branch/common/BranchService.svc/District"
  9. headers = {
  10. "user-agent":
  11. "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36"
  12. }
  13. r = requests.get(url, headers)
  14. r.encoding = 'utf-8'
  15. txt = r.text #txt是双引号
  16. txt1 = json.loads(txt)
  17. # print(txt1)
  18. list_province_id = jsonpath(txt1, '$..Id')
  19. list_num_province_id = list(int(x) for x in list_province_id)
  20. # 省市列表
  21. print(list_num_province_id)
  22. #对省市循环
  23. for province_id in list_num_province_id:
  24. # 这里用来测试,遍历到13000时候就先停止
  25. if (province_id > 130000):
  26. break
  27. url1 = "https://app.abchina.com/branch/common/BranchService.svc/District/" + str(
  28. province_id)
  29. # print(url1)
  30. r = requests.get(url1, headers)
  31. r.encoding = 'utf-8'
  32. txt = r.text #txt是双引号
  33. txt1= json.loads(txt)
  34. print(txt1)
  35. list_city_id = jsonpath(txt1, '$..Id')
  36. print("市区id")
  37. list_num_city_id = list(int(x) for x in list_city_id)
  38. print(list_num_city_id)
  39. # 对地市循环
  40. for district_id in list_num_city_id:
  41. url2 = "https://app.abchina.com/branch/common/BranchService.svc/District/any/" + str(
  42. district_id)
  43. print(url2)
  44. r = requests.get(url2, headers)
  45. r.encoding = 'utf-8'
  46. txt = r.text #txt是双引号
  47. txt1 = json.loads(txt)
  48. # print(txt1)
  49. list_district_id = jsonpath(txt1, '$..Id')
  50. list_district_name = jsonpath(txt1, '$..Name')
  51. # print(list_district_id)
  52. print("市区名称:")
  53. print(list_district_name)
  54. list_num_district_id = list(int(x) for x in list_district_id)
  55. # 市区列表
  56. print("市区ID:")
  57. print(list_num_district_id)
  58. # Branch?p=110000&c=110100&b=110101&q=&t=1&z=0&i=0
  59. # 对市区循环
  60. # zip同时对id和name进行遍历,item2[0]表示选取参数中第0个的参数(id),[1]就代表第二个参数(name)
  61. for item2 in zip(list_num_district_id, list_district_name):
  62. url3 = "https://app.abchina.com/branch/common/BranchService.svc/Branch?p=" + str(province_id) + "&c=" + str(district_id) + "&b=" + str(item2[0]) + "&q=&t=1&z=0&i=0"
  63. # print("url3="+url3)
  64. r = requests.get(url3, headers)
  65. r.encoding = 'utf-8'
  66. txt = r.text #txt是双引号
  67. txt1= json.loads(txt)
  68. # 获取该市区的银行数量
  69. totalcount = jsonpath(txt1, '$.TotalCount')
  70. # 拼接市区名字和数量输出
  71. print(item2[1] + "_" + str(totalcount))
  72. # 市区循环完后,对地市+100,直到地市也循环完
  73. district_id = district_id + 100
  74. # 做一个延时,降低连续爬取对服务器的压力
  75. time.sleep(1)

 运行之后可以看到我们要的数据了。但是代码仍然是有问题的,当我们爬取到海南省的时候,会发现报错了,原因在地市循环的时候,有些地市实际上是空的。他的 b为-1。

 

 

这时候,我们就需要加一个判断,当它为空时就拼接-1,不为空就正常拼接.

  1. import requests #网络请求
  2. from bs4 import BeautifulSoup #页面解析
  3. import pandas as pd #保存数据
  4. import time
  5. import json
  6. from jsonpath import jsonpath
  7. # import urllib
  8. url = "https://app.abchina.com/branch/common/BranchService.svc/District"
  9. headers = {
  10. "user-agent":
  11. "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36"
  12. }
  13. r = requests.get(url, headers)
  14. r.encoding = 'utf-8'
  15. txt = r.text #txt是双引号
  16. txt1 = json.loads(txt)
  17. # print(txt1)
  18. list_province_id = jsonpath(txt1, '$..Id')
  19. list_num_province_id = list(int(x) for x in list_province_id)
  20. # 省市列表
  21. print(list_num_province_id)
  22. #对省市循环
  23. for province_id in list_num_province_id:
  24. # 这里用来测试,
  25. # if(province_id < 460000 or province_id > 510000):
  26. # continue
  27. url1 = "https://app.abchina.com/branch/common/BranchService.svc/District/" + str(
  28. province_id)
  29. # print(url1)
  30. r = requests.get(url1, headers)
  31. r.encoding = 'utf-8'
  32. txt = r.text #txt是双引号
  33. txt1= json.loads(txt)
  34. print(txt1)
  35. list_city_id = jsonpath(txt1, '$..Id')
  36. print("地市id")
  37. list_num_city_id = list(int(x) for x in list_city_id)
  38. print(list_num_city_id)
  39. print("地市name")
  40. list_city_name = jsonpath(txt1, '$..Name')
  41. # 对地市循环
  42. for district in zip(list_num_city_id,list_city_name):
  43. district_id = district[0]
  44. district_name = district[1]
  45. url2 = "https://app.abchina.com/branch/common/BranchService.svc/District/any/" + str(
  46. district_id)
  47. #print("url2 = "+url2)
  48. r = requests.get(url2, headers)
  49. r.encoding = 'utf-8'
  50. txt = r.text #txt是双引号
  51. txt1 = json.loads(txt)
  52. list_district_id = jsonpath(txt1, '$..Id')
  53. list_district_name = jsonpath(txt1, '$..Name')
  54. # 如果该地市为空(例如海南三沙市)
  55. if (list_district_id == False):
  56. b = -1
  57. url3 = "https://app.abchina.com/branch/common/BranchService.svc/Branch?p=" + str(
  58. province_id) + "&c=" + str(district_id) + "&b=" + str(
  59. b) + "&q=&t=1&z=0&i=0"
  60. #print("url3="+url3)
  61. r = requests.get(url3, headers)
  62. r.encoding = 'utf-8'
  63. txt = r.text #txt是双引号
  64. txt1 = json.loads(txt)
  65. totalcount = jsonpath(txt1, '$.TotalCount')
  66. this_city = jsonpath(txt1, '$...City')
  67. print(str(district_name) + str(this_city) + "_" + str(totalcount))
  68. district_id = district_id + 100
  69. else:
  70. print("市区名称:")
  71. print(list_district_name)
  72. list_num_district_id = list(int(x) for x in list_district_id)
  73. # 市区列表
  74. print("市区ID:")
  75. print(list_num_district_id)
  76. # Branch?p=110000&c=110100&b=110101&q=&t=1&z=0&i=0
  77. # 对市区循环
  78. # zip同时对id和name进行遍历
  79. for item2 in zip(list_num_district_id, list_district_name):
  80. url3 = "https://app.abchina.com/branch/common/BranchService.svc/Branch?p=" + str(province_id) + "&c=" + str(district_id) + "&b=" + str(item2[0]) + "&q=&t=1&z=0&i=0"
  81. print("url3="+url3)
  82. r = requests.get(url3, headers)
  83. r.encoding = 'utf-8'
  84. txt = r.text #txt是双引号
  85. txt1= json.loads(txt)
  86. # 获取该市区的银行数量
  87. totalcount = jsonpath(txt1, '$.TotalCount')
  88. # 拼接市区名字和数量输出
  89. print(str(district_name) + "_" + item2[1] + "_" + str(totalcount))
  90. # 市区循环完后,对地市+100,直到地市也循环完
  91. district_id = district_id + 100
  92. # 做一个延时,降低连续爬取对服务器的压力
  93. time.sleep(1)

 至此,A银行的每一个地区的银行数量都爬取完毕。

================================分界线====================================

B银行

分析

实现