为什么我的 Ruby 函数网页只抓取第一页而不抓取分页页面?
Why is my Ruby function web scraping the first page only and not the paginated pages?
我正在使用 nokogiri 在经销商网站的大约 14 个页面上抓取所有车辆,我遇到的错误是我的代码 运行 仅在第一页上抓取了 14 次。我的代码有什么问题?
正如您从输出中看到的那样,一遍又一遍地抓取相同的车辆,而不是下一页中的一组新车辆。
Ruby 版本: 2.6.2
scraper.rb :
require 'nokogiri'
require 'httparty'
require 'byebug'
def scraper
url = "https://www.example.com/new-vehicles/"
unparsed_page = HTTParty.get(url)
parsed_page = Nokogiri::HTML(unparsed_page)
vehicles = Array.new
vehicle_listings = parsed_page.css("//div[@class='vehicle list-view new-vehicle publish']") #20 cars
page = 1
per_page = vehicle_listings.count #20
total = parsed_page.css('span.count').text.to_i #281
last_page = (total.to_f / per_page.to_f).ceil #14
while page <= last_page
pagination_url = "https://www.example.com/new-vehicles/#action=im_ajax_call&perform=get_results&page=#{page}"
pagination_unparsed_page = HTTParty.get(pagination_url)
puts pagination_url
puts "Page: #{page}"
puts ''
pagination_parsed_page = Nokogiri::HTML(pagination_unparsed_page)
pagination_vehicle_listings = pagination_parsed_page.css("//div[@class='vehicle list-view new-vehicle publish']") #20 cars
pagination_vehicle_listings.each do |vehicle_listing|
vehicle = {
title: vehicle_listing.css('h2')&.text&.gsub("New", '').gsub("2021", '').gsub("With", '').gsub("Navigation", ''),
price: vehicle_listing.css('span.price')[0]&.text&.delete('^0-9').to_i,
stock_number: vehicle_listing.css('.stock-label')&.text.gsub("Stock #: ", ''),
exterior_color: vehicle_listing.css('span.detail-content')[3]&.text,
interior_color: vehicle_listing.css('span.detail-content')[4]&.text&.delete('0-9').gsub('MPG', 'unavailable')
}
vehicles << vehicle
puts "Added #{vehicle[:stock_number]}"
puts ""
end
page += 1
end
byebug
end
scraper
输出:
Page: 1
Added vehicle with stock#: 218864
Added vehicle with stock#: 218865
Added vehicle with stock#: 218604
https://www.example.com/new-vehicles/#action=im_ajax_call&perform=get_results&page=2
Page: 2
Added vehicle with stock#: 218864
Added vehicle with stock#: 218865
Added vehicle with stock#: 218604
虽然看起来数据是通过遵循 link 的 GET 请求加载的,但实际上是通过 jQuery 的 POST 请求加载的。
您可以通过打开浏览器开发人员工具的“网络”选项卡来验证这一点。
如果您在浏览器中输入 url,则会发出一个请求:
- 对初始内容的 GET 请求
如果您随后在车辆列表的分页导航中单击 link,则
- URL 已更新以包含页面。请注意,它不是 query (
?
),而是 fragment (#
) 的一部分,它永远不会发送到服务器但留在客户端。
- 请求页面的内容是通过 POST 请求(通过 jQuery)获取的,然后该响应被合并到内容中,看起来它是通过 POST 请求获取的=65=].
如果你想模仿这个POST请求,那就有点复杂了:
它包含一个 nonce,您可以在您必须发送的初始内容(搜索 ajax_nonce
)中找到它(因此获取初始内容,抓取随机数,然后将其与 POST 请求一起发送)
这是一个带有 CURL(替换随机数)的示例,它会将第 2 页的列表返回为 JSON(即使响应 header 说 Content-Type: text/html; charset=UTF-8
curl -X POST https://www.pacificbmw.com/
-H "Content-Type: application/x-www-form-urlencoded"
-d "action=im_ajax_call&perform=get_results&page=2&_nonce=e085e144a19b3b493c7aab9aff479722&_post_id=6&_referer=/new-vehicles/"
这将 return
{"results":"
<table class=\ "results_table\">\n
<tbody>\t\n\n
<tr class=\ "hidden-xs\">\n
<td colspan=\ "7\">\n
<!-- VEHICLE BLOCK -->\n
<div class=\ "vehicle list-view new
....
您需要将其解析为 JSON,然后提取 results
密钥并使用 nokogiri 再次解析,然后抓取列表。
更新:
作为替代方案,您还可以使用支持和执行 javascript(Selenium + Headless Chrome 或类似)
的抓取工具
像这样你可以模仿浏览器和用户,你可以触发点击表单输入。
这篇文章是一个简短的介绍:https://russmorley.net/tutorial/selenium/2019/03/07/Scrape.html
我能够通过使用 Watir 和无头 chrome 浏览器来抓取我需要的数据,该浏览器允许 ajax/javascript 代码在抓取页面之前 运行。
require 'nokogiri'
require 'httparty'
require 'byebug'
require 'watir'
def scraper
url = "https://www.example.com/new-vehicles/"
unparsed_page = HTTParty.get(url)
parsed_page = Nokogiri::HTML(unparsed_page)
vehicles = Array.new
vehicle_listings = parsed_page.css("//div[@class='vehicle list-view new-vehicle publish']") #20 cars
page = 1
per_page = vehicle_listings.count #20
total = parsed_page.css('span.count').text.to_i #281
last_page = (total.to_f / per_page.to_f).ceil #14
# Create instance of headless chrome called browser
browser = Watir::Browser.new :chrome, headless: true
while page <= last_page
pagination_url = "https://www.example.com/new-vehicles/#action=im_ajax_call&perform=get_results&page=#{page}"
browser.goto(pagination_url)
pagination_unparsed_page = Nokogiri::HTML(browser.html)
puts pagination_url
puts "Page: #{page}"
puts ''
pagination_parsed_page = pagination_unparsed_page
pagination_vehicle_listings = pagination_parsed_page.css("//div[@class='vehicle list-view new-vehicle publish']") #20 cars
pagination_vehicle_listings.each do |vehicle_listing|
vehicle = {
stock_number: vehicle_listing.css('.stock-label')&.text.gsub("Stock #: ", '')
}
vehicles << vehicle
puts "Added #{vehicle[:stock_number]}"
puts ""
end
page += 1
end
# Be sure to close the browser
browser.close
byebug
end
scraper
我正在使用 nokogiri 在经销商网站的大约 14 个页面上抓取所有车辆,我遇到的错误是我的代码 运行 仅在第一页上抓取了 14 次。我的代码有什么问题?
正如您从输出中看到的那样,一遍又一遍地抓取相同的车辆,而不是下一页中的一组新车辆。
Ruby 版本: 2.6.2
scraper.rb :
require 'nokogiri'
require 'httparty'
require 'byebug'
def scraper
url = "https://www.example.com/new-vehicles/"
unparsed_page = HTTParty.get(url)
parsed_page = Nokogiri::HTML(unparsed_page)
vehicles = Array.new
vehicle_listings = parsed_page.css("//div[@class='vehicle list-view new-vehicle publish']") #20 cars
page = 1
per_page = vehicle_listings.count #20
total = parsed_page.css('span.count').text.to_i #281
last_page = (total.to_f / per_page.to_f).ceil #14
while page <= last_page
pagination_url = "https://www.example.com/new-vehicles/#action=im_ajax_call&perform=get_results&page=#{page}"
pagination_unparsed_page = HTTParty.get(pagination_url)
puts pagination_url
puts "Page: #{page}"
puts ''
pagination_parsed_page = Nokogiri::HTML(pagination_unparsed_page)
pagination_vehicle_listings = pagination_parsed_page.css("//div[@class='vehicle list-view new-vehicle publish']") #20 cars
pagination_vehicle_listings.each do |vehicle_listing|
vehicle = {
title: vehicle_listing.css('h2')&.text&.gsub("New", '').gsub("2021", '').gsub("With", '').gsub("Navigation", ''),
price: vehicle_listing.css('span.price')[0]&.text&.delete('^0-9').to_i,
stock_number: vehicle_listing.css('.stock-label')&.text.gsub("Stock #: ", ''),
exterior_color: vehicle_listing.css('span.detail-content')[3]&.text,
interior_color: vehicle_listing.css('span.detail-content')[4]&.text&.delete('0-9').gsub('MPG', 'unavailable')
}
vehicles << vehicle
puts "Added #{vehicle[:stock_number]}"
puts ""
end
page += 1
end
byebug
end
scraper
输出:
Page: 1
Added vehicle with stock#: 218864
Added vehicle with stock#: 218865
Added vehicle with stock#: 218604
https://www.example.com/new-vehicles/#action=im_ajax_call&perform=get_results&page=2
Page: 2
Added vehicle with stock#: 218864
Added vehicle with stock#: 218865
Added vehicle with stock#: 218604
虽然看起来数据是通过遵循 link 的 GET 请求加载的,但实际上是通过 jQuery 的 POST 请求加载的。 您可以通过打开浏览器开发人员工具的“网络”选项卡来验证这一点。
如果您在浏览器中输入 url,则会发出一个请求:
- 对初始内容的 GET 请求
如果您随后在车辆列表的分页导航中单击 link,则
- URL 已更新以包含页面。请注意,它不是 query (
?
),而是 fragment (#
) 的一部分,它永远不会发送到服务器但留在客户端。 - 请求页面的内容是通过 POST 请求(通过 jQuery)获取的,然后该响应被合并到内容中,看起来它是通过 POST 请求获取的=65=].
如果你想模仿这个POST请求,那就有点复杂了:
它包含一个 nonce,您可以在您必须发送的初始内容(搜索 ajax_nonce
)中找到它(因此获取初始内容,抓取随机数,然后将其与 POST 请求一起发送)
这是一个带有 CURL(替换随机数)的示例,它会将第 2 页的列表返回为 JSON(即使响应 header 说 Content-Type: text/html; charset=UTF-8
curl -X POST https://www.pacificbmw.com/
-H "Content-Type: application/x-www-form-urlencoded"
-d "action=im_ajax_call&perform=get_results&page=2&_nonce=e085e144a19b3b493c7aab9aff479722&_post_id=6&_referer=/new-vehicles/"
这将 return
{"results":"
<table class=\ "results_table\">\n
<tbody>\t\n\n
<tr class=\ "hidden-xs\">\n
<td colspan=\ "7\">\n
<!-- VEHICLE BLOCK -->\n
<div class=\ "vehicle list-view new
....
您需要将其解析为 JSON,然后提取 results
密钥并使用 nokogiri 再次解析,然后抓取列表。
更新:
作为替代方案,您还可以使用支持和执行 javascript(Selenium + Headless Chrome 或类似)
的抓取工具像这样你可以模仿浏览器和用户,你可以触发点击表单输入。
这篇文章是一个简短的介绍:https://russmorley.net/tutorial/selenium/2019/03/07/Scrape.html
我能够通过使用 Watir 和无头 chrome 浏览器来抓取我需要的数据,该浏览器允许 ajax/javascript 代码在抓取页面之前 运行。
require 'nokogiri'
require 'httparty'
require 'byebug'
require 'watir'
def scraper
url = "https://www.example.com/new-vehicles/"
unparsed_page = HTTParty.get(url)
parsed_page = Nokogiri::HTML(unparsed_page)
vehicles = Array.new
vehicle_listings = parsed_page.css("//div[@class='vehicle list-view new-vehicle publish']") #20 cars
page = 1
per_page = vehicle_listings.count #20
total = parsed_page.css('span.count').text.to_i #281
last_page = (total.to_f / per_page.to_f).ceil #14
# Create instance of headless chrome called browser
browser = Watir::Browser.new :chrome, headless: true
while page <= last_page
pagination_url = "https://www.example.com/new-vehicles/#action=im_ajax_call&perform=get_results&page=#{page}"
browser.goto(pagination_url)
pagination_unparsed_page = Nokogiri::HTML(browser.html)
puts pagination_url
puts "Page: #{page}"
puts ''
pagination_parsed_page = pagination_unparsed_page
pagination_vehicle_listings = pagination_parsed_page.css("//div[@class='vehicle list-view new-vehicle publish']") #20 cars
pagination_vehicle_listings.each do |vehicle_listing|
vehicle = {
stock_number: vehicle_listing.css('.stock-label')&.text.gsub("Stock #: ", '')
}
vehicles << vehicle
puts "Added #{vehicle[:stock_number]}"
puts ""
end
page += 1
end
# Be sure to close the browser
browser.close
byebug
end
scraper