#!/usr/bin/env python # coding: utf-8 # # Web-scraping: сбор данных из баз данных и интернет-источников # # *Алла Тамбовцева, НИУ ВШЭ* # # ## Работа с API ВКонтакте: собираем посты со стены # Загружаем модули и библиотеки, необходимые для работы: # In[1]: import requests import time import pandas as pd # Для начала давайте посмотрим на документацию API и посмотрим, как к нему формировать запросы: https://dev.vk.com/api/api-requests. # В прошлый раз по инструкции мы получили доступ к API, вспомним шаги. # In[2]: # вводим id своего приложения # и проходим по ссылке с этим id app_id = input("Enter your client id: ") 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" print(url) # In[3]: # копируем токен доступа token = input("Enter your token here: ") # На этом практическом занятии мы будем выгружать посты из сообщества [Цитатник ВШЭ](https://vk.com/hseteachers). Сохраним в переменные версию API, ссылку для метода работы со стеной сообщества и название сообщества: # In[4]: v = "5.131" main_wall = "https://api.vk.com/method/wall.get" domain = "hseteachers" # Функция `get()` из библиотеки `requests` умеет подставлять в запрос необходимые параметры и объединять их с помощью `?` и `&`. Сохраним необходимые параметры в виде словаря: # In[5]: params_wall = {"access_token" : token, "domain" : domain, "count" : 100, "v" : v} # А теперь сформируем запрос и выгрузим результаты в формате JSON – в Python данные в таком формате будут представлены в виде словаря: # In[6]: req_wall = requests.get(main_wall, params = params_wall) json_wall = req_wall.json() # json_wall # Извлечём из этого большого словаря элемент, который хранит непосредственно результаты – список из маленьких словарей с информацией о постах (1 словарь = 1 пост): # In[8]: items_wall = json_wall['response']['items'] # Посмотрим на один элемент такого списка: # In[9]: i = items_wall[0] i # Поработаем с ним! # ### Задача 1 # # Извлечь из элемента `i` следующие компоненты: # # * id поста; # * дата поста; # * текст поста; # * число лайков; # * число репостов; # * число просмотров; # * число комментариев. # In[15]: print(i["id"], i["date"], i["text"]) print(i["likes"]["count"], i["reposts"]["count"], i["views"]["count"], i["comments"]["count"]) # ### Задача 2 # # Напишите функцию `get_posts()`, которая принимает на вход словарь, аналогичный сохранённому в `i`, и возвращает список из следующих характеристик: # # * id поста; # * дата поста; # * текст поста; # * число лайков; # * число репостов; # * число просмотров; # * число комментариев. # In[52]: # добавляем ловлю исключений # на случай, если какие-то характеристики будут отсутствовать def get_posts(i): try: id_ = i["id"] date = i["date"] text = i["text"] likes = i["likes"]["count"] repos = i["reposts"]["count"] views = i["views"]["count"] comments = i["comments"]["count"] except: id_ = i["id"] date = i["date"] text = "" likes = 0 repos = 0 views = 0 comments = 0 L = [id_, date, text, likes, repos, views, comments] return L # ### Задача 3 # # Примените функцию `get_posts()` ко всем элементам списка `items_wall` и сохраните полученные результаты в список `posts`. # In[47]: posts = [] for i in items_wall: p = get_posts(i) posts.append(p) # ### Задача 4 # # Прочитайте в документации к API ВКонтакте про аргумент `offset` в методе `wall.get`. Используя полученную информацию и блоки кода ниже, выгрузите и сохраните в список `items_more` данные ещё по 9200 постам на стене сообщества. # # **Подсказка:** чтобы расширять список правильным образом, используйте метод `.extend()`, а не `.append()`, он добавляет не один элемент, а сразу несколько (см. примеры ниже). # In[19]: # с append() A = [] for i in range(5): B = [1, 2, 3] A.append(B) print(A) # In[20]: # с extend() A = [] for i in range(5): B = [1, 2, 3] A.extend(B) print(A) # In[29]: params_wall_long = {"access_token" : token, "domain" : domain, "count" : 100, "offset" : 100, "v" : v} # In[31]: items_more = [] for i in range(92): req_wall_long = requests.get(main_wall, params = params_wall_long) json_wall_long = req_wall_long.json() items_wall_long = json_wall_long['response']['items'] items_more.extend(items_wall_long) params_wall_long["offset"] = params_wall_long["offset"] + 100 time.sleep(1.5) print(i) # Теперь извлечём из каждого элемента `items_more` нужную информацию и расширим список `posts`, который у нас уже был до этого: # In[48]: for i in items_more: p = get_posts(i) posts.append(p) # In[49]: len(posts) # все идёт по плану # In[41]: posts[202] # Преобразуем результат в датафрейм, добавим названия столбцов: # In[50]: dat = pd.DataFrame(posts) dat.columns = ["id", "timestamp", "post", "likes", "reposts", "views", "comments"] # Несколько строк датафрейма для примера: # In[51]: dat.head() # Разобьём текст поста по `#`, чтобы извлечь тэги: # In[54]: dat["post"][10] # In[55]: with_tags = dat["post"].str.split("#", expand = True) with_tags # Основная информация – это первые два тэга, имя преподавателя и курс (по крайней мере, в большинстве случаев это так). Заберём для дальнейшей работы только их: # In[56]: small = with_tags.loc[:, 0:2] small.columns = ["text", "teacher", "course"] # Склеим датафрейм `dat` с датафреймом `small` по столбцам: # In[57]: final = pd.concat([dat, small], axis = 1) # Заполним пропуски – добавим «пустой» текст в ячейки, где нет никаких значений: # In[58]: final = final.fillna("") # Избавимся от лишних пробелов и отступов в текстовых данных: # In[59]: final["text"] = final["text"].apply(lambda x: x.strip()) final["teacher"] = final["teacher"].apply(lambda x: x.strip()) final["course"] = final["course"].apply(lambda x: x.strip()) # Осталось поработать с форматом времени в столбце `timestamp`. # In[60]: final["timestamp"] # In[61]: t = final["timestamp"][0] t # Импортируем из модуля `datetime` функцию `datetime`, она поможет нам получить дату и время в привычном формате: # In[62]: from datetime import datetime # In[63]: datetime.utcfromtimestamp(t) # In[64]: datetime.utcfromtimestamp(t).strftime('%Y-%m-%d %H:%M:%S') # Напишем функцию для преобразования временной метки: # In[65]: def time_transform(t): r = datetime.fromtimestamp(t).strftime('%Y-%m-%d %H:%M:%S') return r # Применим её ко всем элементам столбца: # In[66]: final["datetime"] = final["timestamp"].apply(time_transform) # In[67]: final.head() # Теперь можем разбить дату-время по пробелу, чтобы получить отдельные столбцы с датой и временем (механизм нам уже известен, мы разбивали пост по `#` выше): # In[68]: dt = final["datetime"].str.split(" ", expand = True) dt.columns = ["date", "time"] # In[69]: final2 = pd.concat([final, dt], axis = 1) # In[70]: final2 # In[71]: final2.to_excel("posts.xlsx") # In[74]: final2[final2["course"] == "Психология"] # In[77]: final2[final2["teacher"].str.contains("тамб")] # In[ ]: