Używam cURL do przeciągania strony z serwera. Przekażę go do Tidy i wyrzucę dane wyjściowe do DOMDocument. Wtedy zaczyna się kłopot.Jak poprawić wydajność iteracji DOMDocument?
Strona zawiera około trzech tysięcy (yikes) znaczników tabeli, a ja z nich pobierane są dane. Istnieją dwa rodzaje tabel, w których jeden lub więcej typów B jest typu A.
Sprofilowałem swój skrypt, używając wywołań microtome(true)
. Umieściłem połączenia przed i po każdym etapie mojego skryptu i odejmowałem czasy od siebie nawzajem. Tak więc, jeśli prześledzisz mnie za pomocą mojego kodu, wyjaśnię to, podzielę się wynikami z profilu i wskażę, gdzie jest problem. Może nawet pomożecie mi rozwiązać problem. Oto:
Po pierwsze, dołączam dwa pliki. Jeden obsługuje trochę analizowania, a drugi definiuje dwie klasy "struktury danych".
// Imports
include('./course.php');
include('./utils.php');
Obejmuje nieistotne, o ile wiem, a więc przejdźmy do importu cURL.
// Execute cURL
$response = curl_exec($curl_handle);
Skonfigurowałem cURL tak, aby nie przekraczał limitu czasu i aby opublikować pewne dane nagłówka, które są niezbędne do uzyskania sensownej odpowiedzi. Następnie oczyszczam dane, aby przygotować je do 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;
Do tej pory kod trwał około dziewięć sekund. Biorąc pod uwagę, że jest to praca crona, która działa nieczęsto, nie mam z tym nic wspólnego. Jednak kolejna część kodu naprawdę barfuje. Tutaj biorę to, co chcę z HTML i wrzucam je do moich klas niestandardowych. (. Mam zamiar wepchnąć to do bazy danych MySQL też, ale jest to pierwszy krok)
// 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;
}
}
Dla porównania, oto funkcji użytkowych, które nazywam:
//
// 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;
}
Kod ten trwa 63 sekund . To ponad minuta, aby skrypt PHP mógł pobierać dane ze strony internetowej. Do licha!
Polecono mi podzielić pracę mojej głównej pętli roboczej, ale biorąc pod uwagę jednorodny charakter moich danych, nie jestem do końca pewien jak. Wszelkie sugestie dotyczące ulepszenia tego kodu są bardzo doceniane.
Co mogę zrobić, aby poprawić czas wykonywania kodu?
Szybsze może być użycie 'foreach ($ tables as $ table)', ponieważ wywołujesz '$ tables-> item ($ i)' w tej pętli. Nie jestem pewien, ale * może * może wykonywać liniowe przemieszczenie połączonej listy, aby znaleźć indeks za każdym razem. 'foreach' powinien definitywnie wyliczyć listę w kolejności. –
Problem jest najprawdopodobniej taki, jak @mootinator mówi ... notatki na tej stronie zawierają pewne informacje na ten temat http://php.net/manual/en/domnodelist.item.php – Eliezer
Tak więc 'foreach' rzeczywiście poprawia przetwarzanie razy, ale użycie pętli while i wyeliminowanie wywołań '-> item()' jest jeszcze szybsze. Zobacz moją odpowiedź. – Moshe