Drupal → Parser 2

04.04.2012

Допилил до более-менее рабочего состояния вторую версию парсера. Основные отличия:

  • Парсить можно в любые сущности зарегистрированные на сайте — ноды, термины, пользователи, комментарии, товары и т.д.
  • Работа с сущностями ведётся с помощью Entity metadata wrappers.
  • Поддержка всех, доступных для записи, свойств сущности — автор, статус и т.п.
  • Все файлы, скачанные парсером, сохраняются в кэше (public://parser_cache) и при повторном парсинге берутся из него.

Модуль не совместим с первой версией, поэтому перед установкой анинсталим её, удаляем папку с модулем и устанавливаем вторую версию.

Прямая ссылка на скачку последнего снапшота Parser 2.

Если отписываете о найденных багах, то обязательно прикладывайте экспорт задания.

Написанное актуально для
Drupal 7
Похожие записи

Комментарии

Вадим
06.02.2015, 16:57

Нужен совет:
при парсинге получаю PDOException: SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '6222' for key 'PRIMARY'

в базе смотрел нод с таким nid нет то есть почему она возникает?
Почему так происходит не понимаю.

Игорь
07.02.2015, 23:17

Не знаю глупый вопрос или нет, но задать хочется:
Когда ждать сиё чудо для Drupal 8!? А то и темы коммерческие уже появляются.
Это очень сложно? А то у меня есть простенькие сайтики ещё на 5-ке :-)
Вот и наконец то решился их перенести так уж на 8-ку! :-)

как будут задачи на парсинг под восьмёрку, тогда и ждать

Вадим
12.02.2015, 23:52

Александр, большая человеческая просьба!
Как можно обнаружить что парсер берет не тот entity id. уже дня 4 не приходит решение.

при парсинге получаю PDOException: SQLSTATE[23000]:
Integrity constraint violation: 1062 Duplicate entry '6222' for key 'PRIMARY'

Я удалял ноды через Content-> хотя есть функция rollback для уже напарсеных в самом парсере. Может в этом причина что сбился счетчик, хотя брать его парсер должен из базы,

может есть путь debuginga, или намек где копать? Может бывает такое, когда я $page_url вписываю как Remote_id

Ваш опыт профессионала очень ценен и я готов перечислить сумму для развития, Спасибо

я не знаю причину появления ошибки

Гость
13.02.2015, 22:14

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

1. Задача минимум. Как сделать эту ссылку на источник материала видимой для анонимов?

2. Задача максимум. Как сделать эту ссылку видимой для анонимов и при этом добавить к ней атрибут "rel=nofollow" и чтобы ссылка ссылалась на некий скрипт (модуль) на моём сайте (т.е. для поисковых ботов была внутренней, а не внешней), а уже скрипт бы её перебрасывал через анонимайзер на страницу источник материала?

Спасибо.

получите url с помощью _parser_get_url_by_entity_id()
выведите url в своём node.tpl.php

Вадим
18.02.2015, 19:22

Решение: свой Rules который пересохранял ноду - получалось реально дубль пытался сохранить, хотя в моем понимании это не один и тот же момент и ошибки быть не должно.
Была ошибка: PDOException: SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '***' for key 'PRIMARY'

Код для поля изображения, проверяющий загружаемые изображения на предмет их наличия на нашем севере.

То есть позволяет избавиться от дубликатов изображений.

Логика работы:

1) Загружаем в кеш проверяемое изображение.
2) Ищем в указанной папке на нашем сервере такой же файл путем сравнения md5 хешей.
3) Если находим дубль, находим его идентификатор (FID)
4) Передаем FID существующего файла.

