<- Работа с Yelp API и добавление недостающих данных из Google Places API

Ali Aliyev [2016-03-27]

Введение

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

К сожалению Yelp предоставляет неполные данные о компаниях, которые он отдает через свой API. Для решения данной проблемы мы воспользуемся услугами Google Places API

В данной статье я расскажу как работать с Yelp и Google Places API, которые будут дополнять данными друг друга. Создадим простую API обертку на Django и задеплоим проект на Heroku.

Пару слов о Yelp API

Для работы с Yelp API нужно получить ключи по адресу предварительно необходимо создать аккаунт.

Так же нам пригодится пакет python-yelp-v2, который можно установить через пакетный менеджер pip

pip install python-yelp-v2

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

Давайте создадим функцию, которая возвращает экземпляр объекта Yelp. На протяжении всей статьи мы будем использовать данную функцию.

import yelp

YELP_API_KEY = {
    'consumer_key': '',
    'consumer_secret': '',
    'access_token_key': '',
    'access_token_secret': '',
}

def get_yelp_instance(yelp_key):
    yelp_instance = yelp.Api(**yelp_key)
    return yelp_instance

yelp_instance = get_yelp_instance(YELP_API_KEY)

Я надеюсь вы уже установили пакет python-yelp-v2, пришло время найти компанию по ключевому слову. Обязательным параметром для поиска является location. Список locations можно получить по адресу

yelp_instance.Search(location='US')

Параметр offset указывает на смещение. Его можно использовать для получения следующей страницы.

Очень важен параметр limit, который указывает на количество компаний на смещение. Максимальное количество компаний на страницу равно 20, максимальное смещение равно 50. Если offset будет превышен вы получите сообщение об ошибки. Для расчета смещения можно умножить общий лимит на номер страницы, которую вы хотите получить. Расчет смещения начинается с нуля, по умолчанию если не указывать данный параметр он всегда равен нулю.

limit = 20
page = 1
offset = page * limit
yelp_instance.Search(location='US', limit=limit, offset=offset)

Дополнительно Yelp API поддерживает параметры term для поиска по ключевому слову и category_filter, для фильтрации по категориям. Для фильтрации по категориям необходимо использовать машинное имя категории которое находится по адресу

yelp_instance.Search(location='US', term="google", category_filter="gokarts")

Я думаю мы уже достаточно подробно разобрали основы работы с Yelp API. Пришло время познакомиться с Google Places API.

Пару слов о Google Places API

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

Чтобы получить API токен, для работы с Google Places достаточно зайти по адресу https://console.developers.google.com/ создать API токен и привязать его к вашему приложению.

Для работы с Google Places API мы будем использовать пакет python-google-places, который необходимо предварительно установить через pip:

pip install python-google-places

Так же как и с Yelp API мы создадим функцию, которой будет возвращаться экземпляр класса GooglePlaces:

import googleplaces

GOOGLE_API_KEY = ""

def get_google_instance(google_key):
    google_instance = googleplaces.GooglePlaces(google_key)
    return google_instance

google_instance = get_google_instance(GOOGLE_API_KEY)

Для получения более подробной информации о компании через Google Places мы будем использовать ширину и долготу компании, а так же ее название из Yelp, чтобы повысить точность поиска:

yelp_results = yelp_instance.Search(location='US', term="google", category_filter="gokarts")

data = []

for business in yelp_results.businesses:
    places = google_instance.text_search(
        business.name,
        lat_lng={
            'lat': business.location.coordinate['latitude'],
            'lng': business.location.coordinate['longitude']
        }
    )

    company = {}
    for place in places.places:
        place.get_details()
        company.update(place.details)

    data.append(company)

Стоит так же рассказать о лимитах Google Places. На самом деле, после создания токена количество запросов равно 2000 в сутки. Но количество запросов может быть повышено до 250 000 если добавить кредитную карту в свой аккаунт.

Создаем Django проект

Пришло время создать простой проект на Django. Мы воспользуемся заготовкой django-skeleton, чтобы сэкономить время на настройку Django проекта.

django-admin startproject yelp-api-example --template=https://github.com/aliev/django-skeleton/archive/master.zip
cd yelp-api-example

Теперь создадим виртуальное окружение и активируем его, добавим пакеты, необходимые для работы с google places и yelp api в requirements.txt:

virtualenv .env
source .env/bin/activate
echo "python-yelp-v2==0.5.7" >> requirements.txt
echo "python-google-places==1.1.0" >> requirements.txt
pip install -r requirements.txt

У нас должна получиться такая структура проекта

├── AUTHORS.md
├── Procfile
├── README.md
├── log
│   ├── README.md
│   └── error.log
├── requirements.txt
└── src
    ├── __init__.py
    ├── apps
    │   ├── __init__.py
    │   └── core
    │       ├── __init__.py
    │       ├── apps.py
    │       ├── forms.py
    │       ├── models.py
    │       ├── templates
    │       │   └── index.html
    │       ├── urls.py
    │       ├── utils.py
    │       └── views.py
    ├── conf
    │   ├── __init__.py
    │   ├── base.py
    │   ├── local.example.py
    │   └── local.py
    ├── db
    │   ├── README.md
    │   └── development.db
    ├── manage.py
    ├── static
    │   └── js
    │       └── app.js
    ├── templates
    │   └── base.html
    ├── urls.py
    └── wsgi.py

