数据分析 行政区划实例

数据分析 行政区划实例

​ 这是在我学习完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()



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值