foreach ($doc->find('a.images') as $a) {
  $file = pq($a)->attr('href');
  // $file = str_replace('resize/', '', $file);

  // Загружаем в кеш изображение для дальнейшего сравнения
  $cache_uri = _parser_download_url_to_cache($file);

  // Папка на нашем сервере в которой будем искать изображения для проверки
  $dir = "public://images/";

  // Получаем массив с картинками из указанной директории
  $drupal_files = file_scan_directory($dir, "/.*\.(jpg|png|gif)$/");
  foreach ($drupal_files as $drupal_file) {

    // Сверяем md5 загруженной картинки с md5 каждого из изображения из указанной папки
    if (md5_file($drupal_file->uri) == md5_file($cache_uri)) {
      $filename = $drupal_file->filename;
    }
  }

  // Если идентичный файл был найден, получем его FID по названию файла
  if (isset($filename)) {
    $query = new EntityFieldQuery();
    $result = $query->entityCondition('entity_type', 'file')
      ->propertyCondition('filename', $filename)
      ->execute();
      $file_object = reset($result['file']);
      $file = $file_object->fid;
  }

 $images[] = array(
  'file' => $file,
  'alt' => pq($a)->attr('alt')
 );
}

return $images;
Вадим
23.02.2015, 23:37

Александ, а как можно создать-клонировать parser через php со своими изменениями. Пытался поменять jid на новый и сохранить через entity_save не сохраняет, и нового в списке не появляется. entity_create выдает ошибку. по функциям не нашел?

Александр
26.02.2015, 17:40

Ну никак не могу получить дату )

Код

return $doc->find('div.data11')->text();

возвращает
2015-02-23 12:10:02 +0300

А дата все равно сейчашняя..

Александр
26.02.2015, 18:05

Так тоже не катит
2015-02-23 12:10:02

Александр
28.02.2015, 20:17

Скажите а можно как то из источника получить url название статьи и присвоить ее создаваемой ноде?

Что бы url остался тот же?

Александр
28.02.2015, 22:36

Не знаю как тут расписать все, вот на дурпал ссылка
http://www.drupal.ru/node/115937
Я там описал проблему, она конечно индивидуальна, но может что-то подскажешь.

Спасибо.

Александр
01.03.2015, 03:02

Это вообще возможно?
В момент парсинга брать url .html к примеру страницы с которой парситься и вставлять в алиас создаваемой ноды?

отметьте опцию "Сохранять адреса"

Дмитрий
01.03.2015, 16:20

Александр,
1) Есть ли возможность останавливать процесс через php parser_run_in_background($jid); или проверять его статус что например осталось 10 из 30 допарсить?
2) а функция parser_run_in_background($jid); продолжает парсинг или начинает его заного? и как продолжить (функция php) если уже запускался парсер?
3) можно ли выполнить свой код после заверения работы парсера parser_run_in_background($jid); ?

Спасибо за полезный модуль!

1) нет
2) начинает заново. parser_run_batch($jid, FALSE, TRUE);
3) нет

Александр
01.03.2015, 19:09

отметьте опцию "Сохранять адреса"

У меня истерика....
Всего??
Я настолько не внимателен что неделю мучаюсь?
facepalm...

XandedX еще раз огромное человеческое СПАСИБО..

Еще 1 маленький вопросик.
У меня уже есть 5000 перенесенных новостей парсером, и я в перенесенных уже в 100-200 что-то правил.

Как мне получить их заново, заставив парсер обновить только этот самый адрес а не трогать в нодах все что он перенес до этого?

Александр
01.03.2015, 20:16

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

Отписал тут
http://www.drupal.ru/node/115937#comment-636358

Еще раз ОГРОМНОЕ спасибо, я перенес более 20 000 новостей, с 4 сайтов в совокупности, не потерям при этом ничего, в том числе и URL!

Гость
08.03.2015, 22:43

Добрый день

Планируете ли добавление поддержки metatag ?

Гость
09.03.2015, 01:35

Даже не смотрели что там ?

Может есть какие-то мысли ?

Можно было бы совместно ускорить доработку.

Игорь
09.03.2015, 11:56

Давно парсером не пользовался, но помнится, там есть предобработка - в самом низу добавляемого шаблона - думаю там можно добавить соответствующие данные для полей!?

Гость
10.03.2015, 07:51

Подскажите пожалуйста, а как сохранять количество просмотров используя статистику из ядра ?

Гость
11.03.2015, 11:38

Столкнулся с небольшой проблемой, может быть её решение может стать доп. фичей такого классного модуля.