Наш стартовый Django проект почти полностью готов для работы. Для полной уверенности, что все работает, развернем проект на Heroku. В django-skeleton уже есть готовые настройки для Heroku, нужно просто создать приложение.

git init .
heroku create
git add .
git commit -m 'first commit'
git push heroku master

Последние настройки, которые нам необходимо сделать - установить API ключи для Google Places и Yelp в виртуальное окружение Heroku. Для этого заходим в Dashboard нашего аккаунта, и выбираем приложение, которое создала команда heroku, открываем Settings и нажимаем на кнопку Reveral config vars. Устанавливаем значения для Config Vars как показано на картинке:

Наше Django приложение должно знать о наших API ключах. Откроем файл src/conf/base.py и пропишем следующее:

YELP_API_KEY = {
    'consumer_key': os.getenv('consumer_key'),
    'consumer_secret': os.getenv('consumer_secret'),
    'access_token_key': os.getenv('access_token_key'),
    'access_token_secret': os.getenv('access_token_secret'),
}

GOOGLE_API_KEY = os.getenv('GOOGLE_API_KEY')

Хранить ключи API или пароли в проекте - очень плохая идея, поэтому пусть их местом будет виртуальное окружение сервера.

Для того, чтобы проект подхватил ключи во время локального тестирования проекта создайте в домашней директории файл ~/.env.secret со следующим содержимым:

export access_token_key = 'your yelp access token'
export access_token_secret = 'your yelp token secret'
export consumer_key = 'your yelp consumer key'
export consumer_secret = 'your yelp consumer secret'
export GOOGLE_API_KEY = 'your google api key'

Теперь перед запуском проекта достаточно ввести команду source ~/.env.secret и ваш Django проект автоматически подтянет ключи API.

source ~/.env.secret

Пришло время создать форму поиска. Формы удобны тем, что в них уже встроена валидация и нам не придется писать для этого много ненужного кода. Создадим форму поиска в файле src/core/forms.py

from django import forms


class ApiListForm(forms.Form):
    category_filter = forms.CharField(required=False)
    location = forms.CharField(required=True, max_length=2)
    term = forms.CharField(required=False)
    offset = forms.IntegerField(required=False)
    limit = forms.IntegerField(required=False)

Нам понадобятся также функции, возвращающие экземпляры классов GooglePlaces и yelp.Api, описание которых мы сделали вначале статьи. Давайте перенесем их в src/core/utils.py для удобства

import yelp
import googleplaces

def get_yelp_instance(yelp_key):
    yelp_instance = yelp.Api(**yelp_key)
    return yelp_instance


def get_google_instance(google_key):
    google_instance = googleplaces.GooglePlaces(google_key)
    return google_instance

Пришло время реализовать наше представление. Для начала создадим базовое представление, от которого мы будем наследоваться в файле src/core/views.py.

import json
from django.views.generic import View
from django.http import HttpResponse


class ApiFormView(View):

    def get_data(self, cleaned_data):
        return cleaned_data

    def get(self, request, *args, **kwargs):
        data = {}
        form = self.form_class(self.request.GET)
        if form.is_valid():
            status_code = 200
            data['success'] = True
            try:
                data['data'] = self.get_data(form.cleaned_data)
            except Exception as e:
                data['errors'] = e.message
                data['success'] = False
                status_code = 400
        else:
            status_code = 400
            data['errors'] = form._errors
            data['success'] = False


        return HttpResponse(json.dumps(data, cls=DecimalEncoder),
                            status=status_code,
                            content_type='application/json')

Данное представление реализует метод get, в котором происходит отрисовка формы. В случае, если валидация формы не прошла клиенту вернется ответ со статусом 400 и JSON с описанием ошибок полей формы. Если же валидация прошла успешно будет возвращен результат работы метода get_data, который мы будем реализовывать в нашем дочернем классе.

Реализуем дочерний класс ApiListView, который будет возвращать результат ответа из Google Places API и Yelp API:

import json
from django.conf import settings
from .forms import ApiListForm
from .utils import get_yelp_instance, get_google_instance, DecimalEncoder


class ApiListView(ApiFormView):
    form_class = ApiListForm

    def get_data(self, cleaned_data):
        cleaned_data = super(ApiListView, self).get_data(cleaned_data)

        category_filter = cleaned_data.get('category_filter')
        location = cleaned_data.get('location')
        term = cleaned_data.get('term')
        page = cleaned_data.get('offset') or 0
        limit = cleaned_data.get('limit') or 20

        data = []

        _offset_end = 50
        _page_size = limit

        google_instance = get_google_instance(settings.GOOGLE_API_KEY)
        yelp_instance = get_yelp_instance(settings.YELP_API_KEY)

        kwargs = {
            'location': cleaned_data.get('location'),
            'limit': _page_size,
            'offset': _page_size * page
        }

        if category_filter:
            kwargs.update({'category_filter': category_filter})

        if term:
            kwargs.update({'term': term})

        yelp_response = yelp_instance.Search(**kwargs)

        for business in yelp_response.businesses:
            places = google_instance.text_search(
                business.name,
                lat_lng={
                    'lat': business.location.coordinate['latitude'],
                    'lng': business.location.coordinate['longitude']
                }
            )

            company = {}
            for place in places.places:
                place.get_details()
                company.update(place.details)

            data.append(company)

        return data

Полный исходный код проекта можно посмотреть тут

Tweet to @ali_aliev