Форум программистов, компьютерный форум, киберфорум
C# для начинающих
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 4.63/16: Рейтинг темы: голосов - 16, средняя оценка - 4.63
 Аватар для Mans7
64 / 64 / 14
Регистрация: 05.08.2011
Сообщений: 323
Записей в блоге: 5
.NET 4.x

Сравнение производительности веб-парсинга разными способами

13.07.2012, 21:14. Показов 3295. Ответов 18
Метки нет (Все метки)

Студворк — интернет-сервис помощи студентам
В процессе разработки очередной программы, цель которой отпарсить определенный список страниц, обработать и выдать результат, я задумался - а какой метод будет более быстрым, удобным или коротким? Решил сделать тестер и показать результаты эксперимента сюда. В качестве таймера я использовал QueryPerformanceCounter и QueryPerformanceFrequency из библиотеки Kernel32.dll. Вроде как по отзывам достаточно точно определяет затраченное время. Было сделано по 10 независимых тестов на каждый метод парсинга. Все времена указаны в секундах.

Цель эксперимента: вывести временные затраты при парсинге страницы.
Парсируемая страница: "https://www.google.ru/search?&q=Как+производить+замер+времени"
Необходимо: загрузить страницу в память, найти выданные ссылки, вывести на экран ссылки (название, описание и сам линк).

Методы:
1. Загружаем данные в виртуальный webbrowser, парсим через класс HtmlElement.
Плюсы: Приятный вид парсинга и загрузки данных, очень понятный и достаточно удобный и короткий.
Минусы: Необходимо создавать ивент для ожидания загрузки страницы в WebBrowser. Возможно на странице с каким-то мультимедийным контентом будет ещё дольше "висеть". Крайне мало настроек подключения (точнее их нет).

Среднее время на парсинг: 0,019997724;
Среднее время на инициализацию компонента и загрузку содержимого в память: 2,52409373;
Минимальное время на -//-: 2,140337;
Максимальное время на -//-: 3,079691;
Разница: 0,939354.


