GIS入门 | 如何通过腾讯 / 高德 / 百度地图API获取POI数据

🙇‍♀️对不起我是标题党,但是应该调用不同地图运营商API爬数据大同小异,大家看着不同运营商的开发文档做适当调整就好了~~~我这里就拿腾讯位置服务为例了。

😋爬虫前需要铺垫一些知识,这样可以帮助我们更好地理解代码以及爬虫的机制__其实我也是一知半解,因为最近的课设接触这方面,如果有说的不对请指正🙇‍♀️!而且代码是参考了往年的学姐的资料,学姐如果你发现你的代码被我抄袭了,请原谅我!!!私密马赛😭

目录

1.关于爬取的流程机制

2.关于额度

3.关于坐标系

4.代码及如何使用


1.关于爬取的流程机制

        接触爬虫前,经常会听到讲要“学习HTTP协议”、“考虑反爬虫措施”......🤔啥也不懂,我觉得也不用太懂,因为各种库会帮你解决这个问题,你会用代码以及大概知道代码中哪部分是完成这个工作就行了...(勿喷,我没有深究的志向)

        最最最重要的就是好好看开发文档,里面有说明必填的参数,以及有实际的例子。😇爬取数据可以抽象地理解为:把研究区域细分为圆形、矩形或是行政区县(不同的搜索方式),然后遍历这些细分的研究区域,每次生成新的URL爬取数据

        附上地点搜索开发文档:WebService API | 腾讯位置服务

        每次生成新的URL在代码中的体现(以矩形搜索为例):

url0 = 'https://apis.map.qq.com/ws/place/v1/search'
# 构造查询参数,包括区域边界、API密钥、搜索关键字等
    query = {
        'boundary': 'rectangle(' + str(data['Y_min'][i]) + ',' + str(data['X_min'][i]) + ',' +
                    str(data['Y_max'][i]) + ',' + str(data['X_max'][i]) + ')',
        'key': '',  # 填你的API密钥
        'keyword': '厕所',  # 搜索关键字,这里为厕所
        'page_size': 20,  # 每页返回的最大地点数量
        'page_index': x  # 当前页码
    }

    # 拼接完整的请求URL
    url = url0 + '?' + urllib.parse.urlencode(query)

        而每次新的URL响应返回的消息如何保存呢?因为我没有很了解Python的数据结构🤡,抽象地理解就是要先定义一个字典(相当于定义数据库的“字段”),然后将爬取的数据填到这个字典中。在代码中是这样体现的:

# 初始化一个字典以存储从API返回的结果
res = {
    "id": [],  # 存储地点的ID
    "title": [],  # 存储地点的名称
    "address": [],  # 存储地点的地址
    "lng": [],  # 存储地点的经度
    "lat": []  # 存储地点的纬度
}

 # 将响应的JSON文本解析为Python字典
    response = json.loads(response.text)

    # 检查API返回的状态是否正常(status为0表示成功)
    if response.get('status') == 0:
        POIs = response.get('data')  # 获取地点数据
        for poi in POIs:
            # 将每个地点的信息添加到结果字典中
            res["id"].append(poi['id'])
            res["title"].append(poi['title'])
            res["address"].append(poi['address'])
            res["lng"].append(poi['location']['lng'])
            res["lat"].append(poi['location']['lat'])

           URL会返回的信息都可以爬取下来,详情请看开发文档2e47542e78ff4182b3fb56a22b478451.png

2.关于额度

        我发现其实身边很多人都对这个额度迷迷糊糊的,因此没有选择最佳的搜索方式,经常导致免费额度一下子就用完没有正确爬取某一区域的全部POI数据或者是重复爬取某一区域的POI数据✍

        经过这几天的调用,我自己抽象地理解了一下🐷✍。

        首先,API调用的额度,每天免费是200次。可以理解为生成新的URL发起请求的次数一天不超过200次。

