centrcentrcentr



Собираем базу людей из открытых данных WhatsApp и VK

Тема в разделе "Темы и вопросы о разном", создана пользователем QoB, 9 июл 2015.

  1. QoB

    QoB Пользователь

    [​IMG]
    кадр из фильма Миссия Невыполнима II

    Эта история началась пару месяцев назад, в первый день рождения моего сына. На мой телефон пришло СМС-сообщение с поздравлением и пожеланиями от неизвестного номера. Думаю, если бы это был мой день рождения мне бы хватило наглости отправить в ответ, не совсем культурное, по моему мнению, «Спасибо, а Вы кто?». Однако день рождения не мой, а узнать кто передаёт поздравления было интересно.


    Первый успех

    Было решено попробовать следующий вариант:
    • Добавить неизвестный номер в адресную книгу телефона;
    • Зайти по очереди в приложения, привязанные к номеру (Viber, WhatsApp);
    • Открыть новый чат с вновь созданным контактом и по фотографии определить отправителя.
    Мне повезло и в моём случае в списке контактов Viber рядом с вновь созданным контактом появилась миниатюра фотографии, по которой я, не открывая её целиком, распознал отправителя и удовлетворенный проведенным «расследованием» написал смс с благодарностью за поздравления.

    Сразу же за секундным промежутком эйфории от удачного поиска в голове появилась идея перебором по списку номеров мобильных операторов составить базу [номер_телефона => фото]. А еще через секунду идея пропустить эти фотографии через систему распознавания лиц и связать с другими открытыми данными, например, фотографиями из социальных сетей.

    Естественно на пункте 3 (по фотографии определить отправителя) нас может настигнуть неудача и у этой неудачи есть 6 вариантов:
    • Номер не привязан к WhatsApp или Viber
    • Пользователь есть, но он не устанавливал себе Фото профиля
    • Фото установлено, но настроены параметры «приватности» или «конфиденциальности» («Показывать фото только моим контактам», а Вас в этом списке нет)
    • Вам доступно фото, но на нём изображён кот
    • На фото человек, но черт лица и/или особенностей телосложения не разобрать (отсутствие резкости, маленькое разрешение, затемнённые очки, шляпы, кепки, грим)
    • На фотографии владелец номера; анфас; высокая четкость, но вспомнить этого человека вы не можете
    Четвертый пункт (на фото кот) имеет множество вариантов: это и фотографии знаменитостей, и персонажи мультфильмов, и фотографии еды, автомобилей, оружия и пр.


    Испытания на малых порядках.

    В качестве пробы пера было решено экспериментировать на своём списке контактов. На телефонах с ОС Android фотографии контактов Viber хранит в папке /sdcard/viber/media/User photos в виде [Хэш].jpg. Файлы сохраняются только при условии, что вы общались с данным контактом или хотя бы открывали его профиль и удаляются через некоторое время. Для эксперимента вручную откроем/закроем профили 20-ти пользователей.

    Часть знакомых, для которых были найдены файлы были также найдены в социальных сетях, загружены в программу Picasa с включенной опцией «Распознавать лица автоматически». Распознанные программой лица затем были названы соответственно их владельцам. На следующем шаге скармливаем Picas'e папку с фотографиями из Viber. Для схожих лиц в окне «Пользователи» появляются иконки с вопросительным знаком.

    В моём случае для исходных 20 пользователей Viber Picasa обнаружила только два совпадения. Эти два случая при этом достаточно комично совпали: в каждой паре фотографии различались между собой, но были сняты в один и тот же день (для первого человека на двух фотографиях отличался поворот головы и наличие/отсутствие улыбки, для второго отличалась только улыбка).

    [​IMG]

    Промежуточный итог довольно успешный:
    Выборка из Viber: 20 (все подряд и с котами и с едой)
    Выборка из VK: 5 (только если на фото можно кого-то распознать зрительно)
    Совпадений верных: 2
    Совпадений ложных: 0


    Начинаем перебор

    Попробуем получить данные с помощью телефона. Viber берёт контакты из телефонной книги, которая в свою очередь в ОС Android связана с Google Contacts, которые имеют ограничение в 25 000 номеров на аккаунт. На сегодняшний момент количество номеров мобильных операторов, закрепленных за г. Москва — 93 311 000. Так что идея получить базу таким вот «решением в лоб» отпадает. Тем более, что даже если Viber возьмет хотя бы первую пачку в 25 000 номеров, все равно в каждый профиль нужно зайти, а потом еще связать получившийся файл [Хэш].jpg с конкретным номером (что, наверное, можно отследить по дате создания файла, но это всё равно очень трудоёмко и долго).

    Решение было найдено быстро: в сети есть программная реализация другого популярного сервиса — WhatsApp. Все вызовы идут через php. Есть готовый скрипт регистрации нового пользователя и собственно пример вызова getProfilePicture. Чтобы начать процесс нужен *nix сервер и нужно понять насколько можно обнаглеть с частотой и скоростью запросов. Для эксперимента был написан php код, который авторизуется в Whatsapp и в бесконечном цикле получает/не получает для номеров +7XXXXXXXXXX getProfilePicture и выдаёт на экран временную метку. Этот код при первом и последующих запусках доходил до 220-250 номеров и уходил в timeout — пробуем после каждого 200-го делать паузу sleep(5) — не помогает, всё равно timeout. Пробуем завершить процесс и сразу запустить заново — успех. Соответственно имеем дело либо с ограничением на сервере (необходимо после 200 запросов заново авторизоваться), либо с ошибкой в этой php реализации. Экспериментировать я не стал, а убил двух зайцев, переписав php скрипт, чтобы он обрабатывал только 200 номеров и добавив управляющий скрипт на Bash, который в цикле запускает php с параметром $startPhoneNumber и дожидается его завершения.

    Таким образом получили работающую схему перебора со скоростью 5,7 номеров в секунду.
    Для обработки всей московской ёмкости нам потребуется:

    93 311 000 (номеров) / 5.7 (номеров в секунду) / 60 (секунд) / 60 (минут) / 24 (часа) ~ 190 дней.


    Многопоточность

    Авторизация в WhatsApp идет через связку

    username — номера телефона в формате +7XXXXXXXXXX
    password — полученный через скрипт регистрации (для регистрации нужна работающая симкарта для получения кода подтверждения)
    nickname — может быть любым

    WhatsApp запрещает >1 авторизации на один username одномоментно. В связи с чем в официальном магазине было закуплено три SIM-карты оператора Мегафон по 200р. каждая. С одного и того же сервера на ОС Centos все три номера были зарегистрированы в WhatsApp и на всех трёх было запущено по скрипту с кусочком емкости «Билайн Бизнес». Соотношение количества номеров к сохраненным фотографиям держалось в районе 10 к 1-му; потом разница увеличилась благодаря «сотням» и «тысячам», которые видимо еще не распределены и для всего диапазона не дают ни одного изображения.


    Блокировка

    В лицензионном соглашении WhatsApp (с которым я соглашаюсь, когда прохожу регистрацию) сказано:
    Все мои SIM-карты заблокировали на третий день без объяснения причин. По логам видно, что отключение произошло в 00:22 по Москве по всем картам одновременно (карты успели обработать каждая около 500 000 номеров). Что интересно сервера WhatsApp сначала стали очень долго отвечать на запросы, а потом вообще перестали авторизовывать. На попытку зарегистрировать номер заново сервер отвечает "Failed; Reason: Blocked;". При попытке зарегистрировать WhatsApp по-человечески с телефона выскакивает сообщение: «Извините, вы больше не можете пользоваться сервисом WhatsApp.»

    Кроме того, что все три запущенных скрипта делали одно то же с одной и той же скоростью у них было много общего, чтобы заблокировать их одновременно, а именно: все логинились с одним и тем же nickname «V» и работали с одного и того же IP-адреса.


    Усложняем схему

    [​IMG]

    Считаем, что стали чуть-чуть (а именно на 600р. потраченные на те три SIM) умней. На этот раз покупаем 10 сим карт в «неофициальном магазине» у метро по 100р. за штуку. Усложняем скрипт, создаём массив симкарт и даём каждой свой nickname согласно списку актёров одного замечательного фильма:


    Объявление массива
    Код:
    // username,"password","nickname"
    $SIMDict = array( <br>
    "SIM1" => array(7969XXXXXXX,"123123211231231231231231231=","Zooey"),
    "SIM2" => array(7916XXXXXXX,"123123211231231231231231231=","Martin"),
    "SIM3" => array(7985XXXXXXX,"123123211231231231231231231=","Sam"),
    "SIM4" => array(7916XXXXXXX,"123123211231231231231231231=","Bill"),
    "SIM5" => array(7985XXXXXXX,"123123211231231231231231231=","Mos"),
    "SIM6" => array(7985XXXXXXX,"123123123211231231231231231=","Warwick"),
    "SIM7" => array(7916XXXXXXX,"123123211231231231231231231=","Anna"),
    "SIM8" => array(7985XXXXXXX,"123123123123123123123123121=","John"),
    "SIM9" => array(7,"","Kelly"),
    "SIM10" => array(7,"","Jason")
    );
    

    Заставляем каждую сим карту выходить через отдельный IP адрес
    Команды настройки
    Код:
    LINUX[/b]
    [b]ip netns add net1 && ip link set netns net1 dev eth1
    ip netns exec net1 ip addr add Y.Y.Y.71/24 dev eth1
    ip netns exec net1 ip link set up dev eth1
    ip netns exec net1 ip route add default via Y.Y.Y.1
    ip netns add net2 && ip link set netns net2 dev eth2
    ip netns exec net2 ip addr add Y.Y.Y.72/24 dev eth2
    ip netns exec net2 ip link set up dev eth2
    ip netns exec net2 ip route add default via Y.Y.Y.1 [/b]
    
    [b]CISCO[/b]
    [b]ip nat inside source static Y.Y.Y.71 X.X.X.71
    ip nat inside source static Y.Y.Y.72 X.X.X.72[/b]
    [b]


    Legacy adapter не заработал вообще, поэтому остановимся на 8-ми карточках.

    Теперь скрипт Bash берёт на вход startNumber, endNumber, SIM, NS, где SIM имя симкарты в массиве, NS — имя namespace в котором запустить процесс php. Пример вызова:
    Код:
    ./run.sh 79671380000 79671780000 SIM1 net1 &
    
    Сам скрипт run.sh
    Код:
    ...
    start=$1
    last=$2
    net=$4
    startParam="ip netns exec $net"
    ./pushme.sh "$3 $4" "$start begins"[/b]
    [b]while [ $start -lt $last ]
    do[/b]
    [b]$startParam php ./getProfiles.php instance=$start prefix=$start count=50 sim=$3 net=$4
    #> $3.log
    start=$(( $start + 50 ))[/b]
    [b]done
    ...
    



    Делим емкость на кусочки меньше 2 000 номеров. Также добавляем в Bash и php скрипты Push-уведомления через Pushover («7XXXXX start», «7XXXXX finish», «Houston we have a problem»)

    Последнее отправляем при нестандартных ответах сервера WhatsApp, либо при очень долгом (>5 секунд) отсутствии ответа.

    Забегая вперёд хочу сказать, что WhatsApp хитрее меня и все эти симкарты тоже были заблокированы через некоторое время и тоже почти одновременно. В любом случае 8 000 000 телефонных номеров скрипты всё-таки успешно проработали и у нас на руках 411 279 фотографий. Соотношение 20 к 1. Что уже вполне неплохо.

    [​IMG]
    Выборка из результатов обработки по г. Москва


    Ненецкий автономный округ

    Долгое время не было возможности активировать оставшиеся сим-карты и запустить скрипты. Наконец свободная минута появилась, а вместе с ней силы признать, что начинать перебор с Москвы (население 12 197 596 человек) — это лихо. Поэтому прячем поглубже свой юношеский максимализм и берём менее населенный субъект, например, Ненецкий автономный округ. Почему бы и нет. Запускаем скрипт — и через 12 часов перебор всех емкостей Ненецкого округа завершен. Для 169 995 номеров было найдено всего 2 208 фотографий в WhatsApp. Переходим к следующему этапу.


    ВКонтакте

    Задача: получить все фотографии пользователей VK, у которых указан интересующий нас город.

    Немалое количество времени ушло, чтобы разобраться с VK API. И оказалось, что запрос getuser выполнить можно без проблем, не имея access-token. А вот запрос search (чтобы взять только людей из конкретного города) сработает только с токеном, причём полученным на пользователя, а не на standalone приложение. В этом нам поможет статья <a href="http://habrahabr.ru/sandbox/80273/">Как я получал access token для взаимодействия с vk api. Но даже когда мы получили этот токен — мы утыкаемся в ограничение — получить можно только 1000 пользователей (не 1000 за один запрос, а 1000 всего), при том, что, например, город Нарьян-Мар (Ненецкий АО) указан у 15 660 человек.

    Пробуем пойти через get_users перебором: перебираем всех пользователей вконтакте от $id = 1 до N следующим запросом:

    Код:
    response=$(curl --silent "https:// api.vk.com /method/users.get?user_id=$id&fields=photo_max_orig,country,city")
    
    В сети вконтакте на момент написания этой статьи было N = 300 000 000 пользователей. На один такой запрос без скачивания фото уходила 1 секунда.

    300 000 000 (пользователей) / 1 (в секунду) / 60 (секунд) / 60 (минут) / 24 (часа) ~ 3472 дней.

    Итого около 3472 дней в одном потоке, при условии, что VK не будет блокировать наши IP-адреса. Вариант отбрасываем.

    Возвращаемся к users.search с ограничением на количество в выдаче, но не на «качество» запроса. Поясняю. В запросе можно бесконечно уточнять параметры: например, взять только женщин или мужчин, уменьшив выдачу в два раза, или взять только тех, у кого есть фото, и что самое удобное — взять только людей конкретного возраста, естественно в цикле от $age = 14 до 80. Итого 66 прогонов по 1000, с замечанием, что, если для некоторых возрастов мы перевалим за 1000 — такие запросы мы повторим с дополнительным разделением по полу.

    [​IMG]
    Распределение пользователей VK из г. Нарьян-Мар по возрасту


    Скрипт постепенного скачивания из VK
    Код:
    01: acc_token="abc123abc123abc123abc123abc123abc123abc123abc123"
    02: vk_url="api.vk.com/method/users.search?has_photo=1&count=1000&city=2487&country=1&access_token=$acc_token&fields=photo_max_orig"
    03:
    04: for age in {14..80}
    05: do
    06:    echo -n $id " " >>vk.log
    07:    date +"%T" >>vk.log
    08:    mkdir photos/$id
    09:    list=$(curl --silent "$vk_url&age_from=$age&age_to=$age" | jq '.response | .[] | (.uid|tostring) + " " + .photo_max_orig' | \
    		 sed 's/^"\(.*\)"$/\1/')
    10:    counter=0
    11:    while read -r line; do
    12:	   let counter++
    13:	   arr=($line)
    14:	   echo -n "$counter." >>vk.log
    15:	   photo=${arr[1]}
    16:	   filename=${arr[0]}$(echo $photo | sed "s/.*\(\..*\).*$/\1/")
    17:	   wget $photo -O photos/$id/$filename
    18:    done <<< "$list"
    19:done
    
    На 19 годах VK что-то заподозрили, и стали очень медленно отвечать на запросы. Через полтора часа было скачано 13 967 изображений. У остальных 1 700 человек возраст видимо не был указан вообще. Еще 400 скачанных изображений оказались, по каким-то причинам битыми, либо очень маленькими (<10 кб).


    Распознавание и сравнение лиц

    Как мы уже выяснили среди полученных изображений множество котов и автомобилей, поэтому для начала хочется их отфильтровать. Поможет нам с этим OpenCV (Open Source Computer Vision Library, библиотека компьютерного зрения с открытым исходным кодом).

    Существует множество реализаций, на различных языках программирования, основанных на этой библиотеке. Например, facedetect, которая определяет координаты по которым находится лицо на фото. Она же вместе с ImageMagick вырежет по координатам распознанное лицо в отдельный файл.

    По распознаванию лиц на фото, есть интересная реализация от компании Betaface. У них платное API для большого количества изображений, но есть отличная Для того что-бы видеть ссылки войдите в свой аккаунт, или зарегестрируйтесь., которая распознает всех присутствующих на загруженном фото и кроме прочего определяет пол, наличие усов, очков, улыбки и (женщинам это не понравится) показывает предположительный возраст (мне даёт 27, плюшевому кролику на холодильнике — 42).
    [​IMG]

    Мы же возьмем готовые скрипты на python от английского разработчика Terence Eden. Он написал их в рамках очень Для того что-бы видеть ссылки войдите в свой аккаунт, или зарегестрируйтесь.. В его задаче стояло: скачать открытую коллекцию картин Лондонского музея «Тейт Британия», и распознать на ней изображения людей. Затем с помощью получившейся базы лиц и своей фотографии можно найти картину, на которой изображен человек максимально похожий на тебя. Web API он, к сожалению, не даёт, но все исходники есть на github.

    Корректируем скрипт Тэренса — выключаем скачивание из Британской галереи и удаляем обработку по папкам.

    Скрипт на python
    Код:
    import sys, os
    import cv2
    import urllib
    from urlparse import urlparse
    def detect(path):
    img = cv2.imread(path)
    cascade = cv2.CascadeClassifier("haarcascade_frontalface_alt.xml")
    rects = cascade.detectMultiScale(img, 1.3, 4, cv2.cv.CV_HAAR_SCALE_IMAGE, (20,20))
    if len(rects) == 0:
    return [], img
    rects[:, 2:] += rects[:, :2]
    return rects, img
    def box(rects, img, file_name):
    i = 0 # Track how many faces found
    for x1, y1, x2, y2 in rects:
    print "Found " + str(i) + " face!" # Tell us what's going on
    cut = img[y1:y2, x1:x2] # Defines the rectangle containing a face
    file_name = file_name.replace('.jpg','_') # Prepare the filename
    file_name = file_name + str(i) + '.jpg'
    file_name = file_name.replace('\n','')
    print 'Writing ' + file_name
    cv2.imwrite('detected/' + str(file_name), cut) # Write the file
    i += 1 # Increment the face counter
    def main():
    for filename in os.listdir('whphotos'):
    print filename + " "
    rects, img = detect("whphotos/" + filename)
    box(rects, img, filename)
    os.remove("whphotos/" + filename)
    if __name__ == "__main__":
    main()
    

    Итог обработки:
    13 500 изображений из VK => 6 427 / 5 446 лиц. (с учетом / без учёта случаев с несколькими лицами на одном фото)
    2 208 из WhatsApp => 963 / 876 лиц

    Проглядывается интересная закономерность — каждый второй пользователь не ставит фото на профиль.

    Следом за распознанием идёт создание модели eigenfaces.xml. Скриптом предусмотрено по папке исходных фотографий сформировать XML файл, по которому в будущем будет производиться поиск. Поэтому по одному комплекту готовим такой файл, а из второго потом берём по одному элементу и ищем соответствие. Техника решила за меня какой из комплектов брать за основной: 6 400 фотографий из VK скрипт обработать не смог с ошибкой по памяти "Couldn't allocate over 4GB". Время — 11 ночи — разбираться не охота — пропускаем через скрипт 963 фото из WhatsApp и через 20 минут имеем eigenfaces.xml размером 1.6 ГБ. Можно представить какого бы размера он был в случае с VK. Позже оказалось, переделывая скрипт под себя, я случайно удалил строку идентификации в модели, вместо неё все элементы имели идентификатор «0». Смешно бы было прождать несколько дней выполнения скрипта и только потом это обнаружить. Исправляем ошибку и ждём еще 20 минут.

    Затем для каждого файла из VK запускаем скрипт проверки.
    Код:
    find detectedVK/ -name *.jpg -exec ./myscript.sh {} \;
    #myscript.sh
    python recognise.py detectedWH $1 100000 >> result
    


    `100000` здесь — точность совпадения, при 100 — идеальное совпадение.
    На одно сравнение имеем 40 секунд. 6 400 * 40 ~ 3 дня на одном процессоре. Оставляем на ночь; идем спать — завтра понедельник.

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

    Первое попадание случилось к концу первой тысячи аккаунтов. Девушка Яна из города Нарьян-Мар поставила одинаковую фотографию и в WhatsApp и в VK. Несмотря на то, что фото одинаковые (отличается только разрешение) точность совпадения ~3 000. Для всех результатов с точностью <6000 с помощью convert объединяем совпавшие изображения, чтобы зрительно окончательно принять решение
    Код:
    ...
    if [ "$precise" -lt 6000 ]
      then
        echo $precise $whIMG $vkIMG
        convert detectedWH.bak/$whIMG detectedVK.bak/$vkIMG -append convertResults/${precise}_$whIMG$vkIMG
      fi
    ...
    
    [​IMG]

    Итого к окончанию вторых суток успешно распознаны и привязаны к номеру телефона 25 аккаунтов VK.

    Это 2% от числа найденных лиц WhatsApp, либо 1% от всех изображений.

    [​IMG]
    Результаты анализа по г.Нарьян-Мар

    Я верю, что это отличный результат, и вот почему:
    • У нас маленькие выборки, с большой разницей между ними (16 600 VK против 2 000 WhatsApp);
    • Для поиска совпадений мы использовали только алгоритм Eigenfaces, а можно добавить, например, Fisherfaces;
    • Можно увеличить шансы на распознание добавив по аналогии перебор через Viber;
    • Из VK можно брать не только последнее фото профиля, но и несколько предыдущих;
    • Наконец, к списку социальных сетей можно добавить Facebook, Одноклассники и пр.
    Тут сложно говорить о конкретных цифрах, но можно точно сказать, что процент успешных попаданий значительно увеличится, если соблюсти все пункты. Процессорные мощности для этого конечно тоже надо увеличить в несколько раз.


    Заключение

    К чему это всё. Полезного для общества практического применения полученных данных я пока не придумал. Получилось небольшое исследование ради исследования.

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

    В награду дочитавшим до конца, такой, например, весёлый вариант: Собрать модель eigenfaces.xml по фотографиям известных актрис, российских и зарубежных, и провести перебор на соответствие по полученной из WhatsApp базе москвичей (у нас их на данный момент ~400 000). Не беря во внимание моральную составляющую (а именно, вопрос: культурно ли звонить незнакомым людям), набираем номер и приглашаем в кино. Прикольно ведь пойти в кино с девушкой, которая с точностью 3000 к 100 похожа на главную героиню.

    [​IMG]

    Еще в качестве вывода можно было бы сказать: «Не забывайте ставить галочки в настройках приватности». Но я не большой сторонник параноидальных настроений (" Они знают где я", «Интернет-провайдер знает обо мне всё», «Большой брат следит за нами» и пр.). Меня, например, веселит, когда моя жена выключает сбор анонимных геоданных на телефоне, со словами, что её «найдут» (кто, а главное зачем она пока не придумала).

    Не знаю будет ли меня когда-нибудь кто-нибудь искать — я в своей жизни вроде бы ничего не нарушал (кроме лицензионного соглашения WhatsApp).
     
  2. kuhninazakaz.info

    kuhninazakaz.info Пользователь Пользователь

    Сообщения:
    6
    Симпатии:
    0