Проверка количества проиндексированных страниц:

Posted on March 27, 2009
Разные поисковые машины по разному индексируют сайт. Для того, что-бы узнать сколько страниц проиндексировал поисковик существуют различные способы.
Недавно мой блог был побанен ндексом (до сих пор не понимаю за что) и после написания пары писем в саппорт я решил написать простой скрипт для отслеживания того, появились ли в выдачи страницы с моего блога:

require 'hpricot'
require 'mechanize'

class YaChecker
  class << self
    def get_pages(url)
      url = "http://m.yandex.ru/search?query=host%3D%22www.#{url}%22%20|%20host%3D%22#{url}%22"
      agent = WWW::Mechanize.new
      agent.user_agent_alias = 'Mac Safari'
      utf_page = agent.get_file(url)
      doc = Hpricot(utf_page)
      pages = doc.search("//div[@class='result-txt']")
      result = pages[0] 

      return 0 if result.inner_text == "\nИскомая комбинация слов нигде не встречается\n"
      return result.inner_text.gsub(/Нашлось/,'').to_i
    end
  end
end
Воодушевившись результатом я написал так-же проверялку для Google:

require 'hpricot'
require 'mechanize'

class GooChecker
  class << self
    def get_pages(url)
      url = "http://www.google.ru/search?hl=ru&q=site%3A#{url}+|+site%3Awww.#{url}&btnG=%D0%9F%D0%BE%D0%B8%D1%81%D0%BA+%D0%B2+Google&lr=&aq=f&oq="
      agent = WWW::Mechanize.new
      agent.user_agent_alias = 'Mac Safari'
      utf_page = agent.get_file(url)
      doc = Hpricot(utf_page)
      pages = doc.search("//div[@id='ssb']/p/b[3]")
      return 0 if pages[0].nil?
      pages[0].innerText.to_i
    end
  end
end
Если кому понадобится, то пользуйтесь на здоровье

Поправка к предыдущей статье

Posted on March 20, 2009
В предыдущей статье я совершил довольно большую ошибку, спасибо Dmytro Shteflyuk за критику. На самом деле я выбрал самый медленный способ выборки случайной строки:

SELECT * FROM banners ORDER BY rand() LIMIT 0,1
Более быстрый способ подразумевает 2 запроса:

SELECT COUNT(*) FROM banners;
-- Мы выбрали число строк. Теперь в коде выберем от них случайное значение
-- и второй запрос на выборку строки и подставим его вместо rnd
SELECT * FROM banners LIMIT rnd,1;

Создание баннерной системы на rails часть 1.

Posted on March 19, 2009
В связи с выходом нового rails 2.3.2 появилось множество возможностей ускорить отклик приложения.
Одним из таких способов является Rails Metal. Благодаря нему возможно обрабатывать запросы в обход всей машинерии, что дает огромное преимущество в скорости работы.
Для примера я решил создать приложение для баннерной сети. Приложение крайне просто и используется только как пример.
Вначале создадим новое приложение с помощью шаблона:

git :init

plugin 'restful-authentication', :git => 'git://github.com/technoweenie/restful-authentication.git', :submodule => true
plugin 'haml', :git => "git://github.com/nex3/haml.git", :submodule => true
plugin 'paperclip', :git => "git://github.com/thoughtbot/paperclip.git", :submodule => true
plugin 'acts_as_taggable_redux', :git => 'git://github.com/geemus/acts_as_taggable_redux.git', :submodule => true
plugin 'will_paginate', :git => 'git://github.com/mislav/will_paginate.git', :submodule => true
plugin 'role_requirement', :git => 'git://github.com/timcharper/role_requirement.git', :submodule => true