41c492f708bb4a80950832fafdbecac4.png

        其次是“翻页”这个概念。 返回结果不是有一个“count”的数据嘛,是这个爬取区域内(这一一个区/格子/圆形/多边形)的POI数量。而每次调用返回一页的条目数最多为20条,那就得翻页呀!比如说,鼓楼区有30间厕所,第一页读取了20条,那就得再翻一页读剩下的10条。(每次翻页也算调用一次API哈😜)

96be17b61f1f4c1093d90aeeac717ab4.png        翻页的代码体现:

 # 计算总页数,并遍历剩余的页码以获取所有数据
        page = int(response.get('count') / 20) + 1
        for x in range(2, page + 1):  # 从第二页到最后一页
            run(i, x)  # 调用run函数获取更多数据

        还有细心的uu发现👍,在开发文档中 对于“count”属性的说明中,出现了“本服务限制最多返回200条数据”。这个就对选取哪种搜索方式、怎么细分研究区域有着至关重要的影响。举个例子就是,比如说爬取南京市的学校POI,实际有一千多所,但是如果你直接使用“城市搜索”,翻页爬完也只是撑死了给你返回200条数据(整个南京市只给你返回200所学校哦),那就爬漏了好多数据啊!✋所以,要根据实际情况选择搜索方式、细分研究区域!

        比如,你觉得南京市一个区的厕所不超过200间,那你就可以分区县来爬取~如果你觉得一个区内厕所超过200间,那你就用圆形*(存在无法完全覆盖的问题)、矩形搜索......,范围大小据实际情况调整。总而言之就是,能确保细分的研究范围内,POI个数不超过200条,就能确保没有遗漏。🔚✋

157ac9fbfae0476e868aba97363f7002.png

        所以,综上大家找到最好的“白嫖”方式了吗。——尽可能让细分的研究区域的POI数量接近200条但是又不要超过200条。 ✍所以每天按道理可以免费爬取的POI数量为20*200=4000条。(大佬们我有说错吗,我是这么理解的~)

3.关于坐标系

        众所周知,腾讯地图使用的是火星坐标系GCJ02,ArcGIS里面没有预定义这个坐标系,这样不方便我们后续的空间分析和制图(虽然坐标偏差应该不算太大)。总之希望做得更加精细的uu,可以完善这一步,后面我会提供GCJ02和WGS84互转的代码。🕵️‍♀️

        ArcGIS里划分的圆形/矩形/多边形,导出经纬度坐标,WGS84转为GCJ02再爬取。

        爬取得到的POI经纬度GCJ02转为WGS84,再导入ArcGIS进行后续处理。

4.代码及如何使用

坐标转换的代码参考了:GIS数据格式坐标转换(地球坐标WGS84、GCJ-02、火星坐标、百度坐标BD-09、国家大地坐标系CGCS2000)_gcj02坐标转换为cgcs 2000-CSDN博客

WGS84转GCJ02:(文件默认保存到项目目录下)

(因为我当时是用矩形搜索所以是两对经纬度~大家根据需求让AI修改就可以了)

import math  # 导入数学库,提供数学相关函数
import pandas as pd  # 导入pandas库,用于数据处理和分析
# 常数定义:圆周率的高精度值
PI = 3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280
# 将PI转换成角度的单位,计算出一个与3000度相关的值
x_PI = float(PI * float(3000.0) / float(180.0))
# 地球半径(单位:米)
aa = float(6378245.0)
# 地球偏心率平方
ee = 0.00669342162296594323
# 输入文件名
input_file = '填入你的excel.xls'
# 从Excel文件读取数据,并将其存储在DataFrame中
data = pd.read_excel(input_file)
# 从数据中提取最小和最大经纬度
lng_min = data['需要转换的经度1']  # 经度最小值
lng_max = data['需要转换的经度2']  # 经度最大值
lat_min = data['需要转换的纬度1']  # 纬度最小值
lat_max = data['需要转换的纬度2']  # 纬度最大值
# 定义一个函数,用来判断经纬度是否在中国大陆范围内
def out_of_china(lng, lat):
    # 经纬度分别处于中国的范围内时返回False,其他情况返回True
    if 73.66 < lng < 135.05 and 3.86 < lat < 53.55:
        return False
    return True  # 如果在范围外,返回True表示在中国以外
