VBS/WSH/JS - [решено] Парсинг html-страницы

Аватара пользователя
The_Immortal

VBS/WSH/JS - [решено] Парсинг html-страницы

Сообщение The_Immortal »

Приветствую!



Возникла необходимость отслеживать на сайте
фифы
доступность билетов. Есть список интересующих матчей, которые нужно мониторить.



Алгоритм придумал следующий:



1. Формирую массив MatchName, состоящий из названий необходимых для отслеживания матчей:

- Матч 01 - Россия : Саудовская Аравия - Москва «Лужники»

- Матч 07 - Аргентина : Исландия - Москва «Спартак»

- Матч 11 - Германия : Мексика - Москва «Лужники»



2. Поочередно получаю в NodeList дивы с классом "header" (див внутри первого столбец таблицы) и заглядываю внутрь каждого (через innerText), сверяя название текущего матча с первым элементом массива MatchName:



Код:

Код: Выделить всё

<div class="header" ng-bind="product.productName">Матч 01 - Россия : Саудовская Аравия - Москва «Лужники»</div>

3. Если название совпало, то каким-то образом[/i] перехожу к соседнему диву "col-sm-9 col-xs-12" (второй таблицы) и также каким-то образом изымаю имя класса внутреннго дива для всех 4 квадратов.



Ключевое - это определить класс "class="categoryBox zeroAvailability" - т.е. если этот класс НЕ "zeroAvailability", то далее кидается алерт.



4. Аналогично пройтись по остальным элементам массива MatchName.







Ну в общем, я начал:





Код:

Код: Выделить всё

Option Explicit
Const READYSTATE_COMPLETE = 4 
Const TimeOut = 10000
Const link = "https://tickets.fifa.com/Services/ADService.html?lang=ru"
Dim objNodeList, i, j
Dim MatchName(2)	' массив из искомых названий матчей 
MatchName(0) = "Матч 01 - Россия : Саудовская Аравия - Москва «Лужники»"
MatchName(1) = "Матч 07 - Аргентина : Исландия - Москва «Спартак»"
MatchName(2) = "Матч 11 - Германия : Мексика - Москва «Лужники»"
With WScript.CreateObject("InternetExplorer.Application")
	.Visible = False
	.Navigate(link)
	Do
		WScript.Sleep TimeOut	' ожидаем загрузку страницы
	Loop Until Not .Busy And .ReadyState = READYSTATE_COMPLETE
	objNodeList = .document.getElementsByClassName("header")	' получаем в NodeList все элементы с указанным классом
	For i = 0 to 2 Step 1
		For j = 0 to objNodeList.length Step 1	' !!! Ошибка: "Требуется объект '[object HTMLCollection]'
			' ...
		Next		
	Next	
	Set objNodeList = Nothing
	.Quit
End With
WScript.Quit 0



Вопросы следующие:



1. Понять не могу в чем ошибка. objNodeList - это NodeList, который представляет собой массив из Element'ов. Почему я не могу получить длину этого массива?



2. Вопрос касательно общего алгоритма... Может быть как-то задачу попроще можно решить или логика моя пойдет?



3. Если возможно, то подскажите, пожалуйста, каким образом можно перемещаться по соседним div'ам (из пункта 3. алгоритма)?



Спасибо!
Аватара пользователя
Iska

Re: VBS/WSH/JS - [решено] Парсинг html-страницы

Сообщение Iska »

Цитата The_Immortal:



.Navigate(link)
VBS/WSH/JS - [решено] Парсинг html-страницы




Скобки не нужны.




Цитата The_Immortal:



objNodeList = .document.getElementsByClassName("header")
VBS/WSH/JS - [решено] Парсинг html-страницы




Set objNodeList = …




Цитата The_Immortal:



objNodeList - это NodeList, который представляет собой массив из Element'ов.
VBS/WSH/JS - [решено] Парсинг html-страницы




Вообще-то, нет. Это не массив, это коллекция.
Аватара пользователя
Serguei Kouzmine

Re: VBS/WSH/JS - [решено] Парсинг html-страницы

Сообщение Serguei Kouzmine »

опишите пожалнйста еще раз текстом задачу кот решаете



WScript это что то о чем некоторым хочется поскоее забыть - продираться через дебри вашего кода чтобы выяснить objective честно не сильно хота. но может быть получится написать с нуля
Аватара пользователя
Iska