Проблема:
Парсим данные "списком". При парсинге страниц через n запросов на странице выводится капча (в соседней вкладке браузера её вбиваешь и продолжается разбор). Так вот, парсер собрал большую базу основных страниц и создал сущности только с тех, где не было капчи. Как теперь экономно пройтись по неудачным страницам?

Вариант решения:
1. обратиться к данным задания и узнать какие адреса обработаны в "Original URL"
2. проверять url страницы (как в белом списке) с помощью php, чтобы его не было в обработанных. Эта проверка отличается от "Код проверки для дальнейшего парсинга страницы" тем, что страница не загружается, а проверяется только её адрес.

Гость
11.03.2015, 11:47

То есть, сейчас пришлось для повторного прохода взять адреса

select url from parser_map where jid = 3 group by url;

и добавить их в черный список, чтобы по ним повторно не идти и не вызывать капчу

Игорь
11.03.2015, 12:00

Можно было бы подключить антигате API и распознать капчу. Но мудуль вообще то для официального парсинга, а не для граббинга ;-)

Гость
11.03.2015, 12:15

"Код предварительной инициализации" - вот здесь бы иметь возможность: skip_url, parser_stop

Имеется несколько урлов в "Стартовый URL". Как у спарсенной сущности узнать, по какому из этих урлов ее нашли?

_parser_get_url_by_entity_id($entity_type, $entity_id, $jid = NULL)

Это немного не то. Это адрес самой сущности на стороннем сайте. Мне нужен адрес страницы, с которой модуль перешел на эту страницу )

Например, в стартовом урле страница http://xandeadx.ru, задание парсить все ноды, модуль найдет все страницы и спарсит их и _parser_get_url_by_entity_id вернет адрес http://xandeadx.ru/blog/drupal/554, а мне нужен получить адрес http://xandeadx.ru.

такой информации модуль не хранит

А можно запретить создание ноды если title или body пусты?

возвращайте FALSE в "Код проверки для дальнейшего парсинга страницы"

Гость
29.05.2015, 12:34

НЕ могу сообразить что вписать в поле:
PHP код для поля Родительские термины
В примечании хоть и есть подсказка, но час уже бьюсь не знаю что вписать.
PHP код, который должен вернуть значение поля (тип: list<taxonomy_term>).

массив идентификаторов родительских терминов

Гость
29.05.2015, 13:03

Да это понятно. Но можно образец?

И еще решил проверить парсит ли у меня вообще в таксономию, очистил все поля, оставил только заголовок. Все проверки проходит нормально, но новые сущности не создаются.

Гость
29.05.2015, 14:17

Спасибо за ответ. Я думал как-то сложнее указывается )

Почему-то не срабатывает этот код.

$images = array();
foreach ($doc->find("table table td[width='100%'] a")->attr('id') as $b) {
$images[] = (pq($b)->attr("href"));
}
return $images;

Если убираю из него ->attr('id'), то все работает.
Или если писать $doc->find("table table td[width='100%'] a")->attr('id') вне цикла, то тоже работает.
Весь мозг сломал, не понимаю в чем дело. Все же правильно записано.

метод attr возвращает строку, а не объект phpQuery

