30 07.2015

Walidacja adresów email

W trzeciej części poradnika dla początkujących email marketerów zajmiemy się procesem walidacji adresów email. Walidacja pomoże wychwycić i odfiltrować adresy niepoprawne składniowo, nieaktualne (domena wygasła), oraz takie, w których konfiguracja domeny lub serwera poczty nie pozwoli na dostarczenie maila.

Celem walidacji jest ograniczenie liczby wysyłanych maili do tych, które mają szansę być odczytane.

W dzisiejszym odcinku przedstawimy aż 3 skrypty składające się na kompletne rozwiązanie:

  • skrypt przeprowadzający właściwą walidację Zend Validatorem na partii adresów
  • skrypt dzielący bazę adresów na adresy w zaufanych domenach (np. wp.pl, onet.pl) i pozostałe
  • skrypt dzielący bazę adresów na części w celu zrównoleglenia i przyspieszenia całego procesu
Walidacja adresów

Zacznijmy od skryptu validate.php, przeprowadzającego właściwą walidację Zend Validatorem na przekazanej mu partii adresów.

Aby go uruchomić, najpierw musisz ściągnąć bezpłatny pakiet Zend Framework w wersji 1.12.x Full. Następnie rozpakuj ściągnięte archiwum, po czym wypakowany katalog library/Zend przenieś do katalogu /usr/share/php (lub innego znajdującego się na ścieżce include_path w konfiguracji PHP na Twoim systemie).


ini_set( "error_reporting", E_ALL );
ini_set( "display_errors", 1 );

require_once "Zend/Loader/Autoloader.php";
$autoloader = Zend_Loader_Autoloader::getInstance();

if ( empty($argv[1]) ) {
	$script = $argv[0];
	die("usage: $script  [column_number/-] [deep]\n");
}

$file = $argv[1];
if ( !file_exists($file) )
	die("invalid file name $file\n");

$date = date( "Y-m-d H:i:s" );
$lines = file( $file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES );

$validator = new Zend_Validate_EmailAddress();
$validator->setOptions(array(
	"deep" => true,
	"mx" => (@$argv[3] == "deep")
));

$column = is_numeric(@$argv[2]) ? $argv[2] : -1;

foreach ( $lines as $line ) {
	if ( $column != -1 ) {
		$fields = explode("\t", $line);
		$email = $fields[$column];
	} else
		$email = $line;

	if ( !$validator->isValid($email) )
		foreach ($validator->getMessages() as $message)
			echo "$date\t$email\t$message\n";
}

Zaufane domeny

Jeśli nie czytając reszty artykułu próbowałeś uruchomić powyższy skrypt, zauważyłeś pewnie, że działa i tak naprawdę mógłby być jedynym skryptem składającym się na całe rozwiązanie, ale... działa bardzo powoli. A jednocześnie wykorzystuje znikomą ilość zasobów. Można więc go uruchomić w np. 10, 100, czy 500 instancjach, aby przyspieszyć cały proces. Do tego służą 2 kolejne skrypty.

Skrypt split.php dzieli bazę adresów na adresy w zaufanych domenach, oraz całą resztę. Dla adresów w zaufanych domenach nie przeprowadzamy głębokiej walidacji domeny wierząc, że jest ona aktualna i ma prawidłową konfigurację DNS. Dla pozostałych adresów przeprowadzamy pełną weryfikację.


Poniższa wersja rozpoznaje tylko 12 najważniejszych domen. Nasza pełna wersja rozpoznaje 115 domen i można dopisywać kolejne. Jeśli chciałbyś uzyskać pełną wersję, napisz na adres pomoc@fajne.it lub zadzwoń na numer 603 252 633. Możesz również dopisywać kolejne domeny samodzielnie.



$trusted_domains = array(
	"gmail.com",
	"yahoo.com",
	"hotmail.com",
	"wp.pl",
	"gazeta.pl",
	"onet.pl",
	"o2.pl",
	"go2.pl",
	"tlen.pl",
	"interia.pl",
	"poczta.fm",
	"post.pl",
);

