Форум программистов, компьютерный форум, киберфорум
Delphi: Сети
Войти
Регистрация
Восстановить пароль
Карта форума Темы раздела Блоги Сообщество Поиск Заказать работу  
 
 
Рейтинг 4.63/19: Рейтинг темы: голосов - 19, средняя оценка - 4.63
5 / 5 / 0
Регистрация: 07.07.2012
Сообщений: 32
1

Парсер для результатов запроса

24.10.2012, 17:26. Показов 3670. Ответов 30
Метки нет (Все метки)

Author24 — интернет-сервис помощи студентам
На форме сайта есть 2 Edit-а в которых вводиться пункт отправления и пункт назначения. Точное количество и их название неизвестно. Для кортежа (рейса) есть первичный ключь он же номер рейса. Так же этот номер рейса служит ссылкой на полное расписание этого рейса с пунктами остановки и прочим. Необходимо эти все рейсы спарсить в базу на железо.

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

Нужен алгоритм действий.
Нужен пример для запроса на ресурс для поиска.
Нужна идею как связать таблицы по номеру рейса.


http://apikabu.ru/img_n/2012-10_5/c2e.jpg
0
Лучшие ответы (1)
Programming
Эксперт
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
24.10.2012, 17:26
Ответы с готовыми решениями:

Парсер результатов
Здравствуйте! Пишу на delphi программку которая будет отправлять POST запрос на сайте, а...

Многопоточный парсер и сохранение результатов
Пусть мы хотим написать некий многопоточный парсер. Схема работы такая. Программа на вход получает...

Вычитание результатов одного запроса из результатов другого
Есть 2 запроса,подскажите возможно ли вычесть одно из второго

Парсер файла в двумерный массив и отображение результатов
Доброго времени суток. В полку нубов прибыло. С программированием раньше сталкивался в...

30
1302 / 708 / 107
Регистрация: 25.05.2011
Сообщений: 2,158
Записей в блоге: 51
24.10.2012, 20:25 2
имхо анализировать через FireBug запросы которые делает сайт и эмулировать через TIdhttp + регулярные выражения

или работать через TEmbededWB + jsEmbeddedWB и также после анализа блоков на сайте проводить запросы и нажимать на ссылки через DOM или JavaScript(Jquery)

Нужен пример для запроса на ресурс для поиска.
сайт в студию!
1
5 / 5 / 0
Регистрация: 07.07.2012
Сообщений: 32
24.10.2012, 22:06  [ТС] 3
вот уже набросал, во вложении и сылка
Вложения
Тип файла: rar парсер.rar (2.01 Мб, 21 просмотров)
0
1302 / 708 / 107
Регистрация: 25.05.2011
Сообщений: 2,158
Записей в блоге: 51
24.10.2012, 23:08 4
на сайте используется javascript и сайт не работает в браузере если JS отключен (ссылка на запрос не работает)
и судя по отчету FireBug indy тут не проедет. Используйте схему TEmbebedWB + jsEmbeddedWB или TWebbrowser + jsWebbrowser
Миниатюры
Парсер для результатов запроса  
1
5 / 5 / 0
Регистрация: 07.07.2012
Сообщений: 32
24.10.2012, 23:34  [ТС] 5
Цитата Сообщение от Alex_pac Посмотреть сообщение
Используйте схему TEmbebedWB + jsEmbeddedWB или TWebbrowser + jsWebbrowser
С этим есть проблемы. Не могли бы помочь разобраться?
Ни разу не работал с этим и хотя бы простой пример помогбы сдвинуть дело в положительную сторону
0
1302 / 708 / 107
Регистрация: 25.05.2011
Сообщений: 2,158
Записей в блоге: 51
25.10.2012, 01:18 6
Лучший ответ Сообщение было отмечено как решение

Решение

Delphi
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
unit Unit1;
 
interface
 
uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, OleCtrls, SHDocVw, jsWebbrowser;
 
