Что нового

Простой парсер HTML кода HTMLFILE

inververs

AutoIT Гуру
Сообщения
2,135
Репутация
465
В замен парсеру на регулярных выражениях html кода есть более удобный инструмент как объект HTMLDocument.
Создается так:
Код:
$oHTML = ObjCreate("HTMLFILE")

Позволяет загрузить html код и получать доступ к свойствам и методам.
Все свойства можно найти здесь:
http://msdn.microsoft.com/en-us/library/ms535862
или здесь
http://www.w3schools.com/jsref/dom_obj_document.asp
или же пользоваться функциями из библиотеки IE.au3

Основной метод это write - который загружает html код.

Вот например, получение расположения всех картинок нашего форума:
Код:
Local $oHTTP = ObjCreate("winhttp.winhttprequest.5.1")
$oHTTP.Open("GET", "http://autoit-script.ru/")
$oHTTP.Send()
Local $HTMLSource = $oHTTP.Responsetext

Local $oHTML = ObjCreate("HTMLFILE")
$oHTML.Write($HTMLSource)
Local $imgs = $oHTML.images
For $img in $imgs
	ConsoleWrite($img.src & @LF)
Next




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


Функции из библиотеки Ie.au3


Код:
#include <IE.au3>
$imgs = _IETagNameGetCollection ( $oHTML, 'img')
For $img in $imgs
	ConsoleWrite($img.src & @LF)
Next


Получим объект Window, для передачи его в функцию _IEGetObjByName
Код:
Local $oWindow = $oHTML.parentWindow


Получим приветствие:
Код:
Local $oLi = _IEGetObjByName($oWindow,'name')
ConsoleWrite($oLi.InnerText & @LF)


Если весь текст форума без тэгов
Код:
ConsoleWrite(_IEBodyReadText ( $oWindow ) & @LF)
 

CreatoR

Must AutoIt!
Команда форума
Администратор
Сообщения
8,671
Репутация
2,481
Re: Простой парсер HTML кода

Можно с помощью этой штуки получить типа дерево тегов, или получать данные как то так:

Код:
$oHTML.getElementById("some_id").Div.P.A.Href


:scratch:?
 

firex

AutoIT Гуру
Сообщения
943
Репутация
208
Re: Простой парсер HTML кода

Код:
$oHTML.getElementById("some_id").GetElementsByTagName("div") ;Это уже потенциальная коллекция


В любом случае вы не обойдетесь без вложенных циклов с вашей задачей.
 

CreatoR

Must AutoIt!
Команда форума
Администратор
Сообщения
8,671
Репутация
2,481
Re: Простой парсер HTML кода

А как можно получить объект по его классу?
я знаю что есть getElementsByClassName, но оно работает только в IE 9+, у меня же стоит 8.


Нашёл, это classname.
 

madmasles

Модератор
Глобальный модератор
Сообщения
7,790
Репутация
2,322
Re: Простой парсер HTML кода

CreatoR [?]
как можно получить объект по его классу?
Можно примерно так, у меня тоже 8:
Код:
$sHTML = '...test text</div><div><div><div>' & @CRLF & _
		'<div class="test">' & @CRLF & _
		'<div class="item-box">data1</div>' & @CRLF & _
		'<div class=item-box>data2</div>' & @CRLF & _
		'<div class="item-box">data3</div>' & @CRLF & _
		'<div class="item-box">data4</div>' & @CRLF & _
		'<div class=''item-box''>data5</div>' & @CRLF & _
		'</div>' & @CRLF & _
		'<div class="test1">' & @CRLF & _
		'<div class="item-box">data6</div>' & @CRLF & _
		'</div></div></div></div>test text...'

$oHF = ObjCreate('HTMLFILE')
If @error Then Exit 1
$oHF.Write($sHTML)
$oDivs = $oHF.GetElementsByTagName('div')
For $oTmp In $oDivs
	If $oTmp.GetAttribute('ClassName') == 'test' Then
