【爬虫】AOI

目前几个大厂,高德百度腾讯,都支持POI爬取,而AOI是需要自己找接口的。
换言之,爬虫需谨慎


1 百度AOI

参考链接是:
这两个链接是选定范围爬取范围内选定类别的AOI
黑科技 | 百度地图抓取地块功能(上)
黑科技 | 百度地图获取地块功能属性(下)

而这个链接是用名称爬取。我参考的是这个:利用名称爬取百度AOI
https://www.cnblogs.com/zhangqinglan/p/13301425.html

跟Amap一样,其实是找到了一个接口:
https://map.baidu.com/?newmap=1&qt=s&da_src=searchBox.button&wd=‘+Name+’&c=XXX

其中Name为要搜索的名称。c=XXX的XXX为城市的city_id。这个之前爬风向标的时候已经看过了。

比如我输入朝阳大悦城和c=131
【爬虫】AOI_第1张图片

1.1 获得AOI ID

第一步是先从https://map.baidu.com/?newmap=1&qt=s&da_src=searchBox.button&wd='+Name+'&c=XXX这个接口里找打aoi的id。
跟参考链接不一样的是,我的名字是上一篇爬虫博客中爬好的csv文件。所以这里改动了下,方便读取我的格式。
另外参考博客这个层层嵌套的for和try-except,乍一看挺唬人。

在测试时还有个问题,就是try-except有时候会报错,返回false,因此我没用它。
以及我开代理爬取时有时成功有时不成功,不知道会不会被锁ip

还有就是在request语句中,参考博客是用 verify=False。后续调试代码时多爬几次就服务器报错了。所以也删掉了。gpt显示这个错误是跟SSH证书有关
在这里插入图片描述

import requests
import pandas as pd
from urllib.parse import quote
import time
import random
import json

HEADERS = {'Accept':'*/*',
        'Accept-Encoding':'gzip, deflate, sdch, br',
        'Accept-Language':'zh-CN,zh;q=0.8',
        'Connection':'keep-alive',
        'Cookie':'BAIDUID=C4D08149D7EE627DC037119413418CA3:FG=1;'\
               'BIDUPSID=C4D08149D7EE627DC037119413418CA3; PSTM=1540284487;'\
               'pgv_pvi=9789244416; BDUSS=GF-S3Y5c1MybnhoTkhwMUxyWEhHM3ZreW1'\
               'UTURiQk1TUFllMWc5V1ZWeUVHNGhkRVFBQUFBJCQAAAAAAAAAAAEAAAA~7j4'\
               '5ztLKx8DtuaTIyzMyMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'\
               'AAAAAAAAAAAAAAAAAAAAAAAAAAISOYF2EjmBdV; session_id=1567581077599;'\
               'session_name=; validate=31187; MCITY=-%3A; M_LG_UID=960425535;'\
               'M_LG_SALT=2cf3bbbbd3e5466b66a2529c037b982b',
        'Host':'map.baidu.com',
        'Referer':'https://map.baidu.com/search/%E9%83%91%E5%B7%9E%E5%B8%82%E4'\
               '%BA%8C%E4%B8%83%E4%B8%87%E8%BE%BE%E4%B8%89%E5%8F%B7%E9%99%A2/@'\
               '12650489.832882352,4101769.7450000006,18.35z/maptype%3DB_EARTH_'\
               'MAP?querytype=s&da_src=shareurl&wd=%E9%83%91%E5%B7%9E%E5%B8%82%'\
               'E4%BA%8C%E4%B8%83%E4%B8%87%E8%BE%BE%E4%B8%89%E5%8F%B7%E9%99%A2&c'\
               '=268&src=0&pn=0&sug=0&l=18&b=(12650755.24571287,4101867.11178217'\
               '83;12651854.04020792,4102020.4126732675)&from=webmap&biz_forward='\
               '%7B%22scaler%22:1,%22styles%22:%22pl%22%7D&device_ratio=1',
        'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) '
                    'AppleWebKit/537.36 (KHTML, like Gecko)'
                    'Chrome/55.0.2883.87 Safari/537.36',}


table=pd.read_csv(r"文件")
result_df = pd.DataFrame()
# 依次读取每个城市的名称和内容
for city in table.columns:
    city_name = city
    if city_name=='XXX':
        code=aaa;
    elif city_name=='XXX':
        code=aaa

    city_content = table[city].values.tolist()
    city_content = list(filter(lambda x: str(x) != "nan", city_content))
    
    list_aoiid=[]
    for mall in city_content:
        encoded_mall = quote(mall)
        url = "https://map.baidu.com/?newmap=1&qt=s&da_src=searchBox.button&wd={}&c={}".format(
                encoded_mall, code)
        print(url)
        
        delay = random.randint(2, 5)  # 生成1到2之间的随机整数
        time.sleep(delay)  # 延时随机秒数

        r = requests.get(url,headers=HEADERS, allow_redirects=True)
        data = r.content
        data=data.decode('utf-8')
        data = json.loads(data)
        aoi_id = data['result']['profile_uid']
        print(aoi_id)
        list_aoiid.append(aoi_id)
        
    city_data = pd.DataFrame({
        city_name: city_content,
        'aoiid': list_aoiid})
    
    result_df = pd.concat([result_df, city_data], ignore_index=True, axis=1)
    print(result_df)

