数据分析 行政区划实例
这是在我学习完requests后,我老师第一个让我去写的实例(爬虫),从我师姐那得知她也是从行政区划开始的。在这里和分享一下我写的代码,欢迎大家讨论和指出不足。我会从最开始拿到任务开始,到逐步实现各个模块进行梳理。(我也是小菜鸟,会尽量用大白话来整理,方便大家理解)
任务目标
基于python编程,把数据库中地址信息通过 WebService API|腾讯地址服务 批量爬虫经纬度信息,并将得到的经纬度插入到数据库的表中。
步骤:
1.把数据从数据库中提取出来
2.将地图中的address处理成要求的详细地址
3.爬虫,得到经纬度信息
4.把得到了的信息插入数据库中
5.主函数
6.优化代码
了解任务以后,我们正式开始叭~
数据库展示
初始数据库的结构如图所示:
目前数据库中有的数据列有:id,area_code,address,type,待输入的列为:lng,lat。从表的结构我们可以发现,我们需要的id,area_code,address列均不能为null(空值),这也让我们能省略数据预处理过程。
连接数据库
def data_pre():
# 打开数据库连接
db = pymysql.connect("localhost", "root", "mysql123456789", "BASEONE")
#localhost:本地主机,root:用户名,mysql123456789:密码,BASEONE:要连接的数据库名
# 使用 cursor() 方法创建一个游标对象 cursor
cursor = db.cursor()
try:
cursor.execute("SELECT area_code,address FROM ddb_area GROUP BY area_code,address")#执行select语句
results = cursor.fetchall()
#type(results):tuple 比如((520000000000, '贵州省'),)
#但是我们需要的一个字典{'area_code':'address'}
#方法一 使用列表生成式
res = {x[0]: x[1] for x in results}
#方法二 直接生成
temp = {}
for r in results:
temp.update({r[0]: r[1]})
#最后的结果都为{520000000000: '贵州省',}
except Exception as e:
pass
finally:
db.close()
return res
#try:正常处理(一般把比较容易发生错误或异常的语句写进try结构中,比如这里的查询和生成字典部分)
#except:try语句中发生异常走这一部分,pass表示我不理你的异常
#finally:不管怎样,最后都得走这一部分(这里让它关掉数据库连接)
转换地图信息
为什么要做这一个部分呢,是因为根据现在的数据的特点。area_code,address中的数据包含了镇,以及居委会的信息,但address并不是我们要的详细地址。给大家举个例子就清楚了。如果大家的数据直接是标准的详细地址,跳过这部分就行了。
拿我家来举例:
我们可以看到address数据列中没有省,市,县的信息。但要求的信息中这一部分是必须包含的。这一部分做的就是这件事。查看我们现有的数据,可以通过area_code来实现我们的目的。
#传入两个参数,key为建立的字典的key键(area_code),info就是建立的字典
def areas(key, info):
kk = ''
try:
#省,市,县的经纬度不是我们这次实例的内容,我们是查询镇,居委会的经纬度信息
if (key % 1000) != 0:#居委会
kk = info.get(int(key / 10000000000) * 10000000000) + info.get(
int(key / 100000000) * 100000000) + info.get(int(key / 1000000) * 1000000) + info.get(
int(key / 1000) * 1000) + info.get(key)
elif (key % 1000) == 0 and (int(key / 1000) % 1000) != 0:#镇
kk = info.get(int(key / 10000000000) * 10000000000) + info.get(
int(key / 100000000) * 100000000) + info.get(int(key / 1000000) * 1000000) + info.get(key)
else:
kk = '不用查询'
except Exception as e:
logger.error('Failed to continue', exc_info=True)
pass
finally:
return kk
这一部门可能看着有点绕,我简单阐述一下原理吧。(其实这部分也是老师给了我思路)
得到对应的编码后代人我们上面得到的字典(字典中key为are_code,value为address),info.get()方法得到对应的地址。通过这种方式得到我家的详细地址:贵州省遵义市余庆县龙溪镇(ps:欢迎来贵州玩啊,美食美景美女特别多)
爬虫
获取网页信息
def getHTMLText(url):
maxTryNum = 20
for tries in range(maxTryNum):
try:
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36',
'Referer': 'https://lbs.qq.com/service/webService/webServiceGuide/webServiceGeocoder',
'Host': 'apis.map.qq.com'
}
response = requests.get(url, headers=headers, timeout=3)
return response.text
except:
logger.error('Failed to gethtml', exc_info=True)
time.sleep(random.random() * 3)
if tries < (maxTryNum - 1):
continue
else:
print("Has tried %d times to access url %s, all failed!" % (maxTryNum, url))
break
#拼接一下字符串,转成标准样式
url = 'https://apis.map.qq.com/ws/geocoder/v1/?address=' + location + '&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77'
如果大家学习过requests模块的话,应该能轻松理解try部分的内容,其中‘User-Agent’,‘Referer’,'Host’爬虫头部是需要的,某些网站还会加入其他头部字段,这里这三个就可以认证了。except部分是我踩雷后修改的部分,大部分网站都会反爬虫,如果我们完全按照程序去连接,会让服务器知道我们是机器爬虫,所以设置一个自动休眠,即time.sleep(random.random() * 3),来模拟人为操作。
获取经纬度并插入数据库
向服务器发送请求后,服务器会返回一个json数据。我们需要json数据转换成字典,来读取其中的经纬度字段。
r = getHTMLText(url)
data = json.loads(r)
#经纬度我写成了两个函数,也可以写成一个
def lng_area(data):
try:
lng = data['result']['location']['lng']
return lng
except Exception as e:
pass
return None
def lat_area(data):
try:
lat = data['result']['location']['lat']
return lat
except Exception as e:
pass
return None
展示一下返回数据的样式:
所以我们能得到需要的经纬度信息,不过这部分代码可以再优化一下。这个网站返回的格式很标准,但如果不是很标准(比如缺少了location字段)该怎么做呢,下面代码优化部分会进行讨论。
num_lng = lng_area(data)
num_lat = lat_area(data)
插入数据:
def check_data(key):
db = pymysql.connect("localhost", "root", "mysql123456789", "BASEONE")
try:
# 使用 cursor() 方法创建一个游标对象 cursor
cursor = db.cursor()
sql_check = "select count(1) from ddb_area where lng is NULL or lat is NULL and area_code=%s" % (key)
if cursor.execute(sql_check):
return False
else:
return True
except Exception as e:
pass
finally:
db.close()
主函数
主函数就直接来叭,把上面的部分结合起来
def main():
logger_error()
try:
info = data_pre()
global key
for key in info:
try:
location = areas(key, info)
a = check_data(key)
if location == '不用查询' or a:
continue
else:
url = 'https://apis.map.qq.com/ws/geocoder/v1/?address=' + location + '&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77'
r = getHTMLText(url)
data = json.loads(r)
num_lng = lng_area(data)
num_lat = lat_area(data)
insert_area1(num_lng, num_lat, key)
except Exception as e:
pass
except Exception as e:
pass
if __name__ == '__main__':
main()
代码优化
加入日志记录函数
def logger_error():
# 创建一个logger
global logger
logger = logging.getLogger()
logger.setLevel(logging.INFO) # Log等级总开关
# 创建一个handler,用于写入日志文件
rq = time.strftime('%Y%m%d%H%M', time.localtime(time.time()))
log_path = 'C:/Users/BBD' + '/Logs/'
log_name = log_path + rq + '.log'
logfile = log_name
fh = logging.FileHandler(logfile, mode='w')
fh.setLevel(logging.DEBUG) # 输出到file的log等级的开关
# 定义handler的输出格式
formatter = logging.Formatter("%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s")
fh.setFormatter(formatter)
logger.addHandler(fh)
日志能帮我记录我们需要的问题,并把问题记录下来,方便我们去排查问题。在except中加入日志记录函数。
0 and 1方法
0 and 1
#输出 0
1 and 0
#输出 0
1 and 1
#输出 1
可以通过这种方式提取网页返回数据的经纬度,避免因为缺少字段而报错(没有的话返回0)
lat = (data['result'] and data['result']['location']) and data['result']['location']['lat']
lng = (data['result'] and data['result']['location']) and data['result']['location']['lng']
完整代码
import requests
import json
import pymysql
import time
import random
import os.path
import logging
def logger_error():
# 创建一个logger
global logger
logger = logging.getLogger()
logger.setLevel(logging.INFO) # Log等级总开关
# 创建一个handler,用于写入日志文件
rq = time.strftime('%Y%m%d%H%M', time.localtime(time.time()))
log_path = 'C:/Users/BBD' + '/Logs/'
log_name = log_path + rq + '.log'
logfile = log_name
fh = logging.FileHandler(logfile, mode='w')
fh.setLevel(logging.DEBUG) # 输出到file的log等级的开关
# 定义handler的输出格式
formatter = logging.Formatter("%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s")
fh.setFormatter(formatter)
logger.addHandler(fh)
def data_pre():
# 打开数据库连接
db = pymysql.connect("localhost", "root", "mysql123456789", "BASEONE")
try:
# 使用 cursor() 方法创建一个游标对象 cursor
cursor = db.cursor()
# 数据预处理
cursor.execute("SELECT area_code,address FROM ddb_area GROUP BY area_code,address")
results = cursor.fetchall()
# print(results)
temp = {}
for r in results:
temp.update({r[0]: r[1]})
# res = {}
# for x in results:
# res[x[0]] = x[1]
res = {x[0]: x[1] for x in results}
except Exception as e:
logger.error('Failed to continue', exc_info=True)
pass
finally:
db.close()
# return list_id, num_all, area_dict
return res
def areas(key, info):
kk = ''
try:
if (key % 1000) != 0:
kk = info.get(int(key / 10000000000) * 10000000000) + info.get(
int(key / 100000000) * 100000000) + info.get(int(key / 1000000) * 1000000) + info.get(
int(key / 1000) * 1000) + info.get(key)
elif (key % 1000) == 0 and (int(key / 1000) % 1000) != 0:
kk = info.get(int(key / 10000000000) * 10000000000) + info.get(
int(key / 100000000) * 100000000) + info.get(int(key / 1000000) * 1000000) + info.get(key)
else:
kk = '不用查询'
except Exception as e:
logger.error('Failed to continue', exc_info=True)
pass
finally:
return kk
def getHTMLText(url):
maxTryNum = 20
for tries in range(maxTryNum):
try:
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36',
'Referer': 'https://lbs.qq.com/service/webService/webServiceGuide/webServiceGeocoder',
'Host': 'apis.map.qq.com'
}
response = requests.get(url, headers=headers, timeout=3)
return response.text
except:
logger.error('Failed to gethtml', exc_info=True)
time.sleep(random.random() * 3)
if tries < (maxTryNum - 1):
continue
else:
print("Has tried %d times to access url %s, all failed!" % (maxTryNum, url))
break
def lng_area(data):
try:
lng = data['result']['location']['lng']
# lng = (data['result'] and data['result']['location']) and data['result']['location']['lng']
return lng
except Exception as e:
logger.error('Failed to find lng', exc_info=True)
pass
return None
def lat_area(data):
try:
lat = data['result']['location']['lat']
#lat = (data['result'] and data['result']['location']) and data['result']['location']['lat']
return lat
except Exception as e:
logger.error('Failed to find lat', exc_info=True)
pass
return None
def insert_area1(num_lng, num_lat, num):
db = pymysql.connect("localhost", "root", "mysql123456789", "BASEONE")
global cursor
cursor = db.cursor()
sql_insert = "UPDATE ddb_area SET lng=%s,lat=%s WHERE area_code=%s" % (num_lng, num_lat, num)
try:
cursor.execute(sql_insert)
db.commit()
except Exception as e:
logger.error('Failed to undate date', exc_info=True)
pass
finally:
cursor.close()
db.commit()
db.close()
def check_data(key):
db = pymysql.connect("localhost", "root", "mysql123456789", "BASEONE")
try:
# 使用 cursor() 方法创建一个游标对象 cursor
cursor = db.cursor()
sql_check = "select count(1) from ddb_area where lng is NULL or lat is NULL and area_code=%s" % (key)
if cursor.execute(sql_check):
return False
else:
return True
except Exception as e:
logger.error('Failed to check', exc_info=True)
pass
finally:
db.close()
def main():
logger_error()
try:
info = data_pre()
global key
for key in info:
try:
location = areas(key, info)
a = check_data(key)
if location == '不用查询' or a:
continue
else:
url = 'https://apis.map.qq.com/ws/geocoder/v1/?address=' + location + '&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77'
r = getHTMLText(url)
data = json.loads(r)
num_lng = lng_area(data)
num_lat = lat_area(data)
insert_area1(num_lng, num_lat, key)
except Exception as e:
logger.error('Failed to continue', exc_info=True)
pass
except Exception as e:
logger.error('Failed to continue', exc_info=True)
pass
if __name__ == '__main__':
main()