"""
  * 经纬度偏移转换
"""
# 定义针对纬度进行转换的函数
def transform_lat(lng, lat):
    # 计算纬度偏移量
    ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * math.sqrt(math.fabs(lng))
    # 添加正弦函数修正
    ret += (20.0 * math.sin(6.0 * lng * PI) + 20.0 * math.sin(2.0 * lng * PI)) * 2.0 / 3.0
    ret += (20.0 * math.sin(lat * PI) + 40.0 * math.sin(lat / 3.0 * PI)) * 2.0 / 3.0
    ret += (160.0 * math.sin(lat / 12.0 * PI) + 320 * math.sin(lat * PI / 30.0)) * 2.0 / 3.0
    return ret  # 返回纬度修正后的值
# 定义针对经度进行转换的函数
def transform_lng(lng, lat):
    # 计算经度偏移量
    ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * math.sqrt(math.fabs(lng))
    # 添加正弦函数修正
    ret += (20.0 * math.sin(6.0 * lng * PI) + 20.0 * math.sin(2.0 * lng * PI)) * 2.0 / 3.0
    ret += (20.0 * math.sin(lng * PI) + 40.0 * math.sin(lng / 3.0 * PI)) * 2.0 / 3.0
    ret += (150.0 * math.sin(lng / 12.0 * PI) + 300.0 * math.sin(lng / 30.0 * PI)) * 2.0 / 3.0
    return ret  # 返回经度修正后的值
# 定义WGS-84到GCJ-02坐标系的转换函数
def wgs84_to_gcj02(lng, lat):
    # 检查是否在中国大陆外
    if out_of_china(lng, lat):
        return [lng, lat]  # 如果在外面,返回原始经纬度
    else:
        # 计算偏移量
        dlat = transform_lat(lng - 105.0, lat - 35.0)
        dlng = transform_lng(lng - 105.0, lat - 35.0)
        # 将纬度转换为弧度
        radlat = lat / 180.0 * PI
        magic = math.sin(radlat)
        magic = 1 - ee * magic * magic  # 计算魔法数
        sqrtmagic = math.sqrt(magic)  # 计算魔法数的平方根
        # 计算修正后的纬度和经度
        dlat = (dlat * 180.0) / ((aa * (1 - ee)) / (magic * sqrtmagic) * PI)
        dlng = (dlng * 180.0) / (aa / sqrtmagic * math.cos(radlat) * PI)
        # 修正后的纬度和经度
        mglat = lat + dlat
        mglng = lng + dlng
        return [mglng, mglat]  # 返回修正后的经纬度
# 应用wgs84_to_gcj02函数,将最小纬度和最大纬度进行转换
data['gcj02_X_min'], data['gcj02_Y_min'] = zip(*[(wgs84_to_gcj02(lng, lat_min[i])) for i, lng in enumerate(lng_min)])
data['gcj02_X_max'], data['gcj02_Y_max'] = zip(*[(wgs84_to_gcj02(lng, lat_max[i])) for i, lng in enumerate(lng_max)])
# 输出文件名
output_file = 'wgs84_to_gcj02.csv'
# 将处理后的数据保存为CSV文件,不包含索引
data.to_csv(output_file, index=False)

GCJ02转WGS84:

import math  # 导入数学库,以使用数学函数
import pandas as pd  # 导入pandas库,以便处理Excel和数据框

# 定义常量PI
PI = 3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280
# x_PI用于经纬度转换中的计算,3000.0是一个常量,180.0用于角度转弧度
x_PI = float(PI * float(3000.0) / float(180.0))
# 椭球体半径和偏心率,用于地理坐标转换
aa = float(6378245.0)  # WGS-84椭球体的长半轴 (单位: 米)
ee = 0.00669342162296594323  # WGS-84椭球体的偏心率
# 输入Excel文件,包含地理坐标
input_file = 'poiall.xls'
data = pd.read_excel(input_file)  # 读取Excel文件数据


