<?xml version="1.0" encoding="utf-8"?> 
<rss version="2.0">

<channel>

<title>Блог об аналитике, визуализации данных, data science и BI, заметки с тегом: recommendation</title>
<link>http://test.leftjoin.ru/tags/recommendation/</link>
<description></description>
<generator>E2 (v3365; Aegea)</generator>

<item>
<title>Где поесть? Куда сходить? Ищем ответ на вопрос с помощью пары рекомендаций и скрипта Python</title>
<guid isPermaLink="false">131</guid>
<link>http://test.leftjoin.ru/all/recommendations-from-geotags/</link>
<comments>http://test.leftjoin.ru/all/recommendations-from-geotags/</comments>
<description>
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/roman-bozhko-OXXsAafHDeo-unsplash.jpg" border="0" width="100%" height="100%"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Поскольку наш блог придерживается технологий, аналитики и IT-тематики, то обсуждение политики мы здесь, естественно опустим. Однако, сложно не заметить, что многие сейчас целенаправленно или волей случая оказываются в незнакомых городах и странах. Или планируют переезд, или просто путешествуют. В любом случае, где бы вы ни оказались, всегда хочется выстроить быт, сходить на завтрак или выпить вкусный кофе. Если вам интересно, чем в этих вопросах может помочь наш скрипт и проверить его в действии — продолжайте читать.&lt;/p&gt;
&lt;h2&gt;Что мы придумали?&lt;/h2&gt;
&lt;p&gt;Итак, предположим, что вы оказались в незнакомом городе. У вас есть несколько рекомендаций от друзей или просто несколько проверенных мест, где вам вкусно и красиво.&lt;br /&gt;
Наш алгоритм может быстро увеличить этот список в несколько раз, дополнив его 5-10 рекомендациями того же качества или уровня. Звучит здорово, да?&lt;/p&gt;
&lt;h2&gt;Как мы это реализовали?&lt;/h2&gt;
&lt;p&gt;Мы все еще не умеем колдовать, поэтому решили прибегнуть к более простому способу — написать Python-скрипт для решения этой задачи.&lt;br /&gt;
Начинаем, как всегда, с подготовки. Самая важная шестеренка в нашем скрипте — &lt;a href="https://github.com/adw0rd/instagrapi"&gt;Instagram API&lt;/a&gt; (деятельность социальной сети признана экстремистской и запрещена в Российской Федерации).&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;from instagrapi import Client
import time
import pandas as pd&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Затем, подключаемся к API, чтобы приступить к обработке данных.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;cl = Client()
cl.login(&amp;quot;username&amp;quot;, &amp;quot;password&amp;quot;)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Наша задача реализуется двумя небольшими скриптами. Первый собирает и обрабатывает геометки из Instagram (деятельность этой социальной сети признана экстремистской и запрещена в Российской Федерации), а также находит людей, которые отмечали эти геометки на фотографиях. Мы знаем, что эти места наверняка нравятся не только нам и предполагаем, что мы нашли тех людей, которые разделяют наши вкусы и ценности. Поэтому, мы собираем все недавние геометки в их профиле и так получаем список рекомендаций. Затем, с помощью второго скрипта мы узнаем точные адреса этих мест в Яндекс.Картах, чтобы определиться с выбором.&lt;/p&gt;
&lt;h2&gt;Сбор и обработка данных&lt;/h2&gt;
&lt;p&gt;Начинаем работу. Для того чтобы получить список рекомендаций нам нужны три подходящих примера, например кофейни Санкт-Петербурга: &lt;a href="https://www.instagram.com/smenacafe/"&gt;Смена&lt;/a&gt;, &lt;a href="https://www.instagram.com/coffee.tchk.spb/"&gt;ТЧК&lt;/a&gt;, &lt;a href="https://www.instagram.com/civil.coffeebar/?hl=ru"&gt;Civil&lt;/a&gt;. Наш скрипт принимает на вход идентификаторы геоточек заведений.&lt;br /&gt;
Как их получить?&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;Переходим по ссылке в профиль заведения, например:&lt;br /&gt;
&lt;a href="https://www.instagram.com/smenacafe/"&gt;https://www.instagram.com/smenacafe/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Анализируем геометки в постах (считаем, что официальный профиль содержит правильные геометки)&lt;/li&gt;
&lt;li&gt;Находим ссылку на геометку, например:&lt;br /&gt;
&lt;a href="https://www.instagram.com/explore/locations/727911037416015/smenacafe/"&gt;https://www.instagram.com/explore/locations/727911037416015/smenacafe/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Цифры в ссылке и есть идентификатор геоточки&lt;br /&gt;
&lt;b&gt;727911037416015&lt;/b&gt;&lt;br /&gt;
После того как мы нашли геометки первичных рекомендаций, нам нужно найти тех пользователей, кто ходит в это заведение (= отмечает его на своих фотографиях). Для этого мы берем последние 150 отметок заведения.&lt;br /&gt;
Конечно, это должны быть реальные пользователи, а не бизнес-аккаунты, потому что там могут присутствовать рекламные интеграции, а мы в них не заинтересованы.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;pk_place_ids = [541835746291313, 2103750586526340, 100059475]
 
