SOFT
BLOG
&
Логин
Имя:
Пароль:

Refbot. Программа и исходный код

В этом разделе представлена сама программа, а также исходный код. В первую очередь хочу отметить, что программа не является мультипоточной, работает всего лишь в один поток. В программе нет конвертации кириллических доменов в punycode, поэтому программа не работает с доменами в зоне РФ, но вариант конверации приведу в данном документе.

Логика работы бота

Рефбот, как я писал в вводной статье, в первую очередь выполняет HTTP-запрос вэбсерверу. В заголовке запроса он передает данные и реферер. Приведу пример такого запроса:

GET / HTTP/1.1
Host: target_host.com
Referer: our_site.ru
Connection: close
User-Agent: Refbot/1.0 (+http://our_site.ru)
Accept-Encoding: gzip
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Cache-Control: no-cache

Это GET запрос к серверу,  Host - запрашиваемый домен, Referer указывает откуда пришел бот, User-Agent сообщает серверу тип бота. Остальное это вид сжатия, который воспринимает бот. На самом деле в данном проекте бот не использует содержимое страниц, но для экономии трафика он "запрашивает" сжатые данные.

Данные о сайтах поступают из базы данных refbot, которую мы создали и загрузили, этот процесс описан в предыдущем документе из этого раздела. После выполнения запроса бот получает HTTP-код сервера. Эти коды описаны в той же предыдущей статье. Далее бот осуществляет запись в базу данных и получает следующий адрес для обработки. Весь процесс работы бота автоматический и циклический.

Внешний вид

Внешний вид программы refbot Сама программа это диалоговое окно, с элементами управления и настройки. В первом гроупбоксе производится настройка базы данных MySQL, кнопка тест осуществляет проверку подключения к БД. Если настройки базы отличные от стандартных, то в программе запишите правильные. В случае успешной проверки рефбот сообщит, что тест пройден. После чего отобразит текущие статистические данные.

Во втором гроупбоксе указывается наименование бота, официально - Refbot/1.0 и "продвигаемый" сайт, кнопка старт (стоп).

В 3-ем боксе приводятся статистические данные. Здесь указано количество сайтов, сколько выполнено и сколько осталось. В последнем боксе ведется небольшой пятистрочный лог. Сам лог не записывается в БД или в файл,  в основном будете использовать для мониторинга работы.

Работа рефбота

Запуск программы осуществляется с отдельном потоке, он не блокирует форму программы. Осуществляется блокирование первого гроупбокса. Остановка осуществляется той же кнопкой старт (после запуска она именуется как "стоп"), смотрите скриншот программы.

Файлы проекта

Файлы проекта рефботЭто стандартный набор файлов проекта Refbot. Используются стандартные библиотеки, которые устанавливаются вместе Лазарусом. Для сохранения настроек программы используется файл-конфигурации config.ini. Для работы с БД MySQL используется клиент libmysql.dll версии 5.5, если Вы будете использовать другой коннектор например низкой версии, то необходимо менять dll на соответствующую версию. В Лазарус имеется несколько версии коннекторов: от 4.1 до 5.5.

Файл иконки refbot.ico. Refbot.exe - сама программа.

Исходный код проекта

Детально описывать весь код не буду, приведу лишь основные части кода.

Для основного потока создается класс-потока TMyThread = class(TThread). В TMyThread.Execute осуществляется основной цикл программы:

while (not Terminated) do
begin
    try
      Form1.MySQL55Connection1.Connected:=true;
    except
      status := 'ошибка подключения БД!';
      Synchronize(@Showstatus);
      Synchronize(@Exit);
      Terminate;
    end;
     Form1.SQLQuery1.Close;
     Form1.SQLQuery1.SQL.Text :=
       'SELECT id,LOWER(site) AS domain FROM sites WHERE ready = FALSE LIMIT 1;';
     Form1.SQLQuery1.Open;
     if Form1.SQLQuery1.EOF then
     begin
         status := 'Работа завершена!';
         Synchronize(@CountShow);
         Synchronize(@Showstatus);
         Synchronize(@Exit);
         Terminate;
     end;
     id := Form1.SQLQuery1.FieldByName('id').AsString;
     domain := Form1.SQLQuery1.FieldByName('domain').AsString;
     Form1.SQLQuery1.Close;
     temp := Form1.theget(domain);

     if LeftStr(temp,1) = '2' then
       Form1.SQLQuery1.SQL.Text:=
        'UPDATE sites SET ready=TRUE, err=FALSE, code="200" WHERE id='+id;
     if temp = 'error' then
       Form1.SQLQuery1.SQL.Text:=
        'UPDATE sites SET ready=TRUE, err=TRUE, code="err" WHERE id='+id;
     if LeftStr(temp,1) = '4' then
       Form1.SQLQuery1.SQL.Text:=
        'UPDATE sites SET ready=TRUE, err=TRUE, code="'+temp+'"  WHERE id='+id;
     if LeftStr(temp,1) = '3' then
       Form1.SQLQuery1.SQL.Text:=
        'UPDATE sites SET ready=TRUE, err=TRUE, code="'+temp+'"  WHERE id='+id;
     if LeftStr(temp,1) = '5' then
       Form1.SQLQuery1.SQL.Text:=
        'UPDATE sites SET ready=TRUE, err=TRUE, code="'+temp+'"  WHERE id='+id;
     try
         Form1.SQLQuery1.ExecSQL;
     except
         status := 'Ошибка записи в БД!';
         Synchronize(@Showstatus);
          Terminate;
     end;
     Synchronize(@CountShow);
     status := domain+' - '+temp;
     Synchronize(@Showstatus);
     Form1.MySQL55Connection1.Connected:=false;
end;
Synchronize(@Exit);

Форма основного окна рефботаДля работы с БД на форме присутствуют коннекторы базы, это MySQL55Connection1, SQLTransaction1, SQLQuery1. Эти элементы соединены между собой и посредством этой группы осуществляется работа с сервером MySQL. Бот с помощью запроса SELECT id,LOWER(site) AS domain FROM sites WHERE ready = FALSE LIMIT 1 получает следующий необработанный адрес сайта.

В файле конфигурации производится сохранение и восстановление настроек, используется компонент IniPropStorage1.

Процедура Counter осуществляет запрос в БД, выводит текущую информацию. Запрос выглядит следующим образом: SELECT COUNT(*) AS total, cpl, (COUNT(*)-cpl) AS balance FROM sites,(SELECT COUNT(*) AS cpl FROM sites WHERE ready = TRUE) AS t. Эта функция запускается всего лишь один раз, во время старта программы. Для дальнейшего подсчета используется встроенная процедура потока TMyThread.CountShow. Внутри потока имеется свой счетчик, переменная cnt. Значение этой переменной инкрементируется и осуществляется обычная математика из известных данных. Дело в том, что каждый раз осуществлять подсчет данных с помощью БД не самая правильная идея, так как сервер при подсчете затратит много времени и скорость работы программы существенно снизится.

Функция theget осуществляет соединение с WEB-сервером с помощью сокетов, передает  HTTP запрос и получает ответ. Для таймаута функции установлен временной интервал 2 секунды на подключение и 3 секунды на обмен информацией:

  fillchar(sock.recv,1024,#0);
  fillchar(sock.send,1024,#0);
  WSAStartup($101,wsa);
  sock.host := domain;
  sock.port := 80;
  if sock.uri='' then sock.uri:='/';
  sock.sock:=socket(AF_INET,SOCK_STREAM,0);
  sock.time:=4;
  temp:='GET '+sock.uri+' HTTP/1.1'+#13#10+
          'Host: '+sock.host+#13#10+
          'Referer: http://'+site+#13#10+
          'Connection: close'+#13#10+
          'User-Agent: '+bot+' (+http://'+site+')'+#13#10+
          'Accept-Encoding: gzip'+#13#10+
          'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'+#13#10+
          'Cache-Control: no-cache'+#13#10#13#10;
  sock.send:=temp;
  sock.size:=length(temp);
  ioctlsocket(sock.sock,FIONBIO,on_sock);
  addr.sin_family:=AF_INET;
  addr.sin_port:=htons(sock.port);
  addr.sin_addr.s_addr:=d_addr(sock.host);
  connect(sock.sock,addr,sizeof(addr));
  sock.stat:=CONNECTING_SOCK;
  FD_ZERO(wfds);
  wfds_empty:=true;

  wait:=true;
  while wait do
  begin
    if (sock.stat=CONNECTING_SOCK) and (sock.status='') then
       begin
         FD_SET(sock.sock,wfds);
         wfds_empty:=false;
       end;
    if not wfds_empty then
    begin
      tv.tv_sec:=0;
      tv.tv_usec:=500000;
      select(0,nil,@wfds,nil,@tv);
    end;
    if (sock.stat<>CLOSE_SOCK) and (sock.status='') then
      begin
        sleep(5);
        dec(sock.time);
        if sock.time<=0 then
        begin
          sock.stat:=CLOSE_SOCK;
          sock.status:='timeout';
          ioctlsocket(sock.sock,FIONBIO,off_sock);
          closesocket(sock.sock);
        end;
      end;

      if (sock.stat=CONNECTING_SOCK) and (sock.status='') then
      begin
        if FD_ISSET(sock.sock,wfds) then
          begin
          sock.stat:=CONNECTED_SOCK;
          end;
      end;

      if (sock.stat=CONNECTED_SOCK) and (sock.status='') then
      begin
        FD_CLR(sock.sock,wfds);
        send(sock.sock,sock.send,sock.size,0);
        sock.stat:=SENDED_SOCK;
      end;

      if (sock.stat=SENDED_SOCK) and (sock.status='') then
      begin
         temp:='';
         read:=true;
         a:=0;
         b:=0;
         recvwait:=false;
         while read do
         begin
           if a=100 then read:=false;
           inc(a);
           fillchar(sock.recv,1024,#0);
           recv(sock.sock,sock.recv,1024,0);
           if sock.recv<>'' then recvwait:=true;
           if temp2<>sock.recv then
           begin
             temp2:=sock.recv;
             temp+=sock.recv;
           end
           else
           if recvwait then
           begin
                if (b=1) then
                read:=false
                else inc(b);
           end;
           sleep(30);
         end;
         sock.status:='ok';
         sock.stat:=CLOSE_SOCK;
         ioctlsocket(sock.sock,FIONBIO,off_sock);
         closesocket(sock.sock);
      end;

  wait:=false;
  if sock.status='' then wait:=true;
  end;
  WSACleanup;
  if (sock.status<>'ok') OR (temp = '') then
  begin
    result:='error';
    exit;
  end;
  reg:=TRegExpr.Create;
  reg.Expression:='([^"HTTP/1.1 "][0-9]+)';
  reg.Exec(temp);
  result:=reg.Match[0];
  reg.Free;

Исходный код 1,25Мб и программа Refbot 4,6Мб (отдельно без исходного кода).

В следующей статье приведу немного информации о работе и результатах работы рефбота.