2. Загружаем данные через класс HTTPWebRequest в строку, парсим ручками.
Плюсы: Очень тонкие настройки соединения, GET/POST запросов, кукисов, всего чего только можно, в т.ч. кодировки.
Минусы: Ужасный вид кода парсинга ручками, очень тяжело работать в кривом коде HTML, большой код для парсинга загруженных данных. Возможно, если использовать RegEx, то вид парсинга будет более оптимальный и не такой большой (не занимался ещё RegEx'ом). Так же необходимо создавать дополнительные методы (функции) для очистки HTML кода от мусора (<em>/<br>/<b>) и подобных для вывода чистого текста. А так же очень ненадёжный вид и тяжело понимаемый.

Среднее время на парсинг: 0,018322276 (чуточку быстрее первого метода);
Среднее время на инициализацию компонента и загрузку содержимого в память: 0,38945552 (топ);
Минимальное время на -//-: 0,3817242;
Максимальное время на -//-: 0,3990794;
Разница: 0,0173552.


3. Загружаем данные через HtmlAgilityPack, парсим через методы HtmlAgilityPack
Плюсы: Весьма удобный и короткий вид загрузки данных, очень короткий код на парсинг, не надо ничего нового придумывать, есть немного настроек соединения.
Минусы: Очень непривычный и местами неудобный (а местами очень даже удобный) синтаксис (если позволите так сказать) парсинга. Чистый адрес ссылки мне так и не удалось вытащить, почему-то куча мусора приплюсовалась, но это лишь неприятная мелочь. Хотя я впервые знакомлюсь с HtmlAgilityPack, может быть он лучше и удобнее чем кажется.

Среднее время на парсинг: 0,006282992 (топ);
Среднее время на инициализацию компонента и загрузку содержимого в память: 0,44985426;
Минимальное время на -//-: 0,384168;
Максимальное время на -//-: 0,6345674;
Разница: 0,2503994.

ВЕРДИКТ: Первый метод хорош только видом парсинга. Ужасно долго по сравнению с другими методами происходит получение данных от сервера. Второй идеален для тонкой настройки подключения или цепочки соединений. Если найти хороший способ парсинга текста (обработки полученного кода), то, я думаю, это будет лучший способ для меня (надо бы RegEx почитать). Третий способ для эстетов - всё довольно аккуратно и чётко. Но лично для меня он: непривычный и неудобный.

Прошу Ваших комментариев!

P.S. Я не рассматривал возможность асинхронной загрузки данных.
P.P.S. К сообщению приложил архив с проектом.
Вложения
Тип файла: rar Parsing_Test.rar (158.6 Кб, 89 просмотров)
3
Лучшие ответы (1)
Programming
Эксперт
39485 / 9562 / 3019
Регистрация: 12.04.2006
Сообщений: 41,671
Блог
13.07.2012, 21:14
Ответы с готовыми решениями:

Сохранение информации разными способами
Здравствуйте! Недавно начал разбираться с изображениями. И у меня возник вопрос : Насколько выгодно хранить информацию в виде...

Скорость парсинга ini файла разными методами
Ввиду моей большой лени самому неохота проверять данный вопрос, может кто сталкивался с этим? Имеем ini файл достаточно большой, пусть на...

INSERT разными способами
Добрый вечер! Помогите решить ещё одну задачку. В ней сразу несколько проблем. есть таблица 1, в ней колонки: продукт, номер накладной и...

18
 Аватар для Mans7
64 / 64 / 14
Регистрация: 05.08.2011
Сообщений: 323
Записей в блоге: 5
14.07.2012, 09:37  [ТС]
Кстати, надо будет попробовать сделать загрузку кода страницы через HTTPWebRequest, а потом загнать код в WebBrowser в надежде, что конкретно этот процесс не займёт времени. Тогда парсить будет очень легко (ну вот не понравился мне HtmlAgilityPack - Хочется обойтись родными библиотеками). Или всё-таки использовать Regex - как я убедился, он очень крут, но тяжело читаем (для меня пока что).
0
 Аватар для Петррр
6721 / 3570 / 900
Регистрация: 28.10.2010
Сообщений: 5,937
14.07.2012, 10:50
WebBrowser еще скачивает картинки, CSS и JS
1
 Аватар для Mans7
64 / 64 / 14
Регистрация: 05.08.2011
Сообщений: 323
Записей в блоге: 5
14.07.2012, 11:06  [ТС]
Цитата Сообщение от Петррр Посмотреть сообщение
WebBrowser еще скачивает картинки, CSS и JS
Тогда по идее должен иметься доступ к мультимедийному контенту (и скриптам), но это не нужно для парсинга в принципе. Всё это можно вытащить из кода. Спасибо.
0
Неадекват
 Аватар для freeba
1499 / 1236 / 247
Регистрация: 02.04.2010
Сообщений: 2,807
14.07.2012, 12:13
А что мешает совместить второй и третий способ?
Как нибудь так:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
HtmlDocument LoadPage(string URL)
        {
            HtmlDocument doc = new HtmlDocument();
            WebRequest request = WebRequest.Create(URL);
 
            using (var response = request.GetResponse())
            {
                using (var stream = response.GetResponseStream())
                {
                    doc.Load(stream);
                    return doc;
                }
            }
        }
а потом HtmlAgilityPack для парсинга:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public List<TypeRadio> GetTypeRadio()
        {
            List<TypeRadio> typeRadio = new List<TypeRadio>();
            string mainURL = ParseData.MainURL;
            HtmlDocument doc = LoadPage(mainURL);
            //string XPathTypeRadio = "/html/body/div[5]/div/div/div/div[3]/div/ul//a[@href]";
            foreach (HtmlNode node in doc.DocumentNode.SelectNodes(ParseData.XPathTypeRadio))
            {
                if (node.InnerText == string.Empty || failName.Contains(node.InnerText)) continue;
                HtmlAttribute ha = node.Attributes["href"];
                typeRadio.Add(new TypeRadio() { Name = node.InnerText, URL = CombineUri(mainURL, ha.Value) });
            }
            return typeRadio;
        }
1
 Аватар для Mans7
64 / 64 / 14
Регистрация: 05.08.2011
Сообщений: 323
Записей в блоге: 5
14.07.2012, 12:28  [ТС]
Тоже как вариант, тем более что опыт показал самую шуструю обработку текста (из 3х вариантов) именно у Html Agility Pack. Просто привыкать к нему не хочется )). Надо будет регекс засечь таймером.
0
Неадекват
 Аватар для freeba
