История одного парсера

в 14:00, , рубрики: 1С-Битрикс, csv, php, битрикс, парсинг контента, Песочница, метки: , , ,
Cайт без доступов

По долгу службы на мои плечи легла задача перенести пачку(>100) статей с одного сайта заказчика на другой. Одно из условий было — переносить не только название и текст статьи, но и метатеги keywords и description. В итоге статьи должны были перекочевать в инфоблок битрикса.

В силу реализации сайта-источника на неком конструкторе сайтов сделать sql dump никак не представлялось возможным, т.к. все, что позволяла админка сего конструктора — выбрать шаблон, привязать домен и, как бонус, добавлять/редактировать контент. Не было там ни phpMyAdmin, ни ftp, ни какого-либо инструмента, позволяющего легким движением руки осуществить задуманное.

Реализация

Единственным разумным вариантом, пришедшим в голову, стал парсер. Вроде бы ничего сложного — парсим со
страниц со статьями содержимое нужных тэгов и сохраняем их в csv файл для последующего скармливания битриксу.

Для начала надо было получить ссылки на все статьи. Путь к статье был вида:

http://sitename.com/articles/article_id

Причем article_id были совершенно хаотичны(по крайней мере мне так показалось), и простым циклом с инкремируемым номером статьи весь нужный контент было не заполучить. Пришлось сначала парсить ссылки на статьи на страницах со списками этих самых статей. Благо таких страничек было ограниченное количество, да и запрос был следующего вида:

http://sitename.com/atricles/page/page_number

Тут все просто — перебираем странички в цикле, на каждом шаге инкремируя page_number.

for($num_page = 1; $num_page < $num_page_max; $num_page++){
	$html = file_get_contents('http://sitename.com/articles/page/'.$num_page);
}

И тут на тебе сюрприз — функция file_get_contents ругается, ссылаясь на то, что сервер, с которого она пытается get_contents говорит 403.

file_get_contents 403

Пошел в гугл, первая пачка ссылок вела на stackoverflow, где моим многим товарищам по несчастью другие товарищи по этому поводу яростно советовали забить на этот глупый file_get_contents и использовать curl, ничуть не проливая свет на причины внезапной 403. Curl это конечно круто, но для него нужен соответствующий модуль, подключать который для кратковременной задачи было лениво. И тут один из товарищей пояснил, что правильно настроенный сервер рубает запросы, посланные без заголовков, а file_get_contents
по умолчанию заголовки не отправляет, а только пытается выдернуть нужный документ. По умолчанию.

Смотрим документацию php — в file_get_contents третьим параметром можно передавать некий context, созданный с помощью stream_context_create(). Смотрим эту создавалку контекстов — и вот он примерчик, как присобачить заголовки. В итоге получаем странички без всяких курлов, притворившись браузером:


$context = stream_context_create(array(
      'http'=>array(
        'method'=>"GET",                
        'header'=>"Accept: text/html,application/xhtml+xml,application/xmlrn" .
                  "Accept-Charset: ISO-8859-1,utf-8rn" .
                  "Accept-Encoding: gzip,deflate,sdchrn" .
                  "Accept-Language: ru-RU,ru;q=0.8rn",
        'user_agent'=>"User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.11 (KHTML, like Gecko)rn"              
     )
));

for($num_page = 1; $num_page < $num_page_max; $num_page++){
	$html = file_get_contents('http://sitename.com/articles/page/'.$num_page, FALCE, $context);
}

Парсинг html, просто, как дважды два

Страницы получили, теперь надо решить, как эти страницы обрабатывать — выдергивать нужные ссылки. Тупо
парсить регулярками это конечно круто, но хардкора не хотелось, хотелось чего то простого и приятного. Товарищ гугл подсказал, что есть такой проект, как simplehtmldom, созданный специально для удобного выдергивания нужного контента из html кода страниц. Повтыкал в документацию, скачал, подключил, сделал как в quick start, но то ли в силу невнимательного чтения документации, то ли из-за недостаточной степени прямоты рук, завести это чудо я так и не смог, и решил попробовать найти что-нибудь другое. И нашел.

Второй, более податливый для меня экземпляр назывался phpQuery — название говорит само за себя, можно дергать на php информацию из html с помощью JQuery подобного синтаксиса. Он оказался на порядок жирнее simplehtmldom, но оно определенно того стоило. То, что могло бы глазодробяще выглядеть на регулярках, с помощью этого чуда выглядело так:

   $link_arr = array(); // сюда мы складываем адреса ссылок на статьи
    for($num_page = 1; $num_page < 15; $num_page++){
    
	$html = file_get_contents('http://sitename.com/articles/page/'.$num_page, FALSE, $context); 
        
	$pq = phpQuery::newDocumentHTML($html); // запускаем волшебные процессы PHPQuery, отдавая ему притянутую страницу
        foreach ($pq->find('a.article-title') as $link){ // вот так просто, как в jquery, парсим со страницы все ссылки с классом .article_link
            array_push($link_arr, pq($link)->attr('href')); // и выдергиваем из каждой ссылки ее адрес, заталкивая его в массив
        }      
    }

Хвала, почет и уважуха авторам этой библиотеки.

Парсинг html 2, типы текстовых данных битрикса

Адреса всех статей получили. Теперь тянем каждую статью и выдергиваем ее название, текст, meta keyword и meta description страницы со статьей, попутно заталкивая все это в массив, из которого будем формировать будущий csv:

    $csv_array = array();
    foreach($link_arr as $val){
        $html = file_get_contents($val, FALSE, $context); 
        $pq = phpQuery::newDocumentHTML($html);
        $page_title = pq("title")->text();
        $page_keywords = pq("meta[name=keywords]")->attr('content');
        $page_description = pq("meta[name=description]")->attr('content');
        $article_title = pq("article header h1")->text();
        $article_text = pq(".article-content")->html();
        
        array_push($csv_array, array($page_title, $page_keywords, $page_description, $article_title, $article_text, 'html'));
    }

Внимательный читатель мог заметить, что среди элементов массива, помимо нужной информации присутствует совершенно неадекватно выглядящий элемент, который у всех статей является строкой 'html'. Ибо битрикс. Дело в том, что в тексте статей присутствовала разметка(), которую так же надо было сохранить, т.к. пять абзацев сплошняком — некрасиво, а вставлять тэги в нужные места сотне статей никому бы не захотелось.А если при импорте в инфоблок у csv файла не будет отдельного поля, указывающего битриксу тип данных(text или html), то по умолчанию все теги эта cms считает текстом, и выводит их текст, даже если в настройках инфоблока указать тип по умолчанию html.

Создание csv для импорта в битрикс

Теперь вся информация собрана и структурирована, осталось сделать из нее вменяемый csv файл:

$file = fopen("articles.csv", 'w');

        function createCSV(&$vals, $key, $file) {
            fputcsv($file, $vals, ';', '"');
        }
        array_walk($csv_array, 'createCSV', $file);

        fclose($file);

Вот и конец, битрикс успешно скушал csv со статьями, заказчик доволен.

Автор: moveax3

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js