我正在使用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?
答案 0 :(得分:5)
您希望如何实现缓存实际上取决于您的Python代码将在哪个平台上运行。
你想要一个非常持久的"缓存"因为地址'地点不会经常变化:-),所以数据库(以键值心情)似乎最好。
因此,在很多情况下,我选择sqlite3
,这是一个优秀的,非常轻量级的SQL引擎,它是Python标准库的一部分。除非我喜欢,例如我需要运行的MySQL实例,但一个优点可能是这将允许在不同节点上运行的多个应用程序共享"缓存" - 其他DB(SQL和非DB)对后者都有好处,具体取决于您的约束和偏好。
但是,如果我在谷歌应用引擎上运行,那么我将使用它包含的数据存储。除非我有特定的理由想要分享"缓存"在多个不同的应用程序中,在这种情况下,我可能会考虑替代方案,如谷歌云sql和谷歌存储,以及另一个替代品,包括专用的"缓存服务器"我自己提供RESTful结果的GAE应用程序(也许是端点?)。选择是,再次!非常非常依赖于您的约束和偏好(延迟,每秒查询大小等等)。
因此,请说明您所处的平台,以及您对数据库"缓存"的其他限制和偏好,然后可以轻松显示非常简单的实现代码。但在澄清之前显示六种不同的可能性并不会很有效。
已添加:由于评论提示sqlite3
可能是可以接受的,并且代码中最好显示了一些重要的详细信息(例如,如何将geopy.location.Location
的实例序列化和反序列化为sqlite3
blob - 其他底层数据库可能会出现类似的问题,并且解决方案类似),我决定在代码中最好地展示解决方案示例。所以,作为" geo cache"显然最好作为自己的模块实现,我写了以下简单的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.')
我希望这里所说的想法足够清晰 - 每种设计选择都有其他选择,但我试图保持简单(特别是,我使用一个简单的例子 - 暨迷你 - 直接运行此模块时测试,代替适当的单元测试套件......)。
关于序列化到/从blob的位置,我已选择pickle
使用"最高协议" (-1
)协议 - cPickle
当然在Python 2中会更好(更快:-)但是这些天我尝试编写的代码与Python 2一样好或者3,除非我有具体的理由否则:-)。当然,我为测试中使用的sqlite数据库使用了不同的文件名test.db
,因此您可以毫无疑问地将其擦除以测试某些变体,而默认文件名意味着在&中使用#34;生产"代码保持不变(是非常可疑的设计选择,使用相对的文件名 - 意思是"在当前目录中#34; - 但是以适当的方式来决定放置这样一个文件的位置与平台有关,我不想在这里进入这样的exoterica: - )。
如果还有其他任何问题,请询问(或许最好单独提出一个新问题,因为这个答案已经变得如此之大!)。
答案 1 :(得分:2)
如何创建存储所有地理编码地址的list
或dict
?然后你可以简单地检查一下。
if address in cached:
//skip
答案 2 :(得分: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
答案 3 :(得分:0)
这是使用python shelve
包进行透明和持久缓存的简单实现:
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的超时响应。