- PVSM.RU - https://www.pvsm.ru -

Невизуальные методы защиты сайта от спама. Часть 3. Повторы

Продолжение статьи Невизуальные методы защиты сайта от спама [1]

Часть 3. Повторы подстрок

Как уже говорилось, невизуальные методы защиты сайта от спама используют анализ текста. Один из часто встречающихся сигналов спама — это наличие повторяющихся строк. Как всегда, приведённые примеры взяты из реальных данных компании CleanTalk [2].

Поиск таких повторов должен быть минимально ресурсоёмким. Лучше, если он будет вызываться после тестов из 1 и 2 частей статьи, которые отсеют явный спам и приведут текст к виду, пригодному для анализа. Здесь я приведу некоторую статистику, а также пример кода.

1. Пример кода

Используемая нами функция определения самых длинных повторяющихся подстрок сделана по наивному алгоритму, описанному тут http://algolist.manual.ru/search/lrs/naive.php [3]

Пример вывода представлен ниже.

       s  a  l  e     f  o  r     s  a  l  e     f  o  r     s  a  l  e
       0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21

s  0   +  .  .  .  .  .  .  .  .  +  .  .  .  .  .  .  .  .  +  .  .  .
a  1   .  +  .  .  .  .  .  .  .  .  +  .  .  .  .  .  .  .  .  +  .  .
l  2   .  .  +  .  .  .  .  .  .  .  .  +  .  .  .  .  .  .  .  .  +  .
e  3   .  .  .  +  .  .  .  .  .  .  .  .  +  .  .  .  .  .  .  .  .  +
   4   .  .  .  .  +  .  .  .  +  .  .  .  .  +  .  .  .  +  .  .  .  .
f  5   .  .  .  .  .  +  .  .  .  .  .  .  .  .  +  .  .  .  .  .  .  .
o  6   .  .  .  .  .  .  +  .  .  .  .  .  .  .  .  +  .  .  .  .  .  .
r  7   .  .  .  .  .  .  .  +  .  .  .  .  .  .  .  .  +  .  .  .  .  .
   8   .  .  .  .  .  .  .  .  +  .  .  .  .  +  .  .  .  +  .  .  .  .
s  9   .  .  .  .  .  .  .  .  .  +  .  .  .  .  .  .  .  .  +  .  .  .
a 10   .  .  .  .  .  .  .  .  .  .  +  .  .  .  .  .  .  .  .  +  .  .
l 11   .  .  .  .  .  .  .  .  .  .  .  +  .  .  .  .  .  .  .  .  +  .
e 12   .  .  .  .  .  .  .  .  .  .  .  .  +  .  .  .  .  .  .  .  .  +
  13   .  .  .  .  .  .  .  .  .  .  .  .  .  +  .  .  .  +  .  .  .  .
f 14   .  .  .  .  .  .  .  .  .  .  .  .  .  .  +  .  .  .  .  .  .  .
o 15   .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  +  .  .  .  .  .  .
r 16   .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  +  .  .  .  .  .
  17   .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  +  .  .  .  .
s 18   .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  +  .  .  .
a 19   .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  +  .  .
l 20   .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  +  .
e 21   .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  +

$VAR1 = {
          'sale' => 3,
          'for sale' => 2
        };

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

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

А вот и сама функция на Perl с минимальными изменениями. Для удобства приведён полный текст, выводящий матрицу выше.

#!/usr/bin/perl -w

use strict;
use utf8;
use Data::Dumper;

binmode(STDOUT, ':utf8');

my $min_longest_repeat_length = 4;

my $message = 'sale for sale for sale';
my %longest_repeates = ();

get_longest_repeates($message, %longest_repeates);
print Dumper(%longest_repeates);

