разбор Html

Gor

Well-known member
07.06.2005
517
1
#1
Всем доброго время суток.

Есть такая задача:
локально лежат html файлы. Из каждого html-а надо вытащить большую таблицу и сохранить к примеру в csv.
т.е. по сути дела мне надо в этом html файле найти нужную таблицу, перегнать все данные в массив ну а далее в csv.

Как достучаться до этого html файла, какие классы и методы использовать для разбора html файлика, как обрезать ненужное? Какую технологию? Как с помощью этих классов вести поиск тегов, нужного текста итд итп? DXL?

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

Попробовал получить html таким образом:
Код:
				Set xml = CreateObject("Microsoft.XMLHTTP") 
Call xml.open("GET", "\\SERVER\UDV10031\123JIO\Desktop\Templates\Doc_1.html",False) 

Call xml.send() 
HtmlText$= xml.responseText
Ругается - Object automation Error
В общем Нид Хелп) Если будет какой нибудь пример разбора Html буду очень признателен.
 

Omh

Lotus team
04.07.2007
2 210
1
#2
А что-то вроде открыть файл NotesStream'ом и распарсить содеожимое, не?
 

Gor

Well-known member
07.06.2005
517
1
#3
А что-то вроде открыть файл NotesStream'ом и распарсить содеожимое, не?
Уже да :) Видимо мне хотелось моральной поддержки)

А разбор html таблицы вести тупо в цикле где будут описаны вхождения в строки с тегами?
 

Akupaka

А че я?.. О.о
04.10.2007
3 360
1
#5
либо, чтобы не париться с самим XSL, пройтись по потоку с пом SAX парсера (NotesSax...)
 

Gor

Well-known member
07.06.2005
517
1
#6
to: Akupaka
либо, чтобы не париться с самим XSL, пройтись по потоку с пом SAX парсера (NotesSax...)
А есть какой нибудь пример с использованием NotesSaxParser?
Как считывать значения, вести поиск нужного итд?
Никогда с ним не работал, а в хелпе всё не так очевидно...
 

Omh

Lotus team
04.07.2007
2 210
1
#7
Кстати, я тоже с колько уже с лотусом работаю, ни разу не использовал SAXParser.
Всё как-то через DOMParser.
Насколько я помню, SAX при больших объёмах быстрее гораздо был.
 
13.03.2009
625
1
#8
С большой вероятностью технологии, связанные с XML, не прокатят.
Валидные html встречаются нечасто - незакрытые параграфы, объявления аттрибутов несовместимые с xml и т.п. Плюс кодировка...
Допишите в начало html файла <?xml version="1.0"?> и убедитесь, что файл корректно открывается браузером и строится дерево.
И если он таки открылся - можно начинать думать про xml. Чем пользоваться для парсинга - вопрос религии, я лично предпочитаю xslt.

Насколько я помню, SAX при больших объёмах быстрее гораздо был.
Да, SAX шустрее, т.к. не строит дерево. А dom parser использует sax parser.
 

Akupaka

А че я?.. О.о
04.10.2007
3 360
1
#9
я по хелпу учился :) там достаточно муторно, но разобраться можно...
примера у меня с собой нет, а писать чесгря лениво.
вкратце, есть два парсера XML: DOM, SAX.
DOM - строит дерево объектов, и чтобы найти нужный нужно пройти от корня до искомого объекта. как HTML DOM.
если структура у всех доков одинаковая, то можно сделать прямой код поиска объекта.
пример,
Код:
<XML>
<doc>
<text1>
This this hard code!
</text1>
</doc>
тогда определение нужного объекта с пом DOM будет выглядеть приблизительно так (куски кода вырваны из хелпа и сгенерены в уме без проверки):

Код:
To access the document node for an existing DOM tree, use the Process method to generate the DOM tree. Then use the Document property to access the document node. For example:

Dim domparser As NotesDOMParser
Dim domdoc As NotesDOMDocumentNode
dim domtext1 as notesDOMNode
dim text1 as string

Set domParser = session.CreateDOMParser(inputStream, outputStream) ' получили поток из какого-то ресурса inputStream, см NotesStream

Call domParser.Process ' получили DOM
Set domdoc = domparser.Document ' получаем, по-идее, doc
Set domtext1 = notesDOMNode.FirstChild ' получаем, по-идее, text1
text1 = domtext1.FirstChild.NodeValue ' по-идее, текст, который находится в сущности <text1> является объектом типа NotesDOMTextNode, у которого мы смело берем значение NodeValue
если же документ может иметь первый вид, а может и другой:
Код:
<XML>
<doc>
<text2>
This this hard code TEXT2!
</text2>
<text1>
This this hard code!
</text1>
</doc>
то первый вариант кода нам вернет не то, что нам надо... и тут уже надо обходить дерево для поиска необходимой сущности по имени тега.
пример кода смотри в справке, там многа писать :) (ищи Examples: ParentNode property)


