ПРАВИТЕЛЬСТВО РОССИЙСКОЙ ФЕДЕРАЦИИ Федеральное государственное автономное образовательное учреждение высшего профессионального образования

advertisement
ПРАВИТЕЛЬСТВО РОССИЙСКОЙ ФЕДЕРАЦИИ
Федеральное государственное автономное образовательное
учреждение высшего профессионального образования
"Национальный исследовательский университет
"Высшая школа экономики"
Пермский филиал
Факультет бизнес-информатики
Кафедра информационных технологий в бизнесе
УДК 004.4
Разработка средств автоматизации поиска структурированной
информации в гетерогенной среде
Выпускная квалификационная работа бакалавра
Работу выполнил студент
группы БИ-10-1
4 курса факультета бизнес-информатики
П. С. Крысанов
Научный руководитель:
Доцент кафедры информационных
Технологий в бизнесе, к.ф.-м.н., доцент
Л.Н. Лядова
«05» июня 2014 г.
Пермь 2014
Оглавление
Оглавление ..................................................................................................... 2
Введение......................................................................................................... 3
Глава 1. Аналитический обзор методов и средств для разработки
необходимых алгоритмов и приложения в целом ............................................... 6
1.1 Основные определения ....................................................................... 6
1.2 DLL библиотека для парсинга интернет-страниц ........................... 7
1.3 Проблема сравнения двух основ слов на равенство ........................ 7
1.4 Выводы по главе.................................................................................. 9
Глава 2. Алгоритмы для поиска структурированных данных ............... 10
2.1 Создание модели таблицы в программе ......................................... 10
2.2 Алгоритм сравнения двух заголовков на соответствие друг другу
............................................................................................................................. 13
2.3 Алгоритм сравнения двух уровней заголовков на соответствие
друг другу ........................................................................................................... 15
2.4 Алгоритм сравнения двух таблиц на соответствие друг другу ...... 19
2.3 Выделение табличной информации из файлов различных типов 21
2.3.1 Выделение информации из таблиц в документах excel ......... 21
2.3.2 Выделение информации из таблиц в документах word......... 25
2.3.3 Выделение информации из таблиц в документах html........... 26
Глава 3. Разработка приложения ............................................................... 29
3.1 Разработка приложения для поиска на локальном компьютере.... 29
3.1.1 Логика поиска на локальной машине ....................................... 29
3.1.2 Интерфейс ................................................................................... 30
3.2 Разработка приложения для поиска в интернете ...................................... 33
3.2.1 Логика поиска в интернете .............................................................................. 33
3.2.2 интерфейс ..................................................................................................................... 36
Заключение .................................................................................................. 38
Библиографический список ....................................................................... 39
Приложение А. Результаты работы программы ...................................... 41
Приложение Б. Руководство пользователя .............................................. 48
Приложение В. Исходный код приложения............................................. 58
2
Введение
Объём информации в Интернет растёт c каждым днем, а соответственно и
растут потребности пользователей в поиске информации, которая может быть
представлена в разных форматах.
На данный момент в основных поисковых системах существует поиск
только через строковый запрос, то есть пользователь вводит сроку для поиска, к
примеру, на главную страницу поискового робота Яндекс и он выдаёт ему
соответствующий этому запросу результат. С точки зрения пользователя, он вводит
в поисковую машину только строковый запрос, но Яндекс на основе данной
поисковой строки формирует целый запрос с такими параметрами как дата, язык,
локализация и многое другое. С некоторыми параметрами есть возможность
работать, так как они передаются через GET запрос. Один из таких параметров это
строка запроса, а это в свое очередь означает, что программа в автоматическом
режиме может подавать в Яндекс GET запросы
различными поисковыми
запросами. Более подробно механизм работы с поисковыми системами из
приложения описан ниже.
В запросе к поисковой машине можно задать дополнительные параметры
(через расширенный поиск), но при этом все-таки сложно отобрать данные,
релевантные не запросу, а информационным потребностям пользователей.
Кроме того, часто необходимо найти данные, представленные в виде таблиц
(например:
данные
об
экономическом
статистики,
данные
о
состоянии
развитии,
конкретных
публикуемые
предприятий,
органами
организаций
представленные ими, прайс-листы и пр.) для последующего анализа. Эти данные
могут находится в разных источниках и в разных форматах.
Таким образом, для различных категорий пользователей (исследователей,
аналитиков, клиентов Интернет-магазинов и пр.) актуальной становится задача
поиска именно структурированной информации в гетерогенных источниках.
В
данной работе решается задача поиска таблиц в сети Интернет и на
локальной машине. В качестве параметра для поиска рассматривается не строка, а
таблица, для которой мы будем искать подобные таблицы, где критерии сравнения
задаются пользователем. Целевым объектом для поиска являются таблицы,
3
которые должны соответствовать заданной эталонной таблице и дополнительным
задаваемым
пользователем
параметрам,
таким
как
процент
соответствия
заголовков в таблицах.
Эта задача является актуальной для многих категорий пользователей,
решаемых ими задач. Рассмотрим задачу сбора статистических данных для
аналитики. Для того чтобы решить ее стандартным способом, аналитик вводит
текстовый запрос в поисковую систему, и просматривает информацию по
полученным в
качестве результатов
запроса
ссылкам
в
надежде
найти
необходимую табличную информацию со статистикой. Зайдя на страницу по
какой-либо ссылке, пользователь найдет, скорее всего, некоторую текстовую
информацию соответствующую запросу, но не обязательно там будет таблица, а
если и будет, то далеко не факт, что она будет содержать релевантные
потребностям пользователя данные . Таким образом, поиск может затянуться на
долгое время и далеко не факт, что он закончится успехом.
После поиска
аналитику необходимо обработать полученные данные, сравнить их, привести к
некоторому виду, пригодному для дальнейшей обработки.
Таким образом, актуальной становится задача реализации приложения,
которое позволило бы не только повысить релевантность результатов при поиске
табличных данных, но и снизить трудоёмкость их обработки.
Объектом исследования в данной работе будет информационный поиск, а
предметом исследования методы и средства поиска структурированной
информации.
Целью
данной
работы
является
разработка
приложения,
которое
предназначено для поиска таблиц на локальной машине, в локальной сети и в
Интернет по заданному эталону (эталонной таблице) на основе сопоставления
структур таблиц и соответствия данных заданным параметрам поиска.
Для достижения поставленной цели должны быть решены следующие
задачи:
 Анализ законченных программных решений и методов решения отдельных
задач в области поиска структурированных данных.
 Разработать алгоритм для программного поиска документов (Word, Excel,
Html) на локальном компьютере.
4
 Рассмотреть возможности языка C# при обработке документов выбранных
для поиска (Word, Excel, Html).
 Разработать алгоритм для определения схожести таблиц по структуре.
 Рассмотреть возможности языка C# в для решения задачи разбора html
документов.
 Разработать алгоритм для поиска и выгрузки табличных данных в интернете
в документах (Word, Excel, Html).
 Разработать приложение, реализующее разработанные алгоритмы для