run "touch tmp/.gitignore log/.gitignore vendor/.gitignore"
run %{find . -type d -empty | grep -v "vendor" | grep -v ".git" | grep -v "tmp" | xargs -I xxx touch xxx/.gitignore}
file '.gitignore', <<-END
.DS_Store
log/*.log
tmp/**/*
config/database.yml
db/*.sqlite3
END
run "rm README"
run "rm public/index.html"
run "rm public/favicon.ico"
run "rm public/robots.txt"


generate("authenticated", "user session")
generate("roles", "Role User")

rake('db:create')
rake('db:sessions:create')
rake('acts_as_taggable:db:create')

initializer 'session_store.rb', <<-END
        ActionController::Base.session = { :session_key => '_#{(1..6).map { |x| (65 + rand(26)).chr }.join}_session', :secret => '#{(1..40).map { |x| (65 + rand(26)).chr }.join}' }
        ActionController::Base.session_store = :active_record_store
END


git :submodule => "init"

git :add => '.'
git :commit => "-a -m 'Initial commit'"

с помощью комманды

rails . -m ../template.txt

После этого можно создать обработчик запроса баннера (показ) и обработчик клика на баннер.


script/generate metal get_banner
Появившийся после этого файл app/metal/get_banner.rb выглядит так:

class GetBanner
  def self.call(env)
    if env["PATH_INFO"] =~ /^\/get_banner/
      [200, {"Content-Type" => "text/html"}, "Hello, World!"]
    else
      [404, {"Content-Type" => "text/html"}, "Not Found"]
    end
  end
end

Теперь стоит подумать об организации базы данных для того, что-бы наше преимущество в скорости обработки запроса не съедалось базой данных.
У нас имеется набор объектов WebSite (площадок), набор рекламных проектов (Projects) и набор баннеров в рекламных проектах (Banners). При этом каждый проект имеет тэги и каждая площадка тоже имеет набор тэгов. Нам необходимо создать сущность которая будет звеном между сайтом и баннером. Для того-чтобы выбор баннеров был простым запросом к БД, например:


SELECT *, MD5(RAND()) as rand  FROM sites_banners WHERE site_id=1 ORDER BY rand LIMIT 0,1
А так-же необходимо все показы и клики записывать в другую таблицу с дальнейшим обсчетом по деньгам.

Создадим базовые модели:


script/generate resource website url:string user_id:integer enabled:boolean
script/generate resource project user_id:integer name:string enabled:boolean
script/generate resource banner user_id:integer image_file_name:string image_content_type:string image_file_size:integer project_id:integer enabled:boolean
И отредактируем файлы миграции для установления значения enabled в true. (Мы заботимся о наших пользователях ;))

После этого наши модели будут выглядеть примерно так:


class Banner < ActiveRecord::Base
  belongs_to :project
  belongs_to :user
  has_attached_file :image
  validates_attachment_content_type :image, :content_type => ['image/jpeg', 'image/gif']
end

class Project < ActiveRecord::Base
  has_many :banners
  belongs_to :user
  validates_presence_of :name
end


class Website < ActiveRecord::Base
  belongs_to :user
  validates_presence_of :url
  validates_format_of :url, :with =>  /^(http|https):\/\/[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(([0-9]{1,5})?\/.*)?$/ix
end

Так-же необходимо добавить в модель юзера строки:

has_many :websites
has_many :projects
has_many :banners

Теперь перейдем к работе со статистикой, для этого создадим модель BannerView


script/generate model banner_view website_id:integer banner_id:integer request_ip:string referrer:string
Именно в нее и будет писаться вся статистика по просмотру баннеров. В дальнейшем будет производится обработка данных этой таблицы с целью извлечения из нее необходимых данных с удалением старых. Для ускорения работы модель не имеет ORM связей.

Теперь перейдем к нашему metal раздатчику баннеров. В этой части статьи будем использовать самый примитивный выбор баннера - случайный из всех
поэтому его код будет выглядеть так:


require(File.dirname(__FILE__) + "/../../config/environment") unless defined?(Rails)

class GetBanner
  def self.call(env)
    if env["PATH_INFO"] =~ /^\/get_banner\/(\d+)/
      
      banner = Banner.find :first, :order => 'rand()'
      
      unless banner.nil?
        BannerView.create(
          :website_id => $1,
          :request_ip => env['REMOTE_ADDR'],
          :banner_id => banner.id,
          :referrer => env['HTTP_REFERER']
        )
        return [301, {"location" => banner.image.url}, []]
      end
      [404, {"Content-Type" => "text/html"}, ["Not availible"]]
    else
      [404, {"Content-Type" => "text/html"}, ["Not Found"]]
    end
  end
end
Эта операция делает 2 довольно быстрых SQL запроса.

В следующей статье я напишу биллинг и более "качественный" выбор баннера


Использование процессорных мощностей посетителей сайта. Паразитические вычисления.

Posted on March 04, 2009
Практически все современные браузеры обладают возможностью довольно быстро исполнять javascript.
Благодаря этому появилась возможность использовать посетителей сайта как ноды кластера для обсчета больших массивов данных.
Такое явление называется паразитическими вычислениями. Естественно что алгоритм должен быть хорошо параллелящимся и отдельные части вычисления должны проходить достаточно быстро.
Примером таких вычислений может быть обсчет матриц (нахождение определителя) или подсчет md5 хешей.
Так-же, поскольку все происходит без нашего контроля (обсчет на клиентских машинах) необходимо проверять результат вычисления. Одним из способов может быть сравнение нескольких результатов от разных нод с целью определить какое правильное методом "голосования".
Для примера я написал простое приложение которое обсчитывает md5 хеши.
Приложение расположено по адресу http://parasite.tumalevich.pp.ru/index.html.

Рассмотрим работу скрипта "паразита".
После инициализации он забирает с сервера список задач. В данном случае массив со строками.
После чего устанавливает интервал на то, что-бы раз в 500 миллисекунд обработать одну задачу и отправить ее на сервер.
После того, как список опустошится все повторяется сначала. Результат действия можно наблюдать в таблице в которую выводится результат.

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