print('Getting users started')
 
# получаем пользователей, которые отметили заведение на фото
users = []
for i in pk_place_ids:
    # получаем 150 последних постов с выбранной геометкой
    medias = cl.location_medias_recent(i, amount=150)
    
    for m in medias:
        user_id = m.dict()['user']['pk']
        if not user_id in users:
            users.append(user_id)
    
count_users = len(users)
 
print(f'Getting {count_users} users finished')
 
print('Getting not business users started')
 
# отбираем тех пользователей, чьи аккаунты не являются комерческими
users_not_business = []
for u in users:
    try:        
        u_info = cl.user_info(u).dict()
        
        if not u_info['is_business']:
            users_not_business.append(u)
    except:
        next
 
count_nb_users = len(users_not_business)
 
print(f'Getting {count_nb_users} not business users finished')
 
print('Getting location started')&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Мы нашли людей, которые отмечали эти места у себя в профиле. Теперь мы собираем все места, которые отмечены в профилях этих людей и выводим эти места списком, сортируя по числу упоминаний.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;# получаем места, которые посетил пользователь
locations = {}
 
end_cursor = None
for u in users_not_business:
    # скрипт работает довольно медленно, поэтому анализируем только 100 последних постов пользователя
    # посты получаем порциями 20 раз по 5 с сохранением курсора
    for page in range(20):
        u_medias, end_cursor = cl.user_medias_paginated(u, 5, end_cursor=end_cursor)
        for m in u_medias:
            # обернул в обработку исключений, т.к. иногда парсер падает
            try:
                # задержка для снижения чатсоты запросов в инстаграм
                time.sleep(1)
                # по идентификатору поста получаем данные поста (важно, что есть имя места, но нет его координат)
                info = cl.media_info(m.dict()['pk']).dict()
                if 'location' in info:               
                    loc_key = info['location']['pk']
                    
                    # вывод имени отмеченного места (для лога)
                    #print(info['location']['name'])
                    
                    # если место встретилось первый раз, то узнаем его координаты
                    if loc_key not in locations:
                        
                        # для того, чтобы узнать координаты, берем последний пост с такой геометкой
                        loc_data = cl.location_medias_recent(loc_key, amount=1)[0].dict()
                        
                        lng=''
                        lat=''
                        
                        if 'location' in loc_data:
                            lng=loc_data['location']['lng']
                            lat=loc_data['location']['lat']
                        
                        locations[info['location']['pk']] = [info['location']['name'],1,lng,lat] 
                    else:
                        locations[info['location']['pk']][1] = locations[info['location']['pk']][1] + 1
                    
                    # сохраняем результат в csv файл
                    ids = [i for i in locations]
                    names = [locations[i][0] for i in locations]
                    vizits = [locations[i][1] for i in locations]
                    lngs = [locations[i][2] for i in locations]
                    lats = [locations[i][3] for i in locations]
 
                    df = pd.DataFrame(
                        {'id': ids,
                        'name': names,
                        'vizit': vizits,     
                        'lng': lngs,
                        'lat': lats     
                        })
 
                    df.sort_values('vizit', ascending=False).to_csv('places.csv', index=False)                  
                    
            except:
                next
                
count_locations = len(locations)
 
print(f'Getting {count_locations} location finished')&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Второй скрипт должен находить координаты новых рекомендаций в Яндекс.Картах.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;# получим категории из справочника организаций сервиса Яндекс.Карты
import requests
 
# ключ API можно найти в кабинете разработчика
api_key = &amp;quot;---&amp;quot;
 
addrs = []
urls = []
cat1s = []
cat2s = []
cat3s = []
 
df = pd.read_csv(&amp;quot;places.csv&amp;quot;)
 