可以看见最后获得的dataframe结果是这样的。是的我个人很喜欢搞完一步就存dataframe。
没有表头自己加一下就好。然后我们随机挑选几个进行验证。
【爬虫】AOI_第2张图片

验证链接就是前面代码print(url)输出的链接,或者自己按照前面的方法,设置name和city id,也行。
也是很佩服参考链接的博主怎么找到这个是aoi id 的,以及发现aoi的接口

1.2 AOI获取

不知道博主从哪里找到的接口:
https://map.baidu.com/?newmap=1&qt=ext&uid=‘+AOI ID+’&ext_ver=new&ie=utf-8&l=1
更改里面的AOI ID就行。下图中的Geo就是我们需要的
【爬虫】AOI_第3张图片
根据黑科技 | 百度地图获取地块功能属性(下)链接中的讲解,
aoi抓取到的数据是百度米制坐标,也就是百度墨卡托投影坐标系。而我们一般使用的都是wgs84大地坐标系。
需要转换【百度米制】到【百度经纬度坐标】再到【wgs84】。也就是【bd09mc】到【bd09】到【wgs84】
也就是要将下图中右边各个点的坐标进行转换
【爬虫】AOI_第4张图片
这里的转换跟黑科技 | 百度地图获取地块功能属性(下)提供的下载包中的转换代码做了对比,【bd09mc】到【bd09】没有差别,是一样的,而【bd09】到【wgs84】有出入。看想要哪种吧。我懒得换了。

#from station import stations
import warnings
import  xdrlib ,sys
import xlrd
import time
import socket

#bd墨卡托转BD-09
import math
pi = 3.1415926535897932384626
def Yr(lnglat,b):
    if b!='':
        c=b[0]+b[1]*abs(lnglat[0])
        d=abs(lnglat[1]/b[9])
        d=b[2]+b[3]*d+b[4]*d*d+b[5]*d*d*d+b[6]*d*d*d*d+b[7]*d*d*d*d*d+b[8]*d*d*d*d*d*d
        if 0>lnglat[0]:
            bd=-1*c
        else:
            bd=c
        lnglat[0]=bd
        if 0 > lnglat[0]:
            bd2 = -1 * d
        else:
            bd2 = d
        lnglat[1] = bd2
        return lnglat
    return
def Mecator2BD09(lng,lat):
    lnglat=[0,0]
    Au=[[1.410526172116255E-8, 8.98305509648872E-6, -1.9939833816331, 200.9824383106796, -187.2403703815547,
          91.6087516669843, -23.38765649603339, 2.57121317296198, -0.03801003308653, 1.73379812E7],
         [- 7.435856389565537E-9, 8.983055097726239E-6, -0.78625201886289, 96.32687599759846, -1.85204757529826,
          -59.36935905485877, 47.40033549296737, -16.50741931063887, 2.28786674699375, 1.026014486E7],
         [- 3.030883460898826E-8, 8.98305509983578E-6, 0.30071316287616, 59.74293618442277, 7.357984074871,
          -25.38371002664745, 13.45380521110908, -3.29883767235584, 0.32710905363475, 6856817.37],
         [- 1.981981304930552E-8, 8.983055099779535E-6, 0.03278182852591, 40.31678527705744, 0.65659298677277,
          -4.44255534477492, 0.85341911805263, 0.12923347998204, -0.04625736007561, 4482777.06],
         [3.09191371068437E-9, 8.983055096812155E-6, 6.995724062E-5, 23.10934304144901, -2.3663490511E-4,
          -0.6321817810242, -0.00663494467273, 0.03430082397953, -0.00466043876332, 2555164.4],
         [2.890871144776878E-9, 8.983055095805407E-6, -3.068298E-8, 7.47137025468032, -3.53937994E-6, -0.02145144861037,
          -1.234426596E-5, 1.0322952773E-4, -3.23890364E-6, 826088.5]]
    Sp=[1.289059486E7, 8362377.87, 5591021, 3481989.83, 1678043.12, 0 ]
    lnglat[0]=math.fabs(lng)
    lnglat[1] =abs(lat)
    for d in range(0,6):
        if lnglat[1]>=Sp[d]:
            c=Au[d]
            break
    lnglat=Yr(lnglat,c)
    return lnglat
