УДАЛЕНИЕ ВЕБ-СТРАНИЦ
Веб-скрапинг стал проще с Ruby
Изучите основы веб-парсинга с Nokogiri Gem
В качестве примера предположим, что мы хотим увидеть ведущих авторов на данном носителе, которые пишут под данным именем тега, например Ruby on Rails
?
Для этого нет API https://github.com/Medium/medium-api-docs
Посмотрим, что можно сделать быстро. Позже мы постараемся использовать эти данные в более читаемом формате.
Во-первых, давайте разберемся с основами. Чтобы очистить информацию, нам нужно знать, по какому URL мы хотим попасть.
Следовательно, URL был https://medium.com/tag/ruby-on-rails/latest, и нам нужен список всех ведущих авторов.
Настроить скребок
Предварительные требования:
- Ruby 2.0.0 или новее
- Самоцветы
open-uri
nokogiri
pry
table-print
Давайте создадим файл medium_scraper.rb
touch medium_scraper.rb
и сохраните его с помощью следующего кода:
# medium_scraper.rb unless ARGV[0] puts "Please enter desired url" puts "Help: ruby medium_scraper.rb <tag>" exit end
Давайте запустим ruby medium_scraper.rb
, и вы должны увидеть подсказку:
❯ ruby medium_scraper.rb Please enter desired url Help: ruby medium_scraper.rb <url>
На следующем этапе мы добавим возможность открывать ссылки с помощью open-uri
и читать HTML с помощью nokogiri
.
# medium_scraper.rb unless ARGV[0] puts "Please enter desired tag" puts "Help: ruby medium_scraper.rb <tag>" exit end begin BASE_URL = "https://medium.com/" url = BASE_URL + "tag/#{ARGV[0].strip}" uri = URI.open(url) doc = Nokogiri::HTML(uri) # Fetch and parse HTML document puts doc.inspect rescue => e puts "An error occurred." puts e retry end
Давайте запустим его еще раз ruby medium_scraper.rb ruby-on-rails
, и мы увидим, что в нем есть проанализированные данные.
❯ ruby medium_scraper.rb ruby-on-rails #<Nokogiri::HTML4::Document:0xeed4 name="document" children=[#<Nokogiri::XML::DTD:0x208 name="html">, #<Nokogiri::XML::Element:0xeec0 name="html" attributes=[#<Nokogiri:
Извлеките некоторые данные
Прежде чем мы добьемся желаемых результатов, давайте воспользуемся pry
, чтобы поиграть с нашим doc
объектом.
# medium_scraper.rb + require 'pry' + binding.pry puts doc.inspec
давай снова бежим ruby medium_scraper.rb ruby-on-rails
Мы видим, что он возвращает Nokogiri
документ, теперь нам нужно выполнить поиск Top Writers
, но если вы посмотрите на классы HTML, они не декларативны, поэтому мы не можем извлечь их, выполняя поиск только по именам классов.
Отображение тега p Top Writers
Давайте посмотрим, какие данные у нас есть вокруг тега <p>
, и мы можем получить родительские элементы <p> Top Writers </p>
p = doc.css('p') p.size => 63
Если мы проверим наш первый элемент p
, вы увидите, что у нас есть p.children[0].text
p.first => #(Element:0x230 { name = "p", attributes = [ #(Attr:0x208 { name = "class", value = "ay b eu ba bb" })], children = [ #(Text "12.5K")] })
Мы можем получить доступ к text
методу всех дочерних элементов, но в нашем случае всегда есть один дочерний элемент.
p.first.children.first => #(Text "12.5K") p.first.children.first.text => "12.5K" p.first.children.text => "12.5K"
получим тег p
с текстом Top Writers
p_heading = p.map{ |p| p if p.children.text == 'Top Writers' } => [nil, nil, nil, nil, nil, ..... #(Element:0x13ec { name = "p", attributes = [ #(Attr:0x13c4 { name = "class", value = "ay b dp dq dr ds dt" })], children = [ #(Text "Top Writers")] }), nil,..]
Мы видим, что он сопоставил единственный тег Top Writers
, но теперь у нас так много nils, давайте избавимся от nils и получим только первый результат.
p_heading = p.map{ |p| p if p.children.text == 'Top Writers' }.compact.first => #(Element:0x13ec { name = "p", attributes = [ #(Attr:0x13c4 { name = "class", value = "ay b dp dq dr ds dt" })], children = [ #(Text "Top Writers")] })
Давайте теперь возьмем родительский div, в котором есть все профили, нам нужно подняться на три уровня и выбрать второго дочернего элемента.
# 3 level up, second child p_heading.parent.parent.parent.children[1]
Теперь мы можем видеть результаты профиля
Выбор тега p Top Writers с помощью CSS-селекторов
Другой подход может заключаться в использовании селекторов CSS, выберите div, в котором p и p содержат текст «Top Writers».
p_heading = doc.search("div:has(> p:contains('Top Writers'))").first # 2 level up, second child div_writers = p_heading.parent.parent.children[1]
И мы можем получить тот же результат
Выбор профиля, ссылки и имени
На картинке ниже мы видим, что после 3 div мы хотим получить a
детали тега
# 3 cascading divs and last a content = div_writers.search('div > div > div > a') content.size => 5
Теперь у нас есть 5
элементов в div_writers
, и каждый a
имеет атрибут href
со ссылкой на профиль, а имя Writer находится в первом child
h2
text
Извлечь ссылки теперь можно с помощью:
content.map{|e| e.attr('href') }
Имя и биография:
content.map{|e| e.children.map(&:text) }
Давайте объединим все это в результаты с немного другим подходом, мы будем использовать массив []
и вставим каждый результат хеширования.
results = content.inject([]) do |result, ele| result << { profile: ele.attr('href'), name: ele.search('h2').text, bio: ele.search('p').text } end
Если вы хотите получить немного лучшие результаты на консоли, используйте table_print
gem, а затем вы можете просто использовать tp results
для создания такой красивой таблицы.
Что дальше?
Здесь мы можем расширить сценарий разными способами. Мы можем извлечь данные и преобразовать их в CSV и отправить их в Google Таблицы или Airtable, и мы даже можем обрабатывать несколько аргументов тегов fromARGV[0]
, например ruby medium_scraper.rb ruby-on-rails ruby programming
Мы попробуем другой подход в следующей истории, где мы сможем многое автоматизировать, а также открывать страницы на основе Ajax, щелкая ссылки. Будьте на связи!
Хотели бы вы поделиться своим подходом к парсингу с помощью Ruby? Комментарий ниже, если он у вас есть!