Выявление мошеннических сборов в Instagram

от автора

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

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

Для получения содержимого постов будем использовать Python и библиотеку InstaLoader. Это инструмент, который позволяет в автоматическом режиме получать публикации, комментарии, метаданные и многое другое из Instagram, при этом некоторые функции доступны даже без авторизации. Его можно использовать как в режиме командной строки, так и в виде библиотеки Python.

Библиотека позволяет искать и получать данные множеством разных способов. Так, возможно скачать данные конкретного профиля целиком, включая фото-, видеоконтент, истории, комментарии, а также метаданные, информацию о подписчиках пользователя и его подписках, при этом для такого способа получения данных доступен «режим паузы» — можно приостановить процесс скачивания и в дальнейшем запустить его с того же места. Интересный факт: в метаданных, получаемых библиотекой, можно найти результат работы внутренних алгоритмов Instagram, которые генерируют текстовые описания фотографий («на картинке два человека, мужчина и женщина» и т.п.). Для просмотренных мною фотографий такие описания были на удивление точны.

Однако, в нашем случае мы не знаем, с каких профилей потребуются данные, поэтому воспользуемся другой опцией – поиск по хештегам. При работе с Python взаимодействие с Instagram ведется через класс Instaloader(), поэтому первым шагом мы создаем его экземпляр и при необходимости задаем параметры.

bot = instaloader.Instaloader() bot.download_videos = False bot.download_location = False bot.download_pictures = False bot.download_geotags = False bot.download_video_thumbnails = False bot.filename_pattern = '{shortcode}'

Как отмечалось ранее, часть функционала библиотеки доступны без авторизации, в том числе можно скачивать посты. Однако на практике Instagram блокирует клиента после определенного числа запросов, и в случае работы без авторизации этот лимит достигается гораздо быстрее, в особенности, если ваш IP-адрес уже был «засвечен» или провайдер использует NAT – запросы будут суммироваться, иногда не удается получить вообще ничего. Поэтому лучше авторизоваться сразу, но можно работать в «анонимном» режиме до получения исключения LoginRequired и залогиниться уже после него. Я бы не рекомендовал использовать данные своего личного аккаунта ввиду риска его блокировки. Для поиска по хештегам будет используем следующую функцию. Функционал библиотеки предусматривает вывод как топ-постов, так и всех подряд. Контент в постах бывает трех типов – фото, видео и «карусель» — несколько медиафайлов двух предыдущих типов. Видео мы анализировать не будем, поэтому исключаем такие посты из рассмотрения.

def GetPostsFromHashtag(hashtag_name:str, max_count:int, top_posts:bool=False):     """     Функция загружает заданное количество постов по хештегу     """              hashtag = instaloader.Hashtag.from_name(bot.context, hashtag_name)          if top_posts:         posts = hashtag.get_top_posts()     else:         posts = hashtag.get_posts()
    while max_count > 0:         clear_output(wait=True)         max_count -= 1         post = next(posts)         if post.typename != 'GraphVideo':             bot.download_post(post, pathlib.Path(f'{media_folder}/{post.owner_username}'))         else:             continue 

Теперь среди загруженных выберем публикации, в тексте которых присутствуют номера платежных карт и (опционально) номера телефонов. Для этого используются регулярные выражения – номера карт состоят из 16 цифр, а телефоны – из 8, при этом опытным путем выявлено, что авторы указывают их в формате хештегов, предваряя символом #. Это тоже учтем при написании выражения.

Из выбранных постов извлекается их текст, полный URL и уникальный идентификатор поста (shortcode), который понадобится нам на следующем шаге. Эти коды легко получить, поскольку на этапе скачивания библиотека назначает их как имя папки, в которые помещается содержимое соответствующего поста (этот параметр настраиваемый, shortcode установлен по умолчанию).

Также для внутренних целей нам были нужны временные метки с фотографий, они берутся из json-файла, который скачивается библиотекой в виде архива xz (архивирование можно отключить)

Следующая функция ответственна за описанную обработку данных:

