Что нового

Медленная работа .querySelector по сравнению с .getElementsByClassName

veretragna

Как писал, так и работает.
Сообщения
140
Репутация
10
Собственно, такая проблема.
Попробовал сегодня перенести часть опрашивающего кода одного из своих скриптов-ботов на .querySelector(), и после этого стал наблюдать очень медленную работу скрипта как раз в этой части (до этого использовал .getElementsByClassName()).
Код опрашивает ровно 10 записей "дневника" и выводит их на текстовую панель. Если использовать для их поиска на форме и опроса метод .getElementsByClassName(), эта часть кода выполняется 0,5-0,7 секунд, а если .querySelector(), то 3-4 секунды. Это очень много, и при этом концептуально код не поменялся.
Вопрос: можно ли ускорить выполнение .querySelector()? Или медленность - это его болячка?

Полностью рабочий пример с кодом будет чуть позже, бот большой и сложный, на свежую голову выцарапаю из него пример и выложу.
А пока ограничусь куском проблемного кода для понимания ситуации.

Было:

Код:
Local $temp = _IEGetObjById($oIE, "diary")
	
	If IsObj($temp) Then
		$tdivs = $temp.getElementsByClassName("d_line")
		For $tdiv In $tdivs
			If Not StringInStr($tdiv.style.display, "none") Then
				$cnt += 1
				If $cnt = 11 Then Return
				local $props = $tdiv.getElementsByTagName("div")
				For $z In $props
					If StringInStr($z.className, "d_time") Then
						$time = $z.innertext
					ElseIf StringInStr($z.className, "d_msg") Then
						$msg = $z.innertext
					EndIf
				Next
				; дальше идет создание элементов ListViewItem, ничего интересного.
			EndIf
		Next
	EndIf


Стало:

Код:
Local $temp = _IEGetObjById($oIE, "diary")
	
	If IsObj($temp) Then
		$tdivs = $temp.querySelectorAll("div.d_line")
		For $tdiv In $tdivs
			If Not StringInStr($tdiv.style.display, "none") Then
				$cnt += 1
				If $cnt = 11 Then Return
				$time = $tdiv.querySelector("div.d_time").innertext
				$msg = $tdiv.querySelector("div.d_msg").innertext
				; дальше идет создание элементов ListViewItem, ничего интересного.
			EndIf
		Next
	EndIf
 
Автор
veretragna

veretragna

Как писал, так и работает.
Сообщения
140
Репутация
10
Вопрос все еще актуален.
Привожу пример во вложениях.
К тому же заметил, что, если коллекция элементов получена с помощью .querySelectorAll(), при опрашивании элементов коллекции сам IE вылетает с ошибкой, при этом скрипт продолжает выполняться. Не могу понять, откуда берется ошибка.
IE 11x86, Win 10 x64 Professional, AutoIt v.3.3.12.0.

Скрипт:
Код:
#include <IE.au3>
#include <Array.au3>

Local $oIE = _IECreate(@ScriptDir & "\page.html")

Local $t = TimerInit()
Local $arr = _GetDiary_byClassname()
msgBox(0, "Времени затрачено:", "На постройку списка методом byClassName затрачено " & Round(TimerDiff($t)/1000, 2) & " секунд.")
_ArrayDisplay($arr)

$t = TimerInit()
$arr = _GetDiary_byQuerySelector()
msgBox(0, "Времени затрачено:", "На постройку списка методом byQuerySelector затрачено " & Round(TimerDiff($t)/1000, 2) & " секунд.")
_ArrayDisplay($arr)

_IEQuit($oIE)

Func _GetDiary_byClassname()

	Local $Ret[11][2]

	Local $temp = _IEGetObjById($oIE, "diary")
	Local $items, $item, $z, $props, $cnt
	
	If IsObj($temp) Then
		$items = $temp.getElementsByClassName("d_line")
		For $item In $items
			If Not StringInStr($item.style.display, "none") Then
				$cnt += 1
				If $cnt = 11 Then Return
				$props = $item.getElementsByTagName("div")
				For $z In $props
					If StringInStr($z.className, "d_time") Then
						$Ret[$cnt][0] = $z.innertext
					ElseIf StringInStr($z.className, "d_msg") Then
						$Ret[$cnt][1] = $z.innertext
					endif
				Next
			EndIf
		Next
	EndIf
	
	Return $Ret

EndFunc

