吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 3485|回复: 12
收起左侧

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

[复制链接]
最爱橙子汁 发表于 2020-3-19 00:30
本帖最后由 最爱橙子汁 于 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数据写入数据库。




我以为武汉市为例,写了如下代码:
[Python] 纯文本查看 复制代码
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['id'], poi['name'], poi["address"], poi['typecode'],
                                     poi['location'].split(',')[0], poi['location'].split(',')[1],
                                     poi['pcode'], poi['pname'], poi['citycode'], poi['cityname'], poi['adcode'], poi['adname']]

                            if [] in value:
                                value[value.index([])]=''

                            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'])
        if  data_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太多了
还有前面几个朋友说的递归问题,理论上倒也不至于出现栈溢出,不过我还是改用了队列来实现

贴一下我改过运行的代码
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['id'], poi['name'], poi["address"],
                             poi['typecode'], poi['location'].split(',')[0],
                             poi['location'].split(',')[1], poi['pcode'],
                             poi['pname'], poi['citycode'], poi['cityname'],
                             poi['adcode'], poi['adname']]

                    if [] in value:
                        value[value.index([])] = ''
                    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 = [rec_A, rec_B, rec_C, rec_D]
                    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
抢个沙发,是否行数达到了上限?

免费评分

参与人数 1热心值 +1 收起 理由
最爱橙子汁 + 1 谢谢@Thanks!

查看全部评分

带色的小马甲 发表于 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)方法里面,有这么一段

[Python] 纯文本查看 复制代码
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'])     #返回数据的数量
         if  data_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太多了
还有前面几个朋 ...

大佬,您把武汉的数据爬完了么?我看了下接口的配额已经到上限了。
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-26 18:53

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表