如何提高迭代DOMDocument的性能?

时间:2012-12-18 06:03:23

标签: php performance 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脚本从网页中提取数据的时间超过一分钟。啧!

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

我可以做些什么来改善代码执行时间?

1 个答案:

答案 0 :(得分:12)

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

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

使用DOMNodeList的item()访问器是线性的,在循环中产生指数级缓慢的处理时间。因此,在每次迭代后删除第一个元素会使循环更快。现在,我们总是访问列表的第一个元素。这让我降到了8秒。

在玩了一些之后,我意识到->length的{​​{1}}属性与DOMNodeList一样糟糕,因为它也会产生线性成本。所以我将for循环更改为:

item()

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