最爱橙子汁 发表于 2020-3-19 00:30

蠢新使用python利用高德API爬取高德POI数据程序总是假死,求哥哥们救救孩子。

本帖最后由 最爱橙子汁 于 2020-3-19 00:35 编辑

毕业设计需要用到一些城市的POI数据,本着自己动手丰衣足食的原则,就从自己写了段python代码从高德地图爬取POI数据。
可是爬取过程中总是不顺利,程序总是爬着爬着就不动了,有时爬几千条假死,有时爬几万条假死。数据库中没有新数据增加,程序也不报错,也不中止。CPU,内存占用也不高,硬盘中也还有空间,现在是实在不知道如何解决了。所以想让请教一番。
奈何本人水平真的有限,这程序总跑着跑着就不跑了,整个武汉的poi点少数也有几十万,我总是爬几万程序就停下了。真是气人!!其实,还有个问题,不知道是不是巧合,我这里说下,就是当我运行程序时,如果我此时我时不时的去数据库瞅瞅增加了多少条,数据都会一直增加,但是当我跑去睡觉,或者出门一会回来之后,想看看跑了多少条数据,这时候程序就处于假死状态。

这里对高德地图的接口做个说明:
多边形搜索API接口,请求方式get
https://restapi.amap.com/v3/place/polygon?parameters请求参数:
polygon:如果多边形为矩形,左上,右下经纬度坐标

高德文档上说这个接口一次最多只能获得1000个POI数据,我实际测试了下,其实最多只能获得800多个数据。
因此,当我爬取一个矩形区域时,   
    如果返回数据大于800个,我就认为这个区域的poi数据没有获取完全,将当前区域四等分,再逐个爬取。
    如果反数据小于800个,则认为该区域poi数据就这么多,就将当前区域的poi数据写入数据库。



我以为武汉市为例,写了如下代码:
import requests
import pymysql
import time
import math
import socket

'''
    https://restapi.amap.com/v3/place/polygon?polygon=113.705304,31.366201|115.089744,29.974252&key=10518669c9fd0a0532e41189d61e1e9b&extensions=all&types=010000&offset=10&page=90
'''
# 初始区域的经纬度
lon_l = 113.705304
lan_l = 31.366201
lon_r = 115.089744
lan_r = 29.974252

# 要爬取POI的类型
types = '010000|020000|030000|040000|050000|060000|' \
      '070000|080000|090000|100000|110000|120000|' \
      '130000|140000|150000|160000|170000|180000|' \
      '190000|200000|210000|220000|230000|240000|' \
      '970000|990000'

# 获取POI的接口
url = 'https://restapi.amap.com/v3/place/polygon?' \
   'polygon={lon_l},{lan_l}|{lon_r},{lan_r}' \
   '&key=10518669c9fd0a0532e41189d61e1e9b&extensions=all' \
   '&types={types}&offset=25&page={page}'


# point
class Point:
    """
    用经纬度来描述一个点
    """
    def __init__(self, lon, lan):
      self.lon = lon      # 经度
      self.lan = lan      # 纬度


# area
class Rectangle:
    """
    用左上角的经纬度和右下角的经纬度描述一个矩形区域
    """
    def __init__(self, p_l, p_r):
      self.p_l = p_l      # 左上角的点
      self.p_r = p_r      # 右下角的点


# 初始矩形的左上点和右下点
p_l = Point(lon_l,lan_l)
p_r = Point(lon_r,lan_r)

# 初始矩形,內含两点,左上,右下
rec = Rectangle(p_l,p_r)

'''
打开一个URL,获取返回信息
'''
def open_html(real_url):
    NET_STATUS=False
    while not NET_STATUS:
      try:
            data = requests.get(real_url).json()
            return data
      except socket.timeout:
            print("NET_STATURS IS NOT GOOD")
            NET_STATUS=False
      except :
            print("OTHER WRONG")



