I'm using Ruby and Mechanize to parse a local HTML file but I can't do it. This works if I use a URL though:
agent = Mechanize.new
#THIS WORKS
#url = 'http://www.sample.com/sample.htm'
#page = agent.get(url) #this seems to work just fine but the following below doesn't
#THIS FAILS
file = File.read('/home/user/files/sample.htm') #this is a regular html file
page = Nokogiri::HTML(file)
pp page.body #errors here
page.search('/div[@class="product_name"]').each do |node|
text = node.text
puts "product name: " + text.to_s
end
The error is:
/home/user/code/myapp/app/models/program.rb:35:in `main': undefined method `body' for #<Nokogiri::HTML::Document:0x000000011552b0> (NoMethodError)
How do I get a page object so that I can search on it?
Mechanize uses URI strings to point to what it's supposed to parse. Normally we'd use a "http
" or "https
" scheme to point to a web-server, and that's where Mechanize's strengths are, but other schemes are available, including "file
", which can be used to load a local file.
I have a little HTML file on my Desktop called "test.rb":
<!DOCTYPE html>
<html>
<head></head>
<body>
<p>
Hello World!
</p>
</body>
</html>
Running this code:
require 'mechanize'
agent = Mechanize.new
page = agent.get('file:/Users/ttm/Desktop/test.html')
puts page.body
Outputs:
<!DOCTYPE html>
<html>
<head></head>
<body>
<p>
Hello World!
</p>
</body>
</html>
Which tells me Mechanize loaded the file, parsed it, then accessed the body
.
However, unless you need to actually manipulate forms and/or navigate pages, then Mechanize is probably NOT what you want to use. Instead Nokogiri, which is under Mechanize, is a better choice for parsing, extracting data or manipulating the markup and it's agnostic as to what scheme was used or where the file is actually located:
require 'nokogiri'
doc = Nokogiri::HTML(File.read('/Users/ttm/Desktop/test.html'))
puts doc.to_html
which then output the same file after parsing it.
Back to your question, how to find the node only using Nokogiri:
Changing test.html
to:
<!DOCTYPE html>
<html>
<head></head>
<body>
<div class="product_name">Hello World!</div>
</body>
</html>
and running:
require 'nokogiri'
doc = Nokogiri::HTML(File.read('/Users/ttm/Desktop/test.html'))
doc.search('div.product_name').map(&:text)
# => ["Hello World!"]
shows that Nokogiri found the node and returned the text.
This code in your sample could be better:
text = node.text
puts "product name: " + text.to_s
node.text
returns a string:
doc = Nokogiri::HTML('<p>hello world!</p>')
doc.at('p').text # => "hello world!"
doc.at('p').text.class # => String
So text.to_s
is redundant. Simply use text
.