任务如下:
爬取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字典对象
- import requests #网络请求
- from bs4 import BeautifulSoup #页面解析
-
- import json
- from jsonpath import jsonpath
-
- url = "https://app.abchina.com/branch/common/BranchService.svc/Branch?p=110000&c=110100&b=110101&q=&t=1&z=0&i=0"
- # p是省 c是市 b是区县 z是业务类型 t是营业网点 q是关键字 i是搜索结果的页数
- 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"}
- req = requests.get(url=url,headers=headers) #访问的地址,
- req.encoding='utf-8'
- txt = req.text#txt是str类型
- # print(txt+"\n")
-
- txt1 = json.loads(txt)
- print(txt1)#loads转成字典类型
在一级目录下,能看到一个叫TotalCount的属性,和BranchSearchRests里的数据进行比对,可以知道这个就是当前区县所拥有的A银行数量,也就是北京市东城区的A银行数量。我们要的就是这个,但还不够,我们还需要其他的属性,用于我们后面的处理。
打开BranchSearchRests,可以看到几个主要的数据,BroughId,CityId,ProvinceId,Name, 也就是我上面说的,对应到请求网址中的,p是省 c是市 b是区县,我们需要得到对应的Id,对他们逐个进行循环,就能够得到每一个区县的准确请求网址。对这些网址进行模拟访问,就能够得到每个区县的A银行数量。
因为没有用过jsonpath,所以查了一下用法,参考Python3中JsonPath用法 – 漂泊的小虎 – 博客园
于是我们可以先输出一下看看是否能获取到准确数据,以Name和Address为例
- list_name = jsonpath(txt1, '$..BranchSearchRests[*].BranchBank.Name')
- print(list_name)
-
- list_adress = jsonpath(txt1, '$..BranchSearchRests[*].BranchBank.Address')
- print(list_adress)
回到F12网络页面,能发现初始页面还有其他其他的请求
里面是关于省的Id和名称,我们需要它出现在循环里面,所以将它拿下来。
- url2="https://app.abchina.com/branch/common/BranchService.svc/District"
- 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"}
- req=requests.get(url=url2,headers=headers) #访问的地址,
- req.encoding='utf-8'
- txt=req.text#txt是双引号
- txt1 = json.loads(txt)
- # print(txt1)
- list_province_id = jsonpath(txt1, '$..Id')
- list_province_name = jsonpath(txt1, '$..Name')
- print(list_province_id)
- print(list_province_name)
- print(type(list_province_id[0]))
此时,list_province_id列表里是str类型,为方便后面测试,先改成int型,需要的时候再转换成str。转换方法为新建一个列表,遍历原列表并强转然后赋给新列表,
- list_num_province_id = list(int(x) for x in list_province_id)
- print(type(list_num_province_id[0]))
这样我们就可以开始对省市进行循环了,如下
- url = "https://app.abchina.com/branch/common/BranchService.svc/District"
- try:
- 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"
- }
- r = requests.get(url, headers)
- r.encoding = 'utf-8'
- txt = r.text #txt是双引号
- txt1 = json.loads(txt)
- # print(txt1)
- list_province_id = jsonpath(txt1, '$..Id')
- list_num_province_id = list(int(x) for x in list_province_id)
- # 省市列表
- print(list_num_province_id)
-
- #对省市循环
- for province_id in list_num_province_id:
- # 这里用来测试,遍历到13000时候就先停止
- if (province_id > 130000):
- break
- # 拼接成地市的链接
- url1 = "https://app.abchina.com/branch/common/BranchService.svc/District/" + str(
- province_id)
- print(url1)
- r = requests.get(url1, headers)
- r.encoding = 'utf-8'
- txt = r.text #txt是双引号
- txt1= json.loads(txt)
- print(txt1)
- except:
- print("爬取失败")
可以看到,对省市的链接进行访问,可以得到地市的id与name,那么继续使用地市id就可以获得区县的id与name。同样,对地市使用jsonpath获取id,转换成int,做循环。
- url = "https://app.abchina.com/branch/common/BranchService.svc/District"
- # try:
- 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"
- }
- r = requests.get(url, headers)
- r.encoding = 'utf-8'
- txt = r.text #txt是双引号
- txt1 = json.loads(txt)
- # print(txt1)
- list_province_id = jsonpath(txt1, '$..Id')
- list_num_province_id = list(int(x) for x in list_province_id)
- # 省市列表
- print(list_num_province_id)
-
- #对省市循环
- for province_id in list_num_province_id:
- # 这里用来测试,遍历到13000时候就先停止
- if (province_id > 130000):
- break
- url1 = "https://app.abchina.com/branch/common/BranchService.svc/District/" + str(
- province_id)
- # print(url1)
- r = requests.get(url1, headers)
- r.encoding = 'utf-8'
- txt = r.text #txt是双引号
- txt1= json.loads(txt)
- print(txt1)
- list_city_id = jsonpath(txt1, '$..Id')
- list_num_city_id = list(int(x) for x in list_city_id)
-
- # 对地市循环
- for district_id in list_num_city_id:
- url2 = "https://app.abchina.com/branch/common/BranchService.svc/District/any/" + str(
- district_id)
- print(url2)
- r = requests.get(url2, headers)
- r.encoding = 'utf-8'
- txt = r.text #txt是双引号
- txt1 = json.loads(txt)
- 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()函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的对象,这样做的好处是节约了不少的内存。
- import requests #网络请求
- from bs4 import BeautifulSoup #页面解析
- import pandas as pd #保存数据
- import time
- import json
- from jsonpath import jsonpath
-
- # import urllib
- url = "https://app.abchina.com/branch/common/BranchService.svc/District"
-
- 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"
- }
- r = requests.get(url, headers)
- r.encoding = 'utf-8'
- txt = r.text #txt是双引号
- txt1 = json.loads(txt)
- # print(txt1)
- list_province_id = jsonpath(txt1, '$..Id')
- list_num_province_id = list(int(x) for x in list_province_id)
- # 省市列表
- print(list_num_province_id)
-
- #对省市循环
- for province_id in list_num_province_id:
- # 这里用来测试,遍历到13000时候就先停止
- if (province_id > 130000):
- break
- url1 = "https://app.abchina.com/branch/common/BranchService.svc/District/" + str(
- province_id)
- # print(url1)
- r = requests.get(url1, headers)
- r.encoding = 'utf-8'
- txt = r.text #txt是双引号
- txt1= json.loads(txt)
- print(txt1)
- list_city_id = jsonpath(txt1, '$..Id')
- print("市区id")
- list_num_city_id = list(int(x) for x in list_city_id)
- print(list_num_city_id)
- # 对地市循环
- for district_id in list_num_city_id:
- url2 = "https://app.abchina.com/branch/common/BranchService.svc/District/any/" + str(
- district_id)
- print(url2)
- r = requests.get(url2, headers)
- r.encoding = 'utf-8'
- txt = r.text #txt是双引号
- txt1 = json.loads(txt)
- # print(txt1)
- list_district_id = jsonpath(txt1, '$..Id')
- list_district_name = jsonpath(txt1, '$..Name')
- # print(list_district_id)
- print("市区名称:")
- print(list_district_name)
- list_num_district_id = list(int(x) for x in list_district_id)
-
- # 市区列表
- print("市区ID:")
- print(list_num_district_id)
-
- # Branch?p=110000&c=110100&b=110101&q=&t=1&z=0&i=0
- # 对市区循环
- # zip同时对id和name进行遍历,item2[0]表示选取参数中第0个的参数(id),[1]就代表第二个参数(name)
- for item2 in zip(list_num_district_id, list_district_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"
- # print("url3="+url3)
- r = requests.get(url3, headers)
- r.encoding = 'utf-8'
- txt = r.text #txt是双引号
- txt1= json.loads(txt)
- # 获取该市区的银行数量
- totalcount = jsonpath(txt1, '$.TotalCount')
- # 拼接市区名字和数量输出
- print(item2[1] + "_" + str(totalcount))
- # 市区循环完后,对地市+100,直到地市也循环完
- district_id = district_id + 100
- # 做一个延时,降低连续爬取对服务器的压力
- time.sleep(1)
运行之后可以看到我们要的数据了。但是代码仍然是有问题的,当我们爬取到海南省的时候,会发现报错了,原因在地市循环的时候,有些地市实际上是空的。他的 b为-1。
这时候,我们就需要加一个判断,当它为空时就拼接-1,不为空就正常拼接.
- import requests #网络请求
- from bs4 import BeautifulSoup #页面解析
- import pandas as pd #保存数据
- import time
- import json
- from jsonpath import jsonpath
-
- # import urllib
- url = "https://app.abchina.com/branch/common/BranchService.svc/District"
-
- 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"
- }
- r = requests.get(url, headers)
- r.encoding = 'utf-8'
- txt = r.text #txt是双引号
- txt1 = json.loads(txt)
- # print(txt1)
- list_province_id = jsonpath(txt1, '$..Id')
- list_num_province_id = list(int(x) for x in list_province_id)
- # 省市列表
- print(list_num_province_id)
-
- #对省市循环
- for province_id in list_num_province_id:
- # 这里用来测试,
- # if(province_id < 460000 or province_id > 510000):
- # continue
- url1 = "https://app.abchina.com/branch/common/BranchService.svc/District/" + str(
- province_id)
- # print(url1)
- r = requests.get(url1, headers)
- r.encoding = 'utf-8'
- txt = r.text #txt是双引号
- txt1= json.loads(txt)
- print(txt1)
- list_city_id = jsonpath(txt1, '$..Id')
- print("地市id")
- list_num_city_id = list(int(x) for x in list_city_id)
- print(list_num_city_id)
- print("地市name")
- list_city_name = jsonpath(txt1, '$..Name')
-
- # 对地市循环
- for district in zip(list_num_city_id,list_city_name):
- district_id = district[0]
- district_name = district[1]
- url2 = "https://app.abchina.com/branch/common/BranchService.svc/District/any/" + str(
- district_id)
- #print("url2 = "+url2)
- r = requests.get(url2, headers)
- r.encoding = 'utf-8'
- txt = r.text #txt是双引号
- txt1 = json.loads(txt)
- list_district_id = jsonpath(txt1, '$..Id')
- list_district_name = jsonpath(txt1, '$..Name')
- # 如果该地市为空(例如海南三沙市)
- if (list_district_id == False):
- b = -1
- url3 = "https://app.abchina.com/branch/common/BranchService.svc/Branch?p=" + str(
- province_id) + "&c=" + str(district_id) + "&b=" + str(
- b) + "&q=&t=1&z=0&i=0"
- #print("url3="+url3)
- r = requests.get(url3, headers)
- r.encoding = 'utf-8'
- txt = r.text #txt是双引号
- txt1 = json.loads(txt)
- totalcount = jsonpath(txt1, '$.TotalCount')
- this_city = jsonpath(txt1, '$...City')
- print(str(district_name) + str(this_city) + "_" + str(totalcount))
- district_id = district_id + 100
- else:
- print("市区名称:")
- print(list_district_name)
- list_num_district_id = list(int(x) for x in list_district_id)
- # 市区列表
- print("市区ID:")
- print(list_num_district_id)
-
- # Branch?p=110000&c=110100&b=110101&q=&t=1&z=0&i=0
- # 对市区循环
- # zip同时对id和name进行遍历
- for item2 in zip(list_num_district_id, list_district_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"
- print("url3="+url3)
- r = requests.get(url3, headers)
- r.encoding = 'utf-8'
- txt = r.text #txt是双引号
- txt1= json.loads(txt)
- # 获取该市区的银行数量
- totalcount = jsonpath(txt1, '$.TotalCount')
- # 拼接市区名字和数量输出
- print(str(district_name) + "_" + item2[1] + "_" + str(totalcount))
- # 市区循环完后,对地市+100,直到地市也循环完
- district_id = district_id + 100
- # 做一个延时,降低连续爬取对服务器的压力
- time.sleep(1)
至此,A银行的每一个地区的银行数量都爬取完毕。
================================分界线====================================