sub get_longest_repeates {
    my $test_ref = shift;			# Ссылка на текст для анализа
    my $reps_ref = shift;			# Ссылка на хэш результата

    my @symbols = split //, $$test_ref;
    my $m_len = scalar @symbols;

    my @matrix = ();				# Квадратная матрица совпадений символов

    # Заполнение матрицы справа от главной диагонали
    for (my $i = 0; $i < $m_len; $i++) {	# Строки
	$matrix[$i] = [];
	for (my $j = $i; $j < $m_len; $j++) {	# Столбцы только справа от главной диагонали
	    $matrix[$i][$j] = 1 if $symbols[$i] eq $symbols[$j];
	}
    }

    # Анализ диагоналей матрицы справа от главной диагонали и заполнение результата
    my %repeats_tmp = ();			# Хэш повторов
    my ($i, $j);

    # Перебор диагоналей справа налево, т.е. от коротких повторений к длинным
    for ($i = $m_len - 1; $i > 0; $i--) {	
	my $repeat = '';
	my $repeat_pos = undef;
	my $repeat_temp;

	for ($j = $i; $j < $m_len; $j++) {
	    if (defined($matrix[$j-$i][$j]) && $matrix[$j-$i][$j] == 1) {
		$repeat_temp = $repeat;
		$repeat_temp =~ s/^ //;
               # Если полученная строка повтора уже есть в хэше повторов
		if (defined($repeats_tmp{$repeat_temp})) {
		    $repeat_pos = $j - length($repeat_temp);
		    $repeats_tmp{$repeat_temp}{$repeat_pos} = 1;
		    $repeat = $symbols[$j];
		} else {
		    $repeat .= $symbols[$j];
		}
	    } else {
		if ($repeat ne '') {
		    $repeat =~ s/^ //;
		    $repeat_pos = $j - length($repeat);
		    if (length($repeat) >= $min_longest_repeat_length) {
			if (defined($repeats_tmp{$repeat})) {
			    $repeats_tmp{$repeat}{$repeat_pos} = 1;
			} else {
			    $repeats_tmp{$repeat} = {$repeat_pos => 1};
			}
		    }
		    $repeat = '';
		}
	    }
	}
	if ($repeat ne '') {
	    $repeat =~ s/^ //;
	    $repeat_pos = $j - length($repeat);
	    if (length($repeat) >= $min_longest_repeat_length) {
		if (defined($repeats_tmp{$repeat})) {
		    $repeats_tmp{$repeat}{$repeat_pos} = 1;
		} else {
		    $repeats_tmp{$repeat} = {$repeat_pos => 1};
		}
	    }
	    $repeat = '';
	}
    }

    foreach (keys %repeats_tmp){
	$$reps_ref{$_} = 1 + scalar keys %{$repeats_tmp{$_}};
    }

    # Вывод матрицы для диагностики
    print "n";
    print '     ';
    for (my $i = 0; $i < $m_len; $i++) {
	print '  ' . $symbols[$i];
    }
    print "n";
    print '     ';
    for (my $i = 0; $i < $m_len; $i++) {
	printf '%3d', $i;
    }
    print "n";
    print "n";
    for (my $i = 0; $i < $m_len; $i++) {
	print $symbols[$i];
	printf '%3d ', $i;
	for (my $j = 0; $j < $m_len; $j++) {
	    my $value = '.';
	    $value = '+' if (defined $matrix[$i][$j] && $matrix[$i][$j] == 1);
	    printf('  %1s', $value);
	}
	print "n";
    }
    print "n";
}

2. Статистика повторов

Нами был подобран порог минимальной длины повтора (его я не привожу специально), давший максимальную эффективность в тестах. Его результаты по числу повторов таковы:

Число повторов В спаме, % В не спаме, %
2 78,58 90,28
3 11,93 4,86
4 4,45 2,08
5 2,30 1,39
6 1,93 0
7 0,22 0
8 0,37 0
9 0,07 0

3. Заключение

Я показал реализацию наивного алгоритма поиска повторяющихся подстрок в тексте. Для анализа можно использовать как число повторов, так и сами повторы (например, по стоп-словам). Повторю, что в борьбе со спамом наиболее эффективны комплексные тесты.

Автор: CleanTalk Anti-Spam

Источник [4]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/informatsionnaya-bezopasnost/121346

Ссылки в тексте:

[1] Невизуальные методы защиты сайта от спама: https://habrahabr.ru/company/cleantalk/blog/282586/

[2] CleanTalk: https://cleantalk.org

[3] http://algolist.manual.ru/search/lrs/naive.php: http://algolist.manual.ru/search/lrs/naive.php

[4] Источник: https://habrahabr.ru/post/301302/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best