Feeds, большой XML или несколько источников фида. Как лучше сделать?

Главные вкладки

Аватар пользователя Koreychenko Koreychenko 1 июня 2013 в 12:12

Задача.

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'ам по кусочкам. Есть мнение, что это так и будет, но возникнет проблема с удалением нод, которых в списке нет.

У кого есть какие варианты решения этой интересной проблемы?

Комментарии

Аватар пользователя josgir josgir 2 июня 2013 в 2:06

"Koreychenko" wrote:
1. Есть порядка 25 xml файлов из которых нужно фидсами загрузить на сайт ноды.

Feeds directory fetcher с настройкой по маске /\.xml$/
Должен найти все xml в папке
"Koreychenko" wrote:
1. Есть сомнения, что feeds http fetcher прожует файл размером 121 Мб, а feeds xpath parser, который это все обрабатывает, скорее всего вызовет нехватку памяти и ляжет при первичной обработке файла.

Ставишь галочку
Quote:
"Выполнять в фоне"
Для очень больших объемов данных. Если включено, задачи импорта и удаления, запущенные из веб-интерфейса, будут обрабатываться при старте cron в фоновом режиме независимо от браузера.

Ну и сервер, конечно, должен быть соответствующий:) обычный хостинг может просто тысячу лет загружать такие объемы. Здесь сам еще эту проблему не решил полностью, но и количество обновляемых материалов в 3 раза меньше.
У меня сделано автоматом по фтп закидывается файл в папку, и по крону добавляется "Так часто насколько возможно" За один запуск крона не успевает обработать слишком большой объем, но по-тихонечку в течении некоторого времени все обновляет.

Аватар пользователя Koreychenko Koreychenko 2 июня 2013 в 19:02

"josgir" wrote:
Feeds directory fetcher с настройкой по маске /\.xml$/
Должен найти все xml в папке

Спасибо тебе, добрый человек, сам я этот модуль что-то не нашел. Правда он какой-то глючный местами, но может нужно dev версию попробовать поставить.

Отпишусь по результатам. Пока что-то уже получилось ) [РЕШЕНО] в тему ставить пока не буду.

Аватар пользователя josgir josgir 2 июня 2013 в 19:40
1

Тонкость есть одна, с директорифетчер.
Прописал для файловой системы admin/config/media/file-system путь для private
sites/default/files/feeds (сюда автоматом загружаются файл по фтп)
Затем создавал feed "Связать с типом материала" - "Использовать самостоятельную форму",
Сборщик соответственно Feeds directory fetcher с маской /\.xml$/
После этого через /import загружал первый раз в ручном режиме указывая папку private://
При обновлении файла по крону обновляются данные.
Сам какое-то время разбирался как это работает, надеюсь упростит настройку:)

Аватар пользователя univerico univerico 20 июля 2018 в 18:40

Спасибо! Без Вашего комменатрия непонятно было сначала, в какую папку складывать файлы и где правильно задать 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:// уже указать папку именно для фетчера, и она будет создана автоматически.

Аватар пользователя Koreychenko Koreychenko 3 июня 2013 в 6:00

"josgir" wrote:
Сам какое-то время разбирался как это работает, надеюсь упростит настройку:)

Да, спасибо, уже разобрался где прописывать URI, все нормально завелось. Единственное, что в совокупности с feeds xpath parser оно очень медленно работает и за один запуск крона получается импортировать не более 30 позиций. Тут нужно учитывать, что я еще и картинки с внешнего ресурса утягиваю, поэтому вообще долго.

Аватар пользователя Koreychenko Koreychenko 4 июля 2013 в 20:04

Делюсь с уважаемым сообществом тем, как в итоге решил задачу.

От Фидсов, к сожалению, пришлось отказаться. При всей своей универсальности модуль слишком монструозен. К тому же для парсинга xml файла приходилось использовать плагин feeds xpath parser, а для сбора данных из множества xml файлов приходилось использовать feeds directory fetcher.

В итоге, когда объединились несколько модулей в состоянии альфа и dev версий, то работало это через пень колоду. Очередь периодически зависала и т.п.

Но это еще не все. Мне нужно было дополнительно делать с данными следующее:

1. Были цены в долларах, которые необходимо было на лету конвертировать в рубли, подтягивая курс с rbc.
2. Сервер, с которого брались картинки отвечал 200 ОК, даже если картинки не было (вместо 404 ошибки отдавалась пустая страница), соответственно, к ноде присоединялись пустые файлы как картинки и imagecache ругался.
3. Никак не удалось настроить уникальное поле, чтобы ноды при реимпорте обновлялись, а не создавались новые.
4. Иерархическая таксономия тоже что-то не завелась, хотя mapper taxonomy.inc переписывал полностью.

Было принято решение писать свой собственный модуль импорта с блэкджеком и шлюхами требуемым функционалом. Естественно в лоб такое не решается, нужно использовать Drupal Queue API

<?php

// Здесь мы сообщаем системе, что у нас есть новая очередь и отдельный элемент этой очереди нужно обрабатывать функцией 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 раз в час и т.п.