# 把一个区域的POI点存进数据库
def get_pois(rec,url,data_count):
    url = url.format(lon_l = rec.p_l.lon , lan_l = rec.p_l.lan ,
               lon_r = rec.p_r.lon , lan_r = rec.p_r.lan ,
               types = types,page="{page}")

    f_url = open("url.txt","a",encoding="UTF-8")

    data= open_html(url.format(page=0))

    if data["status"] == "1":
      # 执行到这里,说明url中含有poi数据,下面开始爬取
      all_page=math.ceil(data_count/25)
      for page in range(all_page):
            try:
                # 从第1页开始到第最后一页
                url_real = url.format(page = page+1)
                f_url.write(url_real+'\n')
                data = open_html(url_real)
                if data["status"] == "1" : # 判断第page+1页是否有内容
                  pois = data["pois"]

                  # 将数据储存在数据库
                  # 优化点:db先创建好,一次用完不关,最后再关
                  global db_global
                  db = db_global
                  cursor = db.cursor()
                  str_sql = 'insert into t_poi(id,name,address,typecode,lon,lan,pcode,pname,citycode,cityname,adcode,adname) ' \
                              'values (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)'
                  count=0
                  for poi in pois:
                        try:
                            count += 1
                            value = , poi['name'], poi["address"], poi['typecode'],
                                     poi['location'].split(','), poi['location'].split(','),
                                     poi['pcode'], poi['pname'], poi['citycode'], poi['cityname'], poi['adcode'], poi['adname']]

                            if [] in value:
                              value)]=''

                            try:
                              cursor.execute(str_sql,value)
                              db.commit()
                            except:
                              Exception
                              # 打印日志文件
                              f=open("wrong.txt",'a',encoding="UTF-8")
                              time_fomat = '%Y-%m-%d %X'
                              time_current = time.strftime(time_fomat)
                              f.write(time_current+ '第{page}页,第{index}个出错\n'.format(page=page,index=count))
                        except:
                            pass

                  cursor.close()
            except:
                pass


#检测器
run_count = 0


# 爬取,分析一个区域的POI数量,多于800则将区域四等分,并递归,低于800则进行爬取
def crawl_pois(rec,url):

    # 计算进行了几次分析的计数器,判断程序是否卡死
    global run_count;
    run_count += 1;
    f_count = open("count.txt",'a',encoding="UTF-8");
    f_count.write("这是第{_count}次运行分析一个区域\n".format(_count=run_count))

    # 根据矩形区域的经纬度坐标,拼接url
    url_real = url.format(lon_l=rec.p_l.lon, lan_l=rec.p_l.lan,
                     lon_r=rec.p_r.lon, lan_r=rec.p_r.lan,
                     types=types, page="{page}")

    # 获取url返回的数据
    data = open_html(url_real.format(page=0))

    if data["status"] == "1":
      # 执行到这里,说明url中含有poi数据,下面开始判断区域POI数量
      data_count = int(data['count'])
      ifdata_count> 800:
            # 如果区域POI数据大于800则进行四等分
            rec_A = Rectangle(Point(0,0),Point(0,0))
            rec_B = Rectangle(Point(0,0),Point(0,0))
            rec_C = Rectangle(Point(0,0),Point(0,0))
            rec_D = Rectangle(Point(0,0),Point(0,0))

            # 经纬度差
            lon_d = (rec.p_r.lon - rec.p_l.lon)/2
            lan_d = (rec.p_l.lan - rec.p_r.lan)/2

            # 计算每个小矩形的左上点,和右下点
            rec_A.p_l.lon = float(format(rec.p_l.lon,'6f'))
            rec_A.p_l.lan = float(format(rec.p_l.lan,'6f'))
            rec_A.p_r.lon = float(format(rec.p_l.lon + lon_d,'6f'))
            rec_A.p_r.lan = float(format(rec.p_r.lan + lan_d,'6f'))

            rec_B.p_l.lon = float(format(rec.p_l.lon + lon_d,"6f"))
            rec_B.p_l.lan = float(format(rec.p_l.lan,"6f"))
            rec_B.p_r.lon = float(format(rec.p_r.lon,"6f"))
            rec_B.p_r.lan = float(format(rec.p_r.lan + lan_d,"6f"))

            rec_C.p_l.lon = float(format(rec.p_l.lon,"6f"))
            rec_C.p_l.lan = float(format(rec.p_r.lan + lan_d,"6f"))
            rec_C.p_r.lon = float(format(rec.p_l.lon + lon_d,"6f"))
            rec_C.p_r.lan = float(format(rec.p_r.lan,"6f"))

            rec_D.p_l.lon = float(format(rec.p_l.lon + lon_d,"6f"))
            rec_D.p_l.lan = float(format(rec.p_r.lan + lan_d,"6f"))
            rec_D.p_r.lon = float(format(rec.p_r.lon,"6f"))
            rec_D.p_r.lan = float(format(rec.p_r.lan,"6f"))

            recs = []
            recs.append(rec_A)
            recs.append(rec_B)
            recs.append(rec_C)
            recs.append(rec_D)

            # 对四个小矩形分别进行爬取,这里使用递归
            for rec_s in recs :
                # 如果一个区域出现异常,就进行下个区域
                try:
                  crawl_pois(rec_s,url)
                except:
                  pass

      else:
            # 如果该矩形区域poi点的数量少于800就进行爬取
            get_pois(rec,url,data_count)

    else:
      print("出错")