for i, row in df.iterrows():
    
    time.sleep(1)
    
    lng = row['lng']
    lat = row['lat']
    name = row['name']
    print(name)
        
    req = f&amp;quot;https://search-maps.yandex.ru/v1/?text={name}&amp;amp;results=1&amp;amp;type=biz&amp;amp;lang=ru_RU&amp;amp;ll={lng},{lat}&amp;amp;spn=0.01,0.01&amp;amp;apikey={api_key}&amp;quot;
 
    response = requests.get(req)
 
    addr = ''
    url = ''
    cat1 = ''
    cat2 = ''
    cat3 = ''
 
    if response.status_code == 200:
        # обернули в обработку исключений, т.к. иногда падает
        try:
            company_data = response.json()['features'][0]['properties']['CompanyMetaData']
                        
            addr = company_data['address']
            url = company_data['url']
            
            count_categories = len(company_data['Categories'])
            
            # у организации может быть до 3 категорий, сохраняем их все
            if count_categories &amp;gt; 0:
                if count_categories == 1:
                    cat1 = company_data['Categories'][0]['name']
                elif count_categories == 2:
                    cat1 = company_data['Categories'][0]['name']
                    cat2 = company_data['Categories'][1]['name']
                elif count_categories == 3:
                    cat1 = company_data['Categories'][0]['name']
                    cat2 = company_data['Categories'][1]['name']
                    cat3 = company_data['Categories'][2]['name']             
            
        except:
            pass
        
    addrs.append(addr)
    urls.append(url)
    cat1s.append(cat1)
    cat2s.append(cat2)
    cat3s.append(cat3)
    
df['address'] = addrs
df['url'] = urls
df['cat1'] = cat1s
df['cat2'] = cat2s
df['cat3'] = cat3s
df.to_csv('places_24.csv', index=False)&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Результаты&lt;/h2&gt;
&lt;p&gt;&lt;b&gt;Выпить кофе в Петербурге&lt;/b&gt;&lt;br /&gt;
Мы решили проверить, как работает скрипт на примере трех наших любимых кофеен в Петербурге. Получилось следующее:&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/--2022-03-22--14.28.10.png" width="2210" height="618" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;&lt;b&gt;Поужинать в Петербурге&lt;/b&gt;&lt;br /&gt;
Также мы протестировали наш скрипт, задав три рекомендации классных ресторанов Питера: &lt;a href="https://www.instagram.com/changcafe/"&gt;Chang&lt;/a&gt;, &lt;a href="https://www.instagram.com/cafe_tiger_lily/?hl=ru"&gt;Tiger Lilly&lt;/a&gt;, &lt;a href="https://www.instagram.com/jackandchan/"&gt;Jack and Chan&lt;/a&gt;.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/--2022-03-22--14.30.03.png" width="1846" height="1020" alt="" /&gt;
&lt;/div&gt;
&lt;h2&gt;Ограничения скрипта&lt;/h2&gt;
&lt;ol start="1"&gt;
&lt;li&gt;Скрипт сбора данных работает недостаточно быстро (примерно 100 геоточек в час).&lt;/li&gt;
&lt;li&gt;У геоточек может быть несколько дублей. Бывают геоточки с одинаковыми названиями, но отличающимися по координатам. Если честно, то по сути в геоточках просто хаос. Поэтому остается много ручной работы на этапе обработки и получения результатов.&lt;/li&gt;
&lt;li&gt;Яндекс отдает данные хорошо: база организаций очень большая, но для большинства объектов заполнена не одна, а целых три категории. У кафе может быть первая категория бар, затем кафе, затем ресторан. В общем, приходится опять же проверять многое вручную.&lt;/li&gt;
&lt;li&gt;Сделать единую процедуру пока не очень получается.&lt;br /&gt;
Пока со скриптом можно работать в два этапа:&lt;br /&gt;
— Первый этап — получение данных из инстаграма (название, частота посещений, координаты) и затем полуавтоматическая чистка от ненужных объектов + объединение дублей (это важно, так как бесплатная квота Яндекс.Карт — 500 запросов в день),&lt;br /&gt;
— Второй этап — получение из Яндекс.Карт категорий, адреса, сайта и затем ручная сверка категорий и выбор наиболее точной категории из трех.&lt;/li&gt;
&lt;/ol&gt;
</description>
<pubDate>Wed, 23 Mar 2022 10:55:34 +0300</pubDate>
</item>


</channel>
</rss>