;~ 	If $oTmp.ClassName == 'test' Then; или так
		$oDivs = $oTmp.GetElementsByTagName('div')
		For $oTmp In $oDivs
			ConsoleWrite($oTmp.ClassName & @TAB & $oTmp.innertext & @LF)
		Next
		ExitLoop
	EndIf
Next
$oHF.Close
 

CreatoR

Must AutoIt!
Команда форума
Администратор
Сообщения
8,671
Репутация
2,481
Re: Простой парсер HTML кода

Пытаюсь построить на основе этой штуки некую функцию, чтобы легче обрабатывать Html, вот пример:

Код:
$sHtml = _
	'<body>' & @CRLF & _
	'	<span id="links">' & @CRLF & _
	'		<a id="some" name="123">test1</a>' & @CRLF & _
	'		<a id="link" class="link" href="http://test.com">test2</a>' & @CRLF & _
	'	</span>' & @CRLF & _
	'	<span id="links">' & @CRLF & _
	'		<a id="some" name="123">test3</a>' & @CRLF & _
	'		<a id="link" class="link" href="http://test.com">test4</a>' & @CRLF & _
	'	</span>' & @CRLF & _
	'</body>'

$oHTML = _HtmlDOM_Init($sHtml)
$oTree = _HtmlDOM_GetElementByTree($oHTML, 'body//span{links}(0)//a{link}')
$sResult = _HtmlDOM_GetProperty($oTree, 'Html')

ConsoleWrite($sResult & @LF)

Func _HtmlDOM_Init($sHtml)
	Local $oHTML = ObjCreate('HTMLFILE')
	
	If Not IsObj($oHTML) Then
		Return SetError(1, 0, 0)
	EndIf
	
	$oHTML.Write($sHtml)
	Return $oHTML
EndFunc

Func _HtmlDOM_GetElementByTree($oHTML, $sTree)
	If Not IsObj($oHTML) Then
		Return SetError(1, 0, 0)
	EndIf
	
	Local $aTree, $sTag, $sAttrib, $iNode
	Local $oTree_Tmp = 0
	Local $oTree = $oHTML
	
	$aTree = StringSplit($sTree, '//', 1)
	
	For $i = 1 To $aTree[0]
		$sTag = StringRegExpReplace($aTree[$i], '^([^{(]+).*', '\1')
		If @extended = 0 Then ContinueLoop
		
		$sAttrib = StringRegExpReplace($aTree[$i], '^[^{(]+{([^}]+)}.*', '\1')
		If @extended = 0 Then $sAttrib = ''
		
		$iNode = Number(StringRegExpReplace($aTree[$i], '^[^{(]+(?:{[^}]+})?\((\d+)\)', '\1'))
		
		;Current node have no tags attributes
		If $sAttrib = '' Then
			$oTree_Tmp = $oTree.getElementsByTagName($sTag)
			
			If IsObj($oTree_Tmp) Then
				$oTree = $oTree_Tmp($iNode)
			EndIf
			
			ContinueLoop
		EndIf
		
		$oTree_Tmp = $oTree.getElementById($sAttrib)
		
		ConsoleWrite($oTree.NodeName & @TAB & $sAttrib & @TAB & ' Attrib found = ' & (IsObj($oTree_Tmp) = 1) & @LF)
		
		;If current element found with specified attrib (id/class/name), the assign it and continue with next node
		If IsObj($oTree_Tmp) Then
			$oTree = $oTree_Tmp
			ContinueLoop
		EndIf
		
		;Search by tag name
		$oTree_Tmp = $oTree.getElementsByTagName($sTag)
		
		If IsObj($oTree_Tmp) Then
			For $oTag In $oTree_Tmp
				If $oTag.ID = $sAttrib Or $oTag.ClassName = $sAttrib Or $oTag.Name = $sAttrib Then
					$oTree = $oTag
					ContinueLoop 2
				EndIf
			Next
			
			$oTree = $oTree_Tmp($iNode)
		EndIf
	Next
	
	Return $oTree
