3

geopy用来获取地址列表的纬度/经度坐标。所有文档都指向通过缓存来限制服务器查询(实际上这里有很多问题),但实际上很少有人给出切实可行的解决方案。

实现这一目标的最佳方法是什么?

这是针对我正在从事的独立数据处理工作...不涉及应用程序平台。只是试图减少服务器查询,因为我运行了我以前见过的数据(在我的情况下很可能)。

我的代码如下所示:

from geopy import geocoders
def geocode( address ):
    # address ~= "175 5th Avenue NYC"
    g = geocoders.GoogleV3()
    cache = addressCached( address )

    if ( cache != False ): 
        # We have seen this exact address before,
        # return the saved location
        return cache

    # Otherwise, get a new location from geocoder
    location = g.geocode( address )

    saveToCache( address, location )
    return location

def addressCached( address ):
    # What does this look like?

def saveToCache( address, location ):
    # What does this look like?
4

4 回答 4

5

您希望如何实现缓存实际上取决于您的 Python 代码将在哪个平台上运行。

您需要一个相当持久的“缓存”,因为地址的位置不会经常更改:-),因此数据库(在键值模式下)似乎是最好的。

所以在很多情况下,我会选择sqlite3一个优秀的、非常轻量级的 SQL 引擎,它是 Python 标准库的一部分。除非我可能更喜欢我需要运行的 MySQL 实例,否则一个优点可能是这将允许在不同节点上运行的多个应用程序共享“缓存”——SQL 和非 SQL 的其他 DB 将有利于后者,取决于您的限制和偏好。

但是,如果我在 Google App Engine 上运行,那么我将使用它包含的数据存储。除非我有特定的理由要在多个不同的应用程序之间共享“缓存”,在这种情况下,我可能会考虑替代方案,例如 google cloud sql 和 google storage,以及另一个由专用“缓存服务器”GAE 应用程序组成的替代方案我自己的服务 RESTful 结果(可能带有端点?)。再次选择!非常非常依赖于您的约束和偏好(延迟、每秒查询数大小等)。

因此,请澄清您所在的平台,以及您对数据库“缓存”的其他限制和偏好,然后可以轻松显示实现的非常简单的代码。但是在你澄清之前展示六种不同的可能性不会很有成效。

补充:由于评论建议sqlite3可能是可以接受的,并且代码中最好地显示了一些重要的细节(例如,如何序列化和反序列化 in geopy.location.Location/from sqlite3blob 的实例 - 其他底层数据库很可能会出现类似问题,并且解决方案相似),我决定最好在代码中显示一个解决方案示例。因此,由于“地理缓存”显然最好作为自己的模块实现,我写了以下简单的geocache.py......:

import geopy
import pickle
import sqlite3

class Cache(object):
    def __init__(self, fn='cache.db'):
       self.conn = conn = sqlite3.connect(fn)
       cur = conn.cursor()
       cur.execute('CREATE TABLE IF NOT EXISTS '
                   'Geo ( '
                   'address STRING PRIMARY KEY, '
                   'location BLOB '
                   ')')
       conn.commit()

    def address_cached(self, address):
        cur = self.conn.cursor()
        cur.execute('SELECT location FROM Geo WHERE address=?', (address,))
        res = cur.fetchone()
        if res is None: return False
        return pickle.loads(res[0])

    def save_to_cache(self, address, location):
        cur = self.conn.cursor()
        cur.execute('INSERT INTO Geo(address, location) VALUES(?, ?)',
                    (address, sqlite3.Binary(pickle.dumps(location, -1))))
        self.conn.commit()


if __name__ == '__main__':
    # run a small test in this case
    import pprint

    cache = Cache('test.db')
    address = '1 Murphy St, Sunnyvale, CA'
    location = cache.address_cached(address)
    if location:
        print('was cached: {}\n{}'.format(location, pprint.pformat(location.raw)))
    else:
        print('was not cached, looking up and caching now')
        g = geopy.geocoders.GoogleV3()
        location = g.geocode(address)
        print('found as: {}\n{}'.format(location, pprint.pformat(location.raw)))
        cache.save_to_cache(address, location)
        print('... and now cached.')

我希望这里说明的想法足够清楚——每个设计选择都有替代方案,但我试图让事情保持简单(特别是,我在运行这个模块时使用了一个简单的 example-cum-mini-test直接,代替适当的单元测试套件...)。

关于序列化到/从blob的位,我选择pickle了“最高协议” -1cPickle与 Python 2 或 3 一样好,除非我有特别的理由不这样做:-)。当然,我test.db为测试中使用的 sqlite 数据库使用了不同的文件名,因此您可以毫不犹豫地清除它来测试一些变化,而用于“生产”代码的默认文件名保持不变(它使用相对的文件名一个非常可疑的设计选择 - 意思是“在当前目录中” - 但是决定将此类文件放置在何处的适当方法非常依赖于平台,我没有

如果还有其他问题,请询问(也许最好单独提出一个新问题,因为这个答案已经变得如此之大!-)。

于 2015-02-08T18:52:36.387 回答
2

如何创建一个listdict存储所有地理编码地址的地址?然后你可以简单地检查。

if address in cached:
    //skip
于 2015-02-08T18:53:01.213 回答
2

这个缓存从模块加载的那一刻起就存在,并且在你使用完这个模块后不会被保存。您可能希望将其保存到带有 pickle 的文件或数据库中,并在下次加载模块时加载它。

from geopy import geocoders
cache = {}

def geocode( address ):
    # address ~= "175 5th Avenue NYC"
    g = geocoders.GoogleV3()
    cache = addressCached( address )

    if ( cache != False ): 
        # We have seen this exact address before,
        # return the saved location
        return cache

    # Otherwise, get a new location from geocoder
    location = g.geocode( address )

    saveToCache( address, location )
    return location

def addressCached( address ):
    global cache
    if address in cache:
        return cache[address]
    return None

def saveToCache( address, location ):
    global cache
    cache[address] = location
于 2015-02-08T18:57:03.903 回答
0

这是一个使用 pythonshelve包进行透明和持久缓存的简单实现:

import geopy
import shelve
import time

class CachedGeocoder:
    def __init__(self, source = "Nominatim", geocache = "geocache.db"):
        self.geocoder = getattr(geopy.geocoders, source)()
        self.db = shelve.open(geocache, writeback = True)
        self.ts = time.time()+1.1
    def geocode(self, address):
        if not address in self.db:
            time.sleep(max(1 -(time.time() - self.ts), 0))
            self.ts = time.time()
            self.db[address] = self.geocoder.geocode(address)
        return self.db[address]

geocoder = CachedGeocoder()
print geocoder.geocode("San Francisco, USA")

它存储一个时间戳以确保发出请求的频率不会超过每秒一次(这是 Nominatim 的要求)。一个弱点是它不能处理来自 Nominatim 的超时响应。

于 2019-05-27T10:55:36.840 回答