2015-02-08 98 views
2

我使用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? 

回答

5

正是你要实现你的缓存是如何真正依靠什么平台你的Python代码会被运行。

因为地址的位置不会经常变化,所以你需要一个非常持久的“缓存”:-),所以一个数据库(在键值心情中)似乎是最好的。

因此,在很多情况下,我会选择sqlite3,这是一个优秀的,非常轻量级的SQL引擎,它是Python标准库的一部分。除非可能我更喜欢例如我需要运行的MySQL实例,否则一个优点可能是,这将允许运行在不同节点上的多个应用程序共享“缓存” - 其他DB(包括SQL和非SQL)将对后者取决于你的约束和偏好。

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

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

补充:由于意见建议sqlite3是可以接受的,而且也有代码最佳显示一些重要的细节(比如,如何序列化和从sqlite3 BLOB反序列化的geopy.location.Location实例为/ - 类似的问题可能与其他底层数据库一起出现,并且解决方案相似),我决定在代码中最好地显示解决方案示例。所以,作为“地理缓存”显然是最好的,因为它自己的模块来实现,我写了下面简单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.') 

我希望在这里说明的想法是很清楚 - 有每个方案设计的选择,但我试图让事情变得简单(特别是,当这个模块直接运行时,我使用了一个简单的示例暨小型测试,代替适当的单元测试套件......) 。

有关序列化到/从斑点,我选择pickle的“最高协议”的位(-1)协议 - 当然cPickle将只是在Python 2为良好(快:-)但这些几天后,我尝试编写与Python 2或3相同的代码,除非我有特殊的理由不这样做:-)。当然,我为测试中使用的sqlite数据库使用了不同的文件名test.db,因此您可以毫无顾虑地将其擦除以测试某些变体,而意图用于“生产”代码的默认文件名保持不变(它相当可疑的设计选择使用相对的文件名 - 这意味着“在当前目录中” - 但决定放置这样的文件的位置的适当方式是相当平台依赖的,我不想在这里进入这样的exoterica :-)。

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

+0

@cslstr,SQLite是最自然的解决方案,我 - 一个内存中加载字典在启动瓦特/泡菜,并在程序结束甩可以被认为是“简单”,但它需要一个无界的,数量不断增加的内存和启动/关闭时间随着时间的推移和“缓存”变大,所以我会将它看作是一个非常糟糕的耐用程序体系结构。 – 2015-02-08 19:02:29

+0

这些是我关心的,只是在记忆中保留一个列表/字典,不知道他们会影响事情多少。 +1用于调查选项。 – cslstr 2015-02-08 19:09:06

2

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

if address in cached: 
    //skip 
+0

我会通过... pickle保存列表?还是类似?缓存需要持续多次执行。 – cslstr 2015-02-08 18:54:11

+0

泡菜可能是一种方式,但我不得不承认,我缺乏与他们合作的经验。由于这个原因,我会亲自去[JSON](https://docs.python.org/3.4/library/json.html),但这不一定是最好的解决方案。 – Klaster 2015-02-08 18:58:09

+0

@ alex-martelli我认为SQL DB是一个很好的解决方案,但首先需要一些时间来设置它,(尤其是)如果你从来没有做过。 – Klaster 2015-02-08 19:09:01

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 
+0

不错,显示代码...现在只需处理缓存缓存:) – cslstr 2015-02-08 19:10:35

相关问题