Спасибо большое.
Переправил строку так:
($doc->find("table table td[width='100%'] a[id]")

Еще вопрос:
Можно ли указать в какую папку сохранять изображения?

укажите путь в настройках поля

Там можно задать только фиксированный путь. Токены связанные с текущей нодой не работают. Вместо урла или заголовка, возвращается "Batch".

Проблема в том что на сайте доноре все картинки имеют одно название, но находятся каждая в своей папке.

Евгений
06.06.2015, 12:46

Здрасте. Очень нужна помощь. Все хорошо парсица, но мне надо связать Карточку товара с товаром в DC. Я так понял там в поле prod_reference должно быть значение SKU товара. У меня на одну карточку товара, может быть несколько товаров. Я использую этот код:
$prods = array();
//вытаскиваю то что нужно
foreach ($doc->find('div.kod-tov p') as $a) {
//Изюавляюсь от букв, оставляю лишь числа в SKU функцией eregi_replace
$int = pq($a)->text();
$prods[] = eregi_replace("([^0-9])", "", $int);
}
return $prods;
Ошибок нет, я не совсем понял описание поля, внизу: "PHP код, который должен вернуть массив значений поля (array(значение1, значение2, ...)) (тип: list)" .
Подскажите, как исправить, пожалуйсто.

возвращать нужно идентификаторы товаров, а не артикли

Виталий
16.06.2015, 19:21

При парсинге сайта не требующего авторизации выдаёт ошибку:

Ошибка при закачке "****": Internal Server Error, Результат: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ru" lang="ru"> <head> <link href="/bitrix/cache/css/s1/eshop_blue_copy/template_2a0baf5dbb9a28b826dfac1395bd1251/template_2a0baf5dbb9a28b826dfac1395bd1251_1e5c8172e35c0206a58de4c3c0dcec7a.css?143050916763161" type="text/css" data-template-style="true" rel="stylesheet" /> <link rel="stylesheet" type="text/cs

Попробовал в браузере - всё нормально, в текстовом браузере lynx такая же ошибка 500, но потом после подтверждения приёма cookie: PHPSESSID=1f2bo2un63mplb03omd8hv70l0 Принять? (Y/N/A)Всегда/V)Никогда) открывает страницу.

Вижу выше написано про cookie:
В первый код добавляем:

$result = _parser_post_request('****', [], array('max_redirects' => 0));
if (preg_match('#PHPSESSID=(.+?);#', $result->headers['set-cookie'], $matches)) {
	variable_set('parser_data_PHPSESSID', $matches[1]);
}

В Код предварительной инициализации? Сделал.

В HTTP заголовки:

Cookie: PHPSESSID=[parser_data_PHPSESSID]

Но всё равно - 500. Что не так?

Виталий
16.06.2015, 19:27

Добавлю.

Вот, что выдал код