type
  TForm1 = class(TForm)
    web: TWebBrowser;
    Edit1: TEdit;
    Edit2: TEdit;
    Button1: TButton;
    Memo1: TMemo;
    Label1: TLabel;
    Button2: TButton;
    ListBox1: TListBox;
    Label2: TLabel;
    Label3: TLabel;
    Memo2: TMemo;
    procedure Button1Click(Sender: TObject);
    procedure webBeforeNavigate2(ASender: TObject;
      const pDisp: IDispatch; var URL, Flags, TargetFrameName, PostData,
      Headers: OleVariant; var Cancel: WordBool);
    procedure webDocumentComplete(ASender: TObject;
      const pDisp: IDispatch; var URL: OleVariant);
    procedure Button2Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure ListBox1DblClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    s1: TStringList;
  end;
 
var
  Form1: TForm1;
 
implementation
 
{$R *.dfm}
 
procedure TForm1.Button1Click(Sender: TObject);
begin
  memo1.Clear;
  web.tag:=0; // состояние 0
  web.Navigate('http://www.transtekhnika.by/raspisanie/version2/raspisanie.aspx');
 
end;
 
procedure TForm1.Button2Click(Sender: TObject);
begin
  // поиск
  if web.tag<>0 then exit;
  web.tag:=1;
  web.getElementById('input','TextBox1').setAttribute('value',Edit1.Text,0);
  web.getElementById('input','TextBox2').setAttribute('value',Edit2.Text,0);
  web.getElementById('input','Button1').click;
end;
 
procedure TForm1.FormCreate(Sender: TObject);
begin
  s1:=TStringList.Create;
end;
 
procedure TForm1.ListBox1DblClick(Sender: TObject);
begin
  if ListBox1.ItemIndex<0 then exit;
  web.tag:=2;
  web.RunJS(s1[ListBox1.ItemIndex]);
end;
 
procedure TForm1.webBeforeNavigate2(ASender: TObject;
  const pDisp: IDispatch; var URL, Flags, TargetFrameName, PostData,
  Headers: OleVariant; var Cancel: WordBool);
begin
  Button1.Enabled:=false;
end;
 
procedure TForm1.webDocumentComplete(ASender: TObject;
  const pDisp: IDispatch; var URL: OleVariant);
begin
  Button1.Enabled:=true;
  Memo1.Lines.Add(URL);
  // состояния браузера
  case web.tag of
    0: begin
      // главная страница
      exit;
    end;
    1: begin
      // получаем данные
      web.JqueryLoad; // подключаем Jquery 1.3
      web.RunJS(
        'var mass = []; var mass2 = [];'+
        '$("table#GridView1 tr").each(function(i){ ' +
        '  if (i==0) { return true; } '+
        '  mass.push(String($(this).find("a").eq(0).attr("href")).replace(/javascript:/i,"")); '+
        '  mass2.push($(this).find("td").eq(1).text()); '+
        '});'
      );
      web.getJsStrings('mass',s1);
      web.getJsStrings('mass2',ListBox1.Items);
    end;
    2: begin
      // показываем в списке
      web.JqueryLoad; // подключаем Jquery 1.3
      web.RunJS('$("table#GridView1 tr").eq(0).remove();');
      Memo2.Text:=web.getElementById('table','GridView1').innerText;
      web.Tag:=1;
      web.GoBack;
      exit;
    end;
  end;
  //web.tag:=-1;
end;
 
end.
Миниатюры
Парсер для результатов запроса  
Вложения
Тип файла: zip testWB3.zip (671.0 Кб, 16 просмотров)
1
54 / 54 / 4
Регистрация: 15.12.2010
Сообщений: 260
25.10.2012, 18:28 7
гымс
Однако параметры на скрине ооочень походи на то, что сайт написан на ASP. В принципе при приложении рук и головы - все резается без всяких браузеров. По крайней мере синапсом без проблем всегда их проходил
0
1302 / 708 / 107
Регистрация: 25.05.2011
Сообщений: 2,158
Записей в блоге: 51
25.10.2012, 19:36 8
A чем отличается программа на indy от программы с использованием TWebbrowser(TEmbededWB).

