...

пятница, 6 марта 2015 г.

[Из песочницы] Веб-парсинг на Ruby

Это перевод статьи «Web Scraping with Ruby», которую я нашел полезной при изучении языка программирования Ruby. Парсинг меня интересует в личных целях. Мне кажется, это не только полезный навык, но и хороший способ изучить язык.



Парсинг веба на Ruby легче, чем вы можете думать. Давайте начнем с простого примера: я хочу получить красиво отформатированный JSON массив объектов, представляющий список фильмов с сайта местного независимого кинотеатра.

В начале нам нужен способ скачать html страницу, которая содержит все объявления о фильмах. В Ruby есть встроенный http клиент, Net::HTTP, а также надстройку над ним — open-uri1. Итак, первая вещь, которую надо сделать — это скачать html с удаленного сервера.



require 'open-uri'

url = 'http://ift.tt/1IlEZG8'
html = open(url)




Отлично, теперь у нас есть страница, которую мы хотим парсить, теперь нам нужно вытащить некоторую информацию из нее. Лучший инструмент для этого — Nokogiri. Мы создаем новый экземпляр Nokogiri для нашего html, который мы только что скачали.

require 'nokogiri'

doc = Nokogiri::HTML(html)




Nokogiri крут, потому что позволяет обращаться к html используя CSS селекторы, что, на мой взгляд, гораздо удобнее чем использовать xpath.

Ок, теперь у нас есть документ, из которого мы можем вытащить список кинофильмов. Каждый элемент списка имеет такую html структуру, как показано ниже.



<div class="showing" id="event_7557">
<a href="/programme/event/live-stand-up-monty-python-and-the-holy-grail,7557/">
<img src="/media/diary/thumbnails/montypython2_1.png.500x300_q85_background-%23FFFFFF_crop-smart.jpg" alt="Picture for event Live stand up + Monty Python and the Holy Grail">
</a>
<span class="tags"><a href="/programme/view/comedy/" class="tag_comedy">comedy</a> <a href="/programme/view/dvd/" class="tag_dvd">dvd</a> <a href="/programme/view/film/" class="tag_film">film</a> </span>
<h1>
<a href="/programme/event/live-stand-up-monty-python-and-the-holy-grail,7557/">
<span class="pre_title">Comedy Combo presents</span>
Live stand up + Monty Python and the Holy Grail
<span class="post_title">Rare screening from 35mm!</span>
</a>
</h1>
<div class="event_details">
<p class="start_and_pricing">
Sat 20 December | 19:30
<br>
</p>
<p class="copy">Brave (and not so brave) Knights of the Round Table! Gain shelter from the vicious chicken of Bristol as we gather to bear witness to this 100% factually accurate retelling ... [<a class="more" href="/programme/event/live-stand-up-monty-python-and-the-holy-grail,7557/">more...</a>]</p>
</div>
</div>




Обработка html




Каждый фильм имеет css класс .showing, так что мы можем выбрать все шоу и обработать их по очереди.

showings = []
doc.css('.showing').each do |showing|
showing_id = showing['id'].split('_').last.to_i
tags = showing.css('.tags a').map { |tag| tag.text.strip }
title_el = showing.at_css('h1 a')
title_el.children.each { |c| c.remove if c.name == 'span' }
title = title_el.text.strip
dates = showing.at_css('.start_and_pricing').inner_html.strip
dates = dates.split('<br>').map(&:strip).map { |d| DateTime.parse(d) }
description = showing.at_css('.copy').text.gsub('[more...]', '').strip
showings.push(
id: showing_id,
title: title,
tags: tags,
dates: dates,
description: description
)
end




Давайте разберем по частям код, представленный выше.

showing_id = showing['id'].split('_').last.to_i




В начале мы берем уникальный идентификатор id, который любезно выставлен как атрибут html идентификатора в разметке. Используя квадратные скобки, мы можем получить доступ к атрибутам элементов. Таким образом, в случае html, представленного выше, showing['id'] должен быть «event_7557». Нам интересен только числовой идентификатор, так что мы разделяем результат с помощью подчеркивания .split('_') и затем берем последний элемент из получившегося массива и конвертируем в целочисленный формат .last.to_i.

tags = showing.css('.tags a').map { |tag| tag.text.strip }




Здесь мы находим все теги для фильма, используя .css метод, который возвращает массив совпадающих элементов. Затем мы мапим (применяем метод map) элементы, берем из них текст и убираем в нем пробелы. Для нашего html результат будет ["comedy", "dvd", "film"].