Func _GetDiary_byQuerySelector()

	Local $Ret[11][2]

	Local $temp = _IEGetObjById($oIE, "diary")
	Local $items, $item, $cnt
	
	If IsObj($temp) Then
		$items = $temp.querySelectorAll("div.d_line")
		For $item In $items
			If Not StringInStr($item.style.display, "none") Then
				$cnt += 1
				If $cnt = 11 Then Return
				$Ret[$cnt][0] = $item.querySelector("div.d_time").innertext
				$Ret[$cnt][0] = $item.querySelector("div.d_msg").innertext
			EndIf
		Next
	EndIf
	
	Return $Ret

EndFunc
 

Вложения

  • Page.zip
    176.8 КБ · Просмотры: 2

inververs

AutoIT Гуру
Сообщения
2,135
Репутация
465
Вылетает потому что For In глючная. Вот как я обхожу коллекции.
Код:
Local $elements = $oIE.document.querySelectorAll('[class~="font-error"]')
	If Not IsObj($elements) Then Return ''
	For $i = 0 To $elements.length - 1
		$info &= StringRegExpReplace($elements.item($i).innerText, '(?mi)^[ \t]*$\r?\n', '') & @CRLF
	Next

По поводу быстроты, да медленнее т.к это универсальная функция и она по определению медленнее.
 
Автор
veretragna

veretragna

Как писал, так и работает.
Сообщения
140
Репутация
10
inververs, это не цикл глючный. Если заменить в другой части кода $temp.querySelectorAll("div.d_line") на $temp.getElementsByClassName("d_line"), все работает предельно корректно. Видимо, .querySelectorAll выдает немного неправильную коллекцию.
С Вашим примером согласен, попробую сделать так.

По поводу скорости: а может быть такое, что getElementsByClassName является именно частью бинарного кода IE, a .querySelectorAll выполняется в окружении IE, но не в бинарном коде, а в JS? Это бы многое пояснило.

UPD:
И поясните, будьте добры, в двух словах, что убирает регулярка в примере.
 

inververs

AutoIT Гуру
Сообщения
2,135
Репутация
465
Это цикл глючный т.к не правильно обрабатывает такие коллекции.

veretragna [?]
По поводу скорости: а может быть такое, что getElementsByClassName является именно частью бинарного кода IE, a .querySelectorAll выполняется в окружении IE, но не в бинарном коде, а в JS? Это бы многое пояснило.
На счет этого не знаю, но знаю что есть методы поиска которые работают быстро и медленнее. Вот getElementById вообще самый быстрый.


Добавлено:
Сообщение автоматически объединено:

Можете написать тесты на JS что бы проверить ваше предположение


Добавлено:
Сообщение автоматически объединено:

Убирает строки которые ничего не содержат.
 
Автор
veretragna

veretragna

Как писал, так и работает.
Сообщения
140
Репутация
10
inververs, спасибо за подсказки!
Тема решена.


Добавлено:
Сообщение автоматически объединено:

В конце концов забил на методы DOM и сделал вывод текста на корявой, но рабочей регулярке.
Беру .innerhtml всего дневника и разбираю его по запчастям.

Вот что вышло.
Код:
Local $temp = _IEGetObjById($oIE, "diary"), $data
	Local $a_D, $a_S
	Local $cnt = 0
	
	If IsObj($temp) Then
		$data = $temp.innerhtml
		$a_D = _StringBetween($data, '<div class="d_time', '</div>')
		For $i = 0 To UBound($a_D) - 1
			$a_D[$i] = StringStripWS(StringTrimLeft($a_D[$i], StringInStr($a_D[$i], ">")), 3)
			; обрезаем тег с начала строки, он всегда только один
		Next
		$a_S = _StringBetween($data, '<div class="d_msg', '</div>')
		For $i = 0 To UBound($a_S) - 1
			If StringInStr($a_S[$i], "display") And StringInStr($a_S[$i], "none") Then ContinueLoop
			$cnt += 1
			If $cnt = 11 Then ExitLoop
			; вырезаем все html-коды ссылок, оставляя только innertext
			$a_S[$i] = StringRegExpReplace($a_S[$i], '(?i)<a.*?>', "")
			$a_S[$i] = StringReplace($a_S[$i], '</a>', "")
			; вырезаем все span'ы с текста
			$a_S[$i] = StringRegExpReplace($a_S[$i], '(?i)<span.*?>.*?</span>', "")
			$a_S[$i] = StringReplace($a_S[$i], '</span>', "")
			; вырезаем остатки тегов, оставленные  _StringBetween($data, '<div class="d_msg', '</div>')
			$a_S[$i] = StringStripWS(StringTrimLeft($a_S[$i], StringInStr($a_S[$i], '>')), 3)

			; и получаем фразы для вывода.
			$Time = $a_D[$i]
			$Phrase = $a_S[$i]
	EndIf


Получилось очень быстро - десятые доли секунды.
 
Верх