EndFunc

Func _HtmlDOM_GetProperty($oHTML, $sProperty, $sInnerOuter = 'Inner')
	If Not IsObj($oHTML) Then
		Return SetError(1, 0, 0)
	EndIf
	
	Switch $sProperty
		Case 'Html'
			Return ($sInnerOuter = 'Inner' ? $oHTML.InnerHtml : $oHTML.OuterHtml)
		Case 'Text'
			Return ($sInnerOuter = 'Inner' ? $oHTML.InnerText : $oHTML.OuterText)
	EndSwitch
EndFunc


Здесь я ожидаю на выходе test2, но почему то получаю test1 :scratch:.
Идея в том, чтобы одним вызовом получить объект указанного элемента используя древовидную структуру.

Код:
body//span{links}(0)//a{link}
тут дерево разделено //, каждый элемент это тег, далее его атрибуты (id/class/name), и в конце номер элемента (если найдено несколько одинаковых).
 

CreatoR

Must AutoIt!
Команда форума
Администратор
Сообщения
8,671
Репутация
2,481
Re: Простой парсер HTML кода

Вот оно! :laugh:

Код:
$sHTML = _
	'<body>' & @CRLF & _
	'	<span id="links">' & @CRLF & _
	'		<a id="some" name="123">test1</a>' & @CRLF & _
	'		<a id="link" class="link" href="http://test.com">test2</a>' & @CRLF & _
	'	</span>' & @CRLF & _
	'	<span id="links">' & @CRLF & _
	'		<a id="some" name="123">test3</a>' & @CRLF & _
	'		<a id="link" class="link" href="http://test.com">test4</a>' & @CRLF & _
	'	</span>' & @CRLF & _
	'</body>'

$oHTML = _HtmlDOM_Init($sHTML)

$oTree = _HtmlDOM_GetElementByTree($oHTML, 'span{links}(0)//a{link}')
$sResult = _HtmlDOM_GetProperty($oTree, 'Html') ;test2
ConsoleWrite($sResult & @LF)

$oTree = _HtmlDOM_GetElementByTree($oHTML, 'span{links}(1)//a{link}')
$sResult = _HtmlDOM_GetProperty($oTree, 'Html') ;test4
ConsoleWrite($sResult & @LF)

_HtmlDOM_UnInit($oHTML)

Func _HtmlDOM_Init($sHTML)
	Local $oHTML = ObjCreate('HTMLFILE')
	
	If Not IsObj($oHTML) Then
		Return SetError(1, 0, 0)
	EndIf
	
	$oHTML.Write($sHTML)
	Return $oHTML
EndFunc

Func _HtmlDOM_UnInit($oHTML)
	If Not IsObj($oHTML) Then
		Return SetError(1, 0, 0)
	EndIf
	
	$oHTML.Close
EndFunc