$lines = file($argv[1], FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES );
$generic = array();
$trusted = array();
foreach ($lines as $line)
{
	list($username,$domain) = explode("@", $line);

	if (empty($username) || empty($domain)) {
		echo "invalid line $line\n";
		continue;
	}

	if (in_array($domain, $trusted_domains, true))
		$trusted[] = $line;
	else
		$generic[] = $line;
}

file_put_contents($argv[2], implode("\n", $trusted)."\n");
file_put_contents($argv[3], implode("\n", $generic)."\n");

Szybciej, szybciej...

Ostatni skrypt validator.sh służy do sterowania całym procesem walidacji, w tym dzieleniem wejściowego pliku na części i zrównoleglaniem pracy skryptu validate.php:


infile=$1
group=$2
lines=$3

php split.php $infile in-trusted-$2.txt in-generic-$2.txt
split --lines=$lines in-generic-$2.txt part.$2.

for P in `ls part.$2.*`; do
	php validate.php $P - deep $group >tmp.$2.$P &
done

php validate.php in-trusted-$2.txt - "" $group >out-trusted-$2.txt &

while [ "`ps aux |grep validate.php |grep $group |grep -v grep`" != "" ]; do
	sleep 10
done

cat tmp.$2.part.* >out-generic-$2.txt
rm tmp.$2.part.* part.$2.*

cat out-trusted-$2.txt out-generic-$2.txt \
	|grep -v checkdnsrr |grep -v ^$ >zend-errors-$2.csv

Jak to działa

Mamy już komplet skryptów, spróbujmy więc całość uruchomić:


sh validator.sh baza1.txt baza1 20000

Poniższe polecenie zakłada, że wejściowa baza adresów email do walidacji znajduje się w pliku baza1.txt i ma być walidowana w paczkach po 20 tysięcy adresów. Skrypt validator.sh utworzy tyle instancji skryptu validate.php, aby każda instancja przetwarzała po jednej paczce, oraz jedną dodatkową instancję dla adresów w zaufanych domenach.

Parametr "baza1" jest parametrem pomocniczym, służącym jako element nazwy plików tymczasowych i wyjściowych - dzięki niemu możliwe jest jednoczesne walidowanie kilku osobnych baz:


sh validator.sh baza1.txt baza1 20000 &
sh validator.sh baza2.txt baza2 80000 &

Efektem działania skryptu validator.sh będzie plik CSV o nazwie np. zend-errors-baza1.csv, zawierający listę komunikatów błędów znalezionych przez Zend Validator. Błędy te można próbować ręcznie naprawiać w pliku wejściowym (np. baza1.txt), lub po prostu zignorować (i tym samym zmniejszyć listę odbiorców swoich mailingów).

Pliki CSV generowane przez skrypt będzie można wraz z plikami CSV generowanymi przez skrypt do odbierania zwrotek potraktować jako dane wejściowe do klasyfikatora zwrotek i błędów walidacji, który opiszemy w jednym z kolejnych artykułów.

Całość jest przetestowana na Debianie Wheezy z PHP w domyślnej wersji 5.4.39, powinna jednak działać bez problemów w dowolnej wersji systemu Linux z zainstalowanym PHP.



Tomasz Klim
Administrator serwerów i baz danych, specjalista w zakresie bezpieczeństwa, architekt IT, przedsiębiorca. Prawie 20 lat w branży IT. Pracował dla największych i najbardziej wymagających firm, jak Grupa Allegro czy Wikia. Obecnie zajmuje się doradztwem dla klientów Fajne.IT, a w wolnych chwilach pisze artykuły. Chętnie podejmuje się ciekawych zleceń.


Wzbudziliśmy Twoje zainteresowanie?

Szukasz pomocy? formularz kontaktowy