в свою очередь SAX работает как бы событийно. т.е. фактически он сам запускает проход по всем! сущностям дока, а ты лишь дописываешь обработчик событий, а что, если тег (сущность) такой-то.
по САКСу один большой пример в справке Examples: NotesSAXParser class, по нему немного и скажу.
в твоем случае, тебя интересует обработчик
Код:
On Event SAX_StartElement From saxParser Call SAXStartElement
т.е. этот обработчик срабатывает тогда, когда парсер натыкается на тег, например <text2>, <text1>...
еще тебя будет интересовать обработчик события когда парсер натыкается на окончание тега
Код:
On Event SAX_EndElement From saxParser Call SAXEndElement
я в свое время обходился этими двумя :)
еще пригодится могут события SAX_Error и SAX_FatalError.
оу... забыл главное :)
Код:
On Event SAX_Characters From saxParser Call SAXCharacters
этот обработчик будет читать значения текстовых узлов

теперь как же нам поставить корректно обработку с пом САКС. я делал с помощью флагов.
приблизительно поиск значения узла в <text1> по второму фрагменту должен быть приблизительно таким

Код:
globals
dim bText1 as boolean

Sub SAXCharacters (Source As Notessaxparser, Byval Characters As String, _
Count As Long)

if bText1 then
Messagebox "TEXT1 Characters found: " Characters
end if
End Sub

Sub SAXStartElement (Source As Notessaxparser,_
Byval elementname As String, Attributes As NotesSaxAttributeList)

if elementname = "text1" then
bText1 = true
end if

End Sub

Sub SAXEndElement (Source As Notessaxparser, Byval ElementName As String)
if elementname = "text1" then
bText1 = false
end if
End Sub
в твоем конечном случае это будет не такой простой и маленький код ;)
а если хочешь все быстрее и красивее, твой путь лежит через тернии XSLT :)

Допишите в начало html файла <?xml version="1.0"?> и убедитесь, что файл корректно открывается браузером и строится дерево.
по-идее, можно использовать валидатор объектов нотеса, только дописывать <?xml придется все-равно, но, вроде, можно это вписать в поток, и руци не марать :)
 
13.03.2009
625
1
#10
по-идее, можно использовать валидатор объектов нотеса, только дописывать <?xml придется все-равно, но, вроде, можно это вписать в поток, и руци не марать :)
Да я не саму задачу валидации имел в виду, а то, что возможно использование технологий xml неприменимо к данной задаче...
А простой способ понять - использовать XML или нет - просто открыть док браузером как xml.


З.Ы. Старожилы закидают камнями за такой вариант, но все же:
Судя по коду, на машине windows. А ёксель там часом не установлен? OLE: Открыть файл екселем, save as csv. Финиш.
 
Y

Yakov

#11
и тут уже надо обходить дерево для поиска необходимой сущности по имени тега.
пример кода смотри в справке, там многа писать :)
Не надо много писать и обходить ничего не надо. См. notesDOMDocument.GetElementsByTagName( elementName ).

По теме ищите в гугле java html parser. Кроме всего прочего найдете http://java.sun.com/j2se/1.4.2/docs/api/ja...ge-summary.html, то есть, задачу можно сделать без дополнительных библиотек начиная с версии 7.0 Лотуса.
 
Y

Yakov

#13
Akupaka, я этому учился на Java, там справка (javadoc) удобней.
 

Gor

Well-known member
07.06.2005
517
1
#14
to Akupaka:

Спасибо, более менее понятно.
НО т.к. у меня html, попробовал пользоваться Start и EndElement. Но по сути дела это не принесёт особого результата.

html у меня достаточно большой и по имени тега по сути дела искать практически бесполезно. (тегов с одинаковыми названиями ооочень уж много)
Чтобы брать значения из определённых ячеек мне надо каждый раз при попадании в элемент проверять содержит ли он такую фразу?
т.е. в событии
Sub SAXCharacters (Source As Notessaxparser, Byval Characters As String, _
Count As Long)

Делать проверку каждого элемента? - наподобии if @Contains(Character,"блаблабла") Then ....?

на примере предположим есть вот такая часть файла

Код:
<tr>

<td rowspan="3" style="vertical-align:top; text-align:left; font-size:9pt; border-width: 1;border-style: solid;border-color: Black; height:3.7cm;padding:0.1cm;"><strong>Кому:</strong> ЦЦЦЦЦЦЦ<br>Иванов Иван Иванович<br><strong>Куда:</strong> ЧЧЧ ул,ЧЧЧЧ <br><strong>127473</strong><br><strong>Лицевой счет: </strong>277340553057 (ГенД)<br>7:1:900-15<br></td>

<td style="font-size:10pt; vertical-align:middle; text-align:center">от 31.08.2009<br>за услуги предприятия<br>Открытое акционерное общество "Мобильные ТелеСистемы" / ОАО "МТС"<br>за период с 01.08.2009 по 31.08.2009<br>Оплатить до: 24.09.2009</td>