Func _HtmlDOM_GetElementByTree($oHTML, $sTree)
	If Not IsObj($oHTML) Then
		Return SetError(1, 0, 0)
	EndIf
	
	Local $aTree, $sTag, $sAttrib, $iNode
	Local $oTree_Tmp = 0, $oTag = 0
	Local $oTree = $oHTML
	
	$aTree = StringSplit($sTree, '//', 1)
	
	For $i = 1 To $aTree[0]
		$sTag = StringRegExpReplace($aTree[$i], '^([^{(]+).*', '\1')
		If @extended = 0 Then ContinueLoop
		
		$sAttrib = StringRegExpReplace($aTree[$i], '^[^{(]+{([^}]+)}.*', '\1')
		If @extended = 0 Then $sAttrib = ''
		
		$iNode = StringRegExpReplace($aTree[$i], '^[^{(]+(?:{[^}]+})?\((\d+)\)', '\1')
		
		If @extended = 0 Then
			$iNode = ''
		Else
			$iNode = Number($iNode)
		EndIf
		
		;Current node have no tags attributes
		If $sAttrib = '' Then
			$oTree_Tmp = $oTree.GetElementsByTagName($sTag)
			
			If IsObj($oTree_Tmp) Then
				If $iNode == '' Then $iNode = 0
				$oTree = $oTree_Tmp($iNode)
			EndIf
			
			ContinueLoop
		EndIf
		
		;Search by tag name
		$oTree_Tmp = $oTree.GetElementsByTagName($sTag)
		
		If IsObj($oTree_Tmp) Then
			If $iNode == '' Then
				For $oTag In $oTree_Tmp
					If ($oTag.ID And $oTag.ID = $sAttrib) Or ($oTag.ClassName And $oTag.ClassName = $sAttrib) Or ($oTag.Name And $oTag.Name = $sAttrib) Then
						$oTree = $oTag
						ExitLoop
					EndIf
				Next
			Else
				$oTag = $oTree_Tmp($iNode)
				
				If ($oTag.ID And $oTag.ID = $sAttrib) Or ($oTag.ClassName And $oTag.ClassName = $sAttrib) Or ($oTag.Name And $oTag.Name = $sAttrib) Then
					$oTree = $oTag
				EndIf
			EndIf
		EndIf
	Next
	
	Return $oTree
EndFunc

Func _HtmlDOM_GetProperty($oHTML, $sProperty, $sInnerOuter = 'Inner')
	If Not IsObj($oHTML) Then
		Return SetError(1, 0, 0)
	EndIf
	
	Switch $sProperty
		Case 'Html'
			Return ($sInnerOuter = 'Inner' ? $oHTML.InnerHtml : $oHTML.OuterHtml)
		Case 'Text'
			Return ($sInnerOuter = 'Inner' ? $oHTML.InnerText : $oHTML.OuterText)
	EndSwitch
EndFunc
 
Автор
inververs

inververs

AutoIT Гуру
Сообщения
2,135
Репутация
465
Re: Простой парсер HTML кода

Такая функция вряд ли будет нужна, т.к есть более мощный queryselector который позволяет найти практически любой элемент. А если уже и делать, то это функцию получающую элементы по XPath.
 

madmasles

Модератор
Глобальный модератор
Сообщения
7,790
Репутация
2,322
Re: Простой парсер HTML кода

inververs [?]
Такая функция вряд ли будет нужна, т.к есть более мощный queryselector который позволяет найти практически любой элемент. А если уже и делать, то это функцию получающую элементы по XPath.
А можно пример для IE8?
 
Автор
inververs

inververs

AutoIT Гуру
Сообщения
2,135
Репутация
465
Re: Простой парсер HTML кода

madmasles [?]
А можно пример для IE8?
Откройте консоль браузера и напишите к примеру document.querySelector('p') сработает? если нет, то в IE8 не поддерживает. Нужно обновляться хотя бы до 9, а в идеале до 11 версии
 

madmasles

Модератор
Глобальный модератор
Сообщения
7,790
Репутация
2,322
Re: Простой парсер HTML кода

inververs,
Проверил, в IE8 не работает, увы...
 
Автор
inververs

inververs

AutoIT Гуру
Сообщения
2,135
Репутация
465
Re: Простой парсер HTML кода

madmasles [?]
Проверил, в IE8 не работает, увы...
Ясно, ну ничего. Всегда можно обойтись другими средствами. Но хочу сказать, что IE8, 9, 10 нужно забыть и не использовать.
 

CreatoR

Must AutoIt!
Команда форума
Администратор
Сообщения
8,671
Репутация
2,481
Re: Простой парсер HTML кода

inververs [?]
IE8, 9, 10 нужно забыть и не использовать.
А если я пишу не для себя, не могу же принудить народ обновлять IE только для того, чтобы мой софт работал.

И вообще, нужен такой инструмент который бы не работал вообще с IE, а с собственным движком.
 

CreatoR

Must AutoIt!
Команда форума
Администратор
Сообщения
8,671
Репутация
2,481
Re: Простой парсер HTML кода

