в одном проектике была поставлена задача: геокодировать более 1000 адресов из загружаемого excel-файла с адресами.
1. загрузка excel-файла и его парсинг в csv
2. вывод окна с формой ajax, которая опрашивает сервер каждую минуту до окончания геокодирования
3. если юзер обновляет окно, сервер проверяет наличие "недокодированного" файла с данными,и, если такой есть, показывает форму из п.2
4. по окончании геокодирования выводится список ошибок и файл с данными удаляется.
вот функция собственно геокодирования:
$result = array('html'=>'');
// проверяем существовование файла с данными для геокодирования
if (file_exists($dataraw)) {
// инициализируем memcache
$memcache = new Memcache;
$memcache->pconnect('127.0.0.1', 11211);
// грузим данные для геокодирования
$data = file($dataraw, FILE_IGNORE_NEW_LINES);
// устанавливаем таймаут работы скрипта
$endtime = time() + 60;
$storage = '';
$count = 0;
$geocoded = 0;
$errordata = '';
// инициализируем curl.
$ch = curl_init();
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// цикл геокодирования заканчивается по таймаут либо когда массив с адресами кончится
while ((time() < $endtime) && sizeof($data)) {
$count ++;
$coords = false;
$string = array_shift($data);
$row = str_getcsv($string);
array_walk($row, create_function('&$val', '$val = trim($val);'));
// тут превращаем часть столбцов массива данных в строку адреса
$addr = implode(',', array_slice($row, 4, 4));
// хэш для хранения и поиска в memcache
$hash = md5($addr);
// проверяем наличие уже отгеокодированного адреса в memcache
$coords = $memcache->get($hash);
// обраращение к геокодеру
if ($coords === false)
{
curl_setopt($ch, CURLOPT_URL, 'http://geocode-maps.yandex.ru/1.x/?kind=house'.
'&geocode='.urlencode('Россия,+'.implode(',+', array_slice($row, 4, 4))));
$content = curl_exec($ch);
if (curl_error($ch)) {
break;
}
$xml = simplexml_load_file($content);
// тут коряво, конечно, но проверяем точность геокодирования. нас вполне устроит точность exact, number, near и street
if ($xml->GeoObjectCollection->metaDataProperty->GeocoderResponseMetaData->found !=0 &&
in_array($xml->GeoObjectCollection->featureMember[0]->GeoObject->metaDataProperty->GeocoderMetaData->precision, array('exact', 'number','near', 'street')))
{
// сохраняем в memcache если геокодирование прошло удачно
$coords = sprintf($xml->GeoObjectCollection->featureMember[0]->GeoObject->Point->pos[0]);
$memcache->set($hash, $coords, MEMCACHE_COMPRESSED, strtotime('+30 days'));
} else {
// сохраняем false, если геокодирование прошло неудачно.
$memcache->set($hash, NULL, MEMCACHE_COMPRESSED, strtotime('+1 days'));
}
$geocoded ++;
};
if ($coords) {
// сохраняем результат
list($lng, $lat) = explode(' ', $coords);
$storage .= ($storage ? '|' : '') . implode(":", array(
$row[0],
$lat,
$lng,
isset($row[9]) ? intval($row[9]) : 0,
$row[0].". ".$row[1].' '.$row[2].' '.$row[3].'<br />'. implode(',', array_slice($row, 4, 5))));
} else {
// или сохраняем ошибку
$errordata .= "<tr><td>{$row[0]}</td><td>$addr</td></tr>";
};
};
// сохраняем ошибки в файл
file_put_contents($error, $errordata."<tr colspan='2'>Обработано $count, геокодировано $geocoded<td></tr>");
// если отработан весь файл с данными - удаляем его и выводим ошибки
if (sizeof($data) == 0) {
@unlink($dataraw);
$result['done'] = true;
file_put_contents($error, "<tr><td colspan='2'>Обработка завершена<td></tr>" .
"<tr><td colspan='2'><a href='/mapexcel/'>Нажмите для переходы к карте</a><td></tr>", FILE_APPEND);
} else {
file_put_contents($error, "<tr><td colspan='2'>Обработано $count. Осталось ".sizeof($data)."<td></tr>", FILE_APPEND);
};
$result['html'] = file_exists($error) ? file_get_contents($error) : '<tr><td colspan="2">Ошибок не найдено</td></tr>';
// сохраняем файл с обработанными данными
file_put_contents($datafile, $storage);
// отправляем ответ json
header('Content-Type: application/json');
echo json_encode($result);
exit;
}
клиентская часть
if (file_exists($dataraw))
{
echo '<h1>Обработка загруженного файла</h1>';
echo '<div id="result">Обрабатывается...</div>'.PHP_EOL;
echo '<script type="text/javascript">
$().ready(function() {
var load = function() {
$.get("?action=work", function(response) {
$("#result").html(response.html);
if (!response.done) {
window.setTimeout(load, 1000);
}
});
};
window.setTimeout(load, 1000);
})
</script>
</body>
</html>';
exit;
};
пояснения:
1. cron не использую, так как проект мелкий, да и потом вспомнить, что там было в кроне - сложно
2. почему без mysql? нет смысла морочиться из-за такого мелкого объема. данные обновляются ежедневно, а вот адресов будет 20-50 новых в день
3. memcache? вместо него можно использовать что угодно - mysql, файловый кэш и т.д. (сложнее, медленнее)
4. почему curl? сначала исползовал file_get_contents, но после большого числа обращений к геокодеру, яндекс начинает отдавать результаты с большой задержкой и скрипт вылетает по таймауту. Можно поставить set_time_limit(0), но все расвно вылетает из-за Nginx. в общем, curl хорош тем, что легко настраивается timeout. Кстати, насиловать яндекс тысячами адресов в минуту тоже нехорошо)