def BD092WGS84(lnglat):
    #bd09-gcj

    x_pi = 3.14159265358979324 * 3000.0 / 180.0
    pi = 3.1415926535897932384626  # π
    a = 6378245.0  # 长半轴
    ee = 0.00669342162296594323  # 扁率
    x = lnglat[0] - 0.0065
    y = lnglat[1] - 0.006
    z = math.sqrt(x * x + y * y) - 0.00002 * math.sin(y * x_pi)
    theta = math.atan2(y, x) - 0.000003 * math.cos(x * x_pi)
    lnglat[0] = z * math.cos(theta)
    lnglat[1] = z * math.sin(theta)

    dlat = tranlat1(lnglat[0] - 105.0, lnglat[1] - 35.0)
    dlng = tranlng1(lnglat[0] - 105.0, lnglat[1] - 35.0)
    radlat = lnglat[1] / 180.0 * pi
    magic = math.sin(radlat)
    magic = 1 - ee * magic * magic
    sqrtmagic = math.sqrt(magic)
    dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * pi)
    dlng = (dlng * 180.0) / (a / sqrtmagic * math.cos(radlat) * pi)
    mglat = lnglat[1] + dlat
    mglng = lnglat[0] + dlng
    return [lnglat[0]* 2 - mglng, lnglat[1] * 2 - mglat]
def tranlat1(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 tranlng1(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

大体跟按照名称进行aoi提取的代码是一样的,没怎么改动。只不过原代码因为是id和aoi坐标嵌套的,所以最后输入坐标时可以直接用名称。我这里还需要自己再改改。

import requests
import pandas as pd
from urllib.parse import quote
import time
import random
import json

table=pd.read_csv(r"XXXXX")
none_aoiid=[]
with open(r"XXXXX", 
          'a',newline='') as f:
    for z in range(0,4):
        print('现在的数字是',z)
        city_mall=table.iloc[:,2*z]
        id_mall=table.iloc[:,2*z+1] 
        id_content = id_mall.values.tolist()
        id_content = list(filter(lambda x: str(x) != "nan", id_content))

        for aoiid in id_content:
            url_AOI = 'https://map.baidu.com/?newmap=1&qt=ext&uid={}'\
                        '&ext_ver=new&ie=utf-8&l=11'.format(aoiid)
            print('处理链接',url_AOI)
            r_AOI = requests.get(url_AOI,headers=HEADERS, allow_redirects=True)
            data = r_AOI.content.decode('utf-8')
            data = json.loads(data)

            if 'geo' in data['content']:
                geo_AOI = data['content']['geo']
                geo_AOI = geo_AOI.split('|')
                point = geo_AOI[2].split(",")

                point_transform=[]
                for i in range(int(len(point)/2)):#全部点的坐标,分别是x,y,的形式
                    if i==0: #[2:]的作用:删除第一个坐标的‘1-’字符
                        point[2*i] = point[2*i][2:] 
                    if i==int((len(point)/2)-1):  #删除最后一个坐标的‘;’字符
                        point[2*i+1] = point[2*i+1][:-1]
#                     print('第'+str(i)+'个点的坐标',float(point[2*i]),float(point[2*i+1]))#打印出各点的坐标
                    point_Mecator2BD09 = Mecator2BD09(float(point[2*i]),float(point[2*i+1]))
                    point_BD092WGS84 = BD092WGS84(point_Mecator2BD09)
                    point_transform.append(point_BD092WGS84)
                    point_str = '' #这是创建一个文本存储
                for j in range(len(point_transform)):
                    point_str = point_str+(str(point_transform[j])).replace(' ','')[1:-1]+';'
                print(str(aoiid)+' 处理完毕。开始写入')
                line=aoiid+' '+point_str+'\n'
                f.write(line)
            else:
                print(aoiid+'没有找到坐标')
                none_aoiid.append(aoiid)
    print('没有信息的aoi',str(none_aoiid))

将txt的iaoiid和csv文件进行匹配替换,最后结果是下面这样
【爬虫】AOI_第5张图片

1.3 小结

Amap的爬虫是我之前就做过的,用到就是下面的链接方法。需要一个个自己输入,比较麻烦。
但是抓包工具比自己的代码更强大,能搞定Amap的反扒机制。很强。

由于信息做过筛选所以百度这里只有坐标,最后生成AOI的shapefile的话也没有什么信息。这个还需要改进其实。毕竟信息都爬下来了。

1.4 误差原因

1.2的代码中最后有输出一个没有爬取到的aoi_id信息。而在对上一篇爬虫文里获得的百度风向标结果进行分析时看见,有一些地方的id是重复的。一开始我以为是触发了反扒机制,所以有些返回错误。刚刚对AOI生成的shapefile进行校验时发现,是百度地图的AOI有些不准确,有的范围太大了把其他区域也包括进去了。所以可能返回的商场是相同的id。

经过我自己在高德和百度的实验,百度aoi的位置更准确,也就是前面的代码获得最后转wgs84的aoi在arcgis pro的底图中很贴合。而高德使用下面的链接POI的体量 - AOI数据获取脚本分享获得不太准确,偏移较大。
所以下面链接的半自动化代码其实也是需要改进的。

Amap AOI

这个是一个半自动化的抓取方式。用抓包工具Fiddler
POI的体量 - AOI数据获取脚本分享
这个链接现在会触发高德反扒滑动条,如果躲不了就无法用:Python批量爬取高德AOI边界数据+GIS可视化(超详细)

你可能感兴趣的:(Python,爬虫)