1499 / 1236 / 247
Регистрация: 02.04.2010
Сообщений: 2,807
14.07.2012, 13:02
Нормально составленные регулярки пошустрее будут. Тем не менее зря вы так на HtmlAgilityPack косо смотрите, гибкий и удобный инструмент. Работа с регулярными выражениями - возня со строками. НАР - возня с объектами. ИМХО объекты удобнее.

В плюс HtmlAgilityPack также можно засчитать то, что он парсит страничку один раз. Для извлечения множества разнородной информации он подойдет лучше.
0
 Аватар для Mans7
64 / 64 / 14
Регистрация: 05.08.2011
Сообщений: 323
Записей в блоге: 5
14.07.2012, 13:09  [ТС]
Ну так-то да, с объектами всегда проще и удобнее (всё-таки у нас объектно-ориентированная среда). И грамотнее. Но беда в том, что при попытке получить код ссылки при парсе гугла, допустим, текста "Привет! Как дела?", гугл даёт такую ссылку: "v k . c o m /privetkakdelagroup" (пробелы поставил чтоб линк не делался). Точно так же я получаю ссылку через HtmlElement и ручной парс (и, думаю, получу с регулярки так же). А вот HAP выдал мне такое: "http : // ww w.g oogl e.r u/u rl?sa=t&rct=j&q=&esrc=s&source=web&cd=2& ved=0CE0QFjAB&url=http%3A%2F%2Fvk.com%2F privetkakdelagroup&ei=UTYBUIj0Ks_P4QS6mJ mlCA&usg=AFQjCNGKu_7uUwICKQFHTE-tvjjcb-HrFg". И теперь мне надо тогда отпарсить как-то вот этот кусок "url=http%3A%2F%2Fvk.com%2Fprivetkakdela group" (плюс, перекодировать url в понятный вид). Честно НЕ ЗНАЮ почему именно так HAP мне выдал ссылку, но я полчаса мучался и забил в итоге (цель была всё-таки получить время на получение результатов запроса). Хотя линк я добывал вроде правильно:
C#
1
lnk.URL = h3.ChildNodes[0].Attributes["href"].Value;
Разве нет? Поправьте меня если я не прав, и я буду только рад, если HAP выдаст мне человеческий линк в этом частном примере.
0
 Аватар для Yukikaze
352 / 331 / 49
Регистрация: 12.12.2011
Сообщений: 563
14.07.2012, 13:32
HAP спарсил все правильно, вот как выглядит ссылка в гугле
http://puu.sh/Irm6
1
 Аватар для Mans7
64 / 64 / 14
Регистрация: 05.08.2011
Сообщений: 323
Записей в блоге: 5
14.07.2012, 13:39  [ТС]
Да?
Значит я что-то не так сделал. Позже на досуге проверю. А заодно попробую соединить разные методы коннекта+парса. Заодно засеку по времени (QueryPerformance'ом и Stopwatch'ем). Дело-то плёвое, а интересно! И приведу примерный вид парса для оценки удобности на нескольких сайтах для разнообразия =). Планирую сделать программу для парса и сбора ответов с разных поисковых систем (т.к. надоело по 5-7 окон держать только с поисковиками), вот и исследую различные методы, т.к. в интернете свежей (да и не свежей) комплексной информации с учетом различных методов не нашел. Даже если она и есть - не бросать же дело, надо добивать, совершенствовать, выяснять )). Вечерком или завтра )
0
Неадекват
 Аватар для freeba
1499 / 1236 / 247
Регистрация: 02.04.2010
Сообщений: 2,807
14.07.2012, 14:25
Yukikaze, все правильно сказал. Гугл собирает историю поиска, поэтому все ссылки с редиректом.
Вот примитив для извлечения прямых ссылок:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
using System;
using HtmlAgilityPack;
 