'''
下面是程序的入口
'''
# 创建一个全局的连接
db_global = pymysql.connect("localhost", "root", "111111", "poi")

# 对一个矩形进行爬取分析,这里是对武汉地区的左上角和右下角的经纬度围成的矩形进行爬取
crawl_pois(rec,url)

# 关闭数据库
db_global.close()



天黑我隐身 发表于 2020-3-19 18:04

帮你看了看,程序逻辑上没有问题,程序卡死也没遇到,我觉得你代码里面不必要的try太多了
还有前面几个朋友说的递归问题,理论上倒也不至于出现栈溢出,不过我还是改用了队列来实现

贴一下我改过运行的代码
```python
import requests
import pymysql
from DBUtils.PooledDB import PooledDB
import time
import queue
import math
import socket
from concurrent.futures import ThreadPoolExecutor

'''
    https://restapi.amap.com/v3/place/polygon?polygon=113.705304,31.366201|115.089744,29.974252&key=10518669c9fd0a0532e41189d61e1e9b&extensions=all&types=010000&offset=10&page=90
'''
# 初始区域的经纬度
lon_l = 113.705304
lan_l = 31.366201
lon_r = 115.089744
lan_r = 29.974252

# 要爬取POI的类型
types = '010000|020000|030000|040000|050000|060000|' \
      '070000|080000|090000|100000|110000|120000|' \
      '130000|140000|150000|160000|170000|180000|' \
      '190000|200000|210000|220000|230000|240000|' \
      '970000|990000'

# 获取POI的接口
url = 'https://restapi.amap.com/v3/place/polygon?' \
      'polygon={lon_l},{lan_l}|{lon_r},{lan_r}' \
      '&key=10518669c9fd0a0532e41189d61e1e9b&extensions=all' \
      '&types={types}&offset=25&page={page}'


# point
class Point:
    """
    用经纬度来描述一个点
    """

    def __init__(self, lon, lan):
      self.lon = lon# 经度
      self.lan = lan# 纬度


# area
class Rectangle:
    """
    用左上角的经纬度和右下角的经纬度描述一个矩形区域
    """

    def __init__(self, p_l, p_r):
      self.p_l = p_l# 左上角的点
      self.p_r = p_r# 右下角的点


# 初始矩形的左上点和右下点
p_l = Point(lon_l, lan_l)
p_r = Point(lon_r, lan_r)

# 初始矩形,內含两点,左上,右下
rec = Rectangle(p_l, p_r)
db_pool = PooledDB(pymysql, 10, host='localhost', user='root', port=3306,
                   passwd='123', db='poi', use_unicode=True)
thread_pool = ThreadPoolExecutor(max_workers=10)


def open_html(real_url):
    while True:
      try:
            data = requests.get(real_url).json()
            return data
      except socket.timeout:
            print("NET_STATURS IS NOT GOOD")
      except:
            print("OTHER WRONG")


def get_pois(rec: Rectangle, url: str, data_count: int):
    """获取区域数据
    :param rec: 目标区域
    :param url: api地址
    :param data_count: poi数量
    """
    url = url.format(lon_l=rec.p_l.lon, lan_l=rec.p_l.lan,
                     lon_r=rec.p_r.lon, lan_r=rec.p_r.lan,
                     types=types, page="{page}")
    f_url = open("url.txt", "a", encoding="UTF-8")
    data = open_html(url.format(page=0))
    if data["status"] == "1":
      # 执行到这里,说明url中含有poi数据,下面开始爬取
      all_page = math.ceil(data_count / 25)
      conn = db_pool.connection()
      cursor = conn.cursor()
      for page in range(all_page):
            # 从第1页开始到第最后一页
            url_real = url.format(page=page + 1)
            f_url.write(url_real + '\n')
            data = open_html(url_real)
            if data["status"] == "1":# 判断第page+1页是否有内容
                pois = data["pois"]
                # 将数据储存在数据库
                # 优化点:db先创建好,一次用完不关,最后再关
                str_sql = 'insert into t_poi(id,name,address,typecode,lon,' \
                        'lan,pcode,pname,citycode,cityname,adcode,adname)' \
                        ' values (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)'
                count = 0
                for poi in pois:
                  count += 1
                  value = , poi['name'], poi["address"],
                           poi['typecode'], poi['location'].split(','),
                           poi['location'].split(','), poi['pcode'],
                           poi['pname'], poi['citycode'], poi['cityname'],
                           poi['adcode'], poi['adname']]

                  if [] in value:
                        value)] = ''
                  try:
                        cursor.execute(str_sql, value)
                        conn.commit()
                  except Exception as e:
                        # 打印日志文件
                        print(e)
                        f = open("wrong.txt", 'a', encoding="UTF-8")
                        time_fomat = '%Y-%m-%d %X'
                        time_current = time.strftime(time_fomat)
                        f.write(time_current + '第{page}页,第{index}个出错\n'
                              .format(page=page, index=count))

      cursor.close()
      conn.close()


def crawl_pois(recs, url: str):
    """分析一个区域的POI数量,多于800则将区域四等分,加入队列
    :param recs: 初始队列
    :param url:
    :return:
    """
    count = 0# 计算进行了几次分析的计数器,判断程序是否卡死
    with open("count.txt", 'a', encoding="UTF-8") as f_count:
      while not recs.empty():
            rec = recs.get()
            f_count.write("这是第{_count}次运行分析一个区域\n".format(_count=count))
            # 根据矩形区域的经纬度坐标,拼接url
            url_real = url.format(lon_l=rec.p_l.lon, lan_l=rec.p_l.lan,
                                  lon_r=rec.p_r.lon, lan_r=rec.p_r.lan,
                                  types=types, page="{page}")

            # 获取url返回的数据
            data = open_html(url_real.format(page=0))
            if data["status"] == "1":
                # 执行到这里,说明url中含有poi数据,下面开始判断区域POI数量
                data_count = int(data['count'])
                if data_count > 800:
                  # 经纬度差
                  lon_d = (rec.p_r.lon - rec.p_l.lon) / 2
                  lan_d = (rec.p_l.lan - rec.p_r.lan) / 2
                  rec_A = Rectangle(
                        Point(rec.p_l.lon, rec.p_l.lan),
                        Point(float('%.6f' % (rec.p_l.lon + lon_d)),
                              float('%.6f' % (rec.p_r.lan + lan_d)))
                  )
                  rec_B = Rectangle(
                        Point(float('%.6f' % (rec.p_l.lon + lon_d)),
                              rec.p_l.lan),
                        Point(rec.p_r.lon,
                              float('%.6f' % (rec.p_r.lan + lan_d)))
                  )
                  rec_C = Rectangle(
                        Point(rec.p_l.lon,
                              float('%.6f' % (rec.p_r.lan + lan_d))),
                        Point(float('%.6f' % (rec.p_l.lon + lon_d)),
                              rec.p_r.lan)
                  )
                  rec_D = Rectangle(
                        Point(float('%.6f' % (rec.p_l.lon + lon_d)),
                              float('%.6f' % (rec.p_r.lan + lan_d))),
                        Point(rec.p_r.lon, rec.p_r.lan)
                  )
                  temp =
                  for r in temp:
                        recs.put(r)
                else:
                  thread_pool.submit(get_pois, rec, url, data_count)

            else:
                print("出错")
            count += 1


if __name__ == '__main__':
    recs = queue.LifoQueue()
    recs.put(rec)
    crawl_pois(recs, url)
```