inververs [?]
если уже и делать, то это функцию получающую элементы по XPath.
Во, это я и хотел сделать, забыл как эта штука называется :smile:.
 

CreatoR

Must AutoIt!
Команда форума
Администратор
Сообщения
8,671
Репутация
2,481
Re: Простой парсер HTML кода

Не могу понять, как тут получить все атрибуты определённого узла?

Код:
$sHTML = _
	'<body>' & @CRLF & _
	'	<span id="users">' & @CRLF & _
	'		<div id="user">User1</div>' & @CRLF & _
	'		<div id="user">User2</div>' & @CRLF & _
	'		<div id="user">User3</div>' & @CRLF & _
	'	</span>' & @CRLF & _
	'	<span id="logins" class="logins">' & @CRLF & _
	'		<div id="login">login1</div>' & @CRLF & _
	'		<div id="login">login2</div>' & @CRLF & _
	'		<div id="login">login3</div>' & @CRLF & _
	'	</span>' & @CRLF & _
	'</body>'

$oHTML = ObjCreate('HtmlFile')
$oHTML.Write($sHTML)

$oAttribs = $oHTML.GetElementById('user').Attributes()

For $oAttrib In $oAttribs
	ConsoleWrite($oAttrib.Name & '=' & $oAttrib.Value & @LF)
Next


У меня это выдаёт кучу атрибутов, я так понял всевозможные, но как мне получить только те что относятся к найденному (по id) элементу?
 

madmasles

Модератор
Глобальный модератор
Сообщения
7,790
Репутация
2,322
Re: Простой парсер HTML кода

CreatoR,
GetElementById находит первый элемент, где id="user". Вроде, id должно быть уникальным, name может множественным.
 

CreatoR

Must AutoIt!
Команда форума
Администратор
Сообщения
8,671
Репутация
2,481
Re: Простой парсер HTML кода

madmasles [?]
Вроде, id должно быть уникальным, name может множественным.
Не совсем понял как это относится к получению всех атрибутов?
 

madmasles

Модератор
Глобальный модератор
Сообщения
7,790
Репутация
2,322
Re: Простой парсер HTML кода

CreatoR [?]
Не совсем понял как это относится к получению всех атрибутов?
ИМХО, Вы получаете все имена атрибутов, независимо от наличия их значения, только первого элемента с id="user".
Код:
$sHTML = _
    '<body>' & @CRLF & _
    '   <span id="users">' & @CRLF & _
    '       <div id="user">User1</div>' & @CRLF & _
    '       <div id="user">User2</div>' & @CRLF & _
    '       <div id="user">User3</div>' & @CRLF & _
    '   </span>' & @CRLF & _
    '   <span id="logins" class="logins">' & @CRLF & _
    '       <div id="login">login1</div>' & @CRLF & _
    '       <div id="login">login2</div>' & @CRLF & _
    '       <div id="login">login3</div>' & @CRLF & _
    '   </span>' & @CRLF & _
    '</body>'

$oHTML = ObjCreate('HtmlFile')
$oHTML.Write($sHTML)

$oAttribs = $oHTML.GetElementById('user').Attributes()
ConsoleWrite($oHTML.GetElementById('user').innertext & @LF);User1
For $oAttrib In $oAttribs
    ConsoleWrite($oAttrib.Name & '=' & $oAttrib.Value & @LF)
Next

С вашим HTML-кодом можно примерно так сделать.
Код:
$sHTML = _
		'<body>' & @CRLF & _
		'   <span id="users">' & @CRLF & _
		'       <div id="user">User1</div>' & @CRLF & _
		'       <div id="user">User2</div>' & @CRLF & _
		'       <div id="user">User3</div>' & @CRLF & _
		'   </span>' & @CRLF & _
		'   <span id="logins" class="logins">' & @CRLF & _
		'       <div id="login">login1</div>' & @CRLF & _
		'       <div id="login">login2</div>' & @CRLF & _
		'       <div id="login">login3</div>' & @CRLF & _
		'   </span>' & @CRLF & _
		'</body>'

