12

我正在使用 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 工作,不经常运行,我对此很好。但是,代码的下一部分真的很糟糕。这是我从 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 脚本从网页中提取数据需要一分钟多的时间。嘘!

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

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

4

1 回答 1

12

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

使用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);
}

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

于 2012-12-18T10:58:41.827 回答