this_id = 1 for profile in os.listdir(media_folder):     profile_dir = os.path.join(media_folder, profile)     for file in os.listdir(profile_dir):         if pathlib.Path(file).suffix == '.txt':             full_file_path = os.path.join(profile_dir, file)             with open(full_file_path) as current_file:                 post_text = current_file.read()                 cards=re.findall('\d{16}', post_text)                 phones = re.findall('#8\d{10}', post_text)             json_filename = os.path.join(profile_dir, pathlib.Path(file).stem+'.json.xz')             if os.path.isfile(json_filename):                 with lzma.open(json_filename) as f:                     json_file = json.load(f)                     time = datetime.datetime.utcfromtimestamp(json_file['node']['taken_at_timestamp']).strftime('%Y-%m-%d %H:%M:%S')             if cards and cards not in data['cards']:                 data['id'].append(this_id)                 data['post_id'].append(pathlib.Path(file).stem)                 data['post_link'].append('https://instagram.com/p/'+pathlib.Path(file).stem)                 data['full_text'].append(post_text)                 data['time'].append(time)                 if len(cards) == 1:                     data['cards'].append(*cards)                 else:                     data['cards'].append(tuple(cards))                 if phones:                     data['phones'].append(phones)                 else:                     data['phones'].append('None')                 this_id += 1

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

bot.download_pictures = True bot.download_comments = False bot.save_metadata = False

Получение изображений выглядит так: на предыдущем шаге в словарь записывался в том числе id поста (shortcode). Преобразуем словарь в pandas dataframe с последующим удалением дубликатов. Этот датафрейм также можно легко экспортировать в Excel.

df = pd.DataFrame(data) df = df.drop_duplicates(subset='cards').reset_index(drop=True)

Теперь скачаем медиафайлы постов используя еще одну возможность библиотеки – получение поста по его короткому коду.

for idx, post_id in enumerate(df['post_id'], 1):     this_post = instaloader.Post.from_shortcode(bot.context, post_id)     bot.download_post(this_post, pathlib.Path(f'selected_media/{idx}'))

Отфильтруем текстовые файлы – они нам не нужны

for profile in os.listdir('selected_media'):     profile_dir = os.path.join('selected_media', profile)     for file in os.listdir(profile_dir):         if pathlib.Path(file).suffix == '.txt':             os.remove(os.path.join('selected_media', profile, file))

Получим список всех файлов в папке с изображениями:

def getListOfFiles(dirName):     # create a list of file and sub directories      # names in the given directory      listOfFile = os.listdir(dirName)     allFiles = list()     # Iterate over all the entries     for entry in listOfFile:         # Create full path         fullPath = os.path.join(dirName, entry)         # If entry is a directory then get the list of files in this directory          if os.path.isdir(fullPath):             allFiles = allFiles + getListOfFiles(fullPath)         else:             allFiles.append(fullPath)                      return sorted(allFiles, key=lambda item: int(item.split('/')[-2]))

Теперь запускаем распознавание текста. Для этого используются библиотеки Tesseract и OpenCV.

ocr_results = defaultdict(list)  for img in files:   this_idx = int(img.split('/')[-2])   this_img = cv2.imread(img)   this_gray_img = cv2.cvtColor(this_img, cv2.COLOR_BGR2GRAY)   tmp_file = 'tmp.png'   cv2.imwrite(tmp_file, this_gray_img)   this_txt = pytesseract.image_to_string(Image.open(tmp_file), lang='rus')   ocr_results[this_idx].append(this_txt)   os.remove(tmp_file)

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

max_len = max(len(value) for value in ocr_results.values()) ocr_dict = defaultdict(lambda: [np.nan]*max_len) for key, value in ocr_results.items():   if not value:     ocr_dict[key]   else:     for idx, text in enumerate(value):       ocr_dict[key][idx] = text

Используя регулярные выражения, уберем спецсимволы из текстов и затем экспортируем в Excel

ocr_processed = {key: ['None' if subitem is np.nan else re.sub(r'\W+', ' ', subitem) for subitem in value] for key, value in ocr_dict.items()} df = pd.DataFrame(ocr_processed).T df.to_excel("insta_ocr.xlsx")

На выходе получаем сводный файл – таблицу из потенциально мошеннических постов и соответствующих им банковских карт и номеров телефонов. Аналитики банка могут отобрать эмитированные им карты, к примеру, по БИН, и проверить транзакции.

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


ссылка на оригинал статьи https://habr.com/ru/articles/569864/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *