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