12

サーバーからWebページをプルするためにcURLを使用しています。それをTidyに渡し、出力をDOMDocumentにスローします。それからトラブルが始まります。

Webページには約3000(yikes)のテーブルタグが含まれており、それらからデータを取得しています。テーブルには2種類あり、1つ以上のタイプBがタイプAの後に続きます。

microtome(true)呼び出しを使用してスクリプトのプロファイルを作成しました。スクリプトの各段階の前後に電話をかけ、時間を差し引いています。それで、あなたが私のコードを通して私をフォローするなら、私はそれを説明し、プロファイルの結果を共有し、そして問題がどこにあるかを指摘します。多分あなたは私が問題を解決するのを手伝うことさえできます。どうぞ:

まず、2つのファイルを含めます。1つはいくつかの解析を処理し、もう1つは2つの「データ構造」クラスを定義します。

// 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スクリプトがWebページからデータをプルするのに1分以上かかります。シーシュ!

メインの作業ループのワークロードを分割するようにアドバイスされましたが、データの均質性を考慮すると、その方法が完全にはわかりません。このコードを改善するための提案は大歓迎です。

コードの実行時間を改善するにはどうすればよいですか?

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 に答える