Кроме жесткой зависимости от Windows и IE.

Если главный аргумент быстродействие то TEmbededWB имеет 100500 настроек для оптимизации

с точки зрения парсинга программа на TWebbrowser реализуется "дешевле" по времени и простоте работы с DOM
0
54 / 54 / 4
Регистрация: 15.12.2010
Сообщений: 260
26.10.2012, 11:35 9
Цитата Сообщение от Alex_pac Посмотреть сообщение
A чем отличается программа на indy от программы с использованием TWebbrowser(TEmbededWB).
Отличается (точнее, про индейцев не могу сказать, давно не работал, только про синапс) - тем, что не надо:
1. мидитировать с интерфейсами
2. забыть про туеву кучу не нужной инфы (тянеш только то, что нужно)
3. легче работать с проксями
4. не нужно забивать себе голову вопросом "да как же, блин, добраться до этой кнопочки/поля"

единственный минус - с явой иногда приходится повозится

Добавлено через 9 минут
дополнение

5. в дополнение к п.2 - с использованием веббраузера забываем про API и подобные запросы (к примеру сайт твиттера реально работает на своем API). Для информации - сканер пабликов для контакта (тупой перебор чуть больше 44 млн ID) - отработал часа за три и благополучно собрал все 3217490 пабликов (это на вчерашний день, счас может и больше)
6. с браузером благополучно забываем про многопоток (в решении п. 5 он и использовался, 50 потоков шуршало)

Цитата Сообщение от Alex_pac Посмотреть сообщение
с точки зрения парсинга программа на TWebbrowser реализуется "дешевле" по времени и простоте работы с DOM
Нуууу, здесь я могу поспорить .
Регулярками решается все элементарно и просто, а в некоторых случаях и регулярки нафиг не нужны - на прямую с XML/JSON работаеш
1
1302 / 708 / 107
Регистрация: 25.05.2011
Сообщений: 2,158
Записей в блоге: 51
26.10.2012, 13:32 10
5. в дополнение к п.2 - с использованием веббраузера забываем про API и подобные запросы (к примеру сайт твиттера реально работает на своем API). Для информации - сканер пабликов для контакта (тупой перебор чуть больше 44 млн ID) - отработал часа за три и благополучно собрал все 3217490 пабликов (это на вчерашний день, счас может и больше)
если используется API то конечно браузер не нужен ибо итак все инфа в нужном формате получается от сервера.
0
54 / 54 / 4
Регистрация: 15.12.2010
Сообщений: 260
26.10.2012, 13:46 11
не только .
2gis.ru - элементарный ajax пользуется, можно браузером в один поток "всасывать", а можно разобрать ajax запросы и потоков в 20-50 баренько забрать
1
5 / 5 / 0
Регистрация: 07.07.2012
Сообщений: 32
27.10.2012, 01:05  [ТС] 12
Спасибо за пример, вот допиливаю этот вариант и появилась проблемка, выбивает ошибку. Так при же разборе данных не могу разобраться в том, где какие данные

конкретно непонятно это

Pascal
1
2
3
4
5
6
7
8
9
10
11
12
     
 web.JqueryLoad; // подключаем Jquery 1.3
      web.RunJS(
        'var mass = []; var mass2 = [];'+
        '$("table#GridView1 tr").each(function(i){ ' +
        '  if (i==0) { return true; } '+
        '  mass.push(String($(this).find("a").eq(0).attr("href")).replace(/javascript:/i,"")); '+
        '  mass2.push($(this).find("td").eq(1).text()); '+
        '});'
      );
      web.getJsStrings('mass',s1);
      web.getJsStrings('mass2',ListBox1.Items)
