В последнее время интересуюсь веб-скрейпингом (он же веб-майнинг) и в результате решил написать статью для тех, кто уже слышал о том, что он существует, но пока на вкус не пробовал.
Итак, собственно веб-скрейпинг в моём понимании – это перенос данных, выложенных в сети Интернет в виде HTML-страниц в некое хранилище. Хранилище может представлять собой как обычный текстовый файл, так и файл XML или же базу данных (БД). То есть на лицо обратный (реверсный) процесс – ведь веб-приложение обычно берёт данные как раз из БД.
Для примера возьмём простой случай – разбор страницы сайта auto.ru. Перейдя по ссылке http://vin.auto.ru/resolve.html?vin=TMBBD41Z57B150932 мы увидим некоторую информацию, выводимую для Идентификационного номера TMBBD41Z57B150932 (марка, модель, модификация и т.д.). Представим себе, что нам необходимо вывести эту информацию в окно, например, Windows-приложения. Работа с БД в.Net широко описана, поэтому сосредотачиваться на этой проблеме мы не будем, займёмся сутью.
Итак, создадим проект WinForms-приложения, бросим на форму один компонент TextBox с именем tbText, в котором будет прописан наш адрес (ссылка); кнопку btnStart, при нажатии на которую будет выполняться запрос по указанному адресу, а также ListBox lbConsole, куда выведем полученные данные. В реальном приложении ссылки тоже придётся брать из какого-то внешнего источника, но не забываем, что это – всего лишь пример.
![image](https://habr.com/images/px.gif#%3D%22http%3A%2F%2Fimg715.imageshack.us%2Fimg715%2F9098%2Fpic1wz.png%22)
Собственно с интерфейсом всё, теперь создадим метод, вызываемый в ответ на нажатие кнопки.
В этом методе нам нужно проделать следующие вещи:
1. Обратиться по адресу, указанному в нашем TextBox
2. Получить страницу
3. Выбрать из страницы необходимые данные
4. Вывести данные на форму
Для начала создадим переменную, в которой будет храниться полученная по запросу страница:
Далее создадим запрос, передав в качестве параметра известную нам ссылку:
Зададим свойства запроса, помогут выдать нам себя за браузер. В данном случае это неважно, но некоторые сайты анализируют заголовки запроса, так что это – подсказка на будущее.
Также укажем, что использоваться будет метод GET.
Теперь выполняем запрос и переходим к следующему пункту –
Собственно ответ сервера, а значит и сама страница теперь хранятся у нас в переменной autoResponse. Теперь нужно проанализировать этот ответ, если всё ОК, то можно представить страницу в виде строки:
И если всё действительно ОК, то мы теперь имеем в переменной AutoResult строку такого же вида, которую мы можем посмотреть в браузере с помощью меню «Исходный код страницы». Ну, разве что в неотформатированном виде.
Это всё, конечно, здорово. Но хотелось бы из этой мешанины тэгов выбрать именно то, что нам нужно. Здесь нам на помощь придут регулярные выражения, которые мы задействуем с помощью методов-расширителей. Методы-расширители, напомню, это такие статические методы статического класса, которые можно вызывать как метод объекта другого класса, если этот объект этого класса является первым параметром метода статического класса помеченным ключевым словом this. На примере проще. Если у нас есть метод StringWithEq класса StringOperations
то мы можем использовать этот метод как привычным образом (1), так и как метод-расширитель (2):
Если посмотреть исходный код HTML-страницы в браузере, то можно заметить, что необходимые нам данные содержатся внутри тега , который нигде более не используется:
Поэтому воспользуемся этим, сначала извлечём данные из внутри этого тега, а затем разберем их и поместим, например, в объект класса Dictionary. После чего выведем полученные данные в ListBox lbConsole. Хотелось бы, чтобы конечный код выглядел, например, так:
В первой строке мы получаем строку, содержащую необходимые данные. Здесь мы используем метод-расширитель такого вида:
Далее с помощью ещё одного метода-расширителя выбираем необходимые данные и пишем их в объект класса Dictionary:
Далее в цикле foreach выводим полученные данные в ListBox.
![image](https://habr.com/images/px.gif#%3D%22http%3A%2F%2Fimg203.imageshack.us%2Fimg203%2F3271%2Fpic2jb.png%22)
Конечно, можно было использовать только второй метод расширитель, результат был бы тот же. В реальных же приложениях иногда удобнее выделить часть текста, содержащую необходимые данные, а затем заниматься её разбором. Можно внести другие усовершенствования и/или изменения в этот код, но надеюсь, что цели этой небольшой статьи я достиг – дал вам представление о том, что такое веб-скрейпинг.
Итак, собственно веб-скрейпинг в моём понимании – это перенос данных, выложенных в сети Интернет в виде HTML-страниц в некое хранилище. Хранилище может представлять собой как обычный текстовый файл, так и файл XML или же базу данных (БД). То есть на лицо обратный (реверсный) процесс – ведь веб-приложение обычно берёт данные как раз из БД.
От теории к практике
Для примера возьмём простой случай – разбор страницы сайта auto.ru. Перейдя по ссылке http://vin.auto.ru/resolve.html?vin=TMBBD41Z57B150932 мы увидим некоторую информацию, выводимую для Идентификационного номера TMBBD41Z57B150932 (марка, модель, модификация и т.д.). Представим себе, что нам необходимо вывести эту информацию в окно, например, Windows-приложения. Работа с БД в.Net широко описана, поэтому сосредотачиваться на этой проблеме мы не будем, займёмся сутью.
Итак, создадим проект WinForms-приложения, бросим на форму один компонент TextBox с именем tbText, в котором будет прописан наш адрес (ссылка); кнопку btnStart, при нажатии на которую будет выполняться запрос по указанному адресу, а также ListBox lbConsole, куда выведем полученные данные. В реальном приложении ссылки тоже придётся брать из какого-то внешнего источника, но не забываем, что это – всего лишь пример.
![image](https://habr.com/images/px.gif#%3D%22http%3A%2F%2Fimg715.imageshack.us%2Fimg715%2F9098%2Fpic1wz.png%22)
Собственно с интерфейсом всё, теперь создадим метод, вызываемый в ответ на нажатие кнопки.
В этом методе нам нужно проделать следующие вещи:
1. Обратиться по адресу, указанному в нашем TextBox
2. Получить страницу
3. Выбрать из страницы необходимые данные
4. Вывести данные на форму
Обращаемся по адресу
Для начала создадим переменную, в которой будет храниться полученная по запросу страница:
- string AutoResult = String.Empty;
* This source code was highlighted with Source Code Highlighter.
Далее создадим запрос, передав в качестве параметра известную нам ссылку:
- var autoRequest = (HttpWebRequest)WebRequest.Create(tbLink.Text);
* This source code was highlighted with Source Code Highlighter.
Зададим свойства запроса, помогут выдать нам себя за браузер. В данном случае это неважно, но некоторые сайты анализируют заголовки запроса, так что это – подсказка на будущее.
- autoRequest.UserAgent = "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)";
- autoRequest.Headers.Add("Accept-Language", "ru-Ru");
- autoRequest.Accept = "image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/x-ms-application, application/x-ms-xbap, application/vnd.ms-xpsdocument, application/xaml+xml, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*";
* This source code was highlighted with Source Code Highlighter.
Также укажем, что использоваться будет метод GET.
- autoRequest.Method = "GET";
* This source code was highlighted with Source Code Highlighter.
Теперь выполняем запрос и переходим к следующему пункту –
Получение страницы
- HttpWebResponse autoResponse = (HttpWebResponse)autoRequest.GetResponse();
* This source code was highlighted with Source Code Highlighter.
Собственно ответ сервера, а значит и сама страница теперь хранятся у нас в переменной autoResponse. Теперь нужно проанализировать этот ответ, если всё ОК, то можно представить страницу в виде строки:
- if (autoResponse.StatusCode == HttpStatusCode.OK)
- {
- using (Stream autoStream = autoResponse.GetResponseStream())
- {AutoResult = new StreamReader(autoStream, Encoding.GetEncoding("windows-1251")).ReadToEnd(); }
- }
* This source code was highlighted with Source Code Highlighter.
И если всё действительно ОК, то мы теперь имеем в переменной AutoResult строку такого же вида, которую мы можем посмотреть в браузере с помощью меню «Исходный код страницы». Ну, разве что в неотформатированном виде.
Это всё, конечно, здорово. Но хотелось бы из этой мешанины тэгов выбрать именно то, что нам нужно. Здесь нам на помощь придут регулярные выражения, которые мы задействуем с помощью методов-расширителей. Методы-расширители, напомню, это такие статические методы статического класса, которые можно вызывать как метод объекта другого класса, если этот объект этого класса является первым параметром метода статического класса помеченным ключевым словом this. На примере проще. Если у нас есть метод StringWithEq класса StringOperations
- static class StringOperations
- {internal static string StringWithEq(this string s) {return string.Format("{0} = ", s);}}
* This source code was highlighted with Source Code Highlighter.
то мы можем использовать этот метод как привычным образом (1), так и как метод-расширитель (2):
- string test = "Test";
- (1) Console.Write(StringOperations.StringWithEq(test));
- (2) Console.Write(test.StringWithEq());
* This source code was highlighted with Source Code Highlighter.
Если посмотреть исходный код HTML-страницы в браузере, то можно заметить, что необходимые нам данные содержатся внутри тега , который нигде более не используется:
<dl class="def-list md"><dt><strong>Идентификационный номер</strong></dt><dd>TMBBD41Z57B150932</dd><dt><strong>Марка</strong></dt><dd>SKODA</dd><dt><strong>Модель</strong></dt><dd>Octavia II (A5)</dd><dt><strong>Модификация</strong></dt><dd>Elegance</dd><dt><strong>Модельный год</strong></dt><dd>2007</dd><dt><strong>Тип кузова</strong></dt><dd>седан</dd><dt><strong>Количество дверей</strong></dt><dd>5-дверный</dd><dt><strong>Объем двигателя, куб.см.</strong></dt><dd>2000</dd><dt><strong>Описание двигателя</strong></dt><dd>150лс</dd><dt><strong>Серия двигателя</strong></dt><dd>BLR, BLX, BLY</dd><dt><strong>Система пассивной безопасности</strong></dt><dd>подушки безопасности водителя и переднего пассажира</dd><dt><strong>Сборочный завод</strong></dt><dd>Solomonovo</dd><dt><strong>Страна сборки</strong></dt><dd>Украина</dd><dt><strong>Страна происхождения</strong></dt><dd>Чехия</dd><dt><strong>Производитель</strong></dt><dd>Skoda Auto a.s.</dd><dt><strong>Серийный номер</strong></dt><dd>50932</dd><dt><strong>Контрольный символ</strong></dt><dd><span style='color: #FF0000;'>NOT OK!</span></dd></dl>Партнёр проекта - <a href="http://vinformer.su">vinformer.su</a></div>
Поэтому воспользуемся этим, сначала извлечём данные из внутри этого тега, а затем разберем их и поместим, например, в объект класса Dictionary. После чего выведем полученные данные в ListBox lbConsole. Хотелось бы, чтобы конечный код выглядел, например, так:
- string BetweenDL = AutoResult.BetweenDL();
- Dictionary<string, string> d = BetweenDL.BetweenDTDD();
- foreach (var s in d)
- {
- lbConsole.Items.Add(string.Format("{0}={1}", s.Key, s.Value));
- }
* This source code was highlighted with Source Code Highlighter.
В первой строке мы получаем строку, содержащую необходимые данные. Здесь мы используем метод-расширитель такого вида:
- internal static string BetweenDL(this string dumpFile)
- {
- var _regex = new Regex(@"<dl[^>]*>(?<value>[\s\S]+?)</dl>", RegexOptions.IgnoreCase | RegexOptions.Compiled);
- Match _match = _regex.Match(dumpFile);
- return _match.Success ? _match.Groups["value"].Value : string.Empty;
- }
* This source code was highlighted with Source Code Highlighter.
Далее с помощью ещё одного метода-расширителя выбираем необходимые данные и пишем их в объект класса Dictionary:
- internal static Dictionary<string, string> BetweenDTDD(this string dumpFile)
- {
- var _regex = new Regex(@"<dt[\s\S]+?strong>(?<valDT>[\s\S]+?)</strong></dt><dd[^>]*>(?<valDD>[\s\S]+?)</dd>", RegexOptions.IgnoreCase | RegexOptions.Compiled);
- MatchCollection matches = _regex.Matches(dumpFile);
- Dictionary<string, string> d = new Dictionary<string, string>();
- foreach (Match match in matches)
- {
- GroupCollection groups = match.Groups;
- d.Add(groups["valDT"].Value, groups["valDD"].Value);
- }
- return d;
- }
* This source code was highlighted with Source Code Highlighter.
Далее в цикле foreach выводим полученные данные в ListBox.
![image](https://habr.com/images/px.gif#%3D%22http%3A%2F%2Fimg203.imageshack.us%2Fimg203%2F3271%2Fpic2jb.png%22)
Конечно, можно было использовать только второй метод расширитель, результат был бы тот же. В реальных же приложениях иногда удобнее выделить часть текста, содержащую необходимые данные, а затем заниматься её разбором. Можно внести другие усовершенствования и/или изменения в этот код, но надеюсь, что цели этой небольшой статьи я достиг – дал вам представление о том, что такое веб-скрейпинг.