Подтвердить что ты не робот

Как улучшить производительность, повторяя DOMDocument?

Я использую cURL, чтобы вытащить веб-страницу с сервера. Я передаю его в Tidy и выдаю вывод в DOMDocument. Затем начинается проблема.

Веб-страница содержит около трех тысяч (yikes) табличных тегов, и я извлекаю данные из них. Существует два типа таблиц, в которых один или несколько типов B соответствуют типу A.

Я профилировал свой script с помощью вызовов microtome(true). Я размещал вызовы до и после каждого этапа моего script и вычитал время друг от друга. Итак, если вы последуете за мной через мой код, я объясню это, поделись результатами профиля и укажу, где проблема. Может быть, вы даже можете помочь мне решить проблему. Здесь мы идем:

Во-первых, я включаю два файла. Один обрабатывает некоторый синтаксический анализ, а другой определяет два класса структуры данных.

// Imports
include('./course.php');
include('./utils.php');

Включая, как мне известно, несущественны, поэтому давайте перейдем к импорту cURL.

//  Execute cURL
$response = curl_exec($curl_handle);

Я сконфигурировал cURL, чтобы не выходить из игры, и публиковать некоторые данные заголовка, необходимые для получения значимого ответа. Затем я очищаю данные, чтобы подготовить их к DOMDocument.

// Run about 25 str_replace calls here, to clean up
// then run tidy.



$html = $response; 

//  
//      Prepare some config for tidy
//  
       $config = array(
                  'indent'         => true,
                  'output-xhtml'   => true,
                   'wrap'           => 200);

    //  
    // Tidy up the HTML
    //  

    $tidy = new tidy;
    $tidy->parseString($html, $config, 'utf8');
    $tidy->cleanRepair();

    $html = $tidy;

До сих пор код занял около девяти секунд. Учитывая, что это работа cron, работающая нечасто, я в порядке с этим. Тем не менее, следующая часть кода действительно закрывается. Здесь, где я беру то, что хочу от HTML, и вставляю его в свои пользовательские классы. (Я планирую использовать это в базе данных MySQL, но это первый шаг.)

//  Get all of the tables in the page

$tables = $dom->getElementsByTagName('table');

//  Create a buffer for the courses

$courses = array();

//  Iterate

$numberOfTables = $tables->length;

for ($i=1; $i <$numberOfTables ; $i++) { 

    $sectionTable = $tables->item($i);
    $courseTable = $tables->item($i-1);

    //  We've found a course table, parse it.

    if (elementIsACourseSectionTable($sectionTable)) {

        $course = courseFromTable($courseTable);
        $course = addSectionsToCourseUsingTable($course, $sectionTable);            

        $courses[] = $course;
    }
}   

Для справки, здесь функции утилиты, которые я вызываю:

//  
//  Tell us if a given element is
//  a course section table.
//

function elementIsACourseSectionTable(DOMElement $element){

        $tableHasClass = $element->hasAttribute('class');
        $tableIsCourseTable = $element->getAttribute("class") == "coursetable"; 

        return $tableHasClass && $tableIsCourseTable;
}

//
//  Takes a table and parses it into an 
//  instance of the Course class.
//

function courseFromTable(DOMElement $table){

    $secondRow = $table->getElementsByTagName('tr')->item(1);   
    $cells = $secondRow->getElementsByTagName('td');

    $course = new Course;

    $course->startDate = valueForElementInList(0, $cells);
    $course->endDate = valueForElementInList(1, $cells);        
    $course->name = valueForElementInList(2, $cells);
    $course->description = valueForElementInList(3, $cells);
    $course->credits = valueForElementInList(4, $cells);
    $course->hours = valueForElementInList(5, $cells);
    $course->division = valueForElementInList(6, $cells);
    $course->subject = valueForElementInList(7, $cells);

    return $course;

}


//
//  Takes a table and parses it into an 
//  instance of the Section class.
//

function sectionFromRow(DOMElement $row){

    $cells = $row->getElementsByTagName('td');

    //
    //  Skip any row with a single cell
    //

    if ($cells->length == 1) {
        # code...
        return NULL;
    }

    //
    //  Skip header rows
    //

    if (valueForElementInList(0, $cells) == "Section" || valueForElementInList(0, $cells) == "") {
        return NULL;
    }


    $section = new Section;

    $section->section = valueForElementInList(0, $cells);
    $section->code = valueForElementInList(1, $cells);
    $section->openSeats = valueForElementInList(2, $cells);     
    $section->dayAndTime = valueForElementInList(3, $cells);        
    $section->instructor = valueForElementInList(4, $cells);        
    $section->buildingAndRoom = valueForElementInList(5, $cells);
    $section->isOnline = valueForElementInList(6, $cells);  

    return $section;

}

//
//  Take a table containing course sections
//  and parse it put the results into a
//  give course object.
//

function addSectionsToCourseUsingTable(Course $course, DOMElement $table){

    $rows = $table->getElementsByTagName('tr');
    $numRows = $rows->length;

    for ($i=0; $i < $numRows; $i++) { 

        $section = sectionFromRow($rows->item($i));

        //  Make sure we have an array to put sections into 

        if (is_null($course->sections)) {
            $course->sections = array();
        }

        //  Skip "meta" rows, since they're not really sections

        if (is_null($section)) {
            continue;
        }

        $course->addSection($section);
    }

    return $course;
}

//
//  Returns the text from a cell
//  with a 
//

function valueForElementInList($index, $list){
    $value =  $list->item($index)->nodeValue;
    $value = trim($value);
    return $value;
}

Этот код занимает 63 секунды. Это за минуту для PHP script для вывода данных с веб-страницы. Sheesh!

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

Что я могу сделать, чтобы улучшить время выполнения кода?

4b9b3361

Ответ 1

Оказывается, моя петля ужасно неэффективна.

Использование времени foreach сокращает время от половины до примерно 31 секунды. Но это было недостаточно быстро. Таким образом, я сформулировал несколько сплайнов и провел мозговой штурм примерно с половиной программистов, которые я знаю, как тыкать онлайн. Здесь мы обнаружили:

Использование DOMNodeList item() accessor является линейным, производя экспоненциально медленное время обработки в циклах. Таким образом, удаление первого элемента после каждой итерации делает цикл более быстрым. Теперь мы всегда получаем доступ к первому элементу списка. Это привело меня к 8 секундам.

После того, как я сыграл еще несколько, я понял, что свойство ->length DOMNodeList так же плохо, как item(), так как оно также несет линейную стоимость. Поэтому я изменил цикл for на следующее:

    $table = $tables->item(0);

while ($table != NULL) {

    $table = $tables->item(0);

    if ($table === NULL) {
        break;
    }

    //
    //  We've found a section table, parse it.
    //

    if (elementIsACourseSectionTable($table)) {

        $course = addSectionsToCourseUsingTable($course, $table);           
    }

    //
    //  Skip the last table if it not a course section
    //

    else if(elementIsCourseHeaderTable($table)){
        $course = courseFromTable($table);
        $courses[] = $course;
    }

    //
    //  Remove the first item from the list
    //

    $first = $tables->item(0);
    $first->parentNode->removeChild($first);

    //
    //  Get the next table to parse
    //

    $table = $tables->item(0);
}

Обратите внимание, что я сделал некоторые другие оптимизации с точки зрения таргетинга на данные, которые я хочу, но важная часть - это то, как я обрабатываю переход от одного элемента к другому.