Re: VBS/WSH/JS - [решено] Парсинг html-страницы

Сообщение Iska »

Цитата The_Immortal:



Но возникает ошибка на второй строке...
VBS/WSH/JS - [решено] Парсинг html-страницы




Правильно — у возвращаемого класса нет такого метода.




Цитата The_Immortal:



Далее я хочу сразу прыгнуть на div class="header"
VBS/WSH/JS - [решено] Парсинг html-страницы




Ну, а банально — .FirstChild.FirstChild.FirstChild, не? Ну, и, по-хорошему потом ещё проверить, что класс полученного элемента — header.




Цитата The_Immortal:



div class="pull-left" (последовательно ко всем 4-ым).
VBS/WSH/JS - [решено] Парсинг html-страницы




Опять же — .FirstChild.NextSibling.FirstChild.NextSibling.NextSibling. Далее ко второму, третьему и четвёртому двигаться посредством нужного количества .NextSibling от первого.
Аватара пользователя
The_Immortal

Re: VBS/WSH/JS - [решено] Парсинг html-страницы

Сообщение The_Immortal »

Iska,
Цитата Iska:



Ну, а банально — .FirstChild.FirstChild.FirstChild, не?
VBS/WSH/JS - [решено] Парсинг html-страницы




А если бы вложенность необходимого div'а была бы на 1000-ом уровне, например? Я к тому, что по идее должен быть способ как обратиться сразу к i-тому ребенку.



Нашел вот такую
тему
- там обращаются к ребенку через childNodes:



Код:

Код: Выделить всё

WScript.Echo .document.getElementsByClassName("row rowBox rowBoxEven").childNodes(3).className

У меня же ругается почему-то... Нету такого метода-свойства =/



Или ключевое в getElementById? Т.е. у getElementById имеется свойство
childNodes
, а у getElementsByClassName вдруг такого свойства не оказалось? А где можно найти список всех их методов и свойств?



А на FirstChild[/i], кстати, также ругается.
Аватара пользователя
Iska

Re: VBS/WSH/JS - [решено] Парсинг html-страницы

Сообщение Iska »

Iska, получается, что, используя VBS, обратиться к детям вообще никак нельзя через getElementsByClassName ? Тогда какой от него вообще прок, если нельзя заглянуть внутрь объекта, который возвращает данный метод??



Блин, ну и каким языком распарсить это? Если взять тот же PS, то там та же песня наверняка вылезет.



P.S. Неужели никто никогда не парсил HTML через VBS? :D
Аватара пользователя
The_Immortal

Re: VBS/WSH/JS - [решено] Парсинг html-страницы

Сообщение The_Immortal »

The_Immortal, всё можно, язык тут вовсе не при чём, надо просто документацию копать. Проблема в том, что практически вся вменяемая документация, ранее бывшая на сайтах Microsoft, почила в бозе, а та, что появилась — выглядит как сущее издевательство. У меня, конечно, есть и отдельные документации, но их надо либо искать по сусекам, либо освобождать место для разворачивания локальной справки MSDN. Так что проще взять и тупо воспользоваться каким-либо браузером объектов на соответствующей библиотеке типов.




Цитата The_Immortal:





Код:

Код: Выделить всё

WScript.Echo .document.getElementsByClassName("row rowBox rowBoxEven").childNodes(3).className

У меня же ругается почему-то... Нету такого метода-свойства =/
VBS/WSH/JS - [решено] Парсинг html-страницы




Ругается потому, что класс HTMLElementCollection (.document.getElementsByClassName("row rowBox rowBoxEven")) действительно не содержит свойства .childNodes. Данный класс содержит методы .item, .namedItem, свойство .length и т.д. Вам нужно сначала выбрать из полученной коллекции отдельный элемент, используя указанные методы, проверить его тип, и вот этот отдельный элемент уже может[/i] содержать свойства .childNodes, .firstChild, .nextSibling, .previousSibling и т.п.



Можно даже далеко не ходить, а просто двигаться маленькими шажками:



Код:

Код: Выделить всё

	WScript.Echo 1, TypeName(.document.getElementsByClassName("row rowBox rowBoxEven"))
	WScript.Echo 2, .document.getElementsByClassName("row rowBox rowBoxEven").length
	WScript.Echo 3, TypeName(.document.getElementsByClassName("row rowBox rowBoxEven").item(0))
	WScript.Echo 4, .document.getElementsByClassName("row rowBox rowBoxEven").item(0).hasChildNodes()
	WScript.Echo 5, TypeName(.document.getElementsByClassName("row rowBox rowBoxEven").item(0).childNodes)
	WScript.Echo 6, .document.getElementsByClassName("row rowBox rowBoxEven").item(0).childNodes.length
	WScript.Echo 7, TypeName(.document.getElementsByClassName("row rowBox rowBoxEven").item(0).childNodes.item(0))
	WScript.Echo 8, .document.getElementsByClassName("row rowBox rowBoxEven").item(0).childNodes.item(0).nodeName
	WScript.Echo 9, TypeName(.document.getElementsByClassName("row rowBox rowBoxEven").item(0).childNodes.item(1))
	WScript.Echo 10, .document.getElementsByClassName("row rowBox rowBoxEven").item(0).childNodes.item(1).nodeName


Цитата:





Код:

Код: Выделить всё

1 DispHTMLElementCollection
2 32
3 HTMLDivElement
4 -1
5 DispDOMChildrenCollection
6 5
7 DispHTMLDOMTextNode
8 #text
9 HTMLDivElement
10 DIV





и т.д. — глыбже, глыбже и глыбже[/i]. Потом оставляете из этой простыни только то, что на самом деле нужно.



Хорошо также смотреть внутрь результирующего сгенерированного html посредством Инспектора/Проводника DOM (F12), разумеется, под IE, и под конкретно той версией IE, под которой будет работать скрипт (у меня Проводник DOM не работает, отваливается).



Возвращаясь к этому:


Цитата The_Immortal:



Далее я хочу сразу прыгнуть на div class="header": div class="row rowBox rowBoxEven" -> div class="col-sm-3 col-xs-12" -> div class="row" -> div class="header". Но вот не могу допендрить как это сделать... Пробовал так:



Код:

Код: Выделить всё

Set objNodeList = .document.getElementsByClassName("row rowBox rowBoxEven")
Set child = objNodeList.getElementsByClassName("header")

Но возникает ошибка на второй строке...
VBS/WSH/JS - [решено] Парсинг html-страницы




