Вот небольшая простая программа для извлечения содержимого из URL-адресов для создания информационного бюллетеня по электронной почте в формате HTML с использованием Ruby и драгоценного камня Nokogiri.

Нам потребуются необходимые гемы и файлы. «записи» — это место, где будут находиться наши входные URL-адреса внутри модуля. «константы» будут там, где мы помещаем строку нашего пользовательского агента, фрагменты статического HTML-кода информационного бюллетеня и другие вещи, которые вы хотите отделить от своей логики.

require ‘nokogiri’
require ‘open-uri’
require_relative ‘entries’
require_relative ‘constants’

Мы создаем базовый класс URL с атрибутами «имя» и «записи». Мы сохраним саму строку URL-адреса в переменной экземпляра name и инициализируем записи пустым массивом, который позже будет заполнен удаленными данными.

class Url 
 attr_accessor :entries, :name
 def initialize(name)
 @name = name
 @entries = []
 end
end

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

class Hp_url < Url
 def initialize(name)
  super
 end
 
 def process
  doc = Nokogiri::HTML(open(name, ‘User-Agent’ =>   Constants::USER_AGENT), nil, “UTF-8”)
 
 doc.xpath(“//h1”).each_with_index do |link,i|
  @entries << link.text if i == 3 #conversations with …
 end
 doc.xpath(“//h4”).each_with_index do |link,i| #Intro
  @entries << link.text if i == 1
 end
doc.xpath(“//div[@class=’full-banner-img hidden-xs’]/@style”).each_with_index do |link,i| #Image
  @entries << link.to_s[23..-3] #image
 end
 doc.css(“.rotated-title”).each_with_index do |link,i|
  @entries << link.text #cat
 end
 @entries << name
 puts ‘waiting …’
 sleep 5
 end
 
end
class Trend_url < Url
 def initialize(name)
  super
 end
 
 def process
  doc = Nokogiri::HTML(open(name, ‘User-Agent’ => Constants::USER_AGENT), nil, “UTF-8”)
 doc.xpath(“//h1”).each_with_index do |link,i|
  @entries << link.text if i == 2 #conversations with …
 end
 doc.xpath(“//h4”).each_with_index do |link,i| #Intro
  @entries << link.text if i == 1
 end
 doc.xpath(“//img/@src”).each_with_index do |link,i| #Image
  @entries << link if i == 54 #image
 end
 doc.css(“.rotated-title”).each_with_index do |link,i|
  @entries << link.text #cat
 end
 entries << name
 puts ‘waiting …’
 sleep 6
 end
 
end
class Story_url < Url
 def initialize(name)
  super
 end
 
 def process
  doc = Nokogiri::HTML(open(name, ‘User-Agent’ => Constants::USER_AGENT), nil, “UTF-8”)
 doc.xpath(“//h1”).each_with_index do |link,i|
  @entries << link.text if i == 3 #title
 end
 doc.xpath(“//p”).each_with_index do |link,i|
  @entries << link.text if i == 2 #content
 end
 doc.xpath(“//div[@class=’banner’]/@style”).each_with_index do |link,i|
  @entries << link.to_s[23..-3] #image
 end
 doc.xpath(“//h1/a”).each_with_index do |link,i|
  @entries << link.text #cat
 end
 @entries << name
 puts ‘waiting …’
 sleep 7
 end
 
end
class Bp_url < Url
 def initialize(name)
  super
 end
 
 def process
  doc = Nokogiri::HTML(open(name, ‘User-Agent’ => Constants::USER_AGENT), nil, “UTF-8”)
 doc.xpath(“//h1”).each_with_index do |link,i| #Intro
  @entries << link.text if i == 3 #title
 end
 @entries << name
 puts ‘waiting …’
  sleep 6
 end
 
end
class Lc_url < Url
 def initialize(name)
  super
 end
 
 def process
  doc = Nokogiri::HTML(open(name, ‘User-Agent’ => Constants::USER_AGENT), nil, “UTF-8”)
 doc.xpath(“//h1”).each_with_index do |link,i| #Intro
  @entries << link.text if i == 3 #title
 end
 @entries << name
 puts ‘waiting …’
 sleep 8
 end
 
end

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

Теперь, когда переменные экземпляра наших «записей» могут быть заполнены, давайте создадим класс DataLoader (ниже), который создаст для нас объекты URL. Мы проверяем, пусты ли входы, прежде чем делать это.

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

Эти массивы данных позже повторяются, чтобы «клонировать» биты HTML, которые мне нужны, что делается с помощью методов build_*, которые принимают соответствующие данные в качестве аргумента.

class DataLoader
 
 attr_accessor :story_data, :bp_data, :lc_data
 def initialize
  @@url_instances = []
  @story_data = []
  @bp_data = []
  @lc_data = []
 
  if not URLS::HP_URLS.empty?
   URLS::HP_URLS.each{|url| @@url_instances << Hp_url.new(url) }
  end
 
  if not URLS::TREND_URLS.empty?
   URLS::TREND_URLS.each{|url| @@url_instances << Trend_url.new(url) }
  end
 
  if not URLS::STORY_URLS.empty?
   URLS::STORY_URLS.each{|url| @@url_instances << Story_url.new(url) }
  end
 
  if not URLS::BP_URLS.empty?
   URLS::BP_URLS.each{|url| @@url_instances << Bp_url.new(url) }
  end
 
  if not URLS::LC_URLS.empty?
   URLS::LC_URLS.each{|url| @@url_instances << Lc_url.new(url) }
  end
   p @@url_instances
end
 
 def stage
  @@url_instances.each do |url_instance|
  # This populates entries for each url object with scrapped data
  url_instance.process
 
  case url_instance.class.to_s
  when “Hp_url”
   @story_data << url_instance.entries
  when “Trend_url”
   @story_data << url_instance.entries
  when “Story_url”
   @story_data << url_instance.entries
  when “Bp_url”
   @bp_data << url_instance.entries
  when “Lc_url”
   @lc_data << url_instance.entries
  else
   raise “#{url_instance.class} is unknown to us.”
  end
  end
 end
 def build_lc(lc_data)
  story_snippets = “”
  lc_data.each do |entry|
  story_snippets << %Q{
  <div mc:edit=”lc_item”>
  <blockquote>
  <p><a href=”#{entry[1]}”>#{entry[0]}</a><br>
  <span class=”red-texts”></span> 
  </p>
  </blockquote>
  </div>
  }
  end
  story_snippets
 end
 
 def build_bp(bp_data)
  story_snippets = “”
  bp_data.each do |entry|
  story_snippets << %Q{
  <div class=”company-info”>
  <blockquote>
  <p><a href=”#{entry[1]}”>#{entry[0]}</a><br />
  <span class=”blue-texts”></span><br>
  </p>
  </blockquote>
  </div>
 }
  end
  story_snippets
 end
 
 def build_stories(story_data)
  story_snippets = “”
  story_data.each_with_index do |entry,i|
  if i == 0 
  
  story_snippets << %Q{
  <div id=”featured”>
  <div class=”full” mc:edit=”featured_article”><a href=”#{entry[4]}”  target=”_blank”><img class=”full” src=”#{entry[2]}”></a>
  <div class=”full-content”> <a href=”#{entry[4]}” target=”_blank” alt=””><img class=”hnworth-logo”  src=””></a>
 <h4>#{entry[3]}</h4>
 <h1><a href=”#{entry[4]}” target=”_blank”>#{entry[0]}</a></h1>
 <p>#{entry[1]}</p>
 </div>
 </div>
 </div>
 } 
  else
  story_snippets << %Q{
  <div id=”featured”>
  <div class=”full” mc:edit=”featured_article”><a href=”#{entry[4]}” target=”_blank”><img class=”full” src=”#{entry[2]}”></a>
  <div class=”full-content”> 
  <h4>#{entry[3]}</h4>
  <h1><a href=”#{entry[4]}” target=”_blank”>#{entry[0]}</a></h1>
  <p>#{entry[1]}</p>
  </div>
  </div>
  </div>
 } 
  end
  end
  story_snippets
 end
 
end

Затем мы объединяем все это с классом Newsletter, который создает экземпляр класса DataLoader, который создает все необходимые нам объекты URL.

Затем метод экземпляра этапа передает метод процесса каждому из созданных нами объектов URL, хранящихся в переменной класса @@url_instances. Это заполняет переменные экземпляра записей. В операторе case мы заполняем массивы данных в зависимости от типа URL-адреса вызывающего абонента.

Затем мы создаем наш класс Newsletter, который связывает все в маленький комочек радости. Метод сборки запускает все необходимые объекты URL, создавая новый экземпляр класса DataLoader. Отправка в него метода stage запускает записки и группирует данные в правильный массив, прежде чем они будут повторены.

Мы добавляем биты HTML в нашу переменную экземпляра html перед созданием файла.

class Newsletter
attr_accessor :html, :file_name
 def initialize
 @html = “”
 @file_name = “”
 end
 
 def build
  puts Constants::INTRO_ART
  content = DataLoader.new
  content.stage
  @html << Constants::NEWSLETTER_INTRO
  @html << content.build_stories(content.story_data)
  @html << Constants::NEWSLETTER_STORY_END
  @html << content.build_bp(content.bp_data)
  @html << Constants::NEWSLETTER_BP_END
  @html << content.build_lc(content.lc_data)
  @html << Constants::NEWSLETTER_END
  create_html
 end
 
 def create_html
  name = Time.new.strftime(“%b%d%Y”)
  file_name = “newsletter_”+name+”.html”
  file = File.open(file_name, ‘a:UTF-8’) do |file|
  file.write(@html)
 end
  puts “EDM generated.”
 end
 
end