问题 如何提高迭代DOMDocument的性能?


我正在使用cURL从服务器中提取网页。我将它传递给Tidy并将输出抛出到DOMDocument中。然后麻烦开始了。

该网页包含大约三千个(yikes)表标签,我正在从中抓取数据。有两种表,其中一种或多种类型B遵循类型A.

我已经使用了我的脚本 microtome(true) 调用。我在脚本的每个阶段之前和之后都进行了调用,并相互减去了时间。所以,如果你跟着我完成我的代码,我会解释它,分享个人资料结果,并指出问题所在。也许你甚至可以帮我解决问题。开始了:

首先,我包含两个文件。一个处理一些解析,另一个定义两个“数据结构”类。

// 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;

到目前为止,代码大约需要9秒钟。考虑到这是一个cron工作,不经常运行,我很好。但是,下一部分代码确实是barfs。这是我从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脚本从网页中提取数据的时间超过一分钟。啧!

我被建议分开我的主要工作循环的工作量,但考虑到我的数据的同质性​​,我不完全确定如何。任何有关改进此代码的建议都非常感谢。

我该怎么做才能改善我的代码执行时间?


5554
2017-12-18 06:03


起源

它可能更快使用 foreach($tables as $table) 因为你在打电话 $tables->item($i) 在那个循环中。我不确定,但它 威力 每次都要对链表进行线性遍历以查找索引。 foreach绝对应该按顺序枚举列表。 - Kevin Stricker
这个问题最有可能是因为@mootinator说...这个页面上的注释有关于它的一些信息 php.net/manual/en/domnodelist.item.php - Eliezer
所以 foreach 确实改善了处理时间,但使用while循环并消除 ->item() 通话速度更快。看到我的回答。 - Moshe


答案:


事实证明,我的循环非常低效。

用一个 foreach 减少一半到约31秒的时间。但那还不够快。所以我对一些样条线进行了网络化,并与大约一半的程序员进行了一些头脑风暴,我知道如何在网上戳。这是我们发现的:

使用DOMNodeList的 item() 存取器是线性的,在循环中产生指数缓慢的处理时间。因此,在每次迭代后删除第一个元素会使循环更快。现在,我们总是访问列表的第一个元素。这让我降到了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's 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);
}

请注意,我已针对我想要的数据进行了一些其他优化,但相关部分是我如何处理从一个项目到下一个项目的进度。


10
2017-12-18 10:58



这个解决方案将我脚本的运行时间从大约1天减少到大约25分钟! - John Langford