# 定义函数以判断坐标是否在中国境内
def out_of_china(lng, lat):
    # 经纬度范围是中国的边界
    if 73.66 < lng < 135.05 and 3.86 < lat < 53.55:
        return False  # 在中国境内,返回False
    return True  # 不在中国境内,返回True


"""
  * 经纬度偏移转换函数
"""


def transform_lat(lng, lat):
    # 根据给定的经纬度计算纬度的偏移量
    ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * math.sqrt(math.fabs(lng))
    ret += (20.0 * math.sin(6.0 * lng * PI) + 20.0 * math.sin(2.0 * lng * PI)) * 2.0 / 3.0
    ret += (20.0 * math.sin(lat * PI) + 40.0 * math.sin(lat / 3.0 * PI)) * 2.0 / 3.0
    ret += (160.0 * math.sin(lat / 12.0 * PI) + 320 * math.sin(lat * PI / 30.0)) * 2.0 / 3.0
    return ret  # 返回纬度偏移量


def transform_lng(lng, lat):
    # 根据给定的经纬度计算经度的偏移量
    ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * math.sqrt(math.fabs(lng))
    ret += (20.0 * math.sin(6.0 * lng * PI) + 20.0 * math.sin(2.0 * lng * PI)) * 2.0 / 3.0
    ret += (20.0 * math.sin(lng * PI) + 40.0 * math.sin(lng / 3.0 * PI)) * 2.0 / 3.0
    ret += (150.0 * math.sin(lng / 12.0 * PI) + 300.0 * math.sin(lng / 30.0 * PI)) * 2.0 / 3.0
    return ret  # 返回经度偏移量


def gcj02_to_wgs84(lng, lat):
    # 函数将GCJ-02坐标转换为WGS-84坐标
    if out_of_china(lng, lat):  # 判断是否在中国境外
        return [lng, lat]  # 如果在境外,直接返回原坐标
    else:
        # 计算偏移量
        dlat = transform_lat(lng - 105.0, lat - 35.0)
        dlng = transform_lng(lng - 105.0, lat - 35.0)

        # 将纬度转换为弧度
        radlat = lat / 180.0 * PI

        # 计算转换所需的中间变量
        magic = math.sin(radlat)
        magic = 1 - ee * magic * magic
        sqrtmagic = math.sqrt(magic)

        # 计算最终的纬度和经度转换值
        dlat = (dlat * 180.0) / ((aa * (1 - ee)) / (magic * sqrtmagic) * PI)
        dlng = (dlng * 180.0) / (aa / sqrtmagic * math.cos(radlat) * PI)

        mglat = lat + dlat  # 加上偏移后的纬度
        mglng = lng + dlng  # 加上偏移后的经度

        return [lng * 2 - mglng, lat * 2 - mglat]  # 返回WGS-84坐标


# 用于存储转换后的WGS-84坐标
wgs84_coordinates = []
# 遍历每一行数据,提取经纬度并转换
for index, row in data.iterrows():
    lng, lat = row['lng'], row['lat']  # 获取经纬度
    wgs84_lng, wgs84_lat = gcj02_to_wgs84(lng, lat)  # 转换为WGS-84坐标
    wgs84_coordinates.append({'lng': wgs84_lng, 'lat': wgs84_lat})  # 存储转换结果
# 将结果存储为数据框
wgs84_df = pd.DataFrame(wgs84_coordinates)
# 定义输出的CSV文件路径
csv_file_path = 'gcj02_to_wgs84.csv'
# 将转换结果保存为CSV文件,不包含索引
wgs84_df.to_csv(csv_file_path, index=False)
# 输出转换完成的消息
print(f"转换完成,结果已保存至 {csv_file_path}")

按区域搜索爬虫:

import time  # 导入time库,提供时间相关功能,如延时、获取当前时间等
import requests  # 导入requests库,这是一个方便的库,用于发送HTTP请求,可以处理GET、POST等请求
import urllib.parse  # 导入urllib.parse库,用于解析和构建URL,处理URL编码和解码等
import pandas as pd  # 导入pandas库,这是一个数据分析工具库,提供数据结构和数据分析功能,适用于处理和分析数据
# 将要处理的行政区划代码列表赋值给data变量(这里是南京市)
data = [320102, 320104, 320105, 320106, 320111, 320113, 320114, 320115, 320116, 320117, 320118]
# 设置腾讯地图API的搜索URL
url0 = 'https://apis.map.qq.com/ws/place/v1/search'
# 初始化一个字典以存储从API返回的结果
results = {
    "id": [],  # 存储地点的ID
    "title": [],  # 存储地点的名称
    "address": [],  # 存储地点的地址
    "lng": [],  # 存储地点的经度
    "lat": []  # 存储地点的纬度
}
# 定义一个函数,负责分页查询并处理API返回的结果
def fetch_data(region_id, page_index):
    # 构造查询参数,包括区域边界、API密钥、搜索关键字等
    query = {
        'boundary': f'region({region_id}, 2)',  # 设置区域边界,使用字符串格式化
        'key': '',  # 填你的API密钥
        'keyword': '厕所',  # 搜索关键字,这里为厕所
        'page_size': 20,  # 每页返回的最大地点数量
        'page_index': page_index  # 当前页码
    }
    # 拼接完整的请求URL
    url = f"{url0}?{urllib.parse.urlencode(query)}"  # 使用字符串格式化生成请求URL
    # 设置请求头,以伪装成浏览器发出的请求
    headers = {'User-Agent': 'Mozilla/5.0'}
    try:
        # 发送GET请求,获取API返回的响应
        response = requests.get(url, timeout=10, headers=headers)
        response.raise_for_status()  # 检查请求是否成功,若失败则抛出异常
        return response.json()  # 直接返回解析后的JSON格式数据
    except requests.RequestException as e:
        print(f"请求失败: {e}")  # 如果请求发生异常,打印错误信息
        return None  # 返回None表示请求失败
# 遍历data中的每一行区域ID
for region_id in data:
    # 从第一页开始获取数据
    page_index = 1
    while True:
        response = fetch_data(region_id, page_index)  # 调用fetch_data函数获取数据
        if response and response.get('status') == 0:  # 检查API响应状态是否正常
            POIs = response.get('data', [])  # 获取返回的地点数据
            for poi in POIs:  # 遍历每个地点
                # 将每个地点的信息添加到结果字典中
                results["id"].append(poi['id'])
                results["title"].append(poi['title'])
                results["address"].append(poi['address'])
                results["lng"].append(poi['location']['lng'])
                results["lat"].append(poi['location']['lat'])
            # 检查是否还有更多页面
            if len(POIs) < 20 or (page_index == int(response.get('count') / 20) and response.get('count') % 20 == 0):  
                # 如果返回的POI少于20个,认为已获取完毕(后者处理记录数刚好为整数倍的返回)
                break
            page_index += 1  # 否则继续获取下一页
            time.sleep(1)  # 请求间隔1秒,以避免过于频繁
        else:
            print(f"API错误: {response.get('message') if response else '未收到有效响应'}")  # 输出API错误信息
            break
    print(f"区域 {region_id} 的数据获取完成!")  # 每完成一个区域的读取,输出消息
# 将收集到的结果转换为DataFrame,并输出到CSV文件
final_df = pd.DataFrame(data=results)  # 创建DataFrame对象
final_df.to_csv('D:/poi.csv', index=False, encoding='utf_8_sig')  # 将DataFrame保存为CSV文件,不包含索引,使用UTF-8编码

        其他搜索方式其实大同小异!主要就是改变“boundary”的值。读取excel遍历细分研究区经纬度改变“boundary”的值,让AI帮忙改一下就可以了。创建的文件想要.xlsx,同理AI改改就可以~~我懒。😥

        以后要好好记录学到的技术,鞭策自己多学点才可以!📩

        还有同门如果发现了,不要照抄代码啊,让AI“降降重”...

 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值