Отбираем валидные мобильные номера друзей VK на Python



В процессе изучения Python стало интересно попробовать его в связке с API VK. В ВК есть телефонная книга, она показывает телефоны ваших друзей в более-менее удобном формате. Так как далеко не всегда люди охотно оставляют там полые(валидные) номера своих телефонов, мне показалась интересной идея написать скрипт, который отбирал бы только валидные номера моб.телефонов и выдавал бы их отдельной таблицей. Наша телефонная книга будет генерировать csv-файл, который затем можно будет открыть, например, в excel.

Для использования API VK на Python я нагуглил отличную, на мой взгляд, библиотеку с оригинальный названием vk.
Итак, импортируем необходимые модули:

import vk
from time import sleep
from re import sub, findall
from getpass import getpass
from csv import writer, QUOTE_ALL

Создадим класс User с необходимыми методами:

class User(object):
	"""VK User"""
	def __init__(self, login, password):
		self.login = login
		self.password = password
		self.id = ''
	# аторизирует юзера
	def auth(self):
		session = vk.AuthSession(app_id='5340228', user_login=self.login, user_password=self.password)
		api = vk.API(session)
		return api
	# возвращает массив объектов друзей
	def friends(self, api):
                # возвращает в том порядке, в котором расположены в разделе Мои
		user_friends = api.friends.get(user_id=self.id, order='hints')
		return user_friends
	# возвращает количество друзей
	def friends_count(self, api):
		user_friends = User.friends(self, api)
		friends_count = len(user_friends)
		return friends_count
	# возвращает массив данных о юзере
	def info(self, api):
		user = api.users.get(user_id=self.id)
		return user[0]

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

Далее напишем функцию валидатор, которая будет приводить мобильные номера к общему виду. Мне, как жителю Украины, интересно выбирать только украинские моб.номера, которые должны начинаться на «0». Скрипт легко подправить под любой формат.

def norm_mob(str):
	if len(str) != '':
		norm_mob = sub(r'(\s+)?[+]?[-]?', '', str)
		# проверяем строку на наличие в ней только необходимых символов
		right_mob = findall(r'[\d]', norm_mob)
		# если количество знаков в двух строках совпадает, значит это номер телефона
		if (len(right_mob) == len(norm_mob)) and (len(norm_mob) >= 10):
			rev_norm_mob = norm_mob[::-1]
			norm_mob = rev_norm_mob[0:10]
			if norm_mob[::-1][0] == '0':
				return norm_mob[::-1]
	else:
		return False

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

def find_correct_phone_numbers(api, friends, friends_count):
	users_phones = []
	for i in range(0, friends_count):
		cur_user_id = int(friends[i])
		cur_user = api.users.get(user_id=cur_user_id, fields='contacts')
		try:
			# выбираем номер мобильного телефона
			cur_mob = cur_user[0]['mobile_phone']
		except KeyError:
			sleep(0.3)
			continue
		mob = norm_mob(cur_mob)
		if mob:
			# вставим еще одну строку в наш массив
			users_phones.append({
				'user_name': '{} {}'.format(cur_user[0]['first_name'], cur_user[0]['last_name']),
				'user_phone': '8{}'.format(mob)
				})
		sleep(0.4)
	return users_phones

Сохраняем полученный результат.

def saveCSV(data, path):
    with open(path, 'w') as csvfile:
        my_writer = writer(csvfile, delimiter='	', quotechar='"', quoting=QUOTE_ALL)
        my_writer.writerow(('Имя пользователя', 'Номер моб. телефона'))
        for item in data:
        	try:
        		my_writer.writerow((item['user_name'], item['user_phone']))
        	except Exception:
        		my_writer.writerow(('(Ошибка в кодировке)', item['user_phone']))

Добавим функцию, которая считала бы затраченное время.

class Timer(object):
    def __enter__(self):
        self._startTime = time()
    def __exit__(self, type, value, traceback):
    	howLong = time() - self._startTime
    	print("Операция заняла: {:.2f} минут".format(howLong/60))

Ну и заключительный этап, сделаем вызов написанных функций.

def main():
	while True:
		login = input('E-mail: ')
		password = getpass('Password: ')
		try:
			vk_user = User(login, password)
			api = vk_user.auth()
			print('Авторизация выполнена успешно!')
			break
		except Exception:
			print('Вы ввели неверные данные, пожалуйста, повторите попытку.')
	friends = vk_user.friends(api)
	friends_count = vk_user.friends_count(api)
	print('Найдено {} друзей.'.format(friends_count))
	print('Идет выборка мобильных номеров...')
	with Timer() as p:
		users_phones = find_correct_phone_numbers(api, friends, friends_count)
	print('Выборка окончена. Сохранение...')
	saveCSV(users_phones, 'vk_mob.csv')
	print('Данные успешно сохранены.')
if __name__ == '__main__':
	main()

На выходе получаем csv-файл, который можно открыть в excel в формате удобной таблицы.
Поделиться публикацией

