Внимание! Сайт не обновлялся более 3х лет!
Перейти в портфолио компании

Пишем уникализатор email'ов

13 мая 2013

У меня часто возникает необходимость что-либо уникализировать.

К примеру вам в руки попала база email'ов, количество которых свыше 100 000 штук. Ясное дело, руками уникализировать 100 тысяч email'ов невозможно.

Если у вас похожая ситуация - эта статья вам в помощь. Я покажу и представлю исходник уникализатора email'ов.

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

Ключевой момент: это не просто уникализатор, это парсер. К примеру у вас не просто файлик с email'ами, а файлик в котором кроме целевых данных еще куча всего: телефоны, имена, даты и т.д.

Данный уникализатор найдет все email'ы.

 

Итак, исходные данные: ~2-3 тысячи файлов с беспорядочными данными, среди которых предполагается наличие email-адресов.

Структура данных: 5 каталогов, в которых еще несколько уровней каталогов. Примерно на 3-5-м уровнях расположены файлы SQL,TXT,XLS.

Исходя из этого, обходить дерево дирректорий будем рекурсивно начиная с корневого каталога.

 

Кратко план действий:

  1. Формирование массива файлов путем обхода дерева каталогов. 
  2. Отсечение файлов, которые уже помочены как "спарсеные" (чтоб можно было дозаливать новые файлы с email'ами, не боясь, что старые будут парситься снова)
  3. Обход массива файлов с проходом регуляркой по каждому, формируя массив найденных email'ов
  4. Обход найденных email'ов и добавление в целевой массив только новых + сразу же вставка в БД.
  5. Запись в БД о том, что файл "спарсен"

Почему я записываю в БД по ходу дела?

Потому что, наврятли у вас TimeOut на сервере установлен в более чем 1 минуту)))

У меня TimeOut ожидания выставлен в 10 минут. И за эти 10 минут успевает обойти лишь 3000 файлов))) А у меня ведь никакой нибудь там хостичег, у меня - NetAngels VDS-4.

 

Приступим.

Начнем с объявления констант:

define('ROOT', 'корневой каталог');
define('DB_FILES',  'таблица для файлов'); // id(int,primary,auto-increment), path(string)
define('DB_EMAILS', 'таблица для email-ов'); // email(string, primary)

Пишем рекурсивную функцию обхода дерева каталогов с любым количеством уровней для поиска файлов:

function get_files($dir = '.'){
    $files = array();  
    if ($handle = opendir($dir)) {     
        while (false !== ($item = readdir($handle))) {        
            if (is_file($dir.'/'.$item)) {
                $files[] = $dir.'/'.$item;
            }        
            elseif (is_dir($dir.'/'.$item) && ($item != '.') && ($item != '..')){
                $files = array_merge($files, get_files($dir.'/'.$item));
            }
        } 
        closedir($handle);
    }  
    return $files; 
}

Функция возвращает массив файлов, как и обещал.

Еще нам понадобится функция поиска email'ов в файле.

Не вижу смысла объяснять каждую строку - привожу код. Если что-то неясно - жду вопрос в комменте.

function parse_emails($file, $_emails){
	global $db;
	$content = file_get_contents($file);
	if (!strlen($content)){
		return $_emails;
	}
	$pattern = '/[a-z0-9_\-\+]+@[a-z0-9\-]+\.([a-z]{2,3})(?:\.[a-z]{2})?/i';
	preg_match_all($pattern, $content, $matches);
	if (!is_array($matches[0]) || !count($matches[0]))
		return $_emails;
	foreach ($matches[0] as $email) {
		if (!in_array($email, $_emails)){
			$db->insert(DB_EMAILS, array('email' => $email), true);
			$_emails[] = $email;
		}
	}
	return $_emails;
}

Самая простая регулярка: до собаки не ограничиваем, после - тоже. После точки на домен 1-го уровня даем 2-3 символа.

И наконец, основная часть контроллера. Класс писать не стал, т.к. тут всего-лишь 2 SQL-запроса и 2 функции, не вижу необходимости писать класс))

$temp = $db->query('SELECT * FROM `'.DB_FILES.'`', null, db::FETCH_ALL);
$_parsed = array();
if (count($temp)) foreach ($temp as $v) {
$_parsed[$v['path']] = true;
}

$_files = get_files(ROOT);
$_emails = array();

foreach ($_files as $file) {
	if (isset($_parsed[$file]))
		continue;
	parse_emails($file, &$_emails);
	$db->insert('parser_db', array('path' => $file));	
}

Единственное что тут может показаться странным начинающему PHP-разработчику это передача пораметра по ссылке, а именно:

parse_emails($file, &$_emails);

Т.е. при таком подходе в функции не создается копия принятого параметра, а используется тот, что передан.

Я считаю: это всяко лучше чем использовать global.​ 

Надеюсь, никого не смутил мой класс DB. Его реализация тривиальна, в коде юзаю функции query и insert.

 

В результате у нас в БД уникальные email'ы из всех файлов, расположенных в ROOT)))))

Привожу код целиком. Полностью рабочий, вам осталось, только заменить методы моего класса DB на свои.

define('ROOT', 'корневой каталог');
define('DB_FILES',  'таблица для файлов'); // id(int,primary,auto-increment), path(string)
define('DB_EMAILS', 'таблица для email-ов'); // email(string, primary)

function get_files($dir = '.'){
    $files = array();  
    if ($handle = opendir($dir)) {     
        while (false !== ($item = readdir($handle))) {        
            if (is_file($dir.'/'.$item)) {
                $files[] = $dir.'/'.$item;
            }        
            elseif (is_dir($dir.'/'.$item) && ($item != '.') && ($item != '..')){
                $files = array_merge($files, get_files($dir.'/'.$item));
            }
        } 
        closedir($handle);
    }  
    return $files; 
}


function parse_emails($file, $_emails){
	global $db;
	$content = file_get_contents($file);
	if (!strlen($content)){
		return $_emails;
	}
	$pattern = '/[a-z0-9_\-\+]+@[a-z0-9\-]+\.([a-z]{2,3})(?:\.[a-z]{2})?/i';
	preg_match_all($pattern, $content, $matches);
	if (!is_array($matches[0]) || !count($matches[0]))
		return $_emails;
	foreach ($matches[0] as $email) {
		if (!in_array($email, $_emails)){
			$db->insert(DB_EMAILS, array('email' => $email), true);
			$_emails[] = $email;
		}
	}
	return $_emails;
}


$temp = $db->query('SELECT * FROM `'.DB_FILES.'`', null, db::FETCH_ALL);
$_parsed = array();
if (count($temp)) foreach ($temp as $v) {
$_parsed[$v['path']] = true;
}

$_files = get_files(ROOT);
$_emails = array();

foreach ($_files as $file) {
	if (isset($_parsed[$file]))
		continue;
	parse_emails($file, &$_emails);
	$db->insert('parser_db', array('path' => $file));	
}

← назад