2011-07-14 39 views
10

Tôi đang cố truy xuất mọi liên kết bên ngoài của trang web bằng Ruby. Tôi đang sử dụng String.scan với regex này:Nhận tất cả các liên kết của trang web bằng Ruby

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

Sau đó, tôi có thể sử dụng gsub để loại bỏ các phần href:

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

này hoạt động tốt, nhưng tôi không chắc chắn nếu nó hiệu quả về hiệu suất. Điều này có được sử dụng hay tôi nên làm việc với một trình phân tích cú pháp cụ thể hơn (ví dụ nokogiri)? Cách nào tốt hơn?

Cảm ơn!

+4

Vui lòng không phân tích cú pháp HTML bằng cụm từ thông dụng, trình phân tích cú pháp HTML sẽ phục vụ bạn tốt hơn. –

+0

@mu bạn có thể giải thích cho tôi tại sao không? –

+1

Vì phân tích cú pháp HTML phức tạp hơn bạn có thể nghĩ và có rất nhiều HTML bị hỏng ở đó, các biểu thức thông thường đơn giản sẽ không xử lý: http://stackoverflow.com/questions/4231382/regular-expression-pattern- không phù hợp với bất cứ nơi nào trong chuỗi/4234491 # 4234491 –

Trả lời

3

lý do tại sao bạn không sử dụng các nhóm trong mô hình của bạn? ví dụ:

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

vì vậy nhóm đầu tiên sẽ là liên kết bạn đã tìm kiếm.

1

Bạn có thể đặt các nhóm trong regex của mình không? Điều đó sẽ làm giảm biểu thức thông thường của bạn tới 1 thay vì 2.

+0

Tôi đang học regex ngay bây giờ. Tôi sẽ xem xét theo nhóm. Cảm ơn! –

15

Sử dụng biểu thức thông thường là tốt cho một kịch bản nhanh chóng và bẩn, nhưng Nokogiri là rất đơn giản để sử dụng:

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 

Nếu bạn muốn chỉ là phương pháp, refactor nó một chút để nhu cầu của bạn:

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

Bạn có thể giải thích cho tôi những lợi thế không? Mã trông phức tạp hơn với regex và quét. Tôi cũng tò mò muốn biết giải pháp nào nhanh hơn. –

+0

@tokland, tôi nghĩ bạn muốn Nokogiri :: HTML. Cũng lưu ý yêu cầu chỉ trích xuất các liên kết tuyệt đối. –

6

Mechanize sử dụng Nokogiri dưới mui xe nhưng đã được xây dựng trong niceties cho phân tích cú pháp HTML, bao gồm liên kết:

require 'mechanize' 

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

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

Sử dụng một phân tích cú pháp thường phải lúc nào cũng tốt hơn so với sử dụng biểu thức thông thường để phân tích cú pháp HTML. Đây là câu hỏi thường gặp ở đây trên Stack Overflow, với this là câu trả lời nổi tiếng nhất. Tại sao điều này là trường hợp? Bởi vì xây dựng một biểu thức chính quy mạnh mẽ có thể xử lý các biến thể của HTML thực, một số hợp lệ không, rất khó và cuối cùng phức tạp hơn một giải pháp phân tích cú pháp đơn giản sẽ hoạt động cho tất cả các trang sẽ hiển thị trong trình duyệt.

+0

Tôi đồng ý rằng khi bạn cần phân tích cú pháp html bạn không muốn sử dụng regexes. Nhưng trong trường hợp này tôi nghĩ rằng một regex sẽ đủ, vì bạn không gặp rắc rối với tính không thường xuyên của html (vì không có đệ quy liên quan). Bạn có thể nghĩ về một ví dụ (không phải là giả) mà regex này (với cải tiến của tôi như đã đề cập trong bình luận của tôi về câu hỏi) sẽ thất bại? – markijbema

+0

Tôi thích giải pháp của bạn tốt hơn btw, nó ngắn và dễ đọc, nhưng tôi không thực sự thích những chân lý quá tuyệt đối, như 'bạn sẽ không chạm vào html với regex'. – markijbema

+0

@markijbema Tôi đã thêm một chút để giải thích. Đây là một trường hợp tôi đã thấy: 'foo'. Đôi khi cũng có những dòng mới trong đó. –

4

Tôi là một fan hâm mộ lớn của Nokogiri, nhưng tại sao lại phát minh ra bánh xe?

mô-đun URI Ruby đã có extract phương pháp để làm điều này:

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

Từ các tài liệu:

chiết xuất URI từ một chuỗi. Nếu khối đã cho, lặp lại qua tất cả các URI được so khớp. Trả về nil nếu khối đã cho hoặc mảng có khớp.

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]"] 

Bạn có thể sử dụng Nokogiri đi DOM và kéo tất cả các thẻ có URL, hoặc có nó lấy chỉ là văn bản và vượt qua nó để URI.extract, hoặc chỉ để cho URI.extract làm tất cả.

Và tại sao lại sử dụng trình phân tích cú pháp, chẳng hạn như Nokogiri, thay vì các mẫu regex?Bởi vì HTML và XML, có thể được định dạng theo nhiều cách khác nhau và vẫn hiển thị chính xác trên trang hoặc chuyển dữ liệu một cách hiệu quả. Trình duyệt rất tha thứ khi nói đến việc chấp nhận đánh dấu xấu. Mặt khác, các mẫu Regex hoạt động trong các phạm vi "chấp nhận" rất hạn chế, trong đó phạm vi được xác định bởi bạn dự đoán các biến thể trong đánh dấu như thế nào hoặc ngược lại, bạn dự đoán cách thức mô hình của mình có thể sai khi nào trình bày với các mẫu không mong muốn.

Trình phân tích cú pháp không hoạt động như một regex. Nó xây dựng một đại diện nội bộ của tài liệu và sau đó đi qua đó. Nó không quan tâm làm thế nào các tập tin/đánh dấu được đặt ra, nó hoạt động của nó trên các đại diện nội bộ của DOM. Nokogiri thư giãn phân tích cú pháp của nó để xử lý HTML, bởi vì HTML nổi tiếng vì được viết kém. Điều đó giúp chúng tôi vì hầu hết các Nokogiri HTML không xác thực có thể sửa chữa nó. Thỉnh thoảng tôi sẽ gặp phải một cái gì đó được viết một cách tồi tệ rằng Nokogiri không thể sửa chữa nó một cách chính xác, vì vậy tôi sẽ phải cho nó một cú hích nhỏ bằng cách tinh chỉnh HTML trước khi tôi chuyển nó cho Nokogiri; Tôi vẫn sẽ sử dụng trình phân tích cú pháp, thay vì cố gắng sử dụng các mẫu.

Các vấn đề liên quan