2015-02-08 3 views
2

Я использую geopy, чтобы получить координаты lat/long для списка адресов. Вся документация указывает на ограничение запросов на сервер путем кеширования (на самом деле здесь много вопросов), но мало кто дает практические решения.Самый простой способ кеширования данных геокодирования

Каков наилучший способ для этого?

Это для самостоятельной работы по обработке данных, над которой я работаю ... нет платформы приложения. Просто пытаюсь сократить серверные запросы, когда я просматриваю данные, которые я видел раньше (скорее всего, в моем случае).

Мой код выглядит следующим образом:

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, который мне нужно запустить в любом случае, одно из преимуществ может заключаться в том, что это позволит нескольким приложениям, работающим на разных узлах, совместно использовать «кеш» - другие БД, как SQL, так и non, были бы хороши для последний, в зависимости от ваших ограничений и предпочтений.

Но если бы я работал в Google App Engine, я бы использовал вместо него хранилище данных. Если у меня не было особых причин, чтобы поделиться «кешем» между несколькими разрозненными приложениями, в этом случае я мог бы рассмотреть альтернативы, такие как Google Cloud sql и хранилище google, а также еще одну альтернативу, которая все еще состоит из специального приложения GAE для кеша моих собственных результатов RESTful (возможно, с конечными точками?). Выбор снова, очень! Очень, очень зависит от ваших ограничений и предпочтений (латентность, размер запросов в секундах и т. Д. И т. Д.).

Итак, пожалуйста, уточните, на какой платформе вы находитесь, и какие другие ограничения и предпочтения у вас есть для вашего кеша базы данных, а затем очень простой код для реализации, который можно легко показать. Но показ полдюжины различных возможностей, прежде чем вы уточните, не будет очень продуктивным.

Добавлено: поскольку комментарии предполагают sqlite3 может быть приемлемым, и есть несколько важных деталей лучше всего показано в коде (например, как для сериализации и десериализации экземпляра geopy.location.Location в/из sqlite3 сгустка - подобные вопросы могут хорошо возникают с другими базовыми базами данных, и решения аналогичны), я решил, что пример решения лучше всего показывать в коде. Так что, как «кэш-гео» явно лучше реализован как ее собственный модуль, я написал следующую простую 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.') 

Я надеюсь, что идеи, показанные здесь достаточно ясно - есть альтернативы по каждому дизайн, но я пытался упростить задачу (в частности, я использую простой пример-cum-mini-test, когда этот модуль запускается напрямую, вместо соответствующего набора модульных тестов ...) ,

Для немного о сериализации в/из сгустков, я выбрал pickle с «высшим протоколом» (-1) протокол - cPickle конечно было бы так же хорошо в Python 2 (и быстрее :-), но они дней Я пытаюсь написать код, который одинаково хорош как Python 2 или 3, если у меня нет конкретных причин делать иначе :-).И, конечно же, я использую другое имя файла test.db для базы данных sqlite, используемой в тесте, поэтому вы можете стереть ее без каких-либо проблем, чтобы проверить некоторые варианты, в то время как имя файла по умолчанию, предназначенное для использования в «производственном» коде, остается неизменным (это довольно сомнительный выбор дизайна для использования относительного имени файла, что означает «в текущем каталоге», - но подходящий способ решить, где разместить такой файл, зависит от платформы, и я не хотел Входите в такую ​​экзотерию здесь :-).

Если у вас остался другой вопрос, пожалуйста, спросите (возможно, лучше по отдельному новому вопросу, так как этот ответ уже стал таким большим!).

+0

@cslstr, SQLite является наиболее естественным решением для меня - в памяти ДИКТ загружается при запуске ж/рассола и сбрасывал в конце программы можно рассматривать как «простой», но это требует неограниченного, растущее количества памяти и времени запуска/завершения работы с течением времени, а «кеш» растет, поэтому я считаю это довольно плохой архитектурой для долговременной программы. –

+0

Это были мои проблемы, просто сохраняя список/dict в памяти, не был уверен, насколько они повлияют на вещи. +1 для просмотра опций. – cslstr

2

Как насчет создания list или dict, в котором хранятся все геокодированные адреса? Тогда вы могли бы просто проверить.

if address in cached: 
    //skip 
+0

Я бы сохранил список через ... pickle? Или похожие? Кэш должен сохраняться с помощью нескольких исполнений. – cslstr

+0

Соленые огурцы могут быть способ пойти, но я должен признать, что мне не хватает опыта работы с ними. Из-за этого я лично пошел с [JSON] (https://docs.python.org/3.4/library/json.html), но это не обязательно лучшее решение. – Klaster

+0

@ alex-martelli Я уверен, что SQL DB - это аккуратное решение, но для его установки требуется некоторое время, особенно если вы этого не сделали. – Klaster

2

Этот кэш будет жить с момента модуль загружается и не будут сохранены после того, как вы закончите с помощью этого модуля. Вероятно, вы захотите сохранить его в файл с рассолом или в базу данных и загрузить его при следующем загрузке модуля.

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