Задача.
1. Есть порядка 25 xml файлов из которых нужно фидсами загрузить на сайт ноды.
2. Информация будет обновляться ежедневно. В сумме объектов, подлежащих импорту/обновлению, насчиталось 24000.
3. При объединении всех xml файлов в один получился размер 121 Мб.
Сомнения.
1. Есть сомнения, что feeds http fetcher прожует файл размером 121 Мб, а feeds xpath parser, который это все обрабатывает, скорее всего вызовет нехватку памяти и ляжет при первичной обработке файла.
2. feeds не умеют работать с несколькими лентами. Т.е. я не могу задать список урлов, которые нужно грузить, только 1 урл.
Предполагаемые варианты решения.
1. Писать собственный fetcher, который бы поддерживал несколько урлов, но тут вопрос в том, что feeds http fetcher наследуется от FeedsFetcher, а тот все равно поддерживает только один источник ленты (файл, не файл или не важно что).
2. С помощью консольных команд по крону скачивать под одним и тем же именем файлы из списка и таким образом скармливать feeds'ам по кусочкам. Есть мнение, что это так и будет, но возникнет проблема с удалением нод, которых в списке нет.
У кого есть какие варианты решения этой интересной проблемы?
Комментарии
Feeds directory fetcher с настройкой по маске /\.xml$/
Должен найти все xml в папке
Ставишь галочку
Ну и сервер, конечно, должен быть соответствующий:) обычный хостинг может просто тысячу лет загружать такие объемы. Здесь сам еще эту проблему не решил полностью, но и количество обновляемых материалов в 3 раза меньше.
У меня сделано автоматом по фтп закидывается файл в папку, и по крону добавляется "Так часто насколько возможно" За один запуск крона не успевает обработать слишком большой объем, но по-тихонечку в течении некоторого времени все обновляет.
Спасибо тебе, добрый человек, сам я этот модуль что-то не нашел. Правда он какой-то глючный местами, но может нужно dev версию попробовать поставить.
Отпишусь по результатам. Пока что-то уже получилось ) [РЕШЕНО] в тему ставить пока не буду.
Тонкость есть одна, с директорифетчер.
Прописал для файловой системы admin/config/media/file-system путь для private
sites/default/files/feeds (сюда автоматом загружаются файл по фтп)
Затем создавал feed "Связать с типом материала" - "Использовать самостоятельную форму",
Сборщик соответственно Feeds directory fetcher с маской /\.xml$/
После этого через /import загружал первый раз в ручном режиме указывая папку private://
При обновлении файла по крону обновляются данные.
Сам какое-то время разбирался как это работает, надеюсь упростит настройку:)
Спасибо! Без Вашего комменатрия непонятно было сначала, в какую папку складывать файлы и где правильно задать private://. Теперь все работает и есть еще одно уточнение, у меня сейчас при моих настройках путь, который я указываю после private:// создается автоматически после сохранения настроек фетчера.
Т.е. у меня при попытке создать дополнительную отдельную папку кроме feeds (files/private/feeds_import_fetcher_private) и сделать только ее приватной, при указании этого пути (private/feeds_import_fetcher_private) в обоих настройках: и на странице настройки файлов, и на странице настройки фетчера получился дубль. sites/default/files/private/feeds_import_fetcher_private/private/feeds_import_fetcher_private. Чтобы этого избежать можно в настройках приватных файлов указать нужную папку (потом именно она будет приравнена к private:// на странице настройки фетчера), а в настройках фетчера после private:// уже указать папку именно для фетчера, и она будет создана автоматически.
Да, спасибо, уже разобрался где прописывать URI, все нормально завелось. Единственное, что в совокупности с feeds xpath parser оно очень медленно работает и за один запуск крона получается импортировать не более 30 позиций. Тут нужно учитывать, что я еще и картинки с внешнего ресурса утягиваю, поэтому вообще долго.
А у хостинга сколько оперативной памяти выделяется?
Делюсь с уважаемым сообществом тем, как в итоге решил задачу.
От Фидсов, к сожалению, пришлось отказаться. При всей своей универсальности модуль слишком монструозен. К тому же для парсинга xml файла приходилось использовать плагин feeds xpath parser, а для сбора данных из множества xml файлов приходилось использовать feeds directory fetcher.
В итоге, когда объединились несколько модулей в состоянии альфа и dev версий, то работало это через пень колоду. Очередь периодически зависала и т.п.
Но это еще не все. Мне нужно было дополнительно делать с данными следующее:
1. Были цены в долларах, которые необходимо было на лету конвертировать в рубли, подтягивая курс с rbc.
2. Сервер, с которого брались картинки отвечал 200 ОК, даже если картинки не было (вместо 404 ошибки отдавалась пустая страница), соответственно, к ноде присоединялись пустые файлы как картинки и imagecache ругался.
3. Никак не удалось настроить уникальное поле, чтобы ноды при реимпорте обновлялись, а не создавались новые.
4. Иерархическая таксономия тоже что-то не завелась, хотя mapper taxonomy.inc переписывал полностью.
Было принято решение писать свой собственный модуль импорта с
блэкджеком и шлюхамитребуемым функционалом. Естественно в лоб такое не решается, нужно использовать Drupal Queue API// Здесь мы сообщаем системе, что у нас есть новая очередь и отдельный элемент этой очереди нужно обрабатывать функцией aa_create_node. Один запуск очереди - 60 секунд.
function aa_importer_cron_queue_info() {
$queues['aa_importer_queue'] = array(
'worker callback' => 'aa_create_node',
'time' => 60, // time in second for each worker
);
return $queues;
}
//Для того, чтобы это все взлетело, нужно поставить все наше огромное количество объектов для импорта в очередь. Делаем это по крону, используя Queue API.
function aa_importer_cron() {
$queue = DrupalQueue::get('aa_importer_queue');
$queue->createQueue();
$files = file_scan_directory('public://carcopy/test', '/.*\.xml$/');
foreach ($files as $key => $value) {
$file=simplexml_load_file(drupal_realpath($key));
//dvm($file);
foreach ($file->offers->offer as $offer) {
$queue->createItem($offer->asXML());
}
}
//Здесь мы потихонечку парсим xml файлы и загоняем отдельные объекты в очередь.
}
function aa_create_node($data) {
$data = new SimpleXMLElement($data);
$result = db_select('field_data_field_car_id', 'id')
->fields('id')
->condition('field_car_id_value', $data->id,'=')
->execute()
->fetchAssoc();
//Ноды создаются только в том случае, если в базе еще нет ноды с таким значением id. В противном случае нода не создается.
if (!($result)) {
$files=array();
//Грузим картинки на сервер
//Картинка грузится только в том случае, если она картинка. Т.е. предварительно мы проверяем что там находится на другом конце.
foreach ($data->photos->photo as $photo) {
if (getimagesize($data->photos->attributes().$photo[0])) {
$file=drupal_http_request($data->photos->attributes().$photo[0]);
$newfile=file_save_data($file->data,'public://'.uniqid().'.jpg');
if ($newfile) {
$files[]['fid']=$newfile->fid;
}
}
}
//Формируем объект ноды.
$new_node = new StdClass();
$new_node->type = 'car';
$new_node->status = 1;
$new_node->promote = 0;
$new_node->comment = 2;
$new_node->sticky = 0;
$new_node->uid = 1;
$new_node->field_image['und']=$files; // Изображения машинки
$new_node->field_agency_price['und'][0]['value']=$data->price; // Цена машинки
$new_node->field_model['und'][]['tid']=aa_create_term($data->make,$data->model); // Таксономия модели
$new_node->field_car_id['und'][0]['value']=$data->id; //id машинки
$new_node->field_condition['und'][0]['value']=$data->category; //Состояние
$new_node->field_kuzov['und'][0]['value']=$data->{'body-type'}; //Состояние
$new_node->field_engine_type['und'][0]['value']=$data->{'engine-type'}; //Состояние
$new_node->field_year['und'][0]['value']=$data->year; //Состояние
$new_node->field_track['und'][0]['value']=$data->run; //Состояние
$new_node->field_kuzov_color['und'][0]['value']=$data->color; //Состояние
$new_node->field_dverei['und'][0]['value']=$data->doors; //Состояние
$new_node->field_engine_power['und'][0]['value']=$data->power; //Состояние
$new_node->field_kpp['und'][0]['value']=$data->transmission; //Состояние
$new_node->field_engine_volume['und'][0]['value']=$data->{'cubic-capacity'}; //Состояние
$new_node->field_drive['und'][0]['value']=$data->drive; //Состояние
$new_node->field_mesto_osmotra['und'][0]['value']=$data->{'location-address'}; //Состояние
$new_node->field_prodavec_phone['und'][0]['value']=$data->{'seller-phone'}; //Состояние
$new_node->field_dtp['und'][0]['value']=$data->accident; //Состояние
$new_node->field_sost_auto['und'][0]['value']=$data->condition; //Состояние
$new_node->field_dop_info['und'][0]['value']=$data->comment; //Состояние
node_save($new_node);
}
}
//Тут создаются новые термины таксономии, если их нет.
function aa_create_term($parent,$child = NULL) {
$tree = taxonomy_get_tree(2,0,1);
$parent_tid = 0;
foreach ($tree as $term) {
if ($term->name == $parent) {$parent_tid=$term->tid;}
}
if ($parent_tid == 0) {
$term = array(
'name' => $parent,
'vid' => 2,
'parent' => $parent_tid,
);
$term = (object) $term;
taxonomy_term_save($term);
$tid = $term->tid;
if ($child) {
$term = array(
'name' => $child,
'vid' => 2,
'parent' => $tid,
);
$term = (object) $term;
taxonomy_term_save($term);
$tid = $term->tid;
}
} else {
$tree = taxonomy_get_tree(2,$parent_tid,1);
$child_tid = 0;
foreach ($tree as $term) {
if ($term->name == $child) {$child_tid=$term->tid;}
}
if ($child_tid == 0) {
$term = array(
'name' => $child,
'vid' => 2,
'parent' => $parent_tid,
);
$term = (object) $term;
taxonomy_term_save($term);
$tid = $term->tid;
} else {$tid=$child_tid;}
}
return $tid;
}
Дополнительно:
Чтобы вообще все стало хорошо, нужно поставить модуль Elysia Cron. Таким образом, системый cron у меня запускается каждую минуту, cron, который наполняет очередь для импорта запускается 1 раз в день, разные другие кроны, вроде xmlsitemap и search запускаются 1 раз в час и т.п.