Pascal
1
2
      web.RunJS('$("table#GridView1 tr").eq(0).remove();');
      Memo2.Text:=web.getElementById('table','GridView1').innerText;
По клику на ListBox1 по сути делал разбор информации и занос их в StringGrid1 информацию о рейсе, а в StringGrid2 информация об остоновкай этого маршрута. В последствии хотел заносить эти данные в бд, но до конца разобрать информацию не выходит.
Вложения
Тип файла: rar парсер.rar (178.2 Кб, 7 просмотров)
0
1302 / 708 / 107
Регистрация: 25.05.2011
Сообщений: 2,158
Записей в блоге: 51
27.10.2012, 09:26 13
после анализа блоков на сайте через FireBug там используется Jquery в качестве "парсера"

Формируем 2 массива var mass = []; var mass2 = [];

и заносим в них в цикле $.each нужные данные из блоков на странице.

так как массивы одномерные то их можно извлечь через

web.getJsStrings('mass',s1);
web.getJsStrings('mass2',ListBox1.Items)

web.RunJS('$("table#GridView1 tr").eq(0).remove();');
Memo2.Text:=web.getElementById('table','GridView1').innerText;
это по сути не совсем верно ибо надо разбирать каждую ячейку таблицы, но я этого не делал ибо зачем мне это?
документацию на Jquery можно найти в инете и почитать чо там как
0
5 / 5 / 0
Регистрация: 07.07.2012
Сообщений: 32
27.10.2012, 15:41  [ТС] 14
Ок, разберу тогда сам, вот на чем остановился мой проект. Помогите получить все данные из таблиц, а я уже сам разберу их. Читал про Jquery, там тип находим тег за который цепляемся и читаем с его, но как ни вникал не пробовал выводить в Вашем примере что в лэйблы и прочее, так и не понял его работу(( в добавок или он некорректно срабатывает или это так написано, от этого запутался еще больше((
Вложения
Тип файла: rar парс.rar (1.39 Мб, 6 просмотров)
0
1302 / 708 / 107
Регистрация: 25.05.2011
Сообщений: 2,158
Записей в блоге: 51
27.10.2012, 17:21 15
Delphi
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
unit Unit1;
 
interface
 
uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, OleCtrls, SHDocVw, jsWebbrowser, Grids, ExtCtrls, ComCtrls;
 
type
  TForm1 = class(TForm)
    Edit1: TEdit;
    Edit2: TEdit;
    Button1: TButton;
    Button2: TButton;
    ListBox1: TListBox;
    Label2: TLabel;
    Label5: TLabel;
    Panel1: TPanel;
    web: TWebBrowser;
    ProgressBar1: TProgressBar;
    StringGrid2: TStringGrid;
    procedure Button1Click(Sender: TObject);
    procedure webBeforeNavigate2(ASender: TObject;
      const pDisp: IDispatch; var URL, Flags, TargetFrameName, PostData,
      Headers: OleVariant; var Cancel: WordBool);
    procedure webDocumentComplete(ASender: TObject;
      const pDisp: IDispatch; var URL: OleVariant);
    procedure Button2Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure ListBox1DblClick(Sender: TObject);
    procedure webProgressChange(ASender: TObject; Progress,
      ProgressMax: Integer);
  private
    { Private declarations }
  public
    { Public declarations }
    s1: TStringList;
    function stringLine(const s:string):string;
  end;
 
var
  Form1: TForm1;
 
implementation
 
{$R *.dfm}
 
procedure TForm1.Button1Click(Sender: TObject);
begin
  web.tag:=0; // состояние 0
  web.Navigate('http://www.transtekhnika.by/raspisanie/version2/raspisanie.aspx');
end;
 
procedure TForm1.Button2Click(Sender: TObject);
begin
  // поиск
  if web.tag<>0 then exit;
  web.tag:=1;
  web.getElementById('input','TextBox1').setAttribute('value',Edit1.Text,0);
  web.getElementById('input','TextBox2').setAttribute('value',Edit2.Text,0);
  web.getElementById('input','Button1').click;
end;
 
 
procedure TForm1.FormCreate(Sender: TObject);
begin
  s1:=TStringList.Create;
end;
 
procedure TForm1.ListBox1DblClick(Sender: TObject);
begin
  if not Button1.Enabled then exit;
  if ListBox1.ItemIndex<0 then exit;
  web.tag:=2;
  web.RunJS(s1[ListBox1.ItemIndex]);
end;
 
function TForm1.stringLine(const s: string): string;
begin
  result:=StringReplace(s,#13,' ',[rfReplaceAll]);
  result:=StringReplace(result,#10,'',[rfReplaceAll]);
end;
 
procedure TForm1.webBeforeNavigate2(ASender: TObject;
  const pDisp: IDispatch; var URL, Flags, TargetFrameName, PostData,
  Headers: OleVariant; var Cancel: WordBool);
begin
  Button1.Enabled:=false;
  Button2.Enabled:=false;
end;
 
procedure TForm1.webDocumentComplete(ASender: TObject;
  const pDisp: IDispatch; var URL: OleVariant);
var trs,tds: helems; i,j:integer;
begin
  Button1.Enabled:=true;
  Button2.Enabled:=true;
  // состояния браузера
  case web.tag of
    0: begin
      // главная страница
      exit;
    end;
    1: begin
      // получаем данные
      web.JqueryLoad; // подключаем Jquery 1.3
      web.RunJS(
        'var mass = []; var mass2 = [];'+
        '$("table#GridView1 tr").each(function(i){ ' +
        '  if (i==0) { return true; } '+
        '  mass.push(String($(this).find("a").eq(0).attr("href")).replace(/javascript:/i,"")); '+
        '  mass2.push($(this).find("td").eq(1).text()); '+
        '});'
      );
      web.getJsStrings('mass',s1);
      web.getJsStrings('mass2',ListBox1.Items);
    end;
    2: begin
      // показываем в списке
      web.getElementsByTagName('tr',trs);
      StringGrid2.RowCount:=length(trs);
      for i := 0 to high(trs)-1 do begin
        web.getElementChildren(trs[i],tds);
        if StringGrid2.ColCount < Length(tds) then StringGrid2.ColCount := Length(tds);
        for j := 0 to high(tds) do
          StringGrid2.Cells[j,i]:=stringLine(tds[j].innerText);
      end;
      web.tag:=0;
      web.GoBack;
    end;
  end;
  //web.tag:=-1;
end;
 
procedure TForm1.webProgressChange(ASender: TObject; Progress,
  ProgressMax: Integer);
begin
  ProgressBar1.Max:=ProgressMax;
  ProgressBar1.Position:=Progress;
end;
 
end.
Миниатюры
Парсер для результатов запроса  
Вложения
Тип файла: rar parsWebJS3.1.rar (760.9 Кб, 5 просмотров)
0
1302 / 708 / 107
Регистрация: 25.05.2011
Сообщений: 2,158
Записей в блоге: 51
27.10.2012, 17:31 16
впринципе можно первую таблицу также распарсить также как вторую через getElementsByTagName и getElementChildren
только надо будет дополнительно всеже сохранить содержимое ссылок в s1 через getJsStrings

Добавлено через 5 минут
**
UPD там еще баг в цикле. Надо писать for i := 0 to high(trs) do begin
1
5 / 5 / 0
Регистрация: 07.07.2012
Сообщений: 32
28.10.2012, 13:09  [ТС] 17
Это я так понял вот тут
Pascal
1
2
3
4
5
6
7
8
9
10
11
12
13
    2: begin
      // показываем в списке
      web.getElementsByTagName('tr',trs);
      StringGrid2.RowCount:=length(trs);
      for i := 0 to high(trs)-1 do begin 
        web.getElementChildren(trs[i],tds);
        if StringGrid2.ColCount < Length(tds) then StringGrid2.ColCount := Length(tds);
        for j := 0 to high(tds) do
          StringGrid2.Cells[j,i]:=stringLine(tds[j].innerText);
      end;
      web.tag:=0;
      web.GoBack;
    end;
Ведь при такой записи будет выдоваться результать проглатывая последнюю запись

Pascal
1
2
3
4
5
6
7
8
9
10
11
12
13
    2: begin
      // показываем в списке
      web.getElementsByTagName('tr',trs);
      StringGrid2.RowCount:=length(trs);
       for i := 0 to high(trs) do begin 
        web.getElementChildren(trs[i],tds);
        if StringGrid2.ColCount < Length(tds) then StringGrid2.ColCount := Length(tds);
        for j := 0 to high(tds) do
          StringGrid2.Cells[j,i]:=stringLine(tds[j].innerText);
      end;
      web.tag:=0;
      web.GoBack;
    end;
Исправил как сказали все заработало. Сейчас буду пробовать разбирать первую таблицу, скоро покажу что вышло
0
5 / 5 / 0
Регистрация: 07.07.2012
Сообщений: 32
28.10.2012, 19:09  [ТС] 18
procedure TEmbeddedWB. getElementsByTagName
(tag:string; var elements: Helems; attr: string = ''; value: string = '');


Записывает в elements все элементы удоволетворяющие следующим условиям

tag = название тега
attr = название аттрибута
value = значение аттрибута

Это весьма помогло

И так, сделал как и было сказано, разабрал по такому же принципу данные с первой таблицы. Только вот с закрытыми полями незадача, там тип другая пара тегов
Миниатюры
Парсер для результатов запроса  
Вложения
Тип файла: rar парс.rar (1.69 Мб, 9 просмотров)
0
1302 / 708 / 107
Регистрация: 25.05.2011
Сообщений: 2,158
Записей в блоге: 51
28.10.2012, 19:40 19
анализируй tds[j].innerHTML на наличие в нем подстроки checked

Delphi
1
if pos('checked',tds[j].innerHTML)>0 then StringGrid2.Cells[j,i]:='+';
1
5 / 5 / 0
Регистрация: 07.07.2012
Сообщений: 32
30.10.2012, 23:07  [ТС] 20
Не выходит, вот что пишу для заполнения первой таблицы

Pascal
1
2
3
4
5
6
7
8
9
10
11
12
13
      web.getJsStrings('mass',s1);
      web.getJsStrings('mass2',ListBox1.Items);
      web.getElementsByTagName('tr',trs);
      StringGrid1.RowCount:=length(trs);
      for i := 0 to high(trs) do begin
        web.getElementChildren(trs[i],tds);
         if StringGrid1.ColCount < Length(tds) then StringGrid1.ColCount := Length(tds);
        for j := 0 to high(tds) do
          
if pos('checked',tds[j].innerHTML)>0 then StringGrid2.Cells[j,i]:='+';
 
             StringGrid1.Cells[j,i]:=stringLine(tds[j].innerText);
       end;
Или я что то не так понял??
0
30.10.2012, 23:07
IT_Exp
Эксперт
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
30.10.2012, 23:07
Помогаю со студенческими работами здесь

Парсер страницы сайта с добавлением результатов в DGV
Добрый вечер! Как написать простейший парсер на C#? Можно ли объяснить на примере парсера...

Сортировка результатов запроса
Здравствуйте, имеется таблица для обменивания сообщениями. Структура: id | to | from | date |...

Нумерация результатов запроса
Имеется MS SQL Server 7.0. Пусть запрос SELECT name1 FROM table1 выдает такие результаты: ...

Объединение результатов запроса
Добрый день. Есть несколько таблиц - пусть: A, B, C. В этих таблицах есть только одно совпадающее...


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

Или воспользуйтесь поиском по форуму:
20
Ответ Создать тему
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2024, CyberForum.ru