</tr>
<tr>
<td style="font-weight: bold; font-size: 14pt; text-align:center;">Лицевой счет абонента № 277340553057</td>
</tr>
</table>
мне отсюда надо вытащить лицевой счёт абонента, а этот кусок в середине файла зашит.
т.е. получается если я использую SAXParser надо проверять Character каждого элемента на содержание в нём фразы Лицевой счёт абонента ????
 
A

amigolinx

#15
мне отсюда надо вытащить лицевой счёт абонента, а этот кусок в середине файла зашит.
т.е. получается если я использую SAXParser надо проверять Character каждого элемента на содержание в нём фразы Лицевой счёт абонента ????
если данных по лицевым счетам много, то можно так:
1. зачитать содержимое файла в NotesStream
2. сделать Split по фразе например "Лицевой счёт абонента"
3. на выходе - массив, в котором каждый элемент начиная с 1, будет содержать номер счета
4. потом можно обращаться к элементу и обрезать уже сам номер при помощи ф-ий типа StrLeft/StrRight

если он один на весь файл, то и еще проще - без Split можно обойтись...
 

Gor

Well-known member
07.06.2005
517
1
#16
Вытащить на самом деле из файла надо данные в 5-6 местах по ключевым фразам. + большую таблицу вида:

Код:
..............
<tr style="font-weight: normal; font-size: 7pt;">

<td>17.08.2009</td>

<td>13:32:01</td>

<td align="left"><--+79037011111</td>

<td align="left"></td>

<td align="left"></td>

<td align="left">sms i</td>

<td></td>

<td align="right">1</td>

<td align="right">0.0000</td>

</tr>

<tr style="font-weight: normal; font-size: 7pt;">

<td>17.08.2009</td>

<td>13:45:12</td>

<td align="left">+79627068276</td>

<td align="left"></td>

<td align="left"></td>

<td align="left">sms o</td>

<td></td>

<td align="right">1</td>

<td align="right">1.6525</td>

</tr>

<tr style="font-weight: normal; font-size: 7pt;">

<td>17.08.2009</td>

<td>13:45:33</td>

<td align="left"><--+79037011111</td>

<td align="left"></td>

<td align="left"></td>

<td align="left">sms i</td>

<td></td>

<td align="right">1</td>

<td align="right">0.0000</td>

</tr>
........................
перегнать в массив
 
A

amigolinx

#17
если я правильно понимаю задачу, то исключая всякие клинические случаи, эти ключевые фразы всегда ж одного четко заданного вида? поэтому я бы делал жесткий поиск по ним и забирал бы идущее за ними значение по определенным ориентирам с использованием LS2J (там есть реализация регекспов) или тупо по паттернам в лотусовых стринговых функциях. а вот таблицу имхо не в массив лучче пихать, а в List (проще с ListTag потом будет работать)
и кстати, LS2J а именно JakartaOroWrapper ой как харашо вписывается сюда - там даже в примерах использования либы есть обработка строк с html-тегами (вытащить значения между тегов и т.п.)
 

Akupaka

А че я?.. О.о
04.10.2007
3 360
1
#18
Gor
что я могу сказать... есть золотое правило разработки: отделяй данные от представления этих самых данных!
работать с данными в виде для представления - себе дороже. нет ли возможности получать всю ту инфу в виде нормальной структуры (XML) или хотя бы с идентификаторами элементов в представлении?..
 

Gor

Well-known member
07.06.2005
517
1
#19
Код:
нет ли возможности получать всю ту инфу в виде нормальной структуры (XML) или хотя бы с идентификаторами элементов в представлении?..
Нет. Есть только html-ник. Структура то у него определённая есть, но работать с ней всё равно достаточно тяжело.

Ещё назрел такой вопрос - Предположим я хочу свой поток разделить на части (чтобы с какой то частью потока работать как со строкой, а с другой табличной частью парсером)
Поток определяется:
Код:
	Set stream = session.CreateStream
If Not stream.Open("\\Имя файла","UTF-8") Then
Msgbox "Cannot open Имя файла.html", , "Error"
Exit Sub
End If
If stream.Bytes = 0 Then
Msgbox "File did not exist or was empty", , FileName$
Exit Sub
End If
Я хочу получить два потока:
Один - от начала файла до заглавия страницы БлаБлаБла, второй после заглавия страницы БлаБлаБла

Как это можно сделать?
 
A

amigolinx

#20
Я хочу получить два потока:
Один - от начала файла до заглавия страницы БлаБлаБла, второй после заглавия страницы БлаБлаБла

Как это можно сделать?
Код:
var1 = Strleft(stream.ReadText, {до заглавия страницы БлаБлаБла}) & {до заглавия страницы БлаБлаБла}  ' это если заглавие тоже нужно
var2 = {после заглавия страницы БлаБлаБла} & StrRight(stream.ReadText, {после заглавия страницы БлаБлаБла})