$oHTML = ObjCreate('HtmlFile')
$oHTML.Write($sHTML)

$oDivs = $oHTML.GetElementsByTagName('div')
For $oTmp In $oDivs
	If $oTmp.id <> 'user' Then ContinueLoop
	ConsoleWrite($oTmp.innertext & @LF)
	$oAttribs = $oTmp.Attributes
	For $oAttrib In $oAttribs
		ConsoleWrite($oAttrib.Name & '=' & $oAttrib.Value & @LF)
	Next
	ConsoleWrite('---' & @LF)
Next
ИМХО, еще можно предварительно проверять Value и не отображать его при заданных условиях, типа
Код:
;~ 		...
		$sValue = $oAttrib.Value
		If Not $sValue Then ContinueLoop
		If StringRegExp($sValue, '(?i)^(0|null|false)$') Then ContinueLoop
;~ 		...
 

CreatoR

Must AutoIt!
Команда форума
Администратор
Сообщения
8,671
Репутация
2,481
Re: Простой парсер HTML кода

madmasles [?]
можно предварительно проверять Value и не отображать его при заданных условиях
Это не вариант, что если значение такое же у искомого элемента?

Значит получается что свойство Attributes возвращает всевозможные атрибуты.
Можно конечно получать OuterHtml и парсить строку тега, но это уже не то.

Вот так примерно:
Код:
#include <Array.au3>

$sHTML = _
    '<body>' & @CRLF & _
    '   <span id="users">' & @CRLF & _
    '       <div id="user">User1</div>' & @CRLF & _
    '       <div id="user">User2</div>' & @CRLF & _
    '       <div id="user">User3</div>' & @CRLF & _
    '   </span>' & @CRLF & _
    '   <span id="logins" class="my logins" attrib1 attrib2>' & @CRLF & _
    '       <div id="login">login1</div>' & @CRLF & _
    '       <div id="login">login2</div>' & @CRLF & _
    '       <div id="login">login3</div>' & @CRLF & _
    '   </span>' & @CRLF & _
    '</body>'

$oHTML = ObjCreate('HtmlFile')
$oHTML.Write($sHTML)

$aAttributes = _GetNodeAttributes($oHTML.GetElementById('logins'))

For $i = 1 To $aAttributes[0][0]
    ConsoleWrite($aAttributes[$i][0] & '=' & $aAttributes[$i][1] & @LF)
Next

Func _GetNodeAttributes($oNode)
    If Not IsObj($oNode) Then
        Return SetError(1, 0, 0)
    EndIf
    
    Local $sAttributes, $aAttributes, $iUbnd
    Local $aRet[1][2] = [[0]]
    
    $sAttributes = StringRegExpReplace($oNode.OuterHtml, '(?s)^\s*<[^\h]+\h*([^>]+)>.*', '\1')
    $aAttributes = StringRegExp($sAttributes, '([^=\h]+)(?:\h*=)?(\h*["''][^"'']+["'']|[^\h]+|\h*|$)?', 3)
    $iUbnd = UBound($aAttributes)
	
    If $iUbnd = 0 Then
        Return SetError(2, 0, 0)
    EndIf
    
    For $i = 0 To $iUbnd - 1 Step 2
        $aRet[0][0] += 1
        ReDim $aRet[$aRet[0][0] + 1][2]
        $aRet[$aRet[0][0]][0] = $aAttributes[$i]
        
        If $i + 1 < $iUbnd Then
            $aRet[$aRet[0][0]][1] = StringStripWS($aAttributes[$i + 1], 3)
        EndIf
    Next
    
    Return $aRet
EndFunc
 

madmasles

Модератор
Глобальный модератор
Сообщения
7,790
Репутация
2,322
Re: Простой парсер HTML кода

CreatoR [?]
Вот так примерно
И Name, и Value вроде могут быть с пробелами, типа
Код:
<div id="user id" some_name="some value">
 
Верх