namespace testParse
{
    class Program
    {
        static void Main(string[] args)
        {
            HtmlDocument doc = new HtmlWeb().Load("https://www.google.ru/search?&q=Привет!+Как+дела?", "GET");
 
            foreach (HtmlNode node in doc.DocumentNode.SelectNodes("//h3[@class='r']/a"))
            {
                HtmlAttribute ha = node.Attributes["href"];
                string link = string.Format("{0} - {1}\n", node.InnerText, ha.Value.Replace("/url?q=", "").Replace("amp;", ""));
                Console.WriteLine(link.Substring(0, link.IndexOf("&sa")));
            }
 
            Console.ReadKey(true);
        }
    }
}
1
 Аватар для Yukikaze
352 / 331 / 49
Регистрация: 12.12.2011
Сообщений: 563
14.07.2012, 14:29
Лучший ответ Сообщение было отмечено как решение

Решение

Ну и я тогда приколюху выложу
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using HtmlAgilityPack;
 
namespace GoogleParser
{
    class Program
    {
        static void Main(string[] args)
        {
            string str;
            while ((str = Console.ReadLine()) != "exit")
            {
                Console.Clear();
                foreach (string s in Parse(str))
                {
                    Console.WriteLine(s);
                }
            }
        }
 
        private static IEnumerable<string> Parse(string str)
        {
 
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://www.google.ru/search?oe=UTF-8&q=" + str);
            HttpWebResponse response = (HttpWebResponse)request.GetResponse();
            HtmlDocument doc = new HtmlDocument();
            doc.Load(response.GetResponseStream(), Encoding.UTF8);
            HtmlNode[] nodes = doc.DocumentNode.SelectNodes("//h3[@class='r']/a").ToArray();
            foreach (HtmlNode node in nodes)
            {
                string name = node.InnerText;
                string url = WebUtility.HtmlDecode(node.GetAttributeValue("href", "n/a"));
                int start = url.IndexOf("q=") + 2;
                int end = url.IndexOf('&', start);
                string fine = url.Substring(start, end - start);
                yield return name + "\r\n" + fine;
            }
        }
    }
}
3
Неадекват
 Аватар для freeba
1499 / 1236 / 247
Регистрация: 02.04.2010
Сообщений: 2,807
14.07.2012, 15:17
Генератор? Я думал ими никто уже не пользуется с появления LINQ
0
 Аватар для Yukikaze
352 / 331 / 49
Регистрация: 12.12.2011
Сообщений: 563
14.07.2012, 16:04
freeba, в LINQ нет души
0
 Аватар для Mans7
64 / 64 / 14
Регистрация: 05.08.2011
Сообщений: 323
Записей в блоге: 5
15.07.2012, 00:55  [ТС]
Цитата Сообщение от Yukikaze Посмотреть сообщение
в LINQ нет души
Это почему ещё?

freeba, Я делать так не стал, т.к. боялся что амперсанд (amp может быть и в самой ссылке и в ссылке редиректа. Но я не заострял внимание на этом. Щас займусь.

Цитата Сообщение от Yukikaze Посмотреть сообщение
WebUtility.HtmlDecode
Вот до него-то я и не успел дойти ещё . Мне кажется, что неправильно от "q=" до первого "&" выбирать. Есть ли гарантия что символ "&" не будет относиться к той ссылке которую нужно получить? То есть, может быть, в получаемой ссылке тоже есть всякие "php" плюшки, которые не желательно выкидывать?
0
Неадекват
 Аватар для freeba
1499 / 1236 / 247
Регистрация: 02.04.2010
Сообщений: 2,807
15.07.2012, 01:16

Не по теме:

Цитата Сообщение от Yukikaze Посмотреть сообщение
в LINQ нет души
+1. Функциональная ересь в царстве ООП. Тысячи православных программистов поддались искушению и продали генераторы в обмен на кавайный синтаксис и кажущуюся простоту. Ждем судного дня ибо расставит он все по местам своим.



Цитата Сообщение от Mans7 Посмотреть сообщение
т.к. боялся что амперсанд (amp может быть и в самой ссылке и в ссылке редиректа.
Действительно, не подумал. Тогда меняем Replace("amp;", "") на Replace("amp;", "&").
0
 Аватар для Mans7
64 / 64 / 14
Регистрация: 05.08.2011
Сообщений: 323
Записей в блоге: 5
15.07.2012, 01:31  [ТС]
И-так. Делаем Френкенштейна:
1) Соединим 1 и 2 способы. Загружаем данные через HttpWebRequest, стринг с кодом загоняем в webBrowser.DocumentText. Итого: ничего хорошего. Даже при отключении появления ошибок о скриптах, он все равно ругается на всякие xjs скрипты. При этом, парсинг не проходит. Fail.
2) Будем соединять 2 и 3 способы. Загружаем данные через HttpWebRequest, ответ напрямую получает HAP. Как и ожидалось - лучшая скорость загрузки страницы, лучшая скорость парса.