Это ровно так, как Вам посоветовали на форуме SCI. Только действовать следует точно так же, как я писал выше: Вы получили методом .getElementsByClassName("row rowBox rowBoxEven") коллекцию типа HTMLElementCollection — проверьте её длину, если она не нулевая — либо перебирайте все её члены (item'ы) и обращайтесь к потребному item'у, либо, если количество item'ов всегда неизменно и точно знаете номер — просто сразу обращайтесь к потребному item'у по номеру. И только получив из HTMLElementCollection конкретный item — выполняете для него (а не для самой HTMLElementCollection) метод .getElementsByClassName("header").




Цитата The_Immortal:



P.S. Неужели никто никогда не парсил HTML через VBS? :D
VBS/WSH/JS - [решено] Парсинг html-страницы




И не раз Изображение.
Аватара пользователя
The_Immortal

Re: VBS/WSH/JS - [решено] Парсинг html-страницы

Сообщение The_Immortal »

Iska,
Цитата Iska:



И только получив из HTMLElementCollection конкретный item — выполняете для него (а не для самой HTMLElementCollection) метод .getElementsByClassName("header").
VBS/WSH/JS - [решено] Парсинг html-страницы




Ну вот только оказалось, что обращаться нужно опять-таки к конкретному члену? Т.е. .getElementsByClassName("header").item(0) . Верно?



И, кстати,



Код:

Код: Выделить всё

.document.getElementsByClassName("row rowBox rowBoxEven").item(0)

ведь абсолютно эквивалентно

Код:

Код: Выделить всё

.document.getElementsByClassName("row rowBox rowBoxEven")(0)

Правильно понимаю?



Да и child'ы мне похоже вообще не нужны:
Цитата Iska:



если количество item'ов всегда неизменно и точно знаете номер — просто сразу обращайтесь к потребному item'у по номеру
VBS/WSH/JS - [решено] Парсинг html-страницы








UPD: что-то я зашел в тупик...





Код:

Код: Выделить всё

Option Explicit
Const READYSTATE_COMPLETE = 4 
Const TimeOut = 10000
Const link = "https://tickets.fifa.com/Services/ADService.html?lang=ru"
Dim MatchList,CurMatch, i, Match
CurMatch = 0
Dim MatchName(2)
MatchName(0) = "Матч 01 - Россия : Саудовская Аравия - Москва «Лужники»"
MatchName(1) = "Матч 07 - Аргентина : Исландия - Москва «Спартак»"
MatchName(2) = "Матч 11 - Германия : Мексика - Москва «Лужники»"
With WScript.CreateObject("InternetExplorer.Application")
	.Visible = False
	.Navigate link
	Do
		WScript.Sleep TimeOut
	Loop Until Not .Busy And .ReadyState = READYSTATE_COMPLETE
	Set MatchList = .document.getElementsByClassName("header")	' получаем названия всех матчей в один список
	For i = 0 to 2 Step 1	' перебираем массив MatchName, где содержатся названия искомых матчей
		For each Match in MatchList	' берём каждую ноду коллекции (списка) матчей
			If (MatchName(i) = Match.innerText) Then	' если матч совпал, то ...
				'For i = 0 to 3 Step 1	' проходимся по 4-ым "pull-left" 
					'WScript.Echo .document.getElementsByClassName(Match.parentNode.parentNode.parentNode.className)(???).getElementsByClassName("pull-left")(i).childNodes(0).className
				'Next
				'WScript.Echo Match.NextSibling.NextSibling.NextSibling.className
			End If
			CurMatch = CurMatch + 1 ' фиксируем какой по счету был матч
		Next		
	Next	
	Set MatchList = Nothing
	.Quit
End With
WScript.Quit 0

Я всё-таки решил делать так, как задумал ранее. Получаю через класс "header" колекцию, состоящую из названий матчей. Далее произвожу сравнение элементов коллекций с искомым массивом. Если совпадение обнаружено, то ... Вот тут конкретный тупик. Мне надо каким-то образом внутри этого блока (где находится div "header") прыгнуть на div'ы "pull-left" (их четыре) и анализровать название классов их дочерей.



Сначала я подумал, что проблему с прыжком на нужное можно решить так:



Код:

Код: Выделить всё

For i = 0 to 3 Step 1	' проходимся по 4-ым "pull-left" 
	WScript.Echo .document.getElementsByClassName(Match.parentNode.parentNode.parentNode.className)(???).getElementsByClassName("pull-left")(i).childNodes(0).className
Next

И тут возникает проблема с указанием конкретного номера ноды.

Класса родительских два и они разные:



Код:

Код: Выделить всё

	...


	...

Какая в текущий момент нода по счёту из обрабатываемого списка коллекции и какому конкретно классу из этих двух она относится? Фиг его знает! Да, я там выше оформил счётчик CurMatch = CurMatch + 1, но он ничем не поможет тут.



Ну и второй (более, наверное, правильный) подход - позицинироваться относительно текущего элемента:



Код:

Код: Выделить всё

WScript.Echo Match.NextSibling.NextSibling.NextSibling.className

Но сиё вообще не робит Изображение

Продублирую HTML-код (упрощенный):



+

[spoiler]Код:
[/spoiler]

Код: Выделить всё

	
		
			Матч 01 - Россия : Саудовская Аравия - Москва «Лужники»
		
		
			
				Матч 1
			
			
				Матчи 1
			
			
				 14 ИЮН 18:00
			
		
	
	
		
			CAT 1
		
		
			CAT 2
		
		
			CAT 3
		
		
			CAT 4
		
	







Я нахожусь вот тут:

Код:

Код: Выделить всё

div class="header">Матч 01 - Россия : Саудовская Аравия - Москва «Лужники»

А мне надо вот сюда:

Код:

Код: Выделить всё

			CAT 1
		
		
			CAT 2
		
		
			CAT 3
		
		
			CAT 4
		

Не могли бы Вы мне подсказать через пожалуйста в чем моя ошибка теперь?
Аватара пользователя
Iska

Re: VBS/WSH/JS - [решено] Парсинг html-страницы

Сообщение Iska »

попробуйте то же только в повершел -

Код: Выделить всё

$s = new-object -com 'internetexplorer.application'
$s.visible = $true
$target_url = 'https://tickets.ifa.com/Services/ADService.html?lang=ru'
$s.navigate2($target_url)
$s.ReadyState # 4
$s.Busy # False
# $s.document | get-member
# ... a lot of output ...
$doc = $s.document
# $doc 
#  
$m1 = $doc.documentElement.getElementsByClassName('header')
<#
 $m1 | get-member
   TypeName: System.__ComObject#{3050f50c-98b5-11cf-bb82-00aa00bdce0b}
Name                         MemberType Definition
----                         ---------- ----------
addBehavior                  Method     int addBehavior (string, Variant)
addFilter                    Method     void addFilter (IUnknown)
... сокращено ...
#>
$e1 = $m1[1]
$e1.nodeName # DIV
$e1.textContent # Матч 02 - Египет : Уругвай - Екатеринбург
$e2 = $e1.parentNode
$e2.innerHTML
# <div class="header" ng-bind="product.productName">Матч 02 -Египет : Уругвай - Екатеринбург</div>
$e3 = $e2.parentNode
$e4 = $e3.NextSibling.NextSibling
$e4.textContent
#     CAT 1
#     CAT 2
#     CAT 3
#     CAT 4
$m2 = $e4.getElementsByClassName('categoryBox')
$m2[1]
$m2[1].innerHTML
# CAT 2
$m2[1].outerHTML
# <div class="categoryBox zeroAvailability" ng-bind="cat.categoryName" ng-class="cat.availabilityColor">CAT 2</div>
$m2[1].getAttribute('ng-class')
# cat.availabilityColor
# для поиска "Низкая доступность":
# $m2 | where-object { $_.getAttribute('ng-class') -match '.*Zero.*' }
Аватара пользователя
The_Immortal

Re: VBS/WSH/JS - [решено] Парсинг html-страницы

Сообщение The_Immortal »

Serguei Kouzmine, большое спасибо за труд, но я там почти ничего не понимаю Я вообще на финишной прямой и осталось только разобраться каким образом получить className перемещаясь через NextSibling...





Локализую проблему. HTML-код ещё раз:



+

[spoiler]

Код: Выделить всё

<div class="row rowBox rowBoxEven">
	<div class="col-sm-3 col-xs-12">
		<div class="row">
			<div class="header">Матч 01 - Россия : Саудовская Аравия - Москва «Лужники»</div>
		</div>
		<div class="row">
			<div class="col-lg-3 col-md-3 col-sm-3 col-xs-3">
				<div>Матч 1</div>
			</div>
			<div class="col-lg-3 col-md-3 col-sm-3 col-xs-3 ng-hide">
				<div>Матчи 1</div>
			</div>
			<div class="col-lg-3 col-md-3 col-sm-3 col-xs-3">
				<div> 14 ИЮН 18:00</div>
			</div>
		</div>
	</div>
	<div class="col-sm-9 col-xs-12">
		<div class="pull-left">
			<div class="categoryBox zeroAvailability">CAT 1</div>
		</div>
		<div class="pull-left">
			<div class="categoryBox zeroAvailability">CAT 2</div>
		</div>
		<div class="pull-left">
			<div class="categoryBox zeroAvailability">CAT 3</div>
		</div>
		<div class="pull-left">
			<div class="categoryBox zeroAvailability">CAT 4</div>
		</div>
	</div>
</div>
[/spoiler]
Я на этом объекте (Match - см. выше):



Код:

Код: Выделить всё

<div class="header">Матч 01 - Россия : Саудовская Аравия - Москва «Лужники»</div>

Для теста выполняю вот такое:



Код:

Код: Выделить всё

WScript.Echo Match.NextSibling.className

, ожидая перейти просто на следующий div () и извлечь имя класса этого div'а, но получаю ошибку о том, что объект Match.NextSibling[/i] не поддерживает свойство NextSibling.className. Тогда я пробую так:



Код:

Код: Выделить всё

WScript.Echo (Match.NextSibling).className

, на что получаю сначала вывод [object Text][/i], а после сразу же ошибку:


Цитата:



Требуется объект: 'WScript.Echo(...)'



В общем, ерунда какая-то... По логике мне должно было вернуться имя класса следующего div'а, т.е. просто "row" Изображение
Ответить

Вернуться в «Скриптовые языки администрирования Windows»