{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Python для сбора и анализа данных\n", "*Алла Тамбовцева, НИУ ВШЭ*\n", "\n", "## Работа с API ВКонтакте: собираем посты со стены" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Загружаем модули и библиотеки, необходимые для работы:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import requests\n", "import time\n", "import pandas as pd" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Для начала давайте посмотрим на документацию API и посмотрим, как к нему формировать запросы: https://dev.vk.com/api/api-requests." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "В прошлый раз по инструкции мы получили доступ к API, вспомним шаги." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# вводим id своего приложения\n", "# и проходим по ссылке с этим id\n", "\n", "app_id = input(\"Enter your client id: \")\n", "url = f\"https://oauth.vk.com/authorize?client_id={app_id}&display=page&redirect_uri=http://oauth.vk.com/blank.html&scope=all&response_type=token\"\n", "print(url)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# копируем токен доступа\n", "\n", "token = input(\"Enter your token here: \")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "На этом практическом занятии мы будем выгружать посты из сообщества [Цитатник ВШЭ](https://vk.com/hseteachers). Сохраним в переменные версию API, ссылку для метода работы со стеной сообщества и название сообщества:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "v = \"5.131\"\n", "main_wall = \"https://api.vk.com/method/wall.get\"\n", "domain = \"hseteachers\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Функция `get()` из библиотеки `requests` умеет подставлять в запрос необходимые параметры и объединять их с помощью `?` и `&`. Сохраним необходимые параметры в виде словаря:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "params_wall = {\"access_token\" : token, \n", " \"domain\" : domain, \n", " \"count\" : 100,\n", " \"v\" : v}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "А теперь сформируем запрос и выгрузим результаты в формате JSON – в Python данные в таком формате будут представлены в виде словаря:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "req_wall = requests.get(main_wall, params = params_wall)\n", "json_wall = req_wall.json()\n", "# json_wall" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Извлечём из этого большого словаря элемент, который хранит непосредственно результаты – список из маленьких словарей с информацией о постах (1 словарь = 1 пост):" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "items_wall = json_wall['response']['items']" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Посмотрим на один элемент такого списка:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "i = items_wall[0]\n", "i" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Поработаем с ним!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Задача 1\n", "\n", "Извлечь из элемента `i` следующие компоненты:\n", "\n", "* id поста;\n", "* дата поста;\n", "* текст поста;\n", "* число лайков;\n", "* число репостов;\n", "* число просмотров;\n", "* число комментариев." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# YOUR CODE HERE" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Задача 2\n", "\n", "Напишите функцию `get_posts()`, которая принимает на вход словарь, аналогичный сохранённому в `i`, и возвращает список из следующих характеристик:\n", "\n", "* id поста;\n", "* дата поста;\n", "* текст поста;\n", "* число лайков;\n", "* число репостов;\n", "* число просмотров;\n", "* число комментариев." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# YOUR CODE HERE" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Задача 3\n", "\n", "Примените функцию `get_posts()` ко всем элементам списка `items_wall` и сохраните полученные результаты в список `posts`. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# YOUR CODE HERE" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Задача 4\n", "\n", "Прочитайте в документации к API ВКонтакте про аргумент `offset` в методе `wall.get`. Используя полученную информацию и блоки кода ниже, выгрузите и сохраните в список `items_more` данные по всем постам на стене сообщества.\n", "\n", "**Подсказка:** чтобы расширять список правильным образом, используйте метод `.extend()`, а не `.append()`, он добавляет не один элемент, а сразу несколько (см. примеры ниже)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# с append()\n", "\n", "A = []\n", "for i in range(5):\n", " B = [1, 2, 3]\n", " A.append(B)\n", "print(A)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# с extend()\n", "\n", "A = []\n", "for i in range(5):\n", " B = [1, 2, 3]\n", " A.extend(B)\n", "print(A)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "params_wall_long = {\"access_token\" : token, \n", " \"domain\" : domain, \n", " \"count\" : 100,\n", " \"offset\" : 100,\n", " \"v\" : v}" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# CHANGE CODE HERE\n", "\n", "items_more = [] \n", "\n", "req_wall_long = requests.get(main_wall, params = params_wall_long)\n", "json_wall_long = req_wall_long.json()\n", "items_wall_long = json_wall_long['response']['items']" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Теперь извлечём из каждого элемента `items_more` нужную информацию и расширим список `posts`, который у нас уже был до этого:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "for i in items_more:\n", " p = get_posts(i)\n", " posts.append(p)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "len(posts) # все идёт по плану" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Преобразуем результат в датафрейм, добавим названия столбцов:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "dat = pd.DataFrame(posts)\n", "dat.columns = [\"id\", \"timestamp\", \"post\", \"likes\", \n", " \"reposts\", \"views\", \"comments\"]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Несколько строк датафрейма для примера:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "dat.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Разобьём текст поста по `#`, чтобы извлечь тэги:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "with_tags = dat[\"post\"].str.split(\"#\", expand = True)\n", "with_tags" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Основная информация – это первые два тэга, имя преподавателя и курс (по крайней мере, в большинстве случаев это так). Заберём для дальнейшей работы только их:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "small = with_tags.loc[:, 0:2]\n", "small.columns = [\"text\", \"teacher\", \"course\"]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Склеим датафрейм `dat` с датафреймом `small` по столбцам:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "final = pd.concat([dat, small], axis = 1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Заполним пропуски – добавим «пустой» текст в ячейки, где нет никаких значений:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "final = final.fillna(\"\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Избавимся от лишних пробелов и отступов в текстовых данных:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "final[\"text\"] = final[\"text\"].apply(lambda x: x.strip())\n", "final[\"teacher\"] = final[\"teacher\"].apply(lambda x: x.strip())\n", "final[\"course\"] = final[\"course\"].apply(lambda x: x.strip())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Осталось поработать с форматом времени в столбце `timestamp`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "final[\"timestamp\"]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "t = final[\"timestamp\"][0]\n", "t" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Импортируем из модуля `datetime` функцию `datetime`, она поможет нам получить дату и время в привычном формате:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from datetime import datetime" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "datetime.utcfromtimestamp(t)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "datetime.utcfromtimestamp(t).strftime('%Y-%m-%d %H:%M:%S')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Напишем функцию для преобразования временной метки:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def time_transform(t):\n", " r = datetime.fromtimestamp(t).strftime('%Y-%m-%d %H:%M:%S')\n", " return r" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Применим её ко всем элементам столбца:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "final[\"datetime\"] = final[\"timestamp\"].apply(time_transform)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Теперь можем разбить дату-время по пробелу, чтобы получить отдельные столбцы с датой и временем (механизм нам уже известен, мы разбивали пост по `#` выше):" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "dt = final[\"datetime\"].str.split(\" \", expand = True)\n", "dt.columns = [\"date\", \"time\"]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "final = pd.concat([final, dt], axis = 1)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 2 }