|
12.07.2006, 16:05 | #1 |
Участник
|
Импорт из html-файлов без использования внешнего парсера возможен?
Господа! Сталкивался ли кто-нибудь с необходимостью импорта данных (в данном случае счетов от поставщика услуг) из html-файла? Проблема усугубляется тем, что файл может быть очень большим - десятки мегабайт...
|
|
12.07.2006, 16:11 | #2 |
Moderator
|
А придать этому сборищу данных более сговорчивый формат? Например, xls?
Задачка разовая или постоянно надо будет парсить? |
|
12.07.2006, 16:21 | #3 |
Участник
|
Цитата:
Сообщение от Gustav
А придать этому сборищу данных более сговорчивый формат? Например, xls?
Задачка разовая или постоянно надо будет парсить? Да и файлов несколько штук, отличных по формату. Придать сговорчивый формат - это и значит перегнать нужные данные из хтмл в Ексель с помощью чего-либо? |
|
12.07.2006, 16:25 | #4 |
Moderator
|
Цитата:
Сообщение от Avick
Задача ежемесячная.
Да и файлов несколько штук, отличных по формату. Придать сговорчивый формат - это и значит перегнать нужные данные из хтмл в Ексель с помощью чего-либо? Конкретно в Excel - что-нибудь типа объекта QueryTable. Через этот QueryTable - можно хоть к файлу, хоть напрямую в web. P.S. Посмотрите, как при помощи Excel выкачивается из веба табличная информация здесь: Получение в Excel полного списка пользователей AxForum Там пример на VBA, но сделать перевод на X++ задача вполне посильная. P.P.S. Поиграйтесь сами прямо в Excel: меню Данные - Внешние данные - Создать Web-запрос. Запишите свои действия макрорекордером. Уверен, Вам понравится. Последний раз редактировалось Gustav; 12.07.2006 в 16:37. |
|
12.07.2006, 17:15 | #5 |
Участник
|
[QUOTE=Gustav]
Конкретно в Excel - что-нибудь типа объекта QueryTable. Через этот QueryTable - можно хоть к файлу, хоть напрямую в web. [QUOTE] Попробовал. Возник вопрос: как мне прямо в коде сказать какое именно слово из файла в какую именно ячейку класть? Без регулярных выражений не обойтись, как мне кажется... |
|
12.07.2006, 17:26 | #6 |
Moderator
|
Цитата:
Сообщение от Avick
Без регулярных выражений не обойтись, как мне кажется...
Цитата:
Сообщение от Avick
Возник вопрос: как мне прямо в коде сказать какое именно слово из файла в какую именно ячейку класть?
один - "приёмник", куда всасывается информация из веба и автоматически распределяется по конкретным ячейкам этого листа - т.е. Ваши "слова" сами залетают в эти ячейки без Вашего участия, второй - "накопитель", где Вы уже раскладываете данные "по полочкам", т.е. по колоночкам, читая эти данные из "приёмника" P.S. У вас данные - табличного вида? Или хаотичного? Вы смотрели пример с "Пользователями АхФорум"? Там содержимое "приемника" меняется в цикле 60 с лишним раз (по 100 пользователей на одной странице): запросили очередную страницу, она загрузилась в "приемник", прочитали ее данные в "накопитель", в "накопителе" продвинулись вниз по таблице на последнюю заполненную "запись", запросили следующую страницу в "приемник" и т.д. в цикле. По окончании процесса интересующие Вас данные находятся в "накопителе" ("приемник" больше не нужен, можно выбросить его в пропасть как суп харчо). Из "накопителя" грузите данные в Аксапту любым из желаемых методов (через ODBC, ADO и т.п.). Последний раз редактировалось Gustav; 12.07.2006 в 17:39. |
|
12.07.2006, 16:28 | #7 |
NavAx
|
Цитата:
Сообщение от Avick
Проблема усугубляется тем, что файл может быть очень большим - десятки мегабайт...
__________________
Isn't it nice when things just work? |
|
12.07.2006, 18:43 | #8 |
Участник
|
я бы использовал чтонибудь специально заточенное под разбор HTML (http://www.crummy.com/software/Beaut...mentation.html) а потом транслировал бы в что-нибудь специально заточенное под обмен данными например XML.
|
|
13.07.2006, 13:38 | #9 |
Участник
|
причем жутко извратные
|
|
31.07.2006, 10:44 | #10 |
Moderator
|
Несколько дней после обсуждения по горячим следам потихоньку мусолил тему. Не торопясь, в свободное время сваял джоб. Потом еще несколько дней раскрашивал его комментариями. А потом и забывать стал. Сегодня вспомнил, думаю, надо бы выложить, а то совсем забуду...
Цитата:
Сообщение от Gustav
Допустим, мы захотели регулярно читать его полученные и выданные репутации.
... Однако и на этот случай "номерной неустойчивости таблиц" есть свое решение: веб-страница рассматривается целиком, без деления на таблицы, а точки начала интересующих нас таблиц определяются нахождением на листе "приёмник" ячеек, содержащих текст "Участник получил одобрение от других" и "Участник одобрил других" соответственно. X++: static void WebQuery_DemoJob(Args _args) { // --------------------------------------------------------------------------- // ПРИМЕР получения табличной информации из web-страницы в Excel // --------------------------------------------------------------------------- COM xlApp; // Excel.Application COM wbks, wbk; // Workbooks, Workbook COM wkss; // Worksheets COM wksReceiver; // Worksheet "приёмник" COM wksAccumulator; // Worksheet "накопитель" COM rCells; // все ячейки (Worksheet.Cells) "приёмника" COM aCells; // все ячейки (Worksheet.Cells) "накопителя" COM qts, qt; // QueryTables, QueryTable COM rngUser; // Range - одиночная ячейка, из значения которой получается имя участника COM rngTO; // Range - одиночная ячейка - "Точка Отсчета" ("Точка Опоры") очередной таблицы репутации, // находится поиском на листе "приёмник" строки типа "Участник получил одобрение от других" и др. // дальше от этой ТО "как от печки" всё "вытанцовывается" COM rngHeaders; // Range - блок ячеек - строка заголовков очередной таблицы репутации на листе "приёмник" COM rngRecords; // Range - блок ячеек - строки данных очередной таблицы репутации на листе "приёмник" COM rngAccHeaders; // Range - блок ячеек - строка заголовков таблицы репутации на листе "накопитель" COM rngAccRecords; // Range - блок ячеек - место "вставки" на листе "накопитель" очередной порции данных с листа "приёмник" COM comTemp; // временный COM-объект для промежуточных операций // по превращению раннего связывания (многоточие VBA) // в позднее связывание (одноточие X++) COMVariant cValue; boolean isFirst = true; int rowFirst, rowLast, countRecords, rowHeader; int colFirst, colLast, countCols; str strConnectionBeg = 'URL;http://axforum.info/forums/member.php?u='; // для файла будет что-то вроде strConnectionBeg = 'URL;c:\\myFolder\\myFile.htm' // или при использовании @: strConnectionBeg = @'URL;c:\myFolder\myFile.htm' str strConnection; str strUser; #define.xlAllTables(2) #define.xlSpecifiedTables(3) #define.xlWebFormattingNone(3) #define.xlDown(-4121) #define.xlToRight(-4161) #define.xlShiftToRight(-4161) // =================================================================================================== // СНАЧАЛА несколько служебных методов (потом - основной процесс) // вычленение повторяющихся фрагментов из основного процесса и оформление их процедурами (методами), // вызываемыми из основного процесса или друг из друга достаточно специфично для конкретной веб-страницы // конкретный вариант такой декомпозиции обычно становится ясным на этапе предварительного анализа // -------------------------------------------------------------------------------- // начальное оформление таблицы результатов (при обработке данных первого участника) // -------------------------------------------------------------------------------- void firstArrange() { // "накопитель" еще пустой // набиваем поля rngAccHeaders = wksAccumulator.Range(aCells.Item(1, 1), aCells.Item(1, countCols)); rngAccHeaders.Value2( rngHeaders.Value2() ); // вот это неплохо :-), фактически матричное присваивание A=B rngAccRecords = rngAccHeaders.Offset(1, 0); // необходимое форматирование некоторых колонок "накопителя" // Дата comTemp = COM::createFromVariant(rngAccRecords.Item(1, 2)); comTemp = comTemp.EntireColumn(); comTemp.NumberFormat('ДД.ММ.ГГ чч:мм'); // Автор - если появляется // всего в таблице репутации появляется или 3 колонки, или 5 (открываются колонки Очки и Автор) // в зависимости от того, сохранили ли вы в куках свой логин-пароль на АхФорум // соответственно вы входите на Форум веб-запросом как гость (будет 3 колонки в табл.реп.) или как участник (будет 5 колонок) if (countCols > 3) { // если вход - участника (а не гостя) - "не мальчика, но мужа!" :-) // устанавливаем текстовый формат для колонки "Автор" (иначе проблемы с попыткой Excel распознать формулы на символах =>) comTemp = COM::createFromVariant(rngAccRecords.Item(1, 4)); comTemp = comTemp.EntireColumn(); comTemp.NumberFormat([EMAIL="'@'"]'@'[/EMAIL]); } // на листе "накопитель" вставляем две дополнительные колонки слева от таблицы репутации comTemp = rngAccHeaders.Resize(1, 2); comTemp = comTemp.EntireColumn(); comTemp.Insert(#xlShiftToRight); // расширяем диапазон заголовков на две добавленные колонки rngAccHeaders = rngAccHeaders.Offset(0, -2); rngAccHeaders = rngAccHeaders.Resize(1, countCols + 2); // заголовки двух добавленных слева колонок COM::createFromVariant(rngAccHeaders.Item(1, 1)).Value2('Участник'); COM::createFromVariant(rngAccHeaders.Item(1, 2)).Value2('Направление'); // заголовки - жирным comTemp = rngAccHeaders.Font(); comTemp.Bold(true); // замораживаем строку заголовков COM::createFromVariant(rngAccHeaders.Item(2, 1)).Select(); // лист "накопитель" здесь уже активен comTemp = xlApp.ActiveWindow(); comTemp.FreezePanes(true); isFirst = false; } // -------------------------------------------------------------------- // обработка таблицы репутации участника (1 или 2 = верхняя или нижняя) // -------------------------------------------------------------------- void processReputationTable(int _tableNum) { switch (_tableNum) { case 1: rngTO = rCells.Find('Участник получил одобрение от других'); break; case 2: rngTO = rCells.Find('Участник одобрил других'); break; } if (rngTO == null) // выходим, если соответствующая таблица отсутствует в профиле участника return; // эксельный номер первой строки данных comTemp = rngTO.Offset(2, 1); rowFirst = comTemp.Row(); // эксельный номер последней строки данных comTemp = comTemp.End(#xlDown); rowLast = comTemp.Row(); // количество записей в данных (эксельных строк в таблице репутации) countRecords = rowLast - rowFirst + 1; // эксельный номер строки заголовков колонок (Тема, Дата и т.п.) rowHeader = rowFirst - 1; // эксельный номер первой колонки comTemp = rngTO.Offset(2, 1); colFirst = comTemp.Column(); // эксельный номер последней колонки comTemp = rngTO.Offset(1, 2); comTemp = comTemp.End(#xlToRight); colLast = comTemp.Column(); // количество столбцов (эксельных колонок в таблице репутации) countCols = colLast - colFirst + 1; // создаем объектные переменные для диапазонов: Заголовков и Записей rngHeaders = wksReceiver.Range(rCells.Item(rowHeader, colFirst), rCells.Item(rowHeader, colLast)); rngRecords = wksReceiver.Range(rCells.Item(rowFirst, colFirst), rCells.Item(rowLast, colLast)); // "возвращаем на место отъехавшую Тему" :-) // слово "Тема" при загрузке в Excel попадает не в ту ячейку, в которую нам надо // (все эти нюансы "поведения" изучаются на этапе предварительного анализа конкретной веб-страницы) COM::createFromVariant(rngHeaders.Item(1, 1)).Value2('Тема'); // если проход - первый, то выполняем "аранжировку" накопителя if (isFirst) firstArrange(); // переопределение диапазона в соответствии с требуемым кол-вом записей rngAccRecords = rngAccRecords.Resize(countRecords); // массовое прописывание значений в колонки "Тема"..."Комментарий" на листе "накопитель" rngAccRecords.Value2( rngRecords.Value2() ); // фактически "копирование без копирования" // массовое прописывание значений в колонку "Участник" на листе "накопитель" comTemp = rngAccRecords.Resize(COM::createFromObject(rngAccRecords.Rows()).Count(), 1); comTemp = comTemp.Offset(0, -2); // эта колонка "левее на 2" относительно колонки "Тема" comTemp.Value2(strUser); // массовое прописывание значений в колонку "Направление" на листе "накопитель" comTemp = rngAccRecords.Resize(COM::createFromObject(rngAccRecords.Rows()).Count(), 1); comTemp = comTemp.Offset(0, -1); // эта колонка "левее на 1" относительно колонки "Тема" switch (_tableNum) { case 1: comTemp.Value2('получил одобрение'); break; case 2: comTemp.Value2('одобрил'); break; } // переопределение диапазона для приема следующей порции данных rngAccRecords = rngAccRecords.Offset(countRecords); // фактически здесь нас интересует переопределенное положение только первой строки нового диапазона (новая "закладка") // а необходимое кол-во строк будет задано "выше" на следующей итерации в операторе: rngAccRecords = rngAccRecords.Resize(countRecords); } // -------------------------------------- // обработка данных из профиля участника // -------------------------------------- void processUserData(int _userId) { strConnection = strConnectionBeg + int2str(_userId); if (isFirst) { // если проход - первый, то создаем новый web-запрос qts = wksReceiver.QueryTables(); qt = qts.Add(strConnection, wksReceiver.Range('A1')); qt.Name('SelectUserProfileFromAxForum'); // -------------------------------------- // изначально было так: // qt.WebSelectionType(#xlAllTables); // но на одном из компьютеров, где были какие-то специфичные настройки Internet Explorer // таблицы репутаций стали "разрываться" дополнительными пустыми колонками (какой-то лишний Tab...) // -------------------------------------- // -------------------------------------- // проблема выше была решена за счет исключения из списка таблицы 2 qt.WebSelectionType(#xlSpecifiedTables); qt.WebTables('1,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30'); // на самом деле таблиц на странице "Профиль участника" около 20, // остальные (до 30) - добавлены для надежности (ошибки это не вызывает) // -------------------------------------- qt.WebFormatting(#xlWebFormattingNone); qt.Refresh(false); //BackgroundQuery:=False } else { // если не первый проход, то перестраиваем запрос qt.Connection(strConnection); qt.Refresh(false); //BackgroundQuery:=False } // получение имени участника rngUser = rCells.Find('Просмотр профиля: '); cValue = rngUser.Value2(); strUser = strLRTrim(strReplace(cValue.bStr(), 'Просмотр профиля: ', '')); //обработка 1-й (верхней) таблицы репутации processReputationTable(1); //обработка 2-й (нижней) таблицы репутации processReputationTable(2); } // =================================================================================================== // ТЕПЕРЬ основной процесс xlApp = new COM('Excel.Application'); wbks = xlApp.Workbooks(); wbk = wbks.Add(); wkss = wbk.Worksheets(); wksReceiver = wkss.Item(1); wksReceiver.Name('Receiver'); wksAccumulator = wkss.Item(2); wksAccumulator.Name('Accumulator'); rCells = wksReceiver.Cells(); aCells = wksAccumulator.Cells(); wksAccumulator.Activate(); processUserData( 259); // macklakov processUserData(2552); // belugin processUserData(5046); // Avick processUserData(5597); // Gustav xlApp.Visible(true); } |
|
31.07.2006, 11:23 | #11 |
Участник
|
Цитата:
Сообщение от Gustav
Не торопясь, в свободное время сваял джоб.
2. Чтобы подавать хороший пример начинающим, обработки такого объема лучше сразу делать в классах. |
|
31.07.2006, 15:41 | #12 |
Moderator
|
Цитата:
Сообщение от mazzy
2. Чтобы подавать хороший пример начинающим, обработки такого объема лучше сразу делать в классах.
Однако, в данном случае ситуация мне таковой не показалась, поэтому джобинкой и ограничился. Точнее, на класс бы и не пошёл: разбирается конкретная страница (и декомпозиция по методам заточена именно под нее конкретную), а класс всё же предполагает по крайней мере ростки какой-то будущей универсальности. Иначе тот же начинающий увидит проект с классом и завопит: "Вау! Универсалка!" (а мне будет неудобно, что я его "обману") |
|
|
За это сообщение автора поблагодарили: Corkscrew (1). |