之前介绍了街景数据抓取的核心思想,采用画格网的方式查询街景数据是否存在。
该方法在数据抓取过程漫长一次难以完全抓取数据信息,且按照格网查询街景时由于查询接口是按半径进行搜索难免出现重复街景的现象。为克服以上两个难题,本文采用断点续爬解决爬虫中断后需从头开始的问题,采用将街景ID存入mysql数据库进行街景去重,极大的提高了工作效率。
1.数据来源
之前街景数据的抓取采用的是腾讯官网的接口,实用性不强,多数情况下请求数据不成功,且需要配置key等一系列操作。本文将采用城市吧街景数据,发送请求获取街景id,根据id获取全景图。
传入经纬度获取街景id的接口: (key可不传参)
https://sv.map.qq.com/xf?lat=23.129577531346&lng=113.33253497386&r=1000&key=ba56844b2a30311fe2149b485ca2a031&output=jsonp&pf=jsapi&ref=jsapi&cb=qq.maps._svcb3.cbk3sqht1y3
根据id获取街景的接口:
http://sv1.map.qq.com/thumb?svid=10061047140101161900600&x=0&y=0&from=web&level=0&size=0
街景全景图:
2. 断点续爬
核心代码说明:利用logo.txt记录上次坐标格网的索引,判断是否需要断点续爬,需要的话则会计算原有记录索引值与默认值差值,利用索引差值动态修正索引。值得注意的是,索引值虽被修正但循环次数依旧不变,需要防止索引超限。
1 # 断点续爬 (初始化) 2 filePath='logo.txt' 3 indexArr=read_file(filePath) 4 goFlag='flase' 5 jinFlag='flase' 6 weiFlag='flase' 7 if len(indexArr)!=2: 8 print('无需进行断点续爬') 9 else: 10 print('准备进行断点续爬!!!') 11 goFlag='true' 12 jins_c=0 13 weis_c=0 14 for jins_i in range(jins_num): 15 if goFlag=='true': 16 if len(indexArr)==2: 17 jins_c=int(indexArr[0])-jins_i# 索引差值 18 if jinFlag!='true': 19 jins_i=jins_c+jins_i# 修正经度索引 20 if jins_i>jins_num-jins_c:# 保证索引不越界 21 jinFlag='true' 22 break 23 jin = round(jins[jins_i],3) 24 for weis_i in range(weis_num): 25 if goFlag=='true': 26 if len(indexArr)==2: 27 weis_c=int(indexArr[1])-weis_i# 索引差值 28 indexArr=[]# 清空记录 29 if weiFlag!='true': 30 weis_i=weis_c+weis_i# 修正纬度索引 31 if weis_i>weis_num-weis_c:# 保证索引不越界 32 weiFlag='true' 33 break 34 wei = round(weis[weis_i],3) 35 # 断点续爬 (记录经纬度索引) 36 saveText(filePath,str(jins_i)+'_'+str(weis_i)) 37 # 这里要注意下,对应的经纬度没有街景图的地方,输出的会是无效图片 38 print(jin, wei) 39 img_name = "E:\\dataTest\\streetImgData\\"+cityName+"\\" + str(wei) + "_" + str(jin) +".jpg" 40 getPanoBylocation_(str(wei)+","+str(jin), img_name)
3. 街景ID去重
3.1引入自定义mysql类(源码):
import MySql #自定义mysql类
# 连接数据库
myCon=MySql.connect({'host':'','user':'','password':'','port':'','database':'','charset':''})
3.2数据匹配
核心代码说明:先去数据库查询id是否存在,存在则不再下载图片,否则下载图片保存记录。
# 查询记录是否存在,存在则无需再次下载
sql="SELECT * FROM streeImg WHERE id ='"+pano+"'" resData=MySql.query(myCon,sql) if len(resData)==0: # 插入记录 sql = "INSERT INTO streeImg (id,name,lat,lng,detail) VALUES (%s, %s, %s, %s, %s)" val = (pano,name,float(lat) ,float(lng) ,text) MySql.add(myCon,sql,val) # 全景图url url = "http://sv1.map.qq.com/thumb?svid="+pano+"&x=0&y=0&from=web&level=0&size=0" # 分级瓦片 download_(url, img_name,pano) else: print('图片已下载完成,无需再下载%s'%(pano))
3.3数据库结构
说明:先去数据库表结构以及字段类型如下图所示。
4.(源码):
1 # coding=utf-8 2 import math 3 import json 4 import requests 5 import urllib 6 from urllib.request import urlopen 7 from optparse import OptionParser 8 # PIL Python Imaging Library 已经是Python平台事实上的图像处理标准库了。PIL功能非常强大,但API却非常简单易用 9 # 安装步骤 1.cmd 2.进入python的安装目录中的Scripts目录:3.输入命令:pip install pillow -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com 10 from PIL import Image 11 from io import BytesIO 12 import numpy as np 13 import MySql #自定义mysql类 14 15 # 连接数据库 16 myCon=MySql.connect({'host':'','user':'','password':'','port':'','database':'','charset':''}) 17 18 def getPanoBylocation_(location,img_name): 19 # 将user_agent,referer写入头信息 20 headers={'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36','Referer':'http://ditu.city8.com/gz/canyinfuwu/2716097_w3gd6'} 21 lat=location.split(',')[0] 22 lng=location.split(',')[1] 23 url='https://sv.map.qq.com/xf?lat='+lat+'&lng='+lng+'&r=1000&key=ba56844b2a30311fe2149b485ca2a031&output=jsonp&pf=jsapi&ref=jsapi&cb=qq.maps._svcb3.cbk3rdpp035' 24 text = requests.get(url, headers=headers).text 25 text=text.split("qq.maps._svcb3.cbk3rdpp035&&qq.maps._svcb3.cbk3rdpp035(")[1].split(")")[0] 26 jsonMess =json.loads(text) 27 28 if jsonMess['detail']: 29 pano=str(jsonMess['detail']['svid'])# 街景ID 30 name=jsonMess['detail']['road_name']# 道路名称 31 print('查询成功%s'%(pano)) 32 # 查询记录是否存在,存在则无需再次下载 33 sql="SELECT * FROM streeImg WHERE id ='"+pano+"'" 34 resData=MySql.query(myCon,sql) 35 if len(resData)==0: 36 # 插入记录 37 sql = "INSERT INTO streeImg (id,name,lat,lng,detail) VALUES (%s, %s, %s, %s, %s)" 38 val = (pano,name,float(lat) ,float(lng) ,text) 39 MySql.add(myCon,sql,val) 40 # 全景图url 41 url = "http://sv1.map.qq.com/thumb?svid="+pano+"&x=0&y=0&from=web&level=0&size=0" 42 # 分级瓦片 43 download_(url, img_name,pano) 44 else: 45 print('图片已下载完成,无需再下载%s'%(pano)) 46 47 #发送请求保存照片 48 def download_(url, name,pano): 49 # 将user_agent,referer写入头信息 50 headers={'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36','Referer':'http://ditu.city8.com/gz/canyinfuwu/2716097_w3gd6'} 51 images = requests.get(url, headers=headers) 52 53 if images.status_code == 200: 54 try:# 请求街景数据失败 55 jsonMess=images.json() 56 print('请求图片失败 原因:%s'%(jsonMess)) 57 except json.JSONDecodeError:# json 编码异常捕获(街景请求成功) 58 img = images.content 59 print('图片: %s%s 正在下载..' % ('panoID ',pano)) 60 image = Image.open(BytesIO(img)) 61 image.save(r'' + name ) 62 # 63 64 # wgs84转高德 65 def wgs84togcj02(lng, lat): 66 PI = 3.1415926535897932384626 67 ee = 0.00669342162296594323 68 a = 6378245.0 69 dlat = transformlat(lng - 105.0, lat - 35.0) 70 dlng = transformlng(lng - 105.0, lat - 35.0) 71 radlat = lat / 180.0 * PI 72 magic = math.sin(radlat) 73 magic = 1 - ee * magic * magic 74 sqrtmagic = math.sqrt(magic) 75 dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * PI) 76 dlng = (dlng * 180.0) / (a / sqrtmagic * math.cos(radlat) * PI) 77 mglat = lat + dlat 78 mglng = lng + dlng 79 return [mglng, mglat] 80 # GCJ02/谷歌、高德 转换为 WGS84 gcj02towgs84 81 def gcj02towgs84(localStr): 82 lng = float(localStr.split(',')[0]) 83 lat = float(localStr.split(',')[1]) 84 PI = 3.1415926535897932384626 85 ee = 0.00669342162296594323 86 a = 6378245.0 87 dlat = transformlat(lng - 105.0, lat - 35.0) 88 dlng = transformlng(lng - 105.0, lat - 35.0) 89 radlat = lat / 180.0 * PI 90 magic = math.sin(radlat) 91 magic = 1 - ee * magic * magic 92 sqrtmagic = math.sqrt(magic) 93 dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * PI) 94 dlng = (dlng * 180.0) / (a / sqrtmagic * math.cos(radlat) * PI) 95 mglat = lat + dlat 96 mglng = lng + dlng 97 return str(lng * 2 - mglng) + ',' + str(lat * 2 - mglat) 98 def transformlat(lng, lat): 99 PI = 3.1415926535897932384626 100 ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * \ 101 lat + 0.1 * lng * lat + 0.2 * math.sqrt(abs(lng)) 102 ret += (20.0 * math.sin(6.0 * lng * PI) + 20.0 * 103 math.sin(2.0 * lng * PI)) * 2.0 / 3.0 104 ret += (20.0 * math.sin(lat * PI) + 40.0 * 105 math.sin(lat / 3.0 * PI)) * 2.0 / 3.0 106 ret += (160.0 * math.sin(lat / 12.0 * PI) + 320 * 107 math.sin(lat * PI / 30.0)) * 2.0 / 3.0 108 return ret 109 def transformlng(lng, lat): 110 PI = 3.1415926535897932384626 111 ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + \ 112 0.1 * lng * lat + 0.1 * math.sqrt(abs(lng)) 113 ret += (20.0 * math.sin(6.0 * lng * PI) + 20.0 * 114 math.sin(2.0 * lng * PI)) * 2.0 / 3.0 115 ret += (20.0 * math.sin(lng * PI) + 40.0 * 116 math.sin(lng / 3.0 * PI)) * 2.0 / 3.0 117 ret += (150.0 * math.sin(lng / 12.0 * PI) + 300.0 * 118 math.sin(lng / 30.0 * PI)) * 2.0 / 3.0 119 return ret 120 121 #获取经纬坐标 122 def getPoint(_points): 123 point = _points.split(',') 124 point_jin = point[0] 125 point_wei = point[1] 126 transOpints=wgs84togcj02(float(point_jin),float(point_wei)) 127 return transOpints 128 129 # 以txt文件格式存储 130 def saveText(filePath,str_): 131 fo = open(filePath, "w+")# 打开一个文件 132 fo.write(str_); #内容写入 133 fo.write('\n') 134 fo.close()# 关闭打开的文件 135 # 读取txt内容 136 def read_file(filePath): 137 try: 138 with open(filePath, 'r')as f: 139 LIST = f.readlines() 140 for line in LIST: 141 return line.split('_') 142 except FileNotFoundError: 143 return [] 144 145 # 输入左下以及右上角坐标 根据两点形成等差坐标组 进而获取图片 146 def getImage(start_point,end_point,cityName): 147 # 取得起始坐标 148 start_point_jin = start_point[0] 149 start_point_wei = start_point[1] 150 end_point_jin = end_point[0] 151 end_point_wei = end_point[1] 152 #创建等差数组 153 jins = np.arange(float(start_point_jin)*1000, float(end_point_jin)*1000, 1)*0.001 154 jins_num = len(jins) 155 weis = np.linspace(float(start_point_wei)*1000, float(end_point_wei)*1000, jins_num)*0.001 156 weis_num = len(weis) 157 # 断点续爬 (初始化) 158 filePath='logo.txt' 159 indexArr=read_file(filePath) 160 goFlag='flase' 161 jinFlag='flase' 162 weiFlag='flase' 163 if len(indexArr)!=2: 164 print('无需进行断点续爬') 165 else: 166 print('准备进行断点续爬!!!') 167 goFlag='true' 168 jins_c=0 169 weis_c=0 170 for jins_i in range(jins_num): 171 if goFlag=='true': 172 if len(indexArr)==2: 173 jins_c=int(indexArr[0])-jins_i# 索引差值 174 if jinFlag!='true': 175 jins_i=jins_c+jins_i# 修正经度索引 176 if jins_i>jins_num-jins_c:# 保证索引不越界 177 jinFlag='true' 178 break 179 jin = round(jins[jins_i],3) 180 for weis_i in range(weis_num): 181 if goFlag=='true': 182 if len(indexArr)==2: 183 weis_c=int(indexArr[1])-weis_i# 索引差值 184 indexArr=[]# 清空记录 185 if weiFlag!='true': 186 weis_i=weis_c+weis_i# 修正纬度索引 187 if weis_i>weis_num-weis_c:# 保证索引不越界 188 weiFlag='true' 189 break 190 wei = round(weis[weis_i],3) 191 # 断点续爬 (记录经纬度索引) 192 saveText(filePath,str(jins_i)+'_'+str(weis_i)) 193 # 这里要注意下,对应的经纬度没有街景图的地方,输出的会是无效图片 194 print(jin, wei) 195 img_name = "E:\\dataTest\\streetImgData\\"+cityName+"\\" + str(wei) + "_" + str(jin) +".jpg" 196 getPanoBylocation_(str(wei)+","+str(jin), img_name) 197 198 #定义数据字典 根据起始点坐标推算内容坐标 199 cityJinweiArr=[{"start":"115.442845,39.464988","end":"117.498766,40.978318","city":"beiJing"},{"start":"112.681398,34.269097","end":"114.226897,34.958295","city":"zhengZhou"},{"start":"113.692462,29.971956","end":"115.082138,31.362241","city":"wuHan"}] 200 for city in cityJinweiArr: 201 start_point=getPoint(city['start']) 202 end_point=getPoint(city['end']) 203 cityName=city['city'] 204 getImage(start_point,end_point,cityName)