решения задачи поиска табличных данных в сети интернет и на локальной
машине на языке C#.
Разработанная программа должна решать задачу автоматизации поиска
таблиц, их загрузки в на локальный компьютер и сохранение ссылки на
загруженные файлы в результатах поиска. Таким образом, пользователь
освобождается от просмотра найденных страниц и поиска нужной информации на
них (ссылок на найденные таблицы, просмотра необработанных данных и т.д.).
Использование программы должно снизить трудоёмкость выполнения рутинных
операций, освободить время пользователя для анализа и обработки найденных
данных. На выходе пользователь получит список ссылок на результаты поиска. От
пользователя необходимо только подать структуру данной таблицы (наименования
столбцов), которые соответствуют, по мнению пользователя, структуре искомых
данных, а также задать дополнительные параметры поиска.
Результатом выполнения работы должен стать исследовательский прототип
приложения,
который
может
использоваться
устанавливаемое на рабочих местах пользователей.
5
как
отдельное
приложение,
Глава 1. Аналитический обзор методов и средств для разработки
необходимых алгоритмов и приложения в целом
Существует множество средств решения задачи поиска, однако эта задача
очень широка и решается в абсолютно разных условиях с различными
требованиями к источникам данных. В данной главе уточняется постановка задачи
информационного поиска для работы со структурированными данными, а также
анализируются существующие походы к её решению, методы и средства, которые
можно было бы применить для выполнения отдельных шагов алгоритма.
Для решения поставленных задач необходимо найти средства для парсинга
интернет страниц. Парсинг используется при автоматическом извлечении данных
с интернет-страниц [1]. Решение задачи парсинга в данной работе необходимо для
извлечения таблиц с данными с различных сайтов и извлечение ссылок со
страницы с результатами поиска Яндекса. Также очень важно найти готовые
решения в области нахождения основы от его исходного вида. Это решение
необходимо для разработки алгоритма сравнения двух на соответствие друг другу,
который описан в следующей главе.
1.1 Основные определения
Для того, чтобы можно было свободно ориентироваться по данной работе,
необходимо дать определения нескольким понятиям. Под парсингом, с помощью
которого будут извлекаться данные,
понимается автоматизированный сбор
контента или данных с какого-либо сайта или сервиса [2]. Также понадобится дать
определение слову “релевантный”, так как оно будет использоваться много раз.
Релевантность
определяется
как
соответствие
полученной
информации
информационному запросу [3]. В контекстах, предложенных в данной работе, оно
будет может быть синонимом к слову “соответствующий”. Также в работе
необходимо дать определение слову лемматизация, так как оно будет использовано
в ключевом алгоритме этой работы. Под лемматизацией понимается процесс
привода словоформы к её лемме - нормальной (словарной) форме[4]. Выражение
“поисковый запрос” также имеет большое значение в данной работе. Данное
словосочетание означает некоторую исходную информацию для осуществления
6
поиска с помощью поисковой системы [4]. То есть, может быть не только
строковый запрос, в него могут входить и другие параметры, как, например,
процент допустимой релевантности и многие другие
1.2 DLL библиотека для парсинга интернет-страниц
Парсинг
интернет
страниц
на
сегодняшний
день
очень
широко
распространенная задача. Примером может являться ситуация, когда пользователю
необходимо собирать данные о ценах в нескольких интернет магазинах и
сравнивать их друг с другом или вести какую-нибудь другую аналитику. Если
собирать необходимые данные вручную, то это займет уйму времени , а с помощью
программных решений это займет несколько минут. Для решения этой задача для
пользователей .NET разработана DLL библиотека, которая позволяет извлекать
необходимые данные с интернет страниц [5].
К примеру, с помощью строки на языке C# “HtmlNodeCollection
c =
doc.DocumentNode.SelectNodes ("//a[@class=data']");” можно получить коллекцию
всех ссылок у который атрибут class=”data.” Это очень быстрый способ работы с
HTML документами, так как позволяют получить необходимые данные с помощью
ввода 1-2 строк кода [6].
1.3 Проблема сравнения двух основ слов на равенство
Для написания приложения необходим алгоритм сравнения двух таблиц на
соответствие друг другу, а для разработки этого алгоритма необходим алгоритм
сравнения двух слов на соответствие друг другу, но в данном случае есть одно
ограничение, при сравнении необходимо, чтобы слова имеющие схожую основу
считались равными, несмотря на отличие в суффиксах и окончаниях. К примеру,
есть два слова “красивый” и “красивее”, если напрямую сравнить их на равенство,
то ответ будет отрицательный, но для решения данной задачи необходимо, чтобы
подобные случае давали положительный ответ при сравнении.
Решением данной проблемы будет алгоритм приведения этих 2 слов к их
основам и уже после приведения сравнивать их на равенство. Название у данного
алгоритма приведения к основе – “Стемминг” [7].
Задача нахождения основы слова представляет собой давнюю проблему в
области компьютерных наук. Первая публикация по данному вопросу датируется
7
1968 годом. Стемминг применяется в поисковых системах для расширения
поискового запроса пользователя, является частью процесса нормализации текста.
Конкретный способ решения задачи поиска основы слов называется
алгоритм стемминга, а конкретная реализация — стеммер.
Первый опубликованный стеммер был написан Джули Бет Ловинс в 1968
году.
Позже стеммер был написан Мартином Портером и опубликован в 1980
году. Этот стеммер очень широко использовался и стал де-факто стандартным
алгоритмом для текстов на английском языке. Доктор Портер получил премию
Стрикса в 2000 году за работы по стеммингу и поиску информации.
Многие реализации алгоритма стемминга Портера были написаны и
свободно распространялись; однако, многие из этих реализаций содержат
труднонаходимые недостатки. В результате, данные алгоритмы не работали в
полную силу. Для устранения такого типа ошибок Мартин Портер выпустил
официальную свободную реализацию алгоритма около 2000 года. Он продолжал
эту работу в течение нескольких следующих лет, разработав Snowball, фреймворк
для создания алгоритмов стемминга, и улучшенных стеммеров английского языка,
а также стеммеров для некоторых других языков.
Существует несколько типов алгоритмов стемминга, которые различаются
по отношению производительности, точности, а также как преодолеваются
определенные проблемы стемминга. Также для решения задачи выделения основы
слова используется лемматизация. Это более сложный алгоритм и более затратный
по времени, но он соответственно обладает большей эффективностью.
Стеммер был найден в интернете в виде консольного приложения и далее,
используя Visual Studio, была создана DLL библиотека на основе этого
приложения. Стеммер разработан компанией Ивеоник Системс.
Результаты
работы стеммера на нескольких языках. Слева входная строка, справа то, что
получилось в результате работы стеммера.
И вот что получаем:
Stemmer: Iveonik.Stemmers.RussianStemmer
краcОта --> краcот
красоту --> красот
8
красоте --> красот
КрАсОтОй --> красот
Stemmer: Iveonik.Stemmers.EnglishStemmer
jump --> jump
jumping --> jump
jumps --> jump
jumped --> jump
Stemmer: Iveonik.Stemmers.GermanStemmer
mochte --> mocht
mochtest --> mocht
mochten --> mocht
mochtet --> mochtet [8]
В данной работе была протестирована вышеописанная библиотека на
примере 250 русских слов. В результате получилось 227 удовлетворительных
вариантов, 23 неудовлетворительных. Процент удовлетворительности 90%, что
вполне приемлемо для данной работы.
Стемминг является отличным решением для выделения основы слова, так
работает
вполне
быстро
и
обладает
высоким
коэффициентом
удовлетворительности описанным выше. Роль стемминга и найденной библиотеки
для реализации данного алгоритма в приложении будет описана ниже.
1.4 Выводы по главе
В данной главе решены несколько очень важных проблем. Во-первых,
найдено готовое решение для парсинга интернет-страниц ввиде dll библиотеки,
которое помогает с помощью нескольких строк кода извлекать необходимые
данные со страницы. Во-вторых, найдено решение для задачи приведения
исходного слова к его основе виде готового проекта в .Net, которое в дальнейшем
приведено к dll библиотеке. Полностью готовых приложений, которые решает
задачу поиска структурированных данных в интернете и на локальном компьютере
найти не удалось. Решение этих двух проблем позволяет перейти непосредственно
к разработке необходимых алгоритмов для достижения поставленной цели.
9
Глава 2. Алгоритмы для поиска структурированных данных
В данной главе будут рассматриваться алгоритмы, которые необходимо
разработать для решения задач, поставленных в данной работе. Во-первых, как уже
было сказано в предыдущей главе, это алгоритм сопоставления двух таблиц на
соответствие друг другу. Это один из ключевых алгоритмов данной работы. Он
будет использоваться для сравнения найденных таблиц с эталонной таблицей. Об
эталонной таблице речь пойдет ниже. На вход данному алгоритму подается две
таблицы, а на выходе получается ответ на вопрос: “Имеют ли данные таблицы
схожий смысл?” в виде “Да/Нет”. По аналогичной схеме будет строится алгоритм
сравнения двух заголовков на соответствие. На вход данному алгоритму будет
подаваться две строки(два заголовка), а на выходе будет получен ответ “Да или
Нет”. Параметрами при сравнении двух таблиц на соответствие друг другу будет
процент схожести заголовков этих таблиц, при котором алгоритм даст
положительный ответ на вопрос о соответствии таблиц и процент схожести двух
заголовков друг другу, при котором при сравнении двух заголовков на
релевантность будет положительный ответ. Более подробно об этом при описании
алгоритма.
Далее обязательно необходимы алгоритмы извлечения таблиц из
документов Word, Excel и Html, для того, чтобы сопоставлять данные из них с
эталонной таблицей.
Эталонная таблица – это таблица созданная пользователем, с которой будет
происходить сравнение найденных таблиц на соответствие. Эталонная таблица в
данной работе будет означать поисковый запрос пользователя.
2.1 Создание модели таблицы в программе
Для сравнения двух таблиц на соответствие необходимо выделить некоторые
характеристики, с помощью которых можно описать таблицу. В данной работе
такими характеристиками будут заголовки таблицы, так как именно они отражают
ее смысл. Данные в характеристики таблицы включаться не будут, так как их
обработка существенно замедлит обработку таблиц, а также они практически не
несут смысловой нагрузки.
Для создания алгоритма сравнения таблиц на соответствие, необходимо
создавать модель таблицы, то есть создавать некоторую структуру, которая будет
10
описывать таблицу и которую мы будем сравнивать с другими таблицами. В
качестве примера рассмотрим таблицы, показанные на рис. 2.1 и на рис. 2.2.
Рисунок 2.1 Пример таблицы с многоуровневым заголовком
Рисунок 2.2 Пример таблицы с одноуровневым заголовком
На рисунках, расположенных выше, показаны два типа таблиц, с которыми
мы можем работать. В случае таблицы на рисунке 2.1, имеется таблица с
многоуровневыми заголовками (для примера их показано два),
а на таблице
расположенной на рисунке 2.2 показана таблица с одноуровневым заголовком.
Таким
образом,
необходимо
эти
таблицы
привести
к
некоторой
универсальной форме, с которой можно работать в дальнейшем.
Для реализации алгоритма сравнения таблиц на соответствие друг другу
будут использованы только заголовки таблиц, поэтому модели таблицы будет
создаваться на их основе.
Алгоритм создания модели таблицы на основе заголовков представлен ниже.
11
Алгоритм
работы
заключается
в
следующем,
вначале
создается
двухуровневый список (список списков) и далее приложение будет считывать
каждый уровень заголовков и добавлять в список.
На рисунке 2.3 представлен общий вид такой структуры. По вертикали
находится список уровней заголовка, по горизонтали сами заголовки. В данной
работе не рассматриваются взаимосвязи между уровнями, каждый уровень
существует сам по себе. Создавать взаимосвязи между уровнями не имеет смысла,
так как это существенно осложнит алгоритм и ощутимой пользы не принесет.
Рисунок 2.3 Структура данных заголовков таблицы в общем виде
Ниже будет рассмотрены результаты разбора таблиц с рисунков 2.1 и 2.2.
Для таблицы с рисунка 2.1 макет структуры показан на рис. 2.4.
Рисунок 2.4 Макет структуры таблицы 1
Для таблицы с рисунка 2.2 макет структуры показан на рис. 2.5.
Рисунок 2.5 Макет структуры таблицы 2
В итоге, при разборе каждой таблицы будет создаваться вышеописанная
структура из заголовков.
12
2.2 Алгоритм сравнения двух заголовков на соответствие друг другу
Данный алгоритм будет использоваться в алгоритме сопоставления двух
таблиц. На вход алгоритму подается два списка слов из двух заголовков. Первым
подается тот список, в котором меньше слов. Описание алгоритма приведено ниже.
bool CompareHeaders(List<string> lstMin, List<string> lstMax)
{
int countrelevant=0;
foreach (string str1 in lstMin)
{
foreach (string str2 in lstMax)
{
if (CompareWords(str1, str2))
{
countrelevant++;
break;
}
}
}
if (lstMin.Count > 0)
if ((countrelevant / lstMin.Count)*100 >= percentage_of_relevancyHeaders) return true;
return false;
}
bool CompareWords(string str1, string str2)
{
str1 = Stem(str1);
str2 = Stem(str2);
if (str1 == str2) return true;
else return false;
}
В псевдокоде, описанном выше, присутствуют две функции: CompareHeaders
и CompareWords. В функции CompareРeaders происходит непосредственно
сравнение двух заголовков на соответствие друг другу. Для реализации этой
функции необходимо было выделить функцию сравнения двух слов на
релевантность. В функции
CompareWords используется стемминг, который
вызывается посредством функции Stem.
13
На вход алгоритму поступают два набора слов ( один из первого заголовка,
второй из второго). Для каждого слова из заголовка, в котором количество слов
меньше, проводится сравнение с каждым словом, где количество слов больше. Под
сравнение
слов
понимается
посредством стемминга.
сравнение
основ
слов,
который
реализуется
Если слово из набора слов, в котором меньше слов,
находит равное себе во втором наборе слов, то переменная-счетчик countrelevant
увеличивается на единицу и происходит переход к следующему слову из заголовка,
в котором меньше слов. В итоге, если countrelevant разделить на количество слов в
наборе, в котором меньше слов, и это значение будет больше или равно параметру
– необходимое количество процентов схожести двух заголовков для признания
этих заголовков релевантными, то это означает, что заголовки имеют схожий
смысл.
Если за n считать количество слов в первом заголовке, за m количество слов
во втором, то сложность алгоритма будет стремится к выражению n*m.
Рассмотрим пример сравнения двух заголовков. Есть два заголовка :
“Результаты экзамена” и “Итоговый экзамен по программированию”. Эти два
заголовка будут разделены на следующие наборы слов : (Результаты, экзамена) и
(Итоговый, экзамен, программированию). Предлоги и союзы не входят в набор
слов так как не несут на себе смысловой нагрузки. Сравним эти два набора на
соответствие(см. рис 2.6).
Рисунок 2.6 Два набора слов из заголовков
Далее сравниваем каждое слово из набора, в котором меньше слов с каждым
словом из набора, в котором больше слов(см. рис 2.7).
14
Рисунок 2.7 Сравнения наборов слов
На рисунке 2.7 показано сравнения двух наборов слов. Красная линия
означает, что данные слова не соответствуют друг другу, а зеленая если
соответствуют. В итоге получается, что из двух слов первого набора, только одно
нашло себе соответствующее слово во втором наборе. Теперь необходимо найти
процент соответствия заголовков. Для этого нужно разделить количество слов, из
набора, в котором меньше слов, которые нашли себе соответствие в другом наборе
на количество слов в этом наборе. То есть, в данном случае это будет ½ =50%. Если
в параметрах соответствия заголовков, стоит
значение 50 или менее, то
приложение вернет ответ true на вопрос: ” Соответствуют ли эти заголовки друг
другу?”
2.3 Алгоритм сравнения двух уровней заголовков на соответствие друг другу
Данный алгоритм будет использоваться в алгоритме сопоставления двух
таблиц. На вход алгоритму подается два набора заголовков, которые взяты из
уровней заголовков - по одному из каждой таблицы. Они будут передаваться виде
15
двухуровневого списка, у которого одно измерение это заголовки, второе
измерение это слова, которые входят в заголовки. Первым подается тот список, в
котором меньше заголовков. Описание алгоритма приведено ниже.
bool compareLevel(List<List<string>> minTbl, List<List<string>> maxTbl)
{
int countRelevant = 0;
foreach (List<string> lstMin in minTbl)
{
foreach (List<string> lstMax in maxTbl) //Можно добавить глубину поиска
{
if (lstMin.Count <= lstMax.Count)
{
if (CompareHeaders(lstMin, lstMax))
{
countRelevant++;
break;
}
}
else
{
if (CompareHeaders(lstMax,lstMin))
{
countRelevant++;
break;
}
}
}
}
decimal dblCountRelevant = decimal.Parse(countRelevant.ToString());
if (minTbl.Count > 0)
{
decimal value = dblCountRelevant / minTbl.Count;
if (value * 100 >= percentage_of_relevancyTables) return true;
}
return false;
}
16
В псевдокоде, описанном выше, присутствуют две функции: compareLevel и
CompareHeaders. В функции compareLevel происходит непосредственно сравнение
двух наборов заголовков на соответствие друг другу. Для реализации этой функции
использоваласб функция CompareHeaders, алгоритм работы которой описан в
предыдущем пункте.
На вход алгоритму поступают два набора заголовков (один из первого
уровня заголовков, второй из второго). Для каждого заголовка из набора, в котором
количество заголовков меньше, проводится сравнение с каждым заголовком, где
количество заголовков больше. Под сравнением заголовков понимается сравнение
применение алгоритма описанного в предыдущем пункте.
Если заголовок из
набора, в котором меньше заголовков, находит релевантный себе заголовок во
втором наборе, то переменная-счетчик countrelevant увеличивается на единицу и
происходит переход к следующему заголовку из первого набора. В итоге, если
countrelevant разделить на количество заголовков в наборе, в котором меньше
заголовков, и это значение будет больше или равно параметру – необходимое
количество процентов схожести двух наборов заголовков для признания этих
наборов релевантными, то это означает, что наборы заголовков имеют схожий
смысл.
Если за n считать количество заголовков в первом наборе, за m количество
слов во втором, то сложность алгоритма будет стремится к выражению n*m.
Рассмотрим пример сравнения двух наборов заголовков. Есть два набора
заголовков: (Студент, Результат) и (ФИО, рейтинг до экзамена, экзамен, результат).
Сравним эти два набора на соответствие(см. рис 2.8).
Рисунок 2.8 Два набора заголовков
Далее сравниваем каждый заголовок из набора, в котором меньше
заголовков с каждым заголовком из набора, в котором больше заголовков (см. рис
2.9).
17
Рисунок 2.9 Сравнения наборов заголовков
На рисунке 2.9 показано сравнение двух наборов заголовков. Красная линия
означает, что данные заголовки не соответствуют друг другу, а зеленая если
соответствуют. В итоге получается, что из двух заголовков первого набора, только
один нашел себе соответствующий заголовок во втором наборе. Теперь
необходимо найти процент соответствия наборов. Для этого нужно разделить
количество заголовков, из набора, в котором меньше заголовков, которые нашли
себе соответствие в другом наборе на количество заголовков в этом наборе. То
есть, в данном случае это будет ½ =50%. Если в параметрах соответствия наборов
заголовков, стоит значение 50 или менее, то приложение вернет ответ true на
вопрос: ” Соответствуют ли эти наборы заголовки друг другу?”
18
2.4 Алгоритм сравнения двух таблиц на соответствие друг другу
Как уже было написано во введении, одной главных задач данной работы
является разработка алгоритма сравнения двух таблиц на их соответствие. На вход
в алгоритм поступают две модели таблиц, которые имеют вид, подобный виду
описанном на рис. 2.3. Данная модель будет реализована виде списка списков(List<
List<string>>). На выходе будет ответ в виде “True/False”, где True означает, что
таблицы соответствуют друг другу, а False означает, что не соответствуют.
Псевдокод алгоритма описан ниже.
bool CompareTables(List<List<List<string>>> tbl1,
List<List<List<string>>> tbl2)
{
if (tbl1.Count == 0 || tbl2.Count == 0) return false;
foreach (List<List<string>> lst1 in tbl1)
{
foreach (List<List<string>> lst2 in tbl2)
{
if (lst1.Count == 0 || lst2.Count == 0)
{
continue;
}
if (lst1.Count < lst2.Count)
{
if (compareLevels(lst1, lst2))
{
return true;
}
}
else
{
if (compareLevels(lst2, lst1))
{
return true;
}
}
}
}
19
return false;
}
В данном алгоритме происходит сравнение каждого уровня заголовков с
каждым уровнем заголовков из другой таблицы. Каждый уровень заголовков
состоит из набора заголовков. Для того, чтобы результат сравнения двух таблиц
был равен true достаточно, чтобы хотя бы одно сравнение уровней дало
положительный ответ. В данном алгоритме применяется функция CompareLevels,
алгоритм которой описан в предыдущей пункте.
На вход алгоритму поступают две модели таблиц, состоящие из одного и
более уровней, а каждый уровень содержит непосредственно заголовки таблиц.
Далее в алгоритме идет процесс сравнения каждого уровня заголовка из одной
таблицы с каждым уровнем заголовков и другой. Если хоть одно из сравнений дало
положительный ответ, то алгоритм возвращает true, а это в свою очередь означает,
что он признает эти таблицы соответствующими друг другу. Рассмотрим алгоритм
сравнения двух таблиц на соответствие друг другу на конкретном примере. Для
примера, рассмотрим две структуры, которые мы получили по ходу(см. рис. 2.10 и
рис. 2.11).
Рисунок 2.10 Структура таблицы 1
Рисунок 2.11 Структура таблицы 2
Далее мы будем производить сравнение каждого уровня заголовков из
первой таблицы с каждым уровнем заголовков из второй таблицы как показано на
рис. 2.12. В данном случае у нас получится всего два сравнения. Уровень
заголовков из первой таблицы будет сравниваться с двумя уровнями заголовков из
второй таблицы.
20
Рисунок 2.12 Структура таблицы 2
Сравнение произведено в соответствии с алгоритмом описанным в
предыдущим пункте. Зеленая стрелка означает, что данные уровни релевантные,
красная – не релевантные. Так как для данного алгоритма достаточно всего одного
совпадения, чтобы вернуть true, то результатом сравнения данных таблиц будет
true. Если n – количество уровней в первой табле, а m – во второй, то сложность
алгоритма будет стремится к выражению n*m.
2.3 Выделение табличной информации из файлов различных типов
В данной работе приложению необходимо извлекать таблицы из документов
Word, Excel и HTML для дальнейшей обработки, поэтому встает задача создания
алгоритмов для выделения табличных данных из соответствующих документов.
Ниже описаны алгоритмы для работы с каждым из приведенных выше типов
документов.
2.3.1 Выделение информации из таблиц в документах Excel
Для выделение табличных данных из документов Excel вначале необходимо
выделить на листе часть с данными и далее выделить из нее заголовки.
Во-первых, необходимо проверить лист на пустоту. Для этого, используется
стандартную функцию UsedRange объекта WorkSheet(Рабоичй лист) [9]. В случае,
если UsedRange.Rows.Count и UsedRange.Columns.Count одновременно равны
единице, то можно сделать вывод, что данный пустой. Ну, а далее, когда мы
прошли проверку листа на пустоту, то необходимо выделить диапазон с данными.
21
Для решения данной задачи можно было бы использовать ту же самую
функцию(UsedRange), которая выделяет диапазон используемых ячеек, но у нее
есть один большой недостаток. Недостаток заключается в том, что используемой
ячейкой считается не только заполненная ячейка, но и ячейка в которой когда-либо
находились данные. К примеру, я ввел данные в ячейку А1, а далее удалил их из
этой ячейки. Таким образом, данная функция вернет ячейку А1, несмотря на то, что
на данный момент она пустая, так как ранее она использовалась. Таким образом,
необходимо разработать собственный алгоритм для выделения данных с листа
Excel.
Этапы выделения данных с листа Excel:
1. Найти одну непустую ячейку на листе.
2. От этой ячейки двигаться вверх, вниз, вправо, влево до тех пор, пока не
встретится пустая ячейка(выделить диапазон ячеек).
3. Считать заголовки таблицы.
Ниже будет рассмотрен этап для выделения непустой ячейки. Во-первых,
для выделения
диапазона непустых ячеек необходимо вначале выделить одну
непустую ячейку. Для этого будет использована стандартная функция в языке C#
“UsedRange ”.
Ниже будет рассмотрен алгоритм для выделения диапазона ячеек.
На вход нам попадает непустая ячейка из предыдущего этапа. После того,
как найдена непустая ячейка приложение определяет диапазон этих данных. Для
этого она начинает идти вверх, вниз, влево, вправо до тех пор, пока не встретит
пустую ячейку. На выходе мы получаем диапазон ячеек.
Ниже будет рассмотрен этап выделения заголовков.
На вход нам попадает диапазон ячеек с данными из предыдущего этапа.
После того, как диапазон выделен, приложение перемещается на самую верхнюю
заполненную строчку и считывает заголовки данной таблицы слева направо. Если
количество считанных заголовков меньше или равно ширине диапазона минус 4, то
приложение переходит к следующей строке с данными и формирует новый уровень
заголовков. И так до тех пор, пока количество непустых ячеек в ряде будет больше
или равно Таким образом, после проведения всех этих манипуляций мы получаем,
список заголовков таблицы или null, если лист пуст.
22
Ниже будет рассмотрен пример работы алгоритма.
Пример исходной таблицы расположен на рис. 2.13.
Рисунок 2.13 Пример исходной таблицы
Далее приложение начинает искать в нем первую непустую ячейку. С
помощью функции “UsedCells” можно получить доступ к верхней левой
заполненной ячейке или получить пустую ячейку, если лист пуст. В данном случае
это будет ячейка C4(см. рис. 2.14).
Рисунок 2.14 Выбранная ячейка
23
Далее из ячейки C4 программа начнет двигаться вверх, вниз, влево, вправо
до тех пор пока не встретит пустую ячейку или не дойдет до конца листа(см. рис.
2.15).
Рисунок 2.15 Поиск диапазона ячеек
Таким образом, диапазон ячеек C4:O39. И далее будет произведен процесс
считывания заголовков из таблицы(см. рис. 2.16).
Рисунок 2.16 Выделение заголовков
24
Так как у нас количество заголовков в первой строке больше или равно
ширине диапазона минус 4(14>14-4), то перехода к считыванию следующей строки
не будет. Таким образом, модель таблицы будет состоять только из одного уровня
заголовков, в который будут входить следующие заголовки:
 Хозяева.
 Гости.
 Тур.
 Месяц.
 Место.
 Id команды.
 Класс тренера.
 1.
 Бюджет.
 Голов забито.
 Голов пропущено.
 Голов забито игру назад.
 Голов пропущено игру назад.