return $result = _parser_post_request('****', [], array('max_redirects' => 0));
stdClass Object
(
    [request] => POST **** HTTP/1.0
Content-Type: application/x-www-form-urlencoded
User-Agent: Drupal (+http://drupal.org/)
Host: ****
Content-Length: 0


    [data] => тут содержимое страницы



    [protocol] => HTTP/1.1
    [status_message] => Internal Server Error
    [headers] => Array
        (
            [server] => nginx/1.6.2
            [date] => Tue, 16 Jun 2015 16:23:37 GMT
            [content-type] => text/html; charset=UTF-8
            [connection] => close
            [x-powered-by] => PHP/5.3.3
            [p3p] => policyref="/bitrix/p3p.xml", CP="NON DSP COR CUR ADM DEV PSA PSD OUR UNR BUS UNI COM NAV INT DEM STA"
            [x-powered-cms] => Bitrix Site Manager (DEMO)
            [set-cookie] => PHPSESSID=t1jjuu4qiclu85an6qk8onech5; path=/; HttpOnly
            [expires] => Thu, 19 Nov 1981 08:52:00 GMT
            [cache-control] => no-store, no-cache, must-revalidate, post-check=0, pre-check=0
            [pragma] => no-cache
            [x-frame-options] => SAMEORIGIN
        )

    [code] => 500
    [error] => Internal Server Error
)

смотрите какие заголовки отправляет php, я к сожалению не телепат

В настройке «Задержка между http запросами» — установлено 15 секунд.

Но за минуту создалось около 30 нод.

Какая причина может быть этому?

Скрин результата парсера

Создаются дубли нод. И записывается в БД одинаковый remote_id
Я даже не знаю в какую сторону смотреть, с чем может быть связано. У Вас есть предположение?

P.S.

кэш

Понял.

Создаются дубли нод. И записывается в БД одинаковый remote_id
Я даже не знаю в какую сторону смотреть, с чем может быть связано. У Вас есть предположение?

Отключил проверку по ID — парсер прошел без дублей.

На каждую новую ноду в логах появляется предупреждение:

Warning: Invalid argument supplied for foreach() in _parser_set_property() (line 1555 of /home/admin/web/nb.sitiq.ru/public_html/sites/all/modules/parser-master/parser.inc).
Warning: Invalid argument supplied for foreach()

Предупреждение (Warning) — из-за моего поля изображений.

Дубли пока создаются — ищу выход.

Создаются дубли нод. И записывается в БД одинаковый remote_id

Дубли создаются при включенном фоновом запуске.
Крон - каждые 9 минут
Парсер - каждые 600 сек.
Как считаете нормальные настройки?

Еще вопрос:
Как остановить парсер запущенный в фоне, без удаления задания?

Павел
03.07.2015, 10:51

Добрый день. справится ли этот модуль с парсингом товаров на яндекс.маркет?
Мой заказчик оказался человеком креативным и из 1с грузятся только артикулы а характеристики нужно парсить с яндексмаркета и цеплять к нодам.
XadndeadX , Поможет ли мне Ваш модуль в моем вопросе?

Виталий
04.07.2015, 16:01

Добрый день.

Парсил товары и после ноды товаров.
Все 900 товаров залились, ноды связаны - всё хорошо.

Но после перехожу в список заданий и вижу, что с заданием по загрузке товаров связано всего 500 товаров (SELECT COUNT(*) FROM `parser_map` WHERE `jid`=7 => 500). Запускаю задание заново и появляются ошибки:

Ошибка при сохранении сущности: SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '1916' for key 'sku'. Адрес: ***
Value:
stdClass Object …

При этом:
SELECT COUNT(*) FROM `commerce_product` WHERE 1 => 900 (товаров)
SELECT COUNT(*) FROM `parser_map` WHERE `jid`=3 => 900 (нод)

Что-то ломается в таблице parser_map?

Пробовал очищать таблицу commerce_product, и начинать всё заново - после задания по парсингу нод товаров слетает таблица parser_map для задания по товарам.

Пробовал пару раз так.

Что это может быть?

В документе есть изображения, и путь к каждому изображению разный. Что-то типа такого:

<img src="/dir1/dir2/dir3/image.jpg">
<img src="/dir1/dir5/dir7/image2.jpg">
<img src="/dir1/dir2/image3.jpg">

Как заменить путь на свой для всех изображений?

Второй день голову ломаю:
Вот так например не работает:
$value = str_replase(($doc->find('img[src]')->html()), 'mypath', $value);

foreach ($doc->find('img') as $img) {
  $src = pq($img)->attr('src');
  $src = str_replace('from', 'to', $src);
  pq($img)->attr('src', $src);
}
Гость
16.07.2015, 08:59

Здравствуйте. Как можно парсить зависимые фильтры на странице. например как на skinon.ru

У сайта-донора часть ссылок включают в себя запятые. Из-за этого парсер выдает ошибки, не может их увидеть.
Можно ли как-то это обойти?

Виталий
22.07.2015, 22:12

Как установить язык, значение типа token?

Виталий
23.07.2015, 10:10

В поле Язык, задания на парсинг.
Например установить "Русский" для всех импортируемых сущностей.

Виталий
24.07.2015, 14:44

Почему-то нода становится с ru, а поля все с und языком.
Как сделать и поля с ru?

Сайт донор имеет url'ы такого вида: /svetilniki-pod-lampu-nakalivanija-bytovye-(npo,-nbb,-nvo).html т.е. содержит запятые. Из-за них парсер не может скачать эти страницы.
Что можно с этим сделать?

Всё, разобрался в чем проблема была. Часть ссылок была с .html на конце, хотя на самом деле страницы на которые ведут ссылки без .html.
Решил проблему подправив файл parser.inc. В 1694 строке добавил:

$href = str_replace('.html' , '/', $href);

Перед:

$href = preg_replace('/#.*$/', '', $href);

В поле body я заменяю путь ссылок на свой:

foreach ($doc->find('img') as $img) {
  $src = pq($img)->attr('src');
  $dir = dirname($src);
  $src = str_replace($dir, '/sites/default/files/imgsvet', $src);
  pq($img)->attr('src', $src);
}

Но этот код меняет объект, и т.к. поле body стоит выше поля Image, то и изображения пытаются скачаться по новому пути. Из-за этого ошибка.
Как поставить поле Image перед Body, чтобы парсер сначала создавал массив ссылок, а затем обрабатывал Body?
Иногда после очистки кеша, у меня в парсере менялся порядок полей, не знаю с чем это связано. Image оказывался перед Body, и изображения скачивались без проблем. Специально повторить это у меня не получается.

Спасибо за ответ. Но все равно не понимаю что клонировать, где можно где нельзя заменять пути.

Евгений
24.08.2015, 13:13

Ваш модуль парсинга неоценим. Очень хорошая работа. У меня маленький вопросс: Не настраевается поле с ценой. Я использую код:
return array(
'amount' => 1233,
'currency_code' => 'USD'
'data' => array(),
);
Как это настроить? Спасибо.

Евгений
24.08.2015, 13:23

Я хочу настроить поле цены (price) в сущьносте товара на Commerce.

скопируйте ваш код, вставьте в поле

Евгений
24.08.2015, 13:51

Вот весь код поля:
$price = $doc->find('#id_prod_price')->text();
$price = preg_replace("/[^0-9]/", '', $price);
return array(
'amount' => $price,
'currency_code' => 'USD'
'data' => array(),
)

Евгений
24.08.2015, 14:05

Не помогло c запятой. А у вас нет примера заполнения поля прайс? любого. В комментарии к полю написано:
array(
'amount' => $amount, // Количество (decimal, optional)
'currency_code' => $currency_code, // Валюта (text, optional)
'data' => $data, // Данные (struct, optional)
)
Я не знаю что можно записать в 'data'?

Евгений
24.08.2015, 20:51

Разобрался с полем цена (price) для сущьности товара в Commerce:
Должно быть:
return array('amount' => $price, 'currency_code' => 'UAH', 'data'=>array(),);

Добрый день, пользуюсь вашим модулем довольно давно. Модуль супер. Начал недавно делать новый сайт на связке Дру7 + ubercart 3. Встала необходимость парсинга товаров с другого сайта. Парсятся хорошо все поля кроме Артикула и цены, они просто не забиваются в карточку товара. Подскажите как мне их туда запихать??? Я так понимаю им нужно задать тип значения, т.к. в парсинге ясно сказано "PHP код, который должен вернуть значение поля (тип: decimal)." Подскажите пожалуйста как вернуть значение с этим типом.... Сам код у меня такой:
$price = array();
foreach ($doc->find('.styled_table tbody tr td') as price) {
$price[] = pq($price)->text();
}
return $price[4];

Ладно... По какой причине не добавляются изображеня? тоже самый Юберкарт. код такой:
array(
'file' => $doc->find('#product_info_image img')->attr('src'),
);

Результат выполнения такой:
Array
(
[file] => /images/products/common/3208.jpg
)

Все работает как бы) но при начале работы парсинга, сразе же выдает ошибку:
Возникла AJAX HTTP ошибка. Полученный код HTTP: 500 Следует отладочная информация. Путь: /batch?render=overlay&id=393&op=do Текст Состояния: Service unavailable (with message) Текст Ответа: EntityMetadataWrapperException: There can be only numerical keyed items in a list. в функции EntityListWrapper->get() (строка 989 в файле /sites/all/modules/entity/includes/entity.wrapper.inc)

Как с этим быть, код для парса изображений использую везде. А тут не выходит в чем причина?

Евгений
28.08.2015, 13:12

Попробуйте:
Return Array
(
[file] => /images/products/common/3208.jpg,
)

$images = array();
$images[] = array(
  'file' => $doc->find('#product_info_image img')->attr('src'),
);

Прошу прощения я так и пробовал
Return Array
(
[file] => /images/products/common/3208.jpg,
);

Парсинг ошибку пишет.... хотя на другом сайте такой код работает. Может ли быть так что версия jquery не та?

> There can be only numerical keyed items in a list.
Это значит, что у вас поле из нескольких значений, а ваш код для одного.

Добавить комментарий