title_el = showing.at_css('h1 a')
title_el.children.each { |c| c.remove if c.name == 'span' }
title = title_el.text.strip




Код для получения заголовка немного сложнее, потому что этот элемент в html содержит некоторые добавочные span элементы с префиксами и суффиксами. Мы берем заголовок, используя .at_css, который возвращает один соответствующий элемент. Затем мы перебираем каждого потомка заголовка и удаляем лишние span. В конце, когда span убраны мы получаем текст заголовка и чистим его от лишних пробелов.

dates = showing.at_css('.start_and_pricing').inner_html.strip
dates = dates.split('<br>').map(&:strip).map { |d| DateTime.parse(d) }




Далее код для получения даты и времени показа. Здесь немного сложнее, потому что фильмы могут показывать несколько дней и, иногда, цена может быть в этом же элементе. Мы мапим даты, которые найдем с помощью DateTime.parse и в результате получаем массив Ruby объектов — DateTime.

description = showing.at_css('.copy').text.gsub('[more...]', '').strip




Получение описания довольно простой процесс, единственное что стоит сделать, это убрать текст [more...] используя .gsub

showings.push(
id: showing_id,
title: title,
tags: tags,
dates: dates,
description: description
)




Теперь, имея все необходимые части в переменных, мы можем записать их в наш хеш (hash), созданный для отображения всех фильмов.

Вывод в JSON




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

require 'json'

puts JSON.pretty_generate(showings)




Данный код выводит массив showings перекодированный в формат JSON, при запуске скрипта вывод можно перенаправить в файл или другую программу для дальнейшей обработки.

Собираем все вместе




Собрав все части в одном месте, мы получаем полную версию нашего скрипта:

require 'open-uri'
require 'nokogiri'
require 'json'

url = 'http://ift.tt/1IlEZG8'
html = open(url)

doc = Nokogiri::HTML(html)
showings = []
doc.css('.showing').each do |showing|
showing_id = showing['id'].split('_').last.to_i
tags = showing.css('.tags a').map { |tag| tag.text.strip }
title_el = showing.at_css('h1 a')
title_el.children.each { |c| c.remove if c.name == 'span' }
title = title_el.text.strip
dates = showing.at_css('.start_and_pricing').inner_html.strip
dates = dates.split('<br>').map(&:strip).map { |d| DateTime.parse(d) }
description = showing.at_css('.copy').text.gsub('[more...]', '').strip
showings.push(
id: showing_id,
title: title,
tags: tags,
dates: dates,
description: description
)
end

puts JSON.pretty_generate(showings)




Если сохранить это в файл, например, scraper.rb и запустить ruby scraper.rb, то вы должны увидеть вывод в формате JSON. Он должен быть похож на то, что представлено ниже.

[
{
"id": 7686,
"title": "Harry Dean Stanton - Partly Fiction",
"tags": [
"dcp",
"film",
"ttt"
],
"dates": [
"2015-01-19T20:00:00+00:00",
"2015-01-20T20:00:00+00:00"
],
"description": "A mesmerizing, impressionistic portrait of the iconic actor in his intimate moments, with film clips from some of his 250 films and his own heart-breaking renditions of American folk songs. ..."
},
{
"id": 7519,
"title": "Bang the Bore Audiovisual Spectacle: VA AA LR + Stephen Cornford + Seth Cooke",
"tags": [
"music"
],
"dates": [
"2015-01-21T20:00:00+00:00"
],
"description": "An evening of hacked TVs, 4 screen cinematic drone and electroacoustics. VAAALR: Vasco Alves, Adam Asnan and Louie Rice create spectacles using distress flares, C02 and junk electronics. Stephen Cornford: ..."
}
]




Всё. И это всего лишь базовый пример парсинга. Сложнее парсить сайт, который требует в начале авторизоваться. Для таких случаев я рекомендую посмотреть в сторону mechanize, который работает над Nokogiri.

Надеюсь, данное введение в парсинг даст вам идеи о данных, которые вы хотите видеть в более структурированном формате, используя методы, описанные выше.


Также я планирую перевести другую статью на тему парсинга от этого же автора.


1: Open-uri хорош для базовых вещей, вроде тех, что мы делаем в уроке, но у него есть некоторые проблемы, поэтому, возможно, вы захотите найти другой http клиент для продакшн среды.


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Комментариев нет:

Отправить комментарий