Вот небольшая простая программа для извлечения содержимого из 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