2.3.2 Выделение информации из таблиц в документах WORD
С WORD все обстоит проще. DLL для работы с документами WORD дает
широкие возможности для различных действий на основе этих документов.
В данной библиотеке все таблицы документа хранятся в свойстве
WordDocument.Tables и поэтому доступ к этой таблице сводится к тому, что
необходимо просто указать индекс интересующей таблицы [10]. К примеру, строка
WordDocument.Tables[1] в результате вернет ссылку на первую таблицу в
документе.
Таким образом, алгоритм извлечения таблиц сводится к следующему. Вопервых запрашивается таблица из массива WordDocument.Tables[1] и далее идет
проход по первой строке этой таблицы с целью извлечения ее заголовков.
Единственной проблемой может быть, когда несколько ячеек заголовка
слиты воедино, но эта ситуация обработана с помощью блока try-catch. (см. табл.
2.1)
25
Таблица 2.1 Пример таблицы в документе WORD
Таким образом, результатом обработки таблицы 1 будет список заголовков
таблицы:
 Хозяева.
 Гости.
 Работа.
 Id.
 Место в составе.
2.3.3 Выделение информации из таблиц в документах HTML
На входе в приложение мы получаем HTML файл или ссылку на документ
формате HTML. Для его парсинга, а именно извлечения данных из тэгов будет
использована dll библиотека “HtmlAgilityPack”, которая работает с языком Xpath.
Ниже приведены этапы работы данного алгоритма:
1. Выделить все таблицы из документа
2. Для каждой таблицы, проверить наличие внутренних таблиц в этих
таблицах.
Те,
таблицы,
которые
имеют
внутренние
таблицы
отбросить.
3. Из оставшихся от предыдущего этапа оставить только те таблицы, у
которых количество ячеек больше 5 и, у которых хотя бы 50%
содержат числовые данные.
Чтобы найти таблицы нужно странице обратиться с помощью языка Xpath.
Запрос будет выглядеть следующим образом:
HtmlNodeCollection tables = d.DocumentNode.SelectNodes("//table");
Данный код означает, что из документа берутся все DOM-узлы с тэгом
“table”, то есть таблицей. Итого у нас есть коллекция кодов всех таблиц, которые
имеются на данной странице.
26
Таблицы на страницах могут быть 2 видов: информационные и те, которые
используются для разметки страницы. Таким образом, необходимо оставить только
те таблицы, которые несут информационную нагрузку.
Для этого в цикле обращаемся к каждой таблице из коллекции и начинаем
определять ее тип.
Чтобы найти таблицы с данными, а не таблицы, которые связаны с
разметкой, нам нужно найти только внутренние таблицы, а потом уже,
проанализировав их, определить подходят ли они под данный запрос или нет.
Внутренними таблицами в данном случае являются таблицы, которые не содержат
внутри себя других таблиц (см. рис. 2.17).
Рисунок 2.17 Внутренние таблицы
Для реализации проверки таблицы - является она внутренней или нет - мы
будем искать в коде таблицы фрагмент “<table”, открывающий тэг таблицы,
который говорит о том, что внутри таблицы имеется еще одна таблица. Если мы
обнаруживаем данный фрагмент, то это значит, что таблица не внутренняя, и мы ее
отбрасываем.
Далее, если этот проверка таблицей пройдена, то мы к его коду проводим два
запроса с помощью синтаксиса Xpath, для получения коллекции рядов таблицы и
ячеек таблицы для их дальнейшей обработки(см. рис. 2.18).
27
Рисунок 2.18 Ряды и ячейки таблицы
Проведем следующую проверку таблицы на то, что в ней имеется хотя бы
один ряд и ячейка, потому что могут быть такие ситуации, что таблица окажется
совершенно пустой.
Если эта проверка пройдена, то проверим таблицу на количество ячеек. В
данной работе было определено, что если в таблице менее 6 ячеек, то мы их
отсекаем.
Далее необходимо обойти все ячейки, кроме первого ряда, и посчитать
количество ячеек, в которых представлены числовые данные. Если количество
таких ячеек будет представлять 50 и более процентов от количества всех ячеек, мы
выделяем из данной таблицы заголовки необходимые нам для дальнейшего
анализа. Далее на основе заголовков будет строиться модель таблицы для
дальнейшей работы с ней.
28
Глава 3. Разработка приложения
В данной главе будут рассмотрены варианты поиска данных, работа
приложения в целом как для поиска в интернете, так и для поиска на локальной
машине, а также будет приведен интерфейс приложения.
3.1 Разработка приложения для поиска на локальном компьютере
3.1.1 Логика поиска на локальной машине
Для поиска на локальной машине необходимо получить список документов
Excel и Word из папки, в которой будет произведен поиск, а также из всех
внутренних папок. Далее все таблицы, которые будут извлечены из найденных
файлов, будут проходить сравнение с эталонной таблицей. Порядок работы
приложения представлен на рис. 3.1.
Рисунок 3.1 Порядок работы приложения при поиске на локальной машине
Вначале приложение делает запрос к операционной системе с целью
получить список файлов в папке, которая выбрана для поиска. Операционная
система возвращает приложению ответ со списком данных файлов. Далее для
каждой таблицы, извлеченной из найденных файлов, происходит сравнение с
эталонной таблицей и в случае, если таблицы релевантны, то ссылка на данный
файл сохраняется в результатах работы.
29
3.1.2 Интерфейс
Форма для создания настроек для поиска на локальном компьютере
выглядит следующим образом(см. рис. 3.2):
Рисунок 3.2 Вид формы для поиска на локальной машине
В самом верхнем текстовом поле выбирается файл из которого будет
выгружаться таблица, которая будет играть роль эталонной таблицы. Если в файле
несколько таблиц, к примеру, в файле формата xls несколько листов с таблицами
или в документе формата doc несколько таблиц, то программа будет работать
только с самой первой, на остальные она обращать внимания не будет. Также с
помощью кнопки можно открыть выбранный файл для просмотра.
Ниже располагаются настройки для сравнения 2 таблиц на соответствии. В
первой из них находится процент соответствия заголовков, при котором мы
считаем 2 заголовка соответствующими друг другу.
Во втором поле процент
соответствия таблиц, при котором мы считаем их релевантными друг другу.
Таким образом, манипулируя этими настройками можно сделать поиск более
строгим или наоборот более простым.
При нажатии на кнопку “Выбор поисковой таблицы” откроется диалоговое
окно выбора с фильтрацией документов по типам (xls, xlsx, doc, docx), где
пользователь сможет выбрать тот файл, из которого будет извлекаться таблица(см.
рис. 3.3).
30
Рисунок 3.3 Выбор файла для поиска
При нажатии на кнопку “Выбор папки для поиска” открывается окно для
выбора папки, в которой будет произведен поиск таблиц, схожей выбранной
таблице для поиска (см. рис. 3.4). В самом низу формы расположено текстовое
поле, в котором по ходу работы программы будет выводиться информация о том,
что происходит в приложении. При нажатии кнопки “Запуск” мы запускаем
приложение в работу.
Рисунок 3.4 Выбор папки для поиска
31
На рис. 3.5 и на рис 3.6 показана главная форма приложения во время его
работы и результаты поискового запроса к программе.
Рисунок 3.5 Приложение во время работы
Рисунок 3.6 Результаты поиска
32
3.2 Разработка приложения для поиска в интернете
3.2.1 Логика поиска в интернете
Задача поиска табличных данных в интернете является более сложной
нежели на локальной машине. Порядок работы приложения представлен на рис.
3.7.
Рисунок 3.7 Порядок работы приложения при поиске на локальной машине
Для начала приложение извлекает список заголовков из таблицы, которая
выбрана в качестве поисковой. Во-первых, необходимо из программы с помощью
языка C# обращаться к поисковому работу Яндекс. На первом этапе – этапе работы
с поисковыми системами необходимо на основе введенных ключевых слов
сформировать массив ссылок на результаты, которые выдает Яндекс. По
умолчанию, он выдает 10 ссылок на самые релевантные ресурсы. В данной работе
мы будем исследовать только их. Вторая и следующие страницы поисковой выдаче
затрагиваться не будут.
Для того чтобы сделать это, воспользуемся языком C# и с помощью
стандартных средств платформы .NET возьмем код страницы и присвоим его некой
33
строковой переменной. Реализация данной функции представлена в приложении. В
Яндексе запрос обрабатывается следующим образом, например при вводе в форму
строки: ”Поиск информации в интернете”, поисковый робот обрабатывает ее и
создает следующий URL (см. рис. 3.8).
Рисунок 3.8 Строка поиска в Яндекс
В этой строке нас больше всего интересует параметр text, так как он отвечает
за поисковый запрос.
Таким образом, чтобы нам задать запрос к Яндексу с помощью приложения,
разработанного на языке C# нам нужно взять код страницы:
http://yandex.ru/?text=Поиск+информации+в+интернете
После выполнения функции извлечения кода мы в переменную строкового
типа можем положить код страницы, которая выдаст результат поискового запроса.
На данном этапе работы мы имеем строковую переменную, содержащую код
страницы выдачи поискового робота. Из этой строковой переменной с кодом
страницы нам нужно получить массив ссылок на сайты, которые выдал Яндекс в
результатах поиска.
При анализе кода страниц, которые выдает поисковая система, была
замечена закономерность (см. рис. 3.9), что ссылки на страницы в поисковой
выдаче идут в тэгах “a” с классом:” b-serp-item__title-link" .
Рисунок 3.9 Код страницы поискового робота с выделенными ссылками
34
Таким образом, используя библиотеку “HtmlAgilityPack” и язык XPath, мы
обработаем код полученный от Яндекса с помощью следующего Xpath запроса,
который вернет тэги со ссылками на ответы Яндекса: “HtmlNodeCollection c =
doc.DocumentNode.SelectNodes("//a[@class='b-serp-item__title-link']");”.
В данной строке ключевым является запрос: "//a[@class='b-serp-item__titlelink']". Он означает, что нужно из всего загруженного документа, взять все тэги
“a”(ссылки), у которых класс равен “'b-serp-item__title-link”. Таким образом,
Яндекс возвращает коллекцию узлов со ссылками.
Следующим этапом в работе поискового приложения будет обход каждой из
ссылок и поиск табличных данных на них.
В начале на каждой странице обрабатывается HTML информация.
Происходит извлечение информационных таблиц на основе алгоритма описанного
в пункте 2.3.3 “Выделение табличной информации из таблиц в документах HTML”.
Эти таблицы сравниваются с исходной и в случае, если они соответствуют
критериям релевантности, то данные таблицы выгружаются на компьютер и
сохраняются в результатах поиска в формате xls.
После анализа HTML кода на наличие информационных таблиц, приложение
проводит поиск по файлам в формате doc и xls на которые ссылается данная
страница. Для этого с помощью языка Xpath извлекаются все ссылки (тэги <a>) и
проверяются все атрибуты “href” и далее, если они ссылаются на файлы в форматах
(doc, docx, xls, xlsx), то программа скачивает их в папку с результатами. И далее
проводит анализ данных файлов на соответствие с исходной таблицей аналогично
поиску на локальной машине. Если в файле присутствуют релевантные таблицы, то
приложение оставляет его, и в список результатов добавляет ссылку на
сохраненный файл на локальной машине, при этом указывая индекс таблицы и тип
данных.
При разработке данного алгоритма появилась небольшая проблема,
связанная с тем, что для скачивания файла из интернета необходим полный путь к
файлу(http://домен/....), а ссылки в интернете не всегда хранятся в абсолютном
виде. Ссылка на ресурс может храниться в 3 вариантах:
 Абсолютном (пример : http://perm.hse.ru/).
 Относительный (пример images/1.gif).
35
 Полуотносительный (пример /images/1.gif).
Таким образом, необходимо относительные пути приводить к абсолютным.
Для этого была реализована соответствующая функция, текст которой приведён в
находится в приложении к работе.
3.2.2 Интерфейс
Форма для создания настроек для поиска таблиц в интернете выглядит
следующим образом(см. рис. 3.10):
Рисунок 3.10 Окно для поиска в интернете
В самом верхнем текстовом поле аналогично поиску на локальной машине
выбирается файл, из которого будет извлекаться таблица для поиска.
Ниже располагается текстовое поле с выбором адреса для сохранения
результатов поиска. Это поле понадобилось в связи с тем, что данные из интернета
будут скачиваться и их необходимо где-то хранить.
Текстовое поле поисковые запросы содержит запросы, по которым
приложение будет обращаться к Яндексу. Их можно добавлять и удалять с
помощью одноименных кнопок.
Далее слева находятся настройки глубины поиска данных в Яндексе и на
сайтах, которые вернул Яндекс. Глубина поиска в Яндексе это количество станиц,
36
которые будет обрабатывать система в результатах поиска (по умолчанию один,
это значит, что только первая страница поисковой выдачи).
Глубина поиска на сайте, это максимальный уровень, на который
приложение будет обрабатывать сайт(по умолчанию один, это значит, что поиск
будет вестись только на той странице, на которую была получена ссылка из
Яндекса. Остальные страницы обрабатываться не будут). Ниже располагаются
настройки для сравнения 2 таблиц на соответствие. В первой из них находится
процент соответствия заголовков, при котором мы считаем 2 заголовка имеющими
одинаковый смысл. Во втором поле процент соответствия таблиц, при котором мы
считаем их равными.
Таким образом, манипулируя этими настройками можно сделать поиск более
строгим или наоборот более простым. В самом низу формы расположено текстовое
поле, в котором по ходу работы программы будет выводиться информация о том,
что происходит в приложении.
Также, для удобства работы с программой, работа приложения вынесена в
отдельный
поток,
что
позволяет
пользователю
просматривать
результаты во время поиска.
Внешний вид приложения во время работы показан на рис. 3.11.
Рисунок 3.11 Внешний вид программа во время поиска
37
найденные
Заключение
В ходе преддипломной практики было разработано приложение для поиска
на локальной машине и в интернете. Также разработаны алгоритмы извлечения
необходимой табличной информации из документов WORD, EXCEL, HTML.
Разработан алгоритм для сравнения 2 таблиц на соответствие друг другу. Таким
образом, получилось готовое приложение, которое ведет поиск структурированной
информации в сети интернет и на локальной машине.
В дальнейшем планируется сделать поиск в интернете более широким,
чтобы данная система искала не только на 1 странице сайта, которую она получила
из поисковой выдачи Яндекса, а по всему сайту. Также планируется расширить
список критериев для сопоставления двух таблиц на релевантность.
38
Библиографический список
1. Парсинг (Parsing) // Словарь WestSEO [Электронный ресурс] [Режим
доступа: http://westseo.ru/article/parsing] [Проверено: 15.04.2014].
2. Релевантный // Словари и энциклопедии на Академике [Электронный
ресурс] [Режим доступа: http://dic.academic.ru/dic.nsf/maruso/релевантный]
[Проверено: 16.04.2014].
3. Лемматизация - что это? // SEO блог: поисковые системы | создание,
оптимизация и продвижение сайтов [Электронный ресурс] [Режим доступа:
http://searchenginez.ru/lemmatizaciya-chto-eto/] [Проверено: 17.04.2014].
4. Поисковые запросы. Виды поисковых запросов. // Как создать свой сайт
самому
[Электронный
ресурс]
[Режим
доступа:
http://sites-
builder.ru/view_post.php?id=191] [Проверено: 16.04.2014].
5. html agility pack | Дмитpий Hecтepук // Дмитpий Hecтepук [Электронный
ресурс] [Режим доступа: http://nesteruk.wordpress.com/tag/html-agility-pack/]
[Проверено: 16.04.2014].
6. Язык
запросов
[Электронный
XPath
ресурс]
//
Школа
[Режим
программирования
доступа:
Coding
Craft
http://codingcraft.ru/xpath.php]
[Проверено: 16.04.2014].
7. Стемминг
продвижения
//
сайтов
Система
[Электронный
автоматизированного
ресурс]
[Режим
доступа:
http://wiki.rookee.ru/Stemming/] [Проверено: 17.04.2014].
8. Iveonik Systems|ALL #INCLUDE // Стеммеры Snowball на C# – free download
[Электронный
ресурс]
[Режим
доступа:
www.iveonik.com/blog/2011/08/stemmery-snowball-na-csharp-free-download/]
[Проверено: 17.04.2014].
9. WorksheetBase.UsedRange
-
свойство
(Microsoft.Office.Tools.Excel)//
Microsoft Developer network [Электронный ресурс] [Режим доступа:
http://msdn.microsoft.com/ruru/library/microsoft.office.tools.excel.worksheetbase.usedrange.aspx]
[Проверено: 17.04.2014].
39
10. Практическое руководство. Программное таблиц в Word// Microsoft
Developer
network
[Электронный
ресурс]
http://msdn.microsoft.com/ru-ru/library/w1702h4a.aspx]
17.04.2014].
40
[Режим
доступа:
[Проверено:
Приложение А. Результаты работы программы
Для поиска на локальной машине
Возьмем для примера таблицу, представленную на рис. A.1.
Рисунок А.1 Исходная таблица для поиска на локальной машине
Результаты, которые вернула программа, а именно скриншот вкладки
результатов найденных таблиц и сами таблицы(см. рис. А.2 – А.11).
41
Рисунок А.2 Результат работы приложения в программе
Найденные таблицы:
Рисунок А.3 Результат работы №1
Рисунок А.4 Результат работы №2
Рисунок А.5 Результат работы №3
42
Рисунок А.6 Результат работы №4
Рисунок А.7 Результат работы №5
43
Рисунок А.8 Результат работы №6
Рисунок А.9 Результат работы №7
44
Рисунок А.10 Результат работы №8
Рисунок А.11 Результат работы №9
45
Результаты работы программы для поиска в интернете
Отличным примером для поиска в интернете будет расписание НИУ ВШЭ Пермь (см. рис. А.12).
Рисунок А.12 Исходная таблица для поиска в интернете
Результаты работы приложения(см. рис. А.13):
Рисунок А.13 Результат работы приложения в программе
Поиск в результате вернул 7 файлов в формате xls. Все эти файлы - это
файлы расписаний расположенных на сайте НИУ ВШЭ - Пермь для бакалавров(см.
рис. А.14).
46
Рисунок А.14 Страничка с расписанием в НИУ ВШЭ - Пермь
47
Приложение Б. Руководство пользователя
При запуске
приложения открывается главная форма, состоящая из 3
вкладок (см. рис. Б.1). В качестве начальной будет открыта вкладка “для
компьютера”, также пользователь может переключиться на вкладку “Для
интернета” или на вкладку “Результаты”.
Рассмотрим поиск на локальном компьютере.
Рисунок Б.1 Вкладка для поиска на локальном компьютере
При нажатии на кнопку “Выбор поисковой таблицы” откроется окно выбора
файла, из которого будет извлекаться таблица, которая будет поисковой в данном
запросе (см. рис. Б.2).
48
Рисунок Б.2 Выбор файла для извлечения поисковой таблицы
При помощи кнопки “Открыть файл” можно открыть выбранный файл (см.
рис. Б.3 и рис. Б.4).
Рисунок Б.3 Вкладка для поиска на локальном компьютере с выбранным файлом с поисковой
таблицей
49
Рисунок Б.4 Выбранная таблица для поиска в Excel
Далее необходимо выбрать папку, в которой будет производится поиск
таблиц. Это можно сделать нажав на кнопку “Выбор папки для поиска”. Откроется
окно, показанное на рис. Б.5.
50
Рисунок Б.5 Окно для выбора папки для поиска
После выбора папки, в которой будет вестись поиск необходимо сделать настроить
критерии поиска (см. рис. Б.6).
Рисунок Б.6 Критерии для поиска
Перед запуском приложения необходимо настроить два критерия “Критерий
для сравнения заголовков” и “Критерий для сравнивания таблиц”. Эти критерии
означает, на какой процент заголовки в найденных таблицах и сами таблицы
должны соответствовать эталонной таблице. Чем больше величина их значений,
тем жестче будет фильтр при поиске. Диапазон значений у данных критериев от 10
до 100.
51
После настройки критериев необходимо нажать копку запуск и приложение
приступит к поиску данных. Во время работы приложения в программе ведется лог
поиска и на вкладку “Результаты” добавляются ссылки на найденные результаты
(см. рис. Б.7 и рис. Б.8).
Рисунок Б.7 Приложение во время поиска на локальном компьютере
Рисунок Б.8 Вкладка “Результаты” во время поиска на локальном компьютере
На вкладке результаты хранятся не только ссылки на найденные файлы, а и
указывается тип найденных файлов и номер таблицы в файле, которая
52
соответствует эталонной таблице. При клике на каждую ссылку открывается
соответствующий файл.
Рассмотрим поиск табличных данных в интернете.
Для запуска поиска данных в Сети интернет необходимо перейти на вкладку
“Для интернета” (см. рис. Б.9). Здесь есть несколько настроек аналогичных поиску
на локальном компьютере, но также есть множество существенных различий.
Рисунок Б.9 Вкладка “Для интернета”
При нажатии на кнопку “Выбор поисковой таблицы” откроется окно выбора
файла, из которого будет извлекаться таблица, которая будет поисковой в данном
запросе (см. рис. Б.10).
53
Рисунок Б.10 Выбор файла для извлечения поисковой таблицы
Далее необходимо выбрать папку, в которую будут сохраняться найденные в
интернете результаты (см. рис. Б.11).
Рисунок Б.11 Окно для выбора папки для сохранения файлов
Далее необходимо заполнить текстовое поле “Поисковые запросы”
запросами, на основе которых будут вестись поиск данных. Эти запросы будут
передаваться в Яндекс и на страницах из результатов поиска будет вестись поиск
54
данных. Для того, чтобы добавить один запрос в поле “Поисковые запросы”
необходимо ввести его в поле “Введите запрос” и нажать кнопку добавить (см. рис.
Б.12 и рис. Б.13).
Рисунок Б.12 Поле “Введите запрос”
Рисунок Б.13 Заполненное поле “Поисковые запросы”
55
Далее необходимо заполнить критерии для поиска. В случае поиска в
интернете их 4. ”Критерий для сравнения заголовков” и “Критерий для
сравнивания таблиц”
работает также,
как и в поиске на локальной машине.
Критерий “Глубина поиска в Яндекс” означает сколько страниц в поисковой
выдаче Яндекса будет обрабатываться приложением, а критерий “Глубина поиска
на сайте” означает сколько страниц будет просматривать приложение, начиная от
той, на которую он вошел на сайт.
После настройки критериев необходимо нажать копку запуск и приложение
приступит к поиску данных. Во время работы приложения в программе ведется лог
поиска и на вкладку “Результаты” добавляются ссылки на найденные результаты
(см. рис. Б.14).
Рисунок Б.14 Приложение во время поиска в интернете
Во время поиска в интернете можно время от времени переключаться на
вкладку “Результаты” и просматривать уже найденные таблицы (см. рис. Б.15).
56
Рисунок Б.15 Результаты поиска приложения в интернете
57
Приложение В. Исходный код приложения
using
using
using
using
using
using
using
using
using
using
using
using
using
using
using
using
using
System;
System.Collections.Generic;
System.ComponentModel;
System.Data;
System.Drawing;
System.Linq;
System.Text;
System.Windows.Forms;
Excel = Microsoft.Office.Interop.Excel;
Iveonik.Stemmers;
System.IO;
Word = Microsoft.Office.Interop.Word;
System.Net;
HtmlAgilityPack;
System.Runtime.InteropServices;
System.Diagnostics;
System.Threading;
//Добавлена EXCEL заглушка
namespace searcherDipl
{
public partial class main : Form
{
[DllImport("user32")]
static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
RussianStemmer RS = new RussianStemmer();
List<List<List<string>>> example = new List<List<List<string>>>();
decimal percentage_of_relevancyHeaders = 50;
decimal percentage_of_relevancyTables = 50;
HttpWebResponse XhttpWebResponse;
public string save;
Thread Local;
Thread search;
public string getreq(string url)
{
HttpWebRequest req;
HttpWebResponse resp;
StreamReader sr;
string content;
req = (HttpWebRequest)WebRequest.Create(url);
resp = (HttpWebResponse)req.GetResponse();
sr = new StreamReader(resp.GetResponseStream(), Encoding.GetEncoding("windows1251"));
content = sr.ReadToEnd();
sr.Close();
return content;
}
public string getRequest(string url)
{
try
{
var httpWebRequest = (HttpWebRequest)WebRequest.Create(url);
58
httpWebRequest.AllowAutoRedirect = false;//Запрещаем автоматический редирект
httpWebRequest.Method = "GET"; //Можно не указывать, по умолчанию используется GET.
httpWebRequest.Referer = "http://google.com"; // Реферер. Тут можно указать любой URL
var httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse();
XhttpWebResponse = httpWebResponse;
using (var stream = httpWebResponse.GetResponseStream())
{
using (var reader = new StreamReader(stream,
Encoding.GetEncoding(httpWebResponse.CharacterSet)))
{
return reader.ReadToEnd();
}
}
}
catch
{
return String.Empty;
}
}
public main()
{
InitializeComponent();
}
class xListViewItem : ListViewItem
{
public string numList;
public string type;
public xListViewItem(string text, string NumList, string Type)
: base(text)
{
numList = NumList;
type = Type;
}
}
class ResultList
{
public string name;
public int list;
public string type;
public ResultList(string Name, int List, string Type)
{
name = Name;
list = List;
type = Type;
}
}
private void InitializeListView()
{
results.View = View.Details;
// Allow the user to edit item text.
results.LabelEdit = true;
// Allow the user to rearrange columns.
results.AllowColumnReorder = true;
// Select the item and subitems when selection is made.
results.FullRowSelect = true;
// Display grid lines.
results.GridLines = true;
59
// Sort the items in the list in ascending order.
results.Sorting = System.Windows.Forms.SortOrder.Ascending;
results.Columns.Add("Ссылка", 400, HorizontalAlignment.Left);
results.Columns.Add("№ листа", 50, HorizontalAlignment.Left);
results.Columns.Add("Тип", 50, HorizontalAlignment.Left);
}
private void choosesearchTable_Click(object sender, EventArgs e)
{
OpenFileDialog file = new OpenFileDialog();
file.Multiselect = false;
file.Filter = "Document files (*.xls, *.doc)|*.xls;*.xlsx;*.doc;*.docx";
if (file.ShowDialog() == DialogResult.OK)
exampleFolder.Text = file.FileName;
}
#region parseExcel
string Checklist(ref Excel.Worksheet oSheet, ref string[] provcells)
{
for (int j = 0; j < provcells.GetLength(0); j++)
{
if (oSheet.get_Range(provcells[j], provcells[j]).Text != "")
{
return provcells[j];
}
}
return "";
}
List<List<string>> Getheaders(string diapazone, ref Excel.Worksheet oSheet)
{
List<List<string>> lst = new List<List<string>>();
int limleft = 64;
string[] mas = diapazone.Split('|');
string[] TopLeft = mas[0].Split(';');
string[] BottomRight = mas[1].Split(';');
int StartRow = int.Parse(TopLeft[1]);
int StartCol = int.Parse(TopLeft[0]);
int EndRow = int.Parse(BottomRight[1]);
int EndCol = int.Parse(BottomRight[0]);
bool ok = true;
int num = 0;
while (ok && num<3)
{
lst.Add(new List<string>());
int count = 0;
for (int i = StartCol; i <= EndCol; i++)
{
string val = oSheet.Cells[StartRow, i].Text;
if (oSheet.Cells[StartRow, i].Text != "")
{
lst[num].Add(oSheet.Cells[StartRow, i].Text);
count++;
}
if (count >= EndCol - StartCol)
{
ok = false;
}
}
StartRow++;
60
num++;
}
return lst;
}
private int getCodeString(string str)
{
int summ = 0;
for (int i = 0; i < str.Length; i++)
{
summ += power(26, str.Length - i - 1) * ((int)str[i] - 64);
}
return summ;
}
private string GetLitera(string str)
{
string numbers = "0123456789";
string res = "";
for (int i = 0; i < str.Length; i++)
{
if (numbers.IndexOf(str[i]) == -1)
{
res += str[i];
}
}
return res;
}
private string GetNumb(string str)
{
string numbers = "0123456789";
string res = "";
for (int i = 0; i < str.Length; i++)
{
if (numbers.IndexOf(str[i]) != -1)
{
res += str[i];
}
}
return res;
}
private int power(int chisl, int pow)
{
int res = 1;
if (pow == 0) return 1;
for (int i = 0; i < pow; i++)
{
res *= chisl;
}
return res;
}
string GetTableDiapazone(ref Excel.Worksheet oSheet, string cell)
{
string diapazone="";
int emptyCells = 0;
int LastnotemptyLiteral=-1;
int LastnotemptyNumber=-1;
int limleft = 64;
string lit = GetLitera(cell);
int StartColIndex = lit[0];
61
StartColIndex -= limleft;
int StartRowIndex=int.Parse(GetNumb(cell));
//left
int row = StartRowIndex;
int col = StartColIndex;
while (col > 0 && emptyCells < 1)
{
if (oSheet.Cells[row, col].Text == "")
{
break;
}
col--;
}
if (col == 0) col++;
diapazone += col.ToString() + ';';
row = StartRowIndex;
col = StartColIndex;
emptyCells=0;
while (row > 0 && emptyCells < 1)
{
if (oSheet.Cells[row, col].Text == "")
{
break;
}
row--;
}
if (row == 0) row++;
diapazone += row.ToString() + "|";
//right
row = StartRowIndex;
col = StartColIndex;
emptyCells = 0;
while (emptyCells<1)
{
if (oSheet.Cells[row, col].Text == "")
{
break;
}
col++;
}
diapazone += col.ToString() + ';';
//down
row = StartRowIndex;
col = StartColIndex;
emptyCells = 0;
while (emptyCells < 1)
{
if (oSheet.Cells[row, col].Text == "")
{
break;
}
row++;
}
diapazone += row.ToString();
return diapazone;
}
private string GetLiters(string curcell)
{
string numbers = "0123456789";
string value = "";
62
for (int i = 0; i < curcell.Length; i++)
{
if (numbers.IndexOf(curcell[i]) == -1)
{
value += curcell[i];
}
else
{
break;
}
}
return value;
}
private string GetExcelCode(int symbolcode)
{
if (symbolcode <= 90)
{
return ((char)symbolcode).ToString();
}
else
{
symbolcode -= 64;
int iteration = 0;
while (symbolcode >= 26)
{
symbolcode -= 26;
iteration++;
}
if (iteration > 26) iteration = 26;
return ((char)(iteration + 64)).ToString() + ((char)(symbolcode + 64)).ToString();
}
}
List<List<string>> GetHeadersXLS(string path, int numList, ref bool ok)
{
object _missingObj = System.Reflection.Missing.Value;
Excel.Application excelapp = null;
Excel.Workbook excelappworkbook = null;
uint processId=0;
try
{
int r = 'A';
excelapp = new Excel.Application();
GetWindowThreadProcessId((IntPtr)excelapp.Hwnd, out processId);
excelapp.Visible = false;
//
Excel.Workbooks excelappworkbooks = excelapp.Workbooks;
//Открываем книгу и получаем на нее ссылку
excelappworkbook = excelapp.Workbooks.Open(path,
Type.Missing, true, Type.Missing, Type.Missing,
Type.Missing, true, Type.Missing, Type.Missing,
Type.Missing, false, Type.Missing, Type.Missing,
Type.Missing, Type.Missing);
}
catch
{
ok = false;
if (excelappworkbook!=null)excelappworkbook.Close();
excelapp.Quit();
try
{
Process.GetProcessById((int)processId).Kill();
}
63
catch{ }
}
if (excelappworkbook != null)
{
Excel.Sheets excelsheets = excelappworkbook.Worksheets;
int listscount = excelappworkbook.Worksheets.Count;
Excel.Range oRange;
Excel.Worksheet oSheet = null; // страница книги
string[] provcells = cells.Split(';');
List<List<string>> headers = new List<List<string>>();
string diapazone = "";
string NotEmptyCell = "";
if (numList <= listscount)
{
ok = true;
int i = numList;
oSheet = excelappworkbook.Worksheets[i];
oRange = oSheet.UsedRange;
if (oRange.Columns.Count <= 1 && oRange.Rows.Count <= 1) return null;
NotEmptyCell = Checklist(ref oSheet, ref provcells);
if (NotEmptyCell != "")
{
headers = Getheaders(GetTableDiapazone(ref oSheet, NotEmptyCell), ref oSheet);
}
excelappworkbook.Close();
excelapp.Quit();
try
{
Process.GetProcessById((int)processId).Kill();
}
catch (Exception ex) { }
if (headers.Count == 0) return null;
else return headers;
}
else ok = false;
excelappworkbook.Close();
excelapp.Quit();
try
{
Process.GetProcessById((int)processId).Kill();
}
catch (Exception ex) { }
}
return null;
}
#endregion
#region word
private List<List<string>> GetHeadersDoc(string path, int num, ref bool ok)
{
Word.Application wordapp = new Word.Application();
wordapp.Visible = false;
Object filename = path;
Object confirmConversions = false;
Object readOnly = true;
Object addToRecentFiles = false;
Object passwordDocument = Type.Missing;
Object passwordTemplate = Type.Missing;
Object revert = false;
Object writePasswordDocument = Type.Missing;
Object writePasswordTemplate = Type.Missing;
Object format = Type.Missing;
Object encoding = Type.Missing;
64
Object oVisible = Type.Missing;
Object openConflictDocument = Type.Missing;
Object openAndRepair = Type.Missing;
Object documentDirection = Type.Missing;
Object noEncodingDialog = true;
Object xmlTransform = Type.Missing;
Word.Document worddocument = null;
try
{
worddocument = wordapp.Documents.Open(ref filename,
ref confirmConversions, ref readOnly, ref addToRecentFiles,
ref passwordDocument, ref passwordTemplate, ref revert,
ref writePasswordDocument, ref writePasswordTemplate,
ref format, ref encoding, ref oVisible,
ref openAndRepair, ref documentDirection, ref noEncodingDialog, ref xmlTransform);
}
catch
{
ok = false;
wordapp.Quit();
return null;
}
int tableCount=worddocument.Tables.Count;
List<List<string>> res = new List<List<string>>();
if (num <= tableCount)
{
int row=1;
ok=true;
Word.Table tbl = worddocument.Tables[num];
while (ok && row < 4)
{
res.Add(new List<string>());
int count = 0;
for (int i = 1; i <= worddocument.Tables[num].Columns.Count; i++)
{
try
{
string val = worddocument.Tables[num].Cell(row, i).Range.Text;
if (val != "")
{
res[row-1].Add(val);
count++;
}
}
catch { }
if (count >= worddocument.Tables[num].Rows.Count - 2)
{
ok = false;
}
}
row++;
}
worddocument.Close();
wordapp.Quit();
if (res.Count > 0)
{
return res;
}
else return null;}
else
{
ok = false;
worddocument.Close();
wordapp.Quit();
65
return null;
}
}
#endregion
#region ComparingTwoTables
private bool Compare(List<List<List<string>>> tbl1, List<List<List<string>>> tbl2)
{
if (tbl1.Count == 0 || tbl2.Count == 0) return false;
foreach (List<List<string>> lst1 in tbl1)
{
foreach (List<List<string>> lst2 in tbl2)
{
if (lst1.Count == 0 || lst2.Count == 0)
{
continue;
}
if (lst1.Count < lst2.Count)
{
if (compareTables(lst1, lst2))
{
return true;
}
}
else
{
if (compareTables(lst2, lst1))
{
return true;
}
}
}
}
return false;
}
private bool compareTables(List<List<string>> minTbl, List<List<string>> maxTbl)
{
int countRelevant = 0;
foreach (List<string> lstMin in minTbl)
{
foreach (List<string> lstMax in maxTbl)
//Можно добавить глубину поиска
{
if (lstMin.Count <= lstMax.Count)
{
if (CompareHeaders(lstMin, lstMax))
{
countRelevant++;
break;
}
}
else
{
if (CompareHeaders(lstMax,lstMin))
{
countRelevant++;
break;
}
}
}
}
66
decimal dblCountRelevant = decimal.Parse(countRelevant.ToString());
if (minTbl.Count > 0)
{
decimal value = dblCountRelevant / minTbl.Count;
if (value * 100 >= percentage_of_relevancyTables) return true;
}
return false;
}
List<List<string>> ToLowerCase(List<List<string>> lst)
{
for (int j = 0; j < lst.Count; j++)
{
for (int i = 0; i < lst[j].Count; i++)
{
lst[j][i] = lst[j][i].ToLower();
}
}
return lst;
}
private bool CompareHeaders(List<string> lstMin, List<string> lstMax)
{
int countrelevant=0;
foreach (string str1 in lstMin)
{
foreach (string str2 in lstMax)
{
if (CompareWords(str1, str2))
{
countrelevant++;
break;
}
}
}
decimal value = decimal.Parse(countrelevant.ToString());
if (lstMin.Count > 0)
if ((value / lstMin.Count)*100 >= percentage_of_relevancyHeaders) return true;
return false;
}
private bool CompareWords(string str1, string str2)
{
str1 = RS.Stem(str1);
str2 = RS.Stem(str2);
if (str1 == str2) return true;
else return false;
}
List<List<List<string>>> DivHeadersonWords(List<List<string>> list)
{
List<List<List<string>>> res = new List<List<List<string>>>();
for (int j = 0; j < list.Count; j++)
{
res.Add(new List<List<string>>());
for (int i = 0; i < list[j].Count; i++)
{
if (list[j][i].Length != 0)
{
List<string> lst1 = AddtoListFromMas(list[j][i].Split(' '));
if (lst1.Count > 0)
res[j].Add(lst1);
}
67
}
}
return res;
}
List<string> AddtoListFromMas(string[] mas)//Довести до ума.
{
List<string> lst = new List<string>();
foreach (string elem in mas)
{
if (elem.Length>2)
lst.Add(elem);
}
return lst;
}
List<List<string>> ClearHeaders(List<List<string>> list)
{
for (int j = 0; j < list.Count; j++)
{
for (int i = 0; i < list[j].Count; i++)
{
list[j][i] = GetClearSentense(list[j][i]);
}
}
return list;
}
private string GetClearSentense(string str)
{
string znaki = "абвгдеёжзийклмнопрстуфхцчшщъыьэюяabcdefghijklmnopqrstuvwxyz0123456789";
string changeableZnaki = ")(,.:!?";
int probels = 0;
string result = "";
foreach (char s in str)
{
if (znaki.IndexOf(s) != -1)
{
result += s;
probels=0;
}
if (s == ' ' && probels == 0)
{
probels = 1;
result += s;
}
if (changeableZnaki.IndexOf(s) != -1 && probels == 0)
{
probels = 1;
result += ' ';
}
}
return result.Trim();
}
#endregion
private string GetFormat(string res)
{
if (res.LastIndexOf('.') != -1 && res.LastIndexOf('.') > res.LastIndexOf('/'))
{
return res.Substring(res.LastIndexOf('.'), res.Length - res.LastIndexOf('.'));
}
68
else return "";
}
private void button6_Click(object sender, EventArgs e)
{
if (exampleFolder.Text == "")
{
MessageBox.Show("Не выбран пример для поиска");
return;
}
if (computerSearch.Text == "")
{
MessageBox.Show("Не введена папка для поиска");
}
percentage_of_relevancyHeaders = nudHeaders.Value;
percentage_of_relevancyTables = nudTables.Value;
Local = new Thread(SeekOnLocal);
Local.Start();
}
private void SeekOnLocal()
{
string ExampleFolder = "";
exampleFolder.Invoke((Action)(() =>
{
ExampleFolder = exampleFolder.Text;
}));
bool k = true;
List<List<string>> trpHeaders = new List<List<string>>();
if (GetFormat(ExampleFolder).IndexOf("xls") != -1)
trpHeaders = GetHeadersXLS(ExampleFolder, 1, ref k);
else if (GetFormat(ExampleFolder).IndexOf("doc") != -1)
trpHeaders = GetHeadersDoc(ExampleFolder, 1, ref k);
if (trpHeaders == null)
{
MessageBox.Show("Некорректный файл для работы. Скорее всего он пустой");
return;
}
trpHeaders = ToLowerCase(trpHeaders);
trpHeaders = ClearHeaders(trpHeaders);
example = DivHeadersonWords(trpHeaders);
string[] filteredFiles = Directory
.GetFiles(computerSearch.Text, "*.*", SearchOption.AllDirectories)
.Where(file => file.ToLower().EndsWith("xls") || file.ToLower().EndsWith("xlsx") ||
file.ToLower().EndsWith("doc") || file.ToLower().EndsWith("docx"))
.ToArray();
List<ResultList> lst = new List<ResultList>();
foreach (string res in filteredFiles)
{
rtbLogLocal.Invoke((Action)(() =>
{
rtbLogLocal.Text += "Рассматривается файл : " + res+"\n";
}));
bool ok = true;
int NumList = 1;
if (res == exampleFolder.Text) continue;
while (ok)
{
List<List<string>> trpHeaders2 = new List<List<string>>();
if (GetFormat(res).IndexOf("xls") != -1)
trpHeaders2 = GetHeadersXLS(res, NumList, ref ok);
69
else if (GetFormat(res).IndexOf("doc") != -1)
trpHeaders2 = GetHeadersDoc(res, NumList, ref ok);
NumList++;
if (trpHeaders2 == null) continue;
if (ok)
{
trpHeaders2 = ToLowerCase(trpHeaders2);
trpHeaders2 = ClearHeaders(trpHeaders2);
List<List<List<string>>> found = DivHeadersonWords(trpHeaders2);
if (Compare(example, found))
{
lst.Add(new ResultList(res, NumList - 1, GetFormat(res)));
rtbLogLocal.Invoke((Action)(() =>
{
rtbLogLocal.Text += "Файл : " + res+ " сохранен\n";
}));
}
}
}
}
results.Items.Clear();
foreach (ResultList val in lst)
{
xListViewItem elem = new xListViewItem(val.name, val.list.ToString(), val.type);
elem.SubItems.Add(val.list.ToString());
elem.SubItems.Add(val.type);
results.Invoke((Action)(() =>
{
results.Items.Add(elem);
}));
}
if (lst.Count == 0)
{
MessageBox.Show("Совпадений для данной таблицы не найдено");
}
CloseThread();
}
private void CloseThread()
{
Local.Abort();
Local.Join(500);
}
private void main_Shown(object sender, EventArgs e)
{
InitializeListView();
}
private void button5_Click(object sender, EventArgs e)
{
FolderBrowserDialog fold = new FolderBrowserDialog();
if (fold.ShowDialog() == DialogResult.OK)
computerSearch.Text = fold.SelectedPath;
}
private void fileopen_Click(object sender, EventArgs e)
{
if (exampleFolder.Text == "")
{
MessageBox.Show("Не указан файл");
return;
70
}
if (GetFormat(exampleFolder.Text).IndexOf("xls") != -1)
OpenXlS(exampleFolder.Text);
else if (GetFormat(exampleFolder.Text).IndexOf("doc") != -1)
OpenDOC(exampleFolder.Text);
}
private void OpenDOC(string path)
{
Word.Application wordapp = new Word.Application();
wordapp.Visible = true;
Object filename = path;
Object confirmConversions = false;
Object readOnly = true;
Object addToRecentFiles = false;
Object passwordDocument = Type.Missing;
Object passwordTemplate = Type.Missing;
Object revert = false;
Object writePasswordDocument = Type.Missing;
Object writePasswordTemplate = Type.Missing;
Object format = Type.Missing;
Object encoding = Type.Missing;
Object oVisible = Type.Missing;
Object openConflictDocument = false;
Object openAndRepair = false;
Object documentDirection = Type.Missing;
Object noEncodingDialog = true;
Object xmlTransform = Type.Missing;
Word.Document worddocument = wordapp.Documents.Open(ref filename,
ref confirmConversions, ref readOnly, ref addToRecentFiles,
ref passwordDocument, ref passwordTemplate, ref revert,
ref writePasswordDocument, ref writePasswordTemplate,
ref format, ref encoding, ref oVisible,
ref openAndRepair, ref documentDirection, ref noEncodingDialog, ref xmlTransform);
worddocument.Activate();
}
private void OpenXlS(string path)
{
Excel.Application excelapp = new Excel.Application();
excelapp.Visible = true;
Excel.Workbooks excelappworkbooks = excelapp.Workbooks;
//Открываем книгу и получаем на нее ссылку
Excel.Workbook excelappworkbook = excelapp.Workbooks.Open(path,
Type.Missing, Type.Missing, Type.Missing, Type.Missing,
Type.Missing, Type.Missing, Type.Missing, Type.Missing,
Type.Missing, Type.Missing, Type.Missing, Type.Missing,
Type.Missing, Type.Missing);
excelappworkbook.Activate();
}
#region internet
//Определять ссылку
private string downloadFile(string path,int count,string url)
{
// string link = path;
WebClient webClient = new WebClient();
string format = GetFormat(path);
string name = save + "\\file" + count.ToString() + format;
if (File.Exists(name))
{
File.Delete(name);
71
}
if (path.IndexOf("http") != 0)
{
string domain = GetDomain(url);
if (path.IndexOf("/") == 0)
{
path = domain + path;
}
else
{
string link = url.Remove(url.LastIndexOf('/'));
path = url + path;
}
}
try
{
webClient.DownloadFileAsync(new Uri(path), name); //название и можно путь
провести, если папки не существует то ее надо создать заранее
return name;
}
catch
{
return null;
}
}
private string GetAbsoluteLink(string path, string url)
{
string format = GetFormat(path);
if (format.IndexOf("html") != -1 || format.IndexOf("php") != -1 ||
format.IndexOf("asp") != -1 || format == "")
{
if (path.IndexOf("http") != 0)
{
string val = GetDomaionOfHref(path);
string domain = GetDomain(url);
if (val != "" && val != domain)
{
return "";
}
if (path.IndexOf("/") == 0)
{
path = domain + path;
}
else
{
string link = url.Remove(url.LastIndexOf('/'));
path = url + path;
}
}
}
else return "";
return path;
}
private string GetDomaionOfHref(string href)
{
if (href.IndexOf("/")==0 || href.IndexOf('.')==-1 || href.IndexOf('/')==-1)
{
return "";
}
if (href.IndexOf("http")!=0 && href.IndexOf('.')>href.IndexOf('/'))
{
return "";
72
}
string result=GetDomain(href);
return result;
}
private string GetDomain(string url)
{
if (url.IndexOf("//") != -1)
{
string val = url.Remove(0, url.IndexOf("//") + 2);
return "http://" + val.Substring(0, val.IndexOf('/'));
}
else
{
return "http://" + url.Substring(0, url.IndexOf('/'));
}
}
private void button9_Click(object sender, EventArgs e)
{
if (tbQuerys.Text == "")
{
MessageBox.Show("Неверно задан запрос");
return;
}
lbQuerys.Items.Add(tbQuerys.Text);
}
private void button8_Click(object sender, EventArgs e)
{
OpenFileDialog file = new OpenFileDialog();
file.Multiselect = false;
file.Filter = "Document files (*.xls, *.doc)|*.xls;*.xlsx;*.doc;*.docx";
if (file.ShowDialog() == DialogResult.OK)
eFileSearch.Text = file.FileName;
}
private void button4_Click(object sender, EventArgs e)
{
FolderBrowserDialog fold = new FolderBrowserDialog();
if (fold.ShowDialog() == DialogResult.OK)
eSaveFolder.Text = fold.SelectedPath;
}
private void button3_Click(object sender, EventArgs e)
{
if (eFileSearch.Text == "")
{
MessageBox.Show("Не указан файл");
return;
}
if (GetFormat(eFileSearch.Text).IndexOf("xls") != -1)
OpenXlS(eFileSearch.Text);
else if (GetFormat(eFileSearch.Text).IndexOf("doc") != -1)
OpenDOC(eFileSearch.Text);
}
private void btnDelQuery_Click(object sender, EventArgs e)
{
if (lbQuerys.SelectedItem == null)
{
MessageBox.Show("Не выбран элемент для удаления");
return;
73
}
lbQuerys.Items.Remove(lbQuerys.SelectedItem);
}
private void button7_Click(object sender, EventArgs e)
{
percentage_of_relevancyHeaders = numericUpDown2.Value;
percentage_of_relevancyTables = numericUpDown1.Value;
results.Items.Clear();
if (eFileSearch.Text == "")
{
MessageBox.Show("Не выбран пример для поиска");
return;
}
search = new Thread(SearchInInternet);
search.Start();
}
private void SearchInInternet()
{
int count = 0;
string SaveFolder = "";
eSaveFolder.Invoke((Action)(() =>
{
SaveFolder = eSaveFolder.Text;
}));
string FileSearch = "";
eFileSearch.Invoke((Action)(() =>
{
FileSearch = eFileSearch.Text;
}));
bool k = true;
List<List<string>> trpHeaders = new List<List<string>>();
if (GetFormat(FileSearch).IndexOf("xls") != -1)
trpHeaders = GetHeadersXLS(FileSearch, 1, ref k);
else if (GetFormat(FileSearch).IndexOf("doc") != -1)
trpHeaders = GetHeadersDoc(FileSearch, 1, ref k);
if (trpHeaders == null)
{
MessageBox.Show("Некорректный файл для работы. Скорее всего он пустой");
return;
}
trpHeaders = ToLowerCase(trpHeaders);
trpHeaders = ClearHeaders(trpHeaders);
example = DivHeadersonWords(trpHeaders);
save = SaveFolder + "\\" + DateTime.Now.ToLongDateString() +
DateTime.Now.ToLongTimeString().Replace(":", "-");
System.IO.Directory.CreateDirectory(save);
List<string> queries = new List<string>();
lbQuerys.Invoke((Action)(() =>
{
foreach (string elem in lbQuerys.Items)
{
queries.Add(elem);
}
}));
decimal YandexDeep = 0;
DeepInYandex.Invoke((Action)(() =>
{
YandexDeep = DeepInYandex.Value;
}));
decimal DeepSite = 0;
DeepinSite.Invoke((Action)(() =>
{
74
DeepSite = DeepinSite.Value;
}));
int LinkNumber = 0;
foreach (string query in queries)
{
for (int i = 0; i < YandexDeep; i++)
{
string linkYandex = "http://yandex.ru/yandsearch?text=" + query;
if (i > 0)
{
linkYandex = "http://yandex.ru/yandsearch?text=" + query + "&p=" +
i.ToString();
}
string code = getRequest(linkYandex);
HtmlAgilityPack.HtmlDocument doc = new HtmlAgilityPack.HtmlDocument();
doc.LoadHtml(code);
// string[] lines = new string[15];
HtmlNodeCollection linksOnPages =
doc.DocumentNode.SelectNodes("//a[@class='b-link serp-url__link']");
foreach (HtmlNode linkOnSite in linksOnPages2)
{
if (linkOnSite.Attributes["href"] != null)
{
string linkHref = linkOnSite.Attributes["href"].Value;
if (GetVisited(visited, linkHref) || linkHref.IndexOf("#") != -1) continue;
linkHref = GetAbsoluteLink(linkHref, u);
if (linkHref != "")
{
string CodeOfPage = "";
try
{
rbLog.Invoke((Action)(() =>
{
rbLog.Text = "Рассматривается ссылка : " + u + "\n";
rbLog.Text += "Рассматривается ПодСсылка : " + linkHref +
"\n";
}));
CodeOfPage = getRequest(linkHref);
}
catch
{
continue;
}
visited.Add(linkHref);
CheckUrl(CodeOfPage, ref count, linkHref);
count++;
}
}
}
}
}
}
}
}
}
MessageBox.Show("Закончено");
CloseThread1();
}
75
private void CloseThread1()
{
search.Abort();
search.Join(500);
}
private bool GetVisited(List<string> lst, string link)
{
foreach (string item in lst)
{
if (item == link)
{
return true;
}
}
return false;
}
private void ExportToExcell(string code, int count,string path)
{
uint processId = 0;
Excel._Application oXL=null;
try
{
HtmlAgilityPack.HtmlDocument table = new HtmlAgilityPack.HtmlDocument();
table.LoadHtml(code);
HtmlNodeCollection rows = table.DocumentNode.SelectNodes("//tr");
// HtmlNodeCollection cols = table.DocumentNode.SelectNodes("//td");
oXL = new Microsoft.Office.Interop.Excel.Application();
GetWindowThreadProcessId((IntPtr)oXL.Hwnd, out processId);
var oWB = (Microsoft.Office.Interop.Excel.Workbook)(oXL.Workbooks.Add());
var oSheet = (Microsoft.Office.Interop.Excel.Worksheet)oWB.ActiveSheet;
int i = 2;
int j = 2;
int maxcols = 0;
if (rows != null)
{
foreach (HtmlNode row in rows)
{
HtmlAgilityPack.HtmlDocument RowCode = new HtmlAgilityPack.HtmlDocument();
RowCode.LoadHtml("<tr>" + row.InnerHtml + "</tr>");
HtmlNodeCollection cols = RowCode.DocumentNode.SelectNodes("//td");
j = 2;
if (cols != null)
{
if (cols.Count > maxcols)
{
maxcols = cols.Count;
}
foreach (HtmlNode col in cols)
{
oSheet.Cells[i, j] = col.InnerText;
j++;
}
}
i++;
}
}
oXL.Visible = false;
oXL.UserControl = true;
if (File.Exists(path))
{
File.Delete(path);
76
}
oWB.SaveAs(path);
oWB.Close();
}
catch { }
}
catch (Exception theException)
{
oXL.Quit();
try
{
Process.GetProcessById((int)processId).Kill();
}
catch { }
}
}
private bool prov(string code, ref List<List<string>> headers)
{
headers.Add(new List<string>());
if (code.IndexOf("<table") != -1) return false;
code = "<table>" + code + "</table>";
HtmlAgilityPack.HtmlDocument table = new HtmlAgilityPack.HtmlDocument();
table.LoadHtml(code);
HtmlNodeCollection rows = table.DocumentNode.SelectNodes("//tr");
HtmlNodeCollection xcols = table.DocumentNode.SelectNodes("//td");
if (rows != null && xcols != null)
{
int countRows = rows.Count;
int countCols = xcols.Count;
if (countCols < 6) return false;
string DisAllowSymbols =
"abcdefghijklmnopqrstuvwxyzабвгдеёжзийклмнопрстуфхцчшщъыьэюя";
int i = 0;
int countOfApplyableCells = 0;
int countOfUnApplyableCells = 0;
foreach (HtmlNode row in rows)
{
HtmlAgilityPack.HtmlDocument RowCode = new
HtmlAgilityPack.HtmlDocument();
RowCode.LoadHtml("<tr>" + row.InnerHtml + "</tr>");
HtmlNodeCollection cols = RowCode.DocumentNode.SelectNodes("//td");
int z = 0;
if (cols != null)
{
foreach (HtmlNode col in cols)
{
if (i == 0)
{
headers[0].Add(col.InnerText);
}
else
{
if (z != 0)
{
string a = col.InnerText;
if (a != "")
{
bool ok = true;
foreach (char s in a)
{
if (DisAllowSymbols.IndexOf(s) != -1)
{
77
countOfUnApplyableCells++;
ok = false;
break;
}
}
if (ok) countOfApplyableCells++;
}
else countOfUnApplyableCells++;
}
else z++;
}
i++;
}
if (countOfApplyableCells > countOfUnApplyableCells)
{
foreach (HtmlNode row in rows)
{
HtmlAgilityPack.HtmlDocument RowCode = new HtmlAgilityPack.HtmlDocument();
RowCode.LoadHtml("<tr>" + row.InnerHtml + "</tr>");
HtmlNodeCollection cols = RowCode.DocumentNode.SelectNodes("//td");
int z = 0;
if (cols != null)
{
foreach (HtmlNode col in cols)
{
if (col.InnerText != "")
if (!provString(col.InnerText))
{
countOfApplyableCells--;
countOfUnApplyableCells++;
}
}
}
}
if (countOfApplyableCells > countOfUnApplyableCells)
return true;
else
{
headers.Clear();
return false;
}
}
else
{
headers.Clear();
return false;
}
}
else
{
headers.Clear();
return false;
}
}
bool provString(string text)
{
foreach (char s in text)
{
if (s != ' ' && s != '\0' && s != '\n' && s != '\t')
{ return true; }
}
return false;
78
}
private void CheckUrl(string pageCode, ref int count, string u)
{
// Парсим страничку с резюме
HtmlAgilityPack.HtmlDocument d = new HtmlAgilityPack.HtmlDocument();
d.LoadHtml(pageCode);
// Выбираем ячейки с нужными данными
HtmlNodeCollection pads = d.DocumentNode.SelectNodes("//table"); //Поиск таблиц на
страницах
if (pads != null)
{
foreach (HtmlNode tab in pads)
{
if (tab.InnerText != "")
{
List<List<string>> trpHeaders2 = new List<List<string>>();
bool result = true;
if (prov(tab.InnerHtml, ref trpHeaders2))
{
trpHeaders2 = ToLowerCase(trpHeaders2);
trpHeaders2 = ClearHeaders(trpHeaders2);
List<List<List<string>>> found =
DivHeadersonWords(trpHeaders2);
if (Compare(example, found))
{
string path = save + "file0" + count.ToString() +
".xlsx";
ExportToExcell("<table>" + tab.InnerHtml + "</table>",
count, path);
xListViewItem elem = new xListViewItem(path, "1",
".xlsx");
elem.SubItems.Add("1");
elem.SubItems.Add(".xlsx");
results.Invoke((Action)(() =>
{
results.Items.Add(elem);
}));
count++;
}
}
}
}
}
HtmlNodeCollection linksOnData = d.DocumentNode.SelectNodes("//a");
if (linksOnData != null)
{
foreach (HtmlNode datalink in linksOnData)
{
if (datalink.Attributes["href"] != null)
{
string val = datalink.Attributes["href"].Value;
string fmt = GetFormat(val);
if (fmt.IndexOf("xls") != -1 || fmt.IndexOf("doc") != -1)
{
rbLog.Invoke((Action)(() =>
{
RbLog.Text += "Рассматривается файл : " + val + "\n";
}));
string name = downloadFile(val, count, u);
79
if (name == null) continue;
bool sovpadenie = false;
bool ok = true;
int NumList = 1;
while (ok)
{
List<List<string>> trpHeaders2 = new List<List<string>>();
if (fmt.IndexOf("xls") != -1)
trpHeaders2 = GetHeadersXLS(name, NumList, ref ok);
else if (fmt.IndexOf("doc") != -1)
trpHeaders2 = GetHeadersDoc(name, NumList, ref ok);
NumList++;
if (trpHeaders2 == null) continue;
if (ok)
{
trpHeaders2 = ToLowerCase(trpHeaders2);
trpHeaders2 = ClearHeaders(trpHeaders2);
List<List<List<string>>> found = DivHeadersonWords(trpHeaders2);
if (Compare(example, found))
{
xListViewItem elem = new xListViewItem(name, (NumList - 1).ToString(), fmt);
elem.SubItems.Add((NumList - 1).ToString());
elem.SubItems.Add(fmt);
rbLog.Invoke((Action)(() =>
{
rbLog.Text += "Файл : " + val + " сохранен\n";
}));
results.Invoke((Action)(() =>
{
results.Items.Add(elem);
}));
count++;
sovpadenie = true;
break;
}
}
}
if (!sovpadenie)
{
try
{
File.Delete(name);
}
catch { count++; }
}
}
}
}
}
}
#endregion
private void results_DoubleClick(object sender, EventArgs e)
{
if (results.SelectedItems[0] != null)
{
if (GetFormat(results.SelectedItems[0].Text).IndexOf("xls") != -1)
OpenXlS(results.SelectedItems[0].Text);
else if (GetFormat(results.SelectedItems[0].Text).IndexOf("doc") != -1)
OpenDOC(results.SelectedItems[0].Text);
}
}
80
Download