Похожие публикации

Комментарии 17
    +6
    Слово «отбираем», в заголовке статьи, лучше бы заменить на «фильтруем» или «получаем».
      –1
      Явно армейский сленг.
      0
      if len(str) != '':
      Очень любопытное сравнение, вы пытаетесь сравнить число со строкой.
      Можно просто так:

      if len(str):
        0
        И еще проще if str: (хотя str не очень хорошее имя для переменной)
          0
          Красота!
        +1
        if len(str) != '': вернет True для любых str. Кроме того, str — встроенный тип, использовать его как имя не стоит. Также внутри функции norm_mob определяется переменная с таким же названием(norm_mob), что, на мой взгляд, не очень красивое решение. Отметил бы еще, что смешивать underscore_case и camelCase тоже не самый лучший подход.
          0
          Долго не мог решить проблему, и в гугле как-то не попадалось на глаза, как взять id текущего пользователя. По счастливой случайности нашел выход — надо передать в качестве аргумента пустую строку.
          При использовании клиентской авторизации в вк id текущего пользователя будет прилетать вместе с access-токеном на redirect uri.
            0
            Как-то вы с классами в процедурном стиле работаете. Состояние можно хранить внутри обьекта класса, тогда не нужно будет его каждый раз передавать в метод класса(это я про api). Также, вы, вроде, инкапсулируете использование api, а потом используете его напрямую в функции find_correct_phone_numbers.

            for i in range(0, friends_count):
            for curr_user_id in friends:
              +3
              У Вас в коде табы. Замените по PEP8 на 4 пробела.
                +4
                Думал, что статья будет все таки про валидные номера, которые именно привязаны к аккаунту. а не указаны в качестве телефона связи. И не понял метод валидации номера телефона
                  0
                  Насколько я понял, валидация происходит по параметру «номер телефона/не номер телефона» по количеству знаков, начальному символу (0) и специальным символам (+, разделители), и не проверяет телефон никаким другим способом (например, доступность). Другими словами, нужна только для того, чтобы отсечь номера вида «телефона-нет», вместо цифр номера. Поправьте меня, если я не прав.
                  0
                  Чем вас не устраивает эта ссылка?
                  vk.com/friends?section=phonebook
                    0
                    Неожиданно наивная статья для Хабра. Однако же молодёжи тоже надо чему-то учиться, хотя бы на таких простых примерах.

                    Несколько полезных ссылок:
                    pypi.python.org/pypi/vk — обёртка API для Python2/3
                    vk.com/dev/methods — описание API Вконтактика
                      0
                      Как-то парсил номера телефонов первых 330 миллионов пользователей VK. Для валидации номеров использовал библиотеку phonenumbers на Python. Библиотека позволяет проверить номер как мировой (с +) так и для страны, дописывая код страны в нужное место. Получение номеров с vk.api заняло где-то 2 недели в 4 потока и день на обработку (убрать текст, малую длину номера и т.д), вышло около 1.5 миллиона записей с привязкой к id.
                        0
                        Можете выложить код?
                          0
                          К сожалению не сохранился…
                          Общий смысл такой:
                          Считывание
                          • функция get_users отправляет запрос на vk api вида api.vk.com/method/users.get?user_ids=1,2,3,...,100&fields=contacts по 100 id за раз, ответ парсит json.loads() и возвращает список словарей.
                          • функция write_flie 1000 раз вызывает get_users с id из своего диапазона раз в полсекунды, полученый список сериализирует и пишет в файл папки(книги) с помощью pickle.dump
                          • функция write_book создает папку, файлы в ней и 100 раз вызывает write_flie для своего диапазона id
                          • главная функция выполняет write_book в pool.apply_async 33 раза, запуская 33 асинхронных пула (фактически только 2 или 4 одновременно, по количеству процессоров, остальные ждут в очереди)

                          Сборка
                          • собираем записи из одного файла книги (100 000 записей) в один датафрейм с помощью pandas, удаляем лишние столбцы, оставляя id и номер телефона, удаляем пустые строки и пишем в датафрейм книги
                          • чистим датафреймы книги от мусора (буквы, знаки и т.д.), пишем его в общий датафрейм. У меня вышло где-то миллион записей

                          Валидация
                          Используя phonenumbers.parse три раза с параметрами страны None, "RU" и "UA" (можно еще стран добавить), записываем номера в три колонки. Потом чистка от совпадений пар (id, number) для трех колонок тем же pandas и запись в один датафрейм на 2 колонки (id, номер). Для одного id могут быть разные номера при валидации для разных стран.

                          Примечание: такая разбивка по файлам и фреймам, и поэтапный процесс сборки в один фрейм нужна для предотвращения вылета программы из-за нехватки памяти.
                            0
                            Еще для нахождения номеров в строке использовал phonenumbers.PhoneNumberMatcher, потому что может быть указано 2 или больше номеров в одной строчке.

                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                      Самое читаемое