把没必要的try删掉了,为了提高效率用上了多线程,因为懒得搞个线程锁所以url.txt和wrong.txt文件大概率是不准确的
ps 回复的几分钟,数据已经27万条了

Ts灬花痴 发表于 2020-3-19 01:21

抢个沙发,是否行数达到了上限?

带色的小马甲 发表于 2020-3-19 08:25

你确定你的递归有出口?

最爱橙子汁 发表于 2020-3-19 08:56

Ts灬花痴 发表于 2020-3-19 01:21
抢个沙发,是否行数达到了上限?

是指mysql数据库的的行数么?如果达到行数上限的话,那我每次重新执行,得到的总行数应该是差不多呀,不至于出现有时爬几万条,有时只爬几千条的情况吧。。。。

最爱橙子汁 发表于 2020-3-19 09:13

本帖最后由 最爱橙子汁 于 2020-3-19 09:19 编辑

带色的小马甲 发表于 2020-3-19 08:25
你确定你的递归有出口?
啊,出口?在crawl_pois(rec, url)方法里面,有这么一段

def crawl_pois(rec,url):

   # 无关紧要的部分

    # 根据矩形区域的经纬度坐标,拼接url
   

    # 获取url返回的数据
    data = open_html(url_real.format(page=0))

    if data["status"] == "1":    # 状态码为1,正常
         data_count = int(data['count'])   #返回数据的数量
         ifdata_count> 800:
            # 如果区域POI数据大于800则进行四等分,并再逐个执行本方法
             recs = []
             recs.append(rec_A)
             recs.append(rec_B)
             recs.append(rec_C)
             recs.append(rec_D)
         
             for rec_s in recs :    #进行递归
                crawl_pois(rec_s, url)

         else:
            # 如果该矩形区域poi点的数量少于800,就将数据写入数据库
            get_pois(rec,url,data_count)

    else:
      print("出错")