Yukikaze, скажите, а что даёт "n/a"? И ещё, я укоротил этот кусок:
Цитата Сообщение от Yukikaze Посмотреть сообщение
C#
1
2
3
4
string url = WebUtility.HtmlDecode(node.GetAttributeValue("href", "n/a"));
int start = url.IndexOf("q=") + 2;
int end = url.IndexOf('&', start);
string fine = url.Substring(start, end - start);
до
C#
1
lnk.URL = WebUtility.HtmlDecode(node.GetAttributeValue("href","n/a"));
И на выходе получаю готовую ссылку без всяких танцев с бубном над ней =) Я не заметил что у GetAttributeValue есть 3 конструктора, видел только булевый. Здорово!

Замерил время выполнения StopWatch'ем. Получил:
Среднее время соединения - 462,8 мс (миллисекунд)
Среднее время парса - 6,6 мс

Так же заметил разницу между замерами QueryPerformance и StopWatch. Разницу я наблюдал 0-5%.
0
 Аватар для Yukikaze
352 / 331 / 49
Регистрация: 12.12.2011
Сообщений: 563
15.07.2012, 01:39
Mans7, у вас какой-то бракованый гугл, дает чистые ссылки, без редиректа
0
 Аватар для Mans7
64 / 64 / 14
Регистрация: 05.08.2011
Сообщений: 323
Записей в блоге: 5
15.07.2012, 03:56  [ТС]
Ну, какой есть )
А теперь ещё фишка - сделал то чего боялся и не хотел. Парсинг RegEx'ом . Паттерн получился такой:
C#
1
string pattern = "<h3 class=.r.><a *href=\"(.+?)\"[^>]*>(.+?)</a>.*<span *class=.st.>(.+?)</span>";
Код очень короткий, время парса 3 мс (0,00349404). Но, блин, регулярку составить трудно =(
Я составлять регулярки не умею толком, так что наверняка её можно слегка упростить.

Добавлено через 10 минут
Из вышесказанного можно сказать что парсинг регулярками имеет:
Плюсы:
- очень производительный (быстрый);
- имеет короткий код;
- не нужно загружать сторонние библиотеки.
Минусы:
- почти нечитабельный (нужно глаз набивать);
- индивидуальный под каждую страницу;
- опять-таки местами где надо вывести чистый текст надо чистить код от мусора (<b><br><em>...), но можно Replace сделать;
- в случае изменения кода на странице, скорее всего, регулярку тоже надо будет корректировать.

Добавлено через 2 минуты
Позже, если ничего нового не придумаю, подведу общие итоги всех этих исследований. Хотя надо что-нибудь ещё придумать, хочется по-глубже залезть в вопросы парсинга.

Добавлено через 26 минут
А теперь заценим асинхронную загрузку кода страницы.
C#
1
2
3
4
5
6
7
8
9
10
11
        public void GetPerfomanceTime_5()
        {
            WebClient wc = new WebClient();
            wc.DownloadStringAsync(new Uri("https://www.google.ru/search?q=Как+производить+замер+времени"));
            wc.DownloadStringCompleted += new DownloadStringCompletedEventHandler(downld); 
        }
 
        private void downld(object ob, DownloadStringCompletedEventArgs e)
        {
            string result = e.Result;
        }
На выходе получаем ту же строчку, без заморочек с GET\POST - хотя их можно настроить без проблем.
Средняя скорость получения данных StopWatch: 453 мс
Средняя скорость получения данных QueryPerformance: 0,446738

Для единичной загрузки страницы скорость меньше чем при использовании HttpWebRequest/Response. А вот если делать это в потоках, то, возможно эффект будет более положительным для Async. Плюс, насколько я заметил, основной поток не подвисает во время получения ответа. То есть получен тот же мини-тред.
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
inter-admin
Эксперт
29715 / 6470 / 2152
Регистрация: 06.03.2009
Сообщений: 28,500
Блог
15.07.2012, 03:56
Помогаю со студенческими работами здесь

Сортировка массивов разными способами
У меня завтра зачёт с энономической информатики. И мне нужно запрограмировать следующие методы сортировки массивов средствами МS Excel:...

Интегрирование функции разными способами
Дали задание в университете, мол, реализуйте интегрирование функции вида f(x) методами прямоугольников,трапеций, Симпсона. И вроде всё...

Определенные интегралы разными способами
Столкнулся с трудностью применения функций quad и quad8. Задание обстоит так: Найдите определенные интегралы с помощью функций int,...

Сортировка массивов разными способами
1.Реализовать алгоритм сортировки прямым включением в виде функции 2.Реализовать алгоритм сортировки прямым выбором в виде функции ...

Реализация стек разными способами
Требуется реализовать стек разными способами (требуется три программы): 1) Стек фиксированного размера на массиве 2)...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
19
Ответ Создать тему
Новые блоги и статьи
Новый ноутбук
volvo 07.12.2025
Всем привет. По скидке в "черную пятницу" взял себе новый ноутбук Lenovo ThinkBook 16 G7 на Амазоне: Ryzen 5 7533HS 64 Gb DDR5 1Tb NVMe 16" Full HD Display Win11 Pro
Музыка, написанная Искусственным Интеллектом
volvo 04.12.2025
Всем привет. Некоторое время назад меня заинтересовало, что уже умеет ИИ в плане написания музыки для песен, и, собственно, исполнения этих самых песен. Стихов у нас много, уже вышли 4 книги, еще 3. . .
От async/await к виртуальным потокам в Python
IndentationError 23.11.2025
Армин Ронахер поставил под сомнение async/ await. Создатель Flask заявляет: цветные функции - провал, виртуальные потоки - решение. Не threading-динозавры, а новое поколение лёгких потоков. Откат?. . .
Поиск "дружественных имён" СОМ портов
Argus19 22.11.2025
Поиск "дружественных имён" СОМ портов На странице: https:/ / norseev. ru/ 2018/ 01/ 04/ comportlist_windows/ нашёл схожую тему. Там приведён код на С++, который показывает только имена СОМ портов, типа,. . .
Сколько Государство потратило денег на меня, обеспечивая инсулином.
Programma_Boinc 20.11.2025
Сколько Государство потратило денег на меня, обеспечивая инсулином. Вот решила сделать интересный приблизительный подсчет, сколько государство потратило на меня денег на покупку инсулинов. . . .
Ломающие изменения в C#.NStar Alpha
Etyuhibosecyu 20.11.2025
Уже можно не только тестировать, но и пользоваться C#. NStar - писать оконные приложения, содержащие надписи, кнопки, текстовые поля и даже изображения, например, моя игра "Три в ряд" написана на этом. . .
Мысли в слух
kumehtar 18.11.2025
Кстати, совсем недавно имел разговор на тему медитаций с людьми. И обнаружил, что они вообще не понимают что такое медитация и зачем она нужна. Самые базовые вещи. Для них это - когда просто люди. . .
Создание Single Page Application на фреймах
krapotkin 16.11.2025
Статья исключительно для начинающих. Подходы оригинальностью не блещут. В век Веб все очень привыкли к дизайну Single-Page-Application . Быстренько разберем подход "на фреймах". Мы делаем одну. . .
Фото: Daniel Greenwood
kumehtar 13.11.2025
Расскажи мне о Мире, бродяга
kumehtar 12.11.2025
— Расскажи мне о Мире, бродяга, Ты же видел моря и метели. Как сменялись короны и стяги, Как эпохи стрелою летели. - Этот мир — это крылья и горы, Снег и пламя, любовь и тревоги, И бескрайние. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2025, CyberForum.ru