TL;DR
Мы собираемся использовать гем для поиска темы в веб-статье, чтобы получить список фрагментов.
Прочитайте эту статью, которую я написал, чтобы получить представление о Nokogiri.
Я случайный поклонник бейсбола. На ESPN есть много статей о МЛБ. О чем они рассказывают? Это загадка! Нам нужно просканировать страницу с помощью Nokogiri, чтобы выяснить это. Вы готовы?
Получите эту статью!
➜ ~ irb --simple-prompt
>> require 'nokogiri'
=> true
>> require 'open-uri'
=> true
# I'm going to scan the article at this URL. Feel free to
# find one of your own, since this will probably be outdated
# by the time you read this.
>> url = 'https://www.espn.com/mlb/story/_/id/33855507/mlb-power-rankings-week-4
-new-team-surged-our-no-1-spot'
=> "https://www.espn.com/mlb/story/_/id/33855507/mlb-power-rankings-week-4-...
>> web_page = Nokogiri::HTML5(URI(url))
=>
#(Document:0x5b4 {
...
Теперь нам нужно найти тело статьи, чтобы сканировать только нужные фрагменты. Мы воспользуемся методом #css
с именем div и класса.
>> body = web_page.css('div.article-body')
=> [#<Nokogiri::XML::Element:0x5730 name="div" attributes=[#<Nokogiri::XML:...
>> text = body.text
=> "7:00 AM ETESPNFacebookTwitterFacebook MessengerPinterestEmailprintFour ...
Теперь самое интересное.
Сканирование регулярных выражений
В Ruby есть метод String#scan
, который принимает регулярное выражение в качестве аргумента и возвращает массив всех вхождений.
>> "don't do that dog!".scan(/do/) => ["do", "do", "do"]
Но на самом деле мы не хотим просто получить список вхождений. Мы хотим получить весь контекст, чтобы понять, о чем идет речь в статье. Для этого нам нужно найти индекс каждого вхождения, как если бы наша строка была массивом символов. Затем мы сделаем срез до и после этого индекса, чтобы получить контекст каждого вхождения. Это приводит к появлению (возможно, менее известного) метода под названием #to_enum
(к перечислителю).
Метод to_enum
позволяет нам перечислить строку и передать метод и необязательный аргумент. Вот пример, в котором мы получим код байта для каждого символа ASCII в строке. Мы выведем каждый из них в двоичный формат с помощью to_s(2)
.
>> "abcdefg".to_enum(:each_byte).each { |b| p b.to_s(2) }
"1100001"
"1100010"
"1100011"
"1100100"
"1100101"
"1100110"
"1100111"
Для наших целей мы передадим метод :scan
, аргументом которого будет наше регулярное выражение. Затем мы сопоставим каждое вхождение с Regexp.last_match.begin(0)
, чтобы получить начальный индекс для вхождения. Вот как это работает.
# remember text holds the text of the article body
# each index will go into the indices variable
# we can search for whatever we want, let's search for pitch
# this will work for pitch, pitchers, pitches, etc.
>> indices = text.to_enum(:scan, /pitch/i).map do |pitch|
Regexp.last_match.begin(0)
>> end
=>
[1825,
...
>> indices
=>
[1825,
3699,
4727,
10007,
10127,
10846,
11016,
12617,
13734,
14060,
14585,
14927,
16019,
17835,
18858]
Отлично! Этот список индексов показывает нам, где нужно сделать срез, чтобы получить наши данные. Мы вырежем 30 символов до начала и сделаем длину нашего фрагмента 70 символов. Мы поместим эти фрагменты текста в массив.
>> snippets = []
=> []
?> indices.each do |i|
?> snippets << text.slice(i - 30, 70)
>> end
>> snippets
=>
["n-differential in the majors. Pitching has mostly carried them, but th",
"st year, Milwaukee's starting pitching was basically three deep. That ",
"rt envious: Too many starting pitchers. Clevinger is the sixth member ",
" allowed. While he has a five-pitch repertoire, one thing he's done th",
"eup combo. He threw those two pitches a combined 64.3% of the time las",
"ause his swing rate and first-pitch swing rate in particular are up. H",
"nd him he's going to get more pitches to hit. -- Schoenfield17. Chicag",
"2 batting line this year. The pitching staff has been one of the brigh",
"ice start. Good, right-handed pitching will stymie them this year, tho",
"le against both hard and soft pitching, despite dominating the league ",
" ranks among some of the best pitchers in all of baseball in WAR. -- L",
" back to .500. Their starting pitchers have lifted them, with Zac Gall",
". The Rangers did have better pitching last week, moving them up the l",
"r nine innings in 11⅓ innings pitched. -- Lee29. Washington NationalsR",
" Colorado will do that -- but pitching was a big problem. The Reds com"]
Мы сделали это! Теперь давайте очистим их, чтобы они начинались и заканчивались полными словами. Возьмем каждый фрагмент, разделим его пробелами, удалим первое и последнее неполные слова, а затем снова вставим их вместе с пробелами.
snippets.map do |snippet|
?> words = snippet.split(" ")
?> words.pop
?> words.shift
?> snippet = words.join(" ")
>> end
=>
["in the majors. Pitching has mostly carried them, but",
"year, Milwaukee's starting pitching was basically three deep.",
"envious: Too many starting pitchers. Clevinger is the sixth",
"While he has a five-pitch repertoire, one thing he's done",
"combo. He threw those two pitches a combined 64.3% of the time",
"his swing rate and first-pitch swing rate in particular are up.",
"him he's going to get more pitches to hit. -- Schoenfield17.",
"batting line this year. The pitching staff has been one of the",
"start. Good, right-handed pitching will stymie them this year,",
"against both hard and soft pitching, despite dominating the",
"among some of the best pitchers in all of baseball in WAR. --",
"to .500. Their starting pitchers have lifted them, with Zac",
"The Rangers did have better pitching last week, moving them up the",
"nine innings in 11⅓ innings pitched. -- Lee29. Washington",
"will do that -- but pitching was a big problem. The Reds"]
Вот и все! Надеюсь, эта статья раскрыла лишь некоторые возможности Ruby. Не бойтесь исследовать новые драгоценные камни и искать новые методы на сайте Ruby-Doc.