python

Скачиваем историю переписки со всеми пользователями ВКонтакте с помощью Python

  • суббота, 1 апреля 2017 г. в 03:14:36
https://habrahabr.ru/post/325368/
  • Python
  • API


Для лингвистического исследования мне понадобился корпус прямой речи, порожденной одним человеком. Я решил, что для начала удобнее всего использовать собственную переписку в ВК. Это статья о том, как скачать все сообщения, которые Вы когда-либо отправляли своим друзьям, используя программу на Python и API ВКонтакте. Для работы с API будем использовать библиотеку vk.

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

Итак, токен получен. Импортируем необходимые библиотеки (time и re понадобятся нам позже), подключимся к нашему приложению и начнем работу.

import vk
import time
import re

session = vk.Session(access_token='your_token')
vkapi = vk.API(session)

Так как мы хотим получить переписку со всеми друзьями, начнем с получения списка друзей. Дальнейшая обработка полного списка друзей может оказаться довольно долгой, поэтому для тестирования можно написать id нескольких друзей вручную.

friends = vkapi('friends.get') # получение всего списка друзей для пользователя
# friends = [1111111, 2222222, 33333333] # задаем друзей вручную 

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

def get_dialogs(user_id):
	dialogs = vkapi('messages.getDialogs', user_id=user_id)
	return dialogs

Такая функция возвращает «шапку» диалога с пользователем, id которого равен указанному user_id. Результат её работы выглядит приблизительно так:

[96, {'title': ' ... ', 'body': '', 'mid': 333333, 'read_state': 1, 'uid': 111111, 'date': 1490182267, 'fwd_messages': [{'date': 1490173134, 'body': 'Не, ну все это и так понятно, но нам же там жить.', 'uid': 222222}], 'out': 0}]

В полученном списке содержится количество сообщений (96) и данные последнего сообщения в диалоге. Теперь у нас есть всё необходимое, чтобы скачать нужные диалоги.

Основное неудобство состоит в том, что ВКонтакте позволяет делать максимум около трех запросов в секунду, поэтому после каждого запроса нужно какое-то время ждать. Для этого нам и нужна библиотека time. Самое маленькое время ожидания, которое мне удавалось поставить, чтобы не получить отказ через несколько операций — 0.3 секунды.

Другая сложность в том, что за один запрос можно скачать максимум 200 сообщений. С этим тоже придется бороться. Напишем функцию.

def get_history(friends, sleep_time=0.3):
	all_history = []
	i = 0
	for friend in friends:
		friend_dialog = get_dialogs(friend)
		time.sleep(sleep_time)
		dialog_len = friend_dialog[0]
		friend_history = []
		if dialog_len > 200:
			resid = dialog_len
			offset = 0
			while resid > 0:
				friend_history += vkapi('messages.getHistory', 
					user_id=friend, 
					count=200, 
					offset=offset)
				time.sleep(sleep_time)
				resid -= 200
				offset += 200
				if resid > 0:
					print('--processing', friend, ':', resid, 
						'of', dialog_len, 'messages left')
			all_history += friend_history
		i +=1
		print('processed', i, 'friends of', len(friends))
	return all_history

Разберемся, что здесь происходит.

Мы проходим по списку друзей и получаем диалог с каждым из них. Рассматриваем длину диалога. Если диалог короче, чем 200 сообщений, просто переходим к следующему другу, если длиннее, то скачиваем первые 200 сообщений (аргумент count), добавляем их в историю сообщений для данного друга и рассчитываем, сколько еще сообщений осталось скачать (resid). До тех пор пока остаток больше 0, при каждой итерации увеличиваем аргумент offset, который позволяет задать отступ в количестве сообщений от конца диалога, на 200.

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

N.B.: у метода messages.get есть аргумент out, с помощью которого можно попросить сервер отдавать только исходящие сообщения. Я решил не использовать его и выделить нужные мне сообщения уже после скачивания по следующим причинам: а) файл все равно придется очищать, т.к. сервер отдает каждое сообщение виде словаря, содержащего много технической информации и б) сообщения собеседников тоже могут представлять интерес для моего исследования.

Каждое скачанное сообщение является словарем и выглядит примерно вот так:
{'read_state': 1, 'date': 1354794668, 'body': 'Вот так!<br>Потому что тут модель вышла довольно непонятная.', 'uid': 111111, 'mid': 222222, 'from_id': 111111, 'out': 1}

Далее осталось только очистить результат и сохранить его в файл. Эта часть работы уже не относится к взаимодействию с VK API, поэтому я не буду останавливаться на ней подробно. Да и что тут рассказывать — просто выбираем нужные элементы (body) для нужного пользователя и с помощью re удаляем переносы строк, которые отмечены тегом <br>. Сохраняем все в файл.

Полностью код программы выглядит вот так:

import vk
import time
import re

session = vk.Session(access_token='your_token')
vkapi = vk.API(session)

SELF_ID = 111111
SLEEP_TIME = 0.3

friends = vkapi('friends.get') # получение всего списка друзей для текущего пользователя

def get_dialogs(user_id):
	dialogs = vkapi('messages.getDialogs', user_id=user_id)
	return dialogs

def get_history(friends, sleep_time=0.3):
	all_history = []
	i = 0
	for friend in friends:
		friend_dialog = get_dialogs(friend)
		time.sleep(sleep_time)
		dialog_len = friend_dialog[0]
		friend_history = []
		if dialog_len > 200:
			resid = dialog_len
			offset = 0
			while resid > 0:
				friend_history += vkapi('messages.getHistory', 
					user_id=friend, 
					count=200, 
					offset=offset)
				time.sleep(sleep_time)
				resid -= 200
				offset += 200
				if resid > 0:
					print('--processing', friend, ':', resid, 
						'of', dialog_len, 'messages left')
			all_history += friend_history
		i +=1
		print('processed', i, 'friends of', len(friends))
	return all_history

def get_messages_for_user(data, user_id):
	self_messages = []
	for dialog in data:
		if type(dialog) == dict:
			if dialog['uid'] == user_id and dialog['from_id'] == user_id:
				m_text = re.sub("<br>", " ", dialog['body'])
				self_messages.append(m_text)
	print('Extracted', len(self_messages), 'messages in total')
	return self_messages

def save_to_file(data, file_name='output.txt'):
	with open(file_name, 'w', encoding='utf-8') as f:
	    print(data, file=f)

if __name__ == '__main__':
	all_history = get_history(friends, SLEEP_TIME)
	save_to_file(all_history, 'raw.txt')

	self_messages = get_messages_for_user(all_history, SELF_ID)
	save_to_file(self_messages, 'sm_corpus.txt')

На момент запуска программы у меня в ВК было 879 друзей. На их обработку потребовалось около 25 минут. Файл с необработанным результатом имел объем 74MB. После выделения текста только моих сообщений — 15MB. Всего сообщений в полученном корпусе — около 150 000, а их текст занимает 3707 страниц (в вордовском документе).

Надеюсь, моя статья окажется для кого-то полезной. Все методы, которые можно использовать для обращения к API ВК, детально описаны в разделе для разработчиков ВКонтакте.