2012-12-18 21 views
11

Tôi đang sử dụng cURL để kéo trang web từ máy chủ. Tôi chuyển nó vào Tidy và ném đầu ra vào DOMDocument. Sau đó, rắc rối bắt đầu.Làm cách nào để cải thiện hiệu suất lặp lại DOMDocument?

Trang web chứa khoảng ba nghìn thẻ bảng (yikes) và tôi đang lấy dữ liệu từ chúng. Có hai loại bảng, trong đó một hoặc nhiều loại B theo một loại A.

Tôi đã lược tả tập lệnh của mình bằng cách sử dụng các cuộc gọi microtome(true). Tôi đã đặt các cuộc gọi trước và sau mỗi giai đoạn của kịch bản của tôi và trừ đi thời gian của nhau. Vì vậy, nếu bạn sẽ theo tôi thông qua mã của tôi, tôi sẽ giải thích nó, chia sẻ kết quả hồ sơ, và chỉ ra nơi mà vấn đề là. Có lẽ bạn thậm chí có thể giúp tôi giải quyết vấn đề. Ở đây chúng tôi đi:

Trước tiên, tôi bao gồm hai tệp. Một xử lý một số phân tích cú pháp, và định nghĩa khác hai lớp "cấu trúc dữ liệu".

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

Bao gồm không quan trọng theo như tôi biết và vì vậy hãy tiếp tục quá trình nhập cURL.

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

Tôi đã định cấu hình cURL để không hết thời gian và đăng một số dữ liệu tiêu đề cần thiết để nhận phản hồi có ý nghĩa. Tiếp theo, tôi dọn sạch dữ liệu để chuẩn bị cho 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; 

Cho đến bây giờ, mã đã mất khoảng 9 giây. Xem xét việc này là một công việc cron, chạy thường xuyên, tôi tốt với điều đó. Tuy nhiên, phần tiếp theo của mã thực sự là barfs. Đây là nơi tôi lấy những gì tôi muốn từ HTML và đưa nó vào các lớp tùy chỉnh của tôi. (. Tôi có kế hoạch cụ này vào một cơ sở dữ liệu MySQL cũng vậy, nhưng đây là một bước đầu tiên)

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

Để tham khảo, đây là chức năng tiện ích mà tôi gọi là:

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

Mã này mất 63 giây . Đó là hơn một phút cho một kịch bản PHP để kéo dữ liệu từ một trang web. Sheesh!

Tôi đã được khuyên nên phân chia khối lượng công việc của vòng lặp công việc chính của mình, nhưng xem xét tính chất đồng nhất của dữ liệu của tôi, tôi không hoàn toàn chắc chắn như thế nào. Bất kỳ đề xuất nào về việc cải thiện mã này đều được đánh giá cao.

Tôi có thể làm gì để cải thiện thời gian thực thi mã của mình?

+2

Có thể sử dụng 'foreach ($ tables as $ table) nhanh hơn vì bạn đang gọi' $ tables-> item ($ i) 'trong vòng lặp đó. Tôi không chắc chắn, nhưng nó * có thể * đang làm một traversal tuyến tính của một danh sách liên kết để tìm chỉ mục mỗi lần. 'foreach' chắc chắn chỉ liệt kê danh sách theo thứ tự. –

+2

Vấn đề rất có thể là @mootinator nói ... các ghi chú trên trang này có một số thông tin liên quan đến nó http://php.net/manual/en/domnodelist.item.php – Eliezer

+0

Vì vậy, 'foreach' thực sự cải thiện việc xử lý thời gian, nhưng sử dụng một vòng lặp while và loại bỏ các lệnh '-> item()' thậm chí còn nhanh hơn. Xem câu trả lời của tôi. – Moshe

Trả lời

10

Hóa ra vòng lặp của tôi cực kỳ kém hiệu quả.

Sử dụng foreach thời gian cắt giảm một nửa còn khoảng 31 giây. Nhưng điều đó không đủ nhanh. Vì vậy, tôi đã xem xét một số splines và đã làm một số động não với khoảng một nửa các lập trình viên mà tôi biết làm thế nào để chọc trực tuyến. Dưới đây là những gì chúng tôi đã tìm thấy:

Sử dụng truy cập item() của DOMNodeList là tuyến tính, tạo ra thời gian xử lý chậm theo cấp số nhân trong các vòng lặp. Vì vậy, việc loại bỏ phần tử đầu tiên sau mỗi lần lặp lại làm cho vòng lặp nhanh hơn. Bây giờ, chúng tôi luôn truy cập phần tử đầu tiên của danh sách. Điều này đưa tôi xuống 8 giây.

Sau khi phát thêm một số chi tiết, tôi nhận thấy rằng thuộc tính ->length của DOMNodeList cũng tồi tệ như item(), vì nó cũng phát sinh chi phí tuyến tính.Vì vậy, tôi đã thay đổi tôi vòng lặp for như sau:

$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); 
} 

Lưu ý rằng tôi đã thực hiện một số tối ưu hóa khác về nhắm mục tiêu các dữ liệu tôi muốn, nhưng phần có liên quan là cách tôi xử lý tiến triển từ một mục tiếp theo.

+1

Giải pháp này giảm thời gian chạy tập lệnh của tôi từ khoảng 1 ngày xuống còn 25 phút! –

Các vấn đề liên quan