这个地方,如果获取的数据小于800,就将数据写入数据库,算不算出口呀,小哥哥。

德古拉伯 发表于 2020-3-19 09:49

哥们,你不要用递归,换队列试试,你深度遍历一直传值,他的深度就越来越深,空间复杂度太高了电脑会卡死,你换广度遍历会好一点。

最爱橙子汁 发表于 2020-3-19 10:09

德古拉伯 发表于 2020-3-19 09:49
哥们,你不要用递归,换队列试试,你深度遍历一直传值,他的深度就越来越深,空间复杂度太高了电脑会卡死, ...

其实,电脑也不卡,是程序死了。我试下队列,谢谢小哥哥啦。

最爱橙子汁 发表于 2020-3-19 18:54

天黑我隐身 发表于 2020-3-19 18:04
帮你看了看,程序逻辑上没有问题,程序卡死也没遇到,我觉得你代码里面不必要的try太多了
还有前面几个朋 ...

其实本来是没try的,因为程序老自己停,所以我又不知道哪里出问题,所以就加上try了。真是谢谢大佬了,我试下您的程序。

最爱橙子汁 发表于 2020-3-19 19:17

天黑我隐身 发表于 2020-3-19 18:04
帮你看了看,程序逻辑上没有问题,程序卡死也没遇到,我觉得你代码里面不必要的try太多了
还有前面几个朋 ...

大佬,您把武汉的数据爬完了么?我看了下接口的配额已经到上限了。
页: [1] 2
查看完整版本: 蠢新使用python利用高德API爬取高德POI数据程序总是假死,求哥哥们救救孩子。