2011-07-14 2 views
10

Я пытаюсь получить каждую внешнюю ссылку веб-страницы с помощью Ruby. Я использую String.scan с этим регулярным выражением:Получение всех ссылок на веб-страницу с помощью Ruby

/href="https?:[^"]*|href='https?:[^']*/i 

Тогда я могу использовать GSUB удалить HREF часть:

str.gsub(/href=['"]/) 

Это прекрасно работает, но я не уверен, если это эффективно с точки зрения производительности. Это нормально использовать, или я должен работать с более конкретным парсером (например, nokogiri)? Какой путь лучше?

Спасибо!

+4

Пожалуйста, не пытайтесь анализировать HTML с регулярными выражениями, парсер HTML будет служить вам лучше. –

+0

@mu вы можете объяснить мне почему? –

+1

Поскольку разбор HTML более сложный, чем вы, вероятно, думаете, и есть много разбитого HTML, что простые регулярные выражения не будут обрабатываться: http://stackoverflow.com/questions/4231382/regular-expression-pattern- not-matching-where-in-string/4234491 # 4234491 –

ответ

3

Почему вы не используете группы в своем шаблоне? , например.

/http[s]?:\/\/(.+)/i 

так что первая группа уже будет ссылкой, которую вы искали.

1

Можете ли вы поместить группы в свое регулярное выражение? Это позволит сократить ваши регулярные выражения 1 вместо 2.

+0

Теперь я изучаю регулярное выражение. Я посмотрю в группах. Благодаря! –

15

Использование регулярных выражений отлично подходит для быстрого и грязного сценария, но Nokogiri очень прост в использовании:

require 'nokogiri' 
require 'open-uri' 

fail("Usage: extract_links URL [URL ...]") if ARGV.empty? 

ARGV.each do |url| 
    doc = Nokogiri::HTML(open(url)) 
    hrefs = doc.css("a").map do |link| 
    if (href = link.attr("href")) && !href.empty? 
     URI::join(url, href) 
    end 
    end.compact.uniq 
    STDOUT.puts(hrefs.join("\n")) 
end 

Если вы хотите просто метод, отредактируйте его немного по вашим потребностям:

def get_links(url) 
    Nokogiri::HTML(open(url).read).css("a").map do |link| 
    if (href = link.attr("href")) && href.match(/^https?:/) 
     href 
    end 
    end.compact 
end 
+0

Можете ли вы объяснить мне преимущества? Код выглядит более сложным, чем при регулярном выражении и сканировании. Мне также интересно узнать, какое решение быстрее. –

+0

@tokland, я думаю, вы хотите Nokogiri :: HTML. Также обратите внимание на требование извлечь только абсолютные ссылки. –

6

Mechanize использует Nokogiri под капотом, но имеет встроенный тонкостей для разбора HTML, в том числе ссылки:

require 'mechanize' 

agent = Mechanize.new 
page = agent.get('http://example.com/') 

page.links_with(:href => /^https?/).each do |link| 
    puts link.href 
end 

Используя анализатор, как правило, всегда лучше, чем с помощью регулярных выражений для разбора HTML. Это часто задаваемый вопрос здесь о переполнении стека, причем самый известный ответ - this. Почему это так? Поскольку создание надежного регулярного выражения, которое может обрабатывать версии HTML в реальном мире, некоторые из них некоторые некоторые, очень сложны и в конечном счете сложнее простого решения синтаксического анализа, которое будет работать практически для всех страниц, которые будут отображаться в браузере.

+0

Я согласен, что когда вам нужно разобрать html, вы не хотите использовать регулярные выражения. Но в этом случае я думаю, что регулярное выражение будет достаточным, так как вы не столкнетесь с проблемой неравномерности html (поскольку рекурсия не задействована). Не могли бы вы подумать о (не надуманном) примере, когда это регулярное выражение (с моим улучшением, как указано в моем комментарии к вопросу), не получилось бы? – markijbema

+0

Мне нравится ваше решение лучше всего, оно короткое и удобочитаемое, но мне не очень нравятся сверх-абсолютные истины, такие как «ты не должен касаться html с помощью регулярных выражений». – markijbema

+0

@markijbema Я добавил немного, чтобы объяснить. Вот один случай, который я видел: 'foo'. Также иногда там появляются новые строки. –

4

Я большой поклонник Нокигири, но зачем изобретать колесо?

URI модуль Руби уже имеет extract способ сделать это:

URI::extract(str[, schemes][,&blk]) 

Из документов:

Экстракты Юрис из строки. Если данный блок задан, выполняется итерация через все согласованные URI. Возвращает nil, если задан блок или массив со спичками.

require "uri" 

URI.extract("text here http://foo.example.org/bla and here mailto:[email protected] and here also.") 
# => ["http://foo.example.com/bla", "mailto:[email protected]"] 

Вы можете использовать Nokogiri ходить DOM и тянуть все теги, которые имеют URL-адреса, или это получить только текст и передать его в URI.extract, или просто позволить URI.extract делать все это.

И зачем использовать синтаксический анализатор, такой как Nokogiri, вместо шаблонов регулярных выражений?Поскольку HTML и XML можно форматировать по-разному и по-прежнему правильно отображать на странице или эффективно передавать данные. Браузеры очень прощают, когда дело доходит до принятия плохой разметки. С другой стороны, шаблоны регулярных выражений работают в очень ограниченном диапазоне «приемлемости», где этот диапазон определяется тем, насколько хорошо вы ожидаете изменений в разметке или, наоборот, насколько хорошо вы ожидаете, что ваш шаблон может пойти не так, когда представлены неожиданными шаблонами.

Парсер не работает как регулярное выражение. Он создает внутреннее представление документа, а затем просматривает его. Неважно, как выкладывается файл/разметка, он выполняет свою работу по внутреннему представлению DOM. Nokogiri релаксирует свой синтаксический анализ для обработки HTML, потому что HTML известен тем, что плохо написан. Это помогает нам, потому что с большинством неадаптируемых HTML Nokogiri может это исправить. Иногда я сталкиваюсь с чем-то, что так плохо написано, что Nokogiri не может исправить это правильно, поэтому мне придется немного подтолкнуть его, изменив HTML, прежде чем передать его Nokogiri; Я все равно использую синтаксический анализатор, а не пытаюсь использовать шаблоны.

Смежные вопросы