跳至内容
免费开始
返回博客

使用 Ruby 进行网页抓取:从 Faraday 和 Nokogiri 到 Selenium

Raluca Penciuc最后更新于 3 min read
使用 Ruby 进行网页抓取:从 Faraday 和 Nokogiri 到 Selenium
简而言之:开始使用 Ruby 进行网页抓取时,先找出数据所在的位置,然后选择能够获取该数据且功能最精简的工具。 对于响应中已存在的 HTML,使用 Faraday 和 Nokogiri 进行处理,将经过验证的记录模式导出为 CSV 文件,并仅在需要渲染或交互时才配合 Capybara 使用 Selenium。最终得到的爬虫,其请求层即使发生变化,也不会影响解析、验证或导出功能。

使用 Ruby 进行网页抓取,是指请求网页内容,在 HTML 或浏览器渲染的文档中定位有用值,并使用 Ruby 代码将这些值转换为结构化记录的过程。最易于维护的方法是使用能够可靠访问数据的最轻量级技术栈。

这通常意味着使用 Faraday 进行 HTTP 传输,使用 Nokogiri 进行解析。它们启动迅速、测试简单,只要服务器响应中包含目标字段,就能满足需求。 如果内容由 JavaScript 生成,或者允许的工作流需要交互操作,Selenium 和 Capybara 可以提供浏览器备用方案,但它们也会增加资源消耗,并带来更多可能导致失败的因素。

本教程将围绕一个稳定的本地目录数据集,构建一个渐进式的 Ruby 网络爬取工作流。您将通过显式设置超时和响应检查来获取 HTML,提取一致的记录结构,规范化 URL 和价格,验证行数据,并导出确定性的 CSV 文件。 随后,您将添加有限重试、速率控制、选择器测试、基于浏览器的等待、受保护的凭据以及并发限制。目标并非构建一个依赖框架的爬虫,而是一个小型 Ruby 网页抓取工具——即使目标、网络或访问方式发生变化,其行为依然清晰可理解。

最终, output/products.csv 将生成可预测的请求头,且每行对应一个经过验证的产品。该提取器既可处理下载的标记,也可处理浏览器渲染的标记,这使得工具的选择具有可逆性。当 gem、浏览器、打包方式或法律细节发生变化时,请对照当前的官方文档进行验证。这样既能确保本教程的可重现性,又能让你根据已批准的目标调整选择器。

选择最轻量级的 Ruby 爬虫栈(使用 Ruby 进行网页爬取)

《使用 Ruby 进行 Web 抓取》的默认技术栈应为 HTTP 客户端加 HTML 解析器。如果响应正文中已包含所需的记录,Faraday 可处理传输,Nokogiri 可处理提取,其开销远低于浏览器。 仅当数据在 JavaScript 执行后才出现,或者工作流确实需要点击、滚动或表单输入时,才升级使用 Selenium 和 Capybara。

请将这些职责分开。一个 fetch_html(url) 函数应返回 HTML,而 parse_records(html, page_url:) 则不应涉及代理、重试或浏览器等细节。这种职责分离使小型 Ruby 网页爬虫能够持续演进,而无需因访问策略的每次变更就重写解析器。

检查原始 HTML、嵌入数据和渲染后的 DOM

在选择 gems 之前,先定位数据。使用 Faraday 或 curl,保存请求主体,并搜索已知的标题或标识符。在浏览器中,对比“查看源代码”与“元素”面板的内容,然后在页面加载时检查“网络”面板。

请按照以下诊断顺序操作:

  1. 服务器渲染的 HTML:数据位于响应正文中,因此请使用 Faraday 和 Nokogiri。
  2. 嵌入式数据:script 标签中包含 JSON 或序列化状态,因此在允许的情况下直接解析该有效载荷。
  3. 后台响应:经过授权的 XHR 或 fetch 请求会返回记录,这些记录可能比渲染后的标记更简洁。
  4. 仅渲染的 DOM:JavaScript 构建元素,因此使用无头浏览器可能是合理的。

此顺序可避免将浏览器自动化作为每个缺失选择器的自动解决方案。

根据任务需求选择 Faraday、HTTParty、Nokogiri、Selenium、Capybara 或 Kimurai

工具

最适合

主要权衡

Faraday

可配置的 HTTP 请求、中间件、适配器以及显式错误处理

比单调用客户端的配置稍多一些

HTTParty

适用于简洁请求 API 的小型脚本

若请求逻辑在抓取类内部不断扩展,则分离程度较低

Nokogiri

利用 CSS 选择器和 XPath 快速解析 HTML 或 XML

不执行 JavaScript

Selenium

可直接控制已渲染或交互式页面的浏览器

内存占用更大、启动时间更长,且驱动程序更复杂

Capybara

更高层次的浏览器交互,支持自动等待

增加了对静态页面而言不必要的抽象层

Kimurai

一款值得在大型 Ruby 数据抓取任务中评估的爬虫式框架

其结构和组件比这个专注的工作流所需更为复杂

在本教程中,Faraday 配合 Nokogiri 是主要方案,而 Selenium 配合 Capybara 则是备选方案。如果您的团队更青睐 HTTParty 的 API,那么使用 HTTParty 进行网页抓取仍是一个合理的替代方案。若您的工作负载超出了普通 HTML 文档的范围,则 Ruby HTML 和 XML 解析器的对比分析将有所帮助。

构建从请求到 CSV 的静态爬虫

使用本地测试数据作为稳定的练习目标,而非不断变化的电商页面。创建 fixtures/catalog.html 包含两个或更多产品卡片,然后通过 ruby -run -e httpd fixtures -p 8000。URL 为 http://127.0.0.1:8000/catalog.html。

在整个示例中统一使用一种数据结构: id, title, price_text, price_cents,以及 url。将原始价格与标准化整数并列保留,可在解析规则需要审查时提供依据。

<section id="catalog">
  <article class="product-card" data-product-id="sku-101">
    <a class="product-link" href="/products/steel-mug">
      <h2 class="product-title">Steel Mug</h2>
    </a>
    <span class="product-price">$18.50</span>
  </article>
</section>

搭建一个小型 Bundler 项目

创建一个目录,初始化 Bundler,并仅添加所选路径中使用的库:

mkdir ruby_scraper && cd ruby_scraper
bundle init
mkdir -p fixtures test output
touch scraper.rb
# Gemfile
source "https://rubygems.org"

gem "faraday"
gem "faraday-follow_redirects"
gem "nokogiri"
gem "selenium-webdriver"
gem "capybara"
gem "csv"

Gemfile 中显式包含 csv,以确保该示例在所有受支持的 Ruby 安装环境中保持稳定;请根据项目所用的 Ruby 基准版本验证所有 gem 的版本。

使用 Faraday 安全地获取 HTML

面向生产环境的请求应具备可识别的 User-Agent、受限的连接数和读取超时、刻意设置的重定向限制,并在解析前通过检查拒绝错误页面。以下配置刻意保持简洁:

require "faraday"
require "faraday/follow_redirects"

TARGET_URL = "http://127.0.0.1:8000/catalog.html"

HTTP = Faraday.new do |f|
  f.response :follow_redirects, limit: 3
  f.options.open_timeout = 5
  f.options.timeout = 15
  f.headers["User-Agent"] =
    "RubyCatalogCollector/1.0 (contact: developer@example.com)"
  f.adapter Faraday.default_adapter
end

def fetch_html(url)
  response = HTTP.get(url)

  unless response.success?
    raise "HTTP #{response.status} for #{url}"
  end

  content_type = response.headers["content-type"].to_s.downcase
  unless content_type.include?("text/html")
    raise "Expected HTML, received #{content_type.inspect}"
  end

  response.body
end

如果目标系统要求身份验证,请将示例中的联系信息替换为真实的运营联系信息。切勿将 403, 429、登录页面或 JSON 错误正文,仿佛它们是普通 HTML 页面一样。记录状态、主机、尝试次数以及简短的错误类,但避免输出凭据或完整的响应正文。

Faraday Ruby 网页抓取在此处非常有用,因为连接策略始终可见且可测试。使用 HTTParty 时,请应用相同的概念:设置头部字段、配置超时、决定如何处理重定向,并验证状态码和内容类型。 当服务器根据接受的格式或客户端身份改变响应时,一份关于网络爬虫中 HTTP 头部的深入指南会有所帮助。

使用 Nokogiri 检查标记并提取记录

使用开发者工具(DevTools)确定代表单条记录的最小容器,然后针对该容器查询字段。范围选择器可防止导航栏或页脚中的价格与产品标题配对。优先选择持久的属性,例如 data-product-id、语义化元素名称或稳定的链接模式,而非仅描述间距或颜色的类。

require "nokogiri"
require "uri"

class ParseError < StandardError; end

def clean_text(node)
  node&.text&.gsub(/\s+/, " ")&.strip
end

def absolute_url(base_url, href)
  return nil if href.to_s.strip.empty?

  URI.join(base_url, href).to_s
rescue URI::InvalidURIError
  nil
end

def parse_records(html, page_url:)
  document = Nokogiri::HTML(html)
  cards = document.css("#catalog .product-card")
  raise ParseError, "No product cards matched" if cards.empty?

  cards.map do |card|
    link = card.at_css("a.product-link")

    {
      id: card["data-product-id"]&.strip,
      title: clean_text(card.at_css(".product-title")),
      price_text: clean_text(card.at_css(".product-price")),
      price_cents: nil,
      url: absolute_url(page_url, link&.[]("href"))
    }
  end
end

safe-navigation 运算符可防止一个可选节点导致整个运行崩溃,但不应将必填数据转换为无提示的 nil 值。下一步的验证将决定哪些字段为必填项。如果所有卡片都消失,解析器会立即报错,因为这通常意味着标记发生变化、出现意外的 HTML 或页面错误。

对于组件化的标记,通常使用 CSS 选择器最为简便。当选择依赖于文档关系、混合命名空间或 CSS 无法清晰表达的文本导向条件时,XPath 便派上用场。当选择器超出简单的后代和属性范围时,一份《XPath 与 CSS 选择器对比指南》将是非常有用的参考资料。

这是 Nokogiri 网络爬取的核心:分批下载字节、一次性解析、限定每条记录的范围,并使用 URI.join。相同的 parse_records 函数稍后也可处理浏览器捕获的HTML。

规范化值并生成确定性的 CSV 输出

仅根据声明的数据契约进行规范化。下面的价格解析器假设使用点作为小数分隔符,并可选地使用逗号作为千位分隔符。对于其他语言环境,请添加特定于该语言环境的规则,而不是凭猜测处理。保留 price_text 以便能够审核任何转换操作。

require "bigdecimal"
require "csv"

HEADERS = %w[id title price_text price_cents url].freeze

def price_to_cents(raw)
  number = raw.to_s.match(/-?\d[\d,]*(?:\.\d{1,2})?/)&.to_s
  return nil unless number

  (BigDecimal(number.delete(",")) * 100).round.to_i
end

def prepare_records(records)
  normalized = records.map do |record|
    record.merge(price_cents: price_to_cents(record[:price_text]))
  end

  valid = normalized.select do |record|
    record[:id] && !record[:title].to_s.empty? &&
      record[:price_cents] && record[:url]
  end

  valid.uniq { |record| record[:id] }
end

def write_csv(records, path)
  CSV.open(path, "w", write_headers: true, headers: HEADERS,
           encoding: "UTF-8") do |csv|
    records.sort_by { |record| record[:id] }.each do |record|
      csv << HEADERS.map { |field| record[field.to_sym] }
    end
  end
end

排序和固定标题可确保差异比较的可重复性。防止重复应使用最稳定的键,通常是源标识符或规范 URL,而非易变的标题。输出已解析、有效、被拒绝和重复记录的计数,以免“成功”运行掩盖了输出量的减少。

强化抓取程序以支持重复运行

一次性脚本即使失败也会明显报错,并可手动重跑。而定时任务则需要可预测的限制、足够的上下文来诊断故障,以及能够区分网络问题与选择器故障的测试。对于使用 Ruby 进行 Web 抓取,可靠性首先要确保每次重试都有上限,且每次空结果都能给出合理解释。 定义运行报告,其中应包含请求的 URL、成功获取的记录、已解析的行、被拒绝的行、重复项以及失败情况。这些计数器能比下游消费者更早地揭示数据漂移。

处理故障、重试和速率控制

在重试前对故障进行分类。DNS 错误、连接重置、超时、 429以及部分 5xx 响应通常是暂时的。大多数 4xx 响应对于当前请求而言是永久性的,应直接判定为失败,而无需引发重试风暴。身份验证错误、URL 格式错误和解析器故障需要纠正,而非采用退避策略。

TRANSIENT_STATUS = [429, 500, 502, 503, 504].freeze
TRANSIENT_ERRORS = [Faraday::TimeoutError,
                    Faraday::ConnectionFailed].freeze

def retry_delay(response, attempt)
  header = response&.headers&.fetch("retry-after", nil)
  return header.to_i if header&.match?(/\A\d+\z/)

  [0.5 * (2**(attempt - 1)) + rand * 0.25, 8].min
end

def fetch_with_retry(url, max_attempts: 4)
  1.upto(max_attempts) do |attempt|
    response = nil

    begin
      response = HTTP.get(url)
      if response.success?
        type = response.headers["content-type"].to_s
        return response.body if type.include?("text/html")

        raise "Unexpected content type #{type.inspect}"
      end

      raise "Permanent HTTP #{response.status}" unless
        TRANSIENT_STATUS.include?(response.status)
    rescue *TRANSIENT_ERRORS => error
      raise if attempt == max_attempts
      warn "fetch_failed host=#{URI(url).host} attempt=#{attempt} " \
           "error=#{error.class}"
    end

    raise "Retries exhausted for #{url}" if attempt == max_attempts

    delay = retry_delay(response, attempt)
    warn "retrying host=#{URI(url).host} attempt=#{attempt} delay=#{delay.round(2)}"
    sleep(delay)
  end
end

该 Retry-After 字段还可能包含 HTTP 日期,因此应解析《HTTP 语义》(RFC 9110)中描述的两种形式。对延迟和尝试次数设置上限,添加抖动,即使请求成功也应保持基准速率。并发操作绝不能超过网站公布的限制。

将失败的 URL 写入专用文件或任务表中,并附带时间戳、状态、尝试次数和错误类别。对查询值和头部信息进行脱敏处理。这样既能生成可操作的日志,又不会使其变成凭据存档。

使用保存的测试数据验证记录并测试选择器

将具有代表性的 HTML 保存至 test/fixtures,包括正常页面、缺少可选字段的页面以及结构被故意更改的页面。这样测试即可在本地运行,而无需反复向目标服务器发送请求。当源文件发生变化时,应有意识地刷新测试数据,并在接受新选择器前审查差异。

require "minitest/autorun"
require_relative "../scraper"

class ParserTest < Minitest::Test
  PAGE_URL = "https://example.test/catalog"

  def setup
    html = File.read("test/fixtures/catalog.html", encoding: "UTF-8")
    @records = prepare_records(parse_records(html, page_url: PAGE_URL))
  end

  def test_extracts_the_contract
    assert_equal 2, @records.length
    assert_equal %i[id price_cents price_text title url],
                 @records.first.keys.sort
    assert @records.all? { |record| record[:url].start_with?("https://") }
  end

  def test_empty_selector_result_is_an_error
    assert_raises(ParseError) do
      parse_records("<html><body></body></html>", page_url: PAGE_URL)
    end
  end
end

此外,应将 URL 规范化、ID 重复、价格无效以及可选节点缺失作为独立测试用例进行测试。在测试失败时包含页面 URL 和测试数据名称,以便轻松定位违约点。关键行为不仅仅是“方法返回了一个数组”。应验证记录数量和必填字段,并确保零记录的结果导致任务失败。 选择器的回归应以警报形式呈现,而非生成空的 CSV 文件——下游代码可能会误将其视为有效数据。

处理基于 JavaScript 的页面时,应避免过度依赖浏览器

浏览器自动化可以解决渲染和交互问题,但它会增加进程启动时间、内存消耗、浏览器更新、驱动程序问题,并带来更多与时间相关的故障。此外,由于 Cookie、下载内容和屏幕截图可能包含敏感状态信息,这也会扩大安全风险面。 在《使用 Ruby 进行 Web 爬取》中,应将无头浏览器视为数据位置诊断后的升级方案,而非其替代方案。在运行大量并发会话之前,阅读一篇关于无头浏览器架构的入门指南会很有帮助。

确认 JavaScript 确实是阻塞点

首先在原始响应中搜索已知的记录。然后检查 script 标签中是否嵌入了状态,并在开发者工具中观察经过授权的后台请求。如果 JSON 响应中包含数据且允许使用该数据,则直接请求该响应通常比重现视觉交互更简单。

当使用 Ruby 抓取 JavaScript 时,此检查通常可省去渲染步骤。仅当页面执行生成的表示形式是唯一可访问的、必需的交互会改变状态,或者经过身份验证的流程依赖于浏览器行为时,才需要使用浏览器。切勿使用自动化手段绕过访问控制。在编写备用方案之前,请确认内容和交互均在您的权限范围内。

结合 Selenium 和 Capybara 使用等待与清理机制

Selenium 控制浏览器,而 Capybara 提供更高层次的查找器和等待行为。为每个独立任务创建一个会话,等待页面达到有意义的状态,并始终关闭该会话。 现代 Selenium 配置可能会自动解析兼容的驱动程序,但该行为取决于 Selenium 版本、浏览器、操作系统和部署环境。请在每个受支持的平台上进行验证,而非假设会进行手动安装或自动管理。

require "capybara"
require "capybara/dsl"
require "selenium-webdriver"

Capybara.register_driver :chrome_headless do |app|
  options = Selenium::WebDriver::Chrome::Options.new
  options.add_argument("--headless")
  options.add_argument("--window-size=1440,1200")

  Capybara::Selenium::Driver.new(
    app, browser: :chrome, options: options
  )
end

Capybara.default_max_wait_time = 8

def fetch_rendered_records(url)
  session = Capybara::Session.new(:chrome_headless)

  begin
    session.visit(url)

    if session.has_button?("Accept", wait: 2)
      session.click_button("Accept")
    end

    session.find("#catalog", wait: 8)

    session.all("#catalog .product-card", minimum: 1).map do |card|
      link = card.find("a.product-link")

      {
        id: card["data-product-id"],
        title: card.find(".product-title").text.strip,
        price_text: card.find(".product-price").text.strip,
        price_cents: nil,
        url: URI.join(session.current_url, link[:href]).to_s
      }
    end
  rescue StandardError
    session.save_screenshot("output/browser-failure.png")
    raise
  ensure
    session.quit
  end
end

Capybara 查找器会持续轮询,直到条件满足或配置的等待时间到期。建议优先采用这种行为,或使用 Selenium 文档中记载的等待策略,而非固定的 sleep 调用。硬睡眠可作为临时调试手段,但不应作为同步逻辑使用。

弹出窗口的处理应尽可能精简且可选,因为按钮文本和同意流程因网站和地区而异。当浏览器提取失败时,应保存屏幕截图及当前 URL,但尽可能避免在文件名和屏幕截图中包含机密信息。提取完成后,将这些哈希值通过相同的 prepare_records 和 write_csv 方法。这种共享的架构使 Selenium Ruby 网页抓取成为传输层备用方案,而非第二条数据管道。

负责任地扩展和运行爬虫

可扩展性意味着在维持正确输出的同时,避免对目标网站或自身运行时造成过载。 对于使用 Ruby 进行 Web 爬取,与为每个 URL 启动一个线程相比,采用小型工作线程池、显式请求配额以及可观察的故障机制更为安全。定义请求配额和工作线程限制,以防止不良输入产生无限制的流量。从一个工作线程开始,进行性能监测,仅在目标服务器的限制条件和您的错误率均支持的情况下才提高限制。

限制并发并保持传输分离

使用队列和固定数量的 worker。如果需要渲染,每个 worker 都应创建自己的浏览器会话,因为在线程间共享驱动程序会引发状态和清理隐患。对于 HTTP 数据采集,请验证所选的 Faraday 适配器和连接对象是否适合您的并发模型,或者为每个 worker 建立一个独立的连接。

def scrape(url, transport:)
  html = transport.call(url)
  parse_records(html, page_url: url)
end

jobs = Queue.new
urls.each { |url| jobs << url }

worker_count = [Integer(ENV.fetch("SCRAPER_WORKERS", "3")), urls.length].min
mutex = Mutex.new
records = []

workers = Array.new(worker_count) do
  Thread.new do
    while (url = jobs.pop(true) rescue nil)
      batch = scrape(url, transport: method(:fetch_with_retry))
      mutex.synchronize { records.concat(batch) }
    end
  end
end

workers.each(&:join)

工作线程限制是一个上限,而非性能目标。应添加按主机限流机制,并在 429 响应时间、延迟或故障率上升时,停止或降低并发度。保持 transport 调用层分离,意味着您可以将直接请求替换为经过批准的代理或受管请求层,而无需更改 Nokogiri 的数据提取、验证或 CSV 处理逻辑。与将并行处理视为免费提速相比,这种做法能更好地支持网络爬取,同时避免被封锁。

保护凭据并遵守网站限制

从环境变量中加载密钥。若服务支持授权头部,请优先使用;若凭据必须作为查询参数,请由客户端进行编码。切勿记录密钥、完整头部或包含凭据的 URL。

api_key = ENV.fetch("WEBSCRAPINGAPI_API_KEY")
target_url = ENV.fetch("TARGET_URL")

client = Faraday.new(url: "https://api.webscrapingapi.com")

response = client.get("/v2", {
  api_key: api_key,
  url: target_url,
  render_js: 0
})

raise "HTTP #{response.status}" unless response.success?

html = response.body

操作许可的范围比解析器的技术能力更为广泛。请审查适用的条款、访问控制、速率限制、隐私义务以及所收集字段的敏感性。《机器人排除协议》定义了标准化的爬虫指令,但 robots.txt 这并非普遍授予的许可,也不是完整的法律分析。对于涉及重大风险的决策,请采用最新的网络爬取合规框架并咨询合格的法律顾问。

按症状排查故障

症状

可能原因

初步解决方法

选择器返回零张卡片

标记代码已更改或响应错误

将保存的 HTML 与测试用例进行对比

HTML 是一个登录页或验证码页面

访问状态异常

检查状态、请求头、Cookie 和权限

重复 403 或 429

请求被禁止或过于频繁

停止请求,审查限制条件,然后降低请求频率

频繁超时

网络速度慢或并发过高

减少工作进程数量并检查超时类型

浏览器驱动程序错误

浏览器、驱动程序或平台不匹配

请在该主机上验证 Selenium 的配置

文本乱码

解码假设错误

检查 Content-Type、字符集和原始字节

重复行

键不稳定或输入重叠

使用源ID或规范URL

字段突然缺失

选择器回归或部分渲染

验证失败并采集证据

关键要点

  • 在每个 Ruby Web 爬取项目中,请先定位原始 HTML、嵌入式状态、后台响应或渲染后的 DOM 中的数据,然后再选择库,并记录允许使用该数据源的原因。
  • 将传输、解析、规范化、验证和导出保持分离,以确保访问层的任何变更都不会重写数据处理管道,也不会改变静态路径和浏览器路径下的记录契约。
  • 将空的选择器结果、被拒绝的记录、重试、重复项和输出计数视为可观察的任务结果,而非偶然的调试细节,并随每次输出持久化这些指标。
  • 仅在必需的渲染或交互场景中使用浏览器自动化,并确保采用基于条件的等待机制、隔离的会话、故障截图、有保障的清理操作以及每会话凭据边界。
  • 在增加处理量之前,应限制并发数、控制请求频率、保护机密信息并审查网站限制;当错误或延迟增加时,应先降低限制,而非直接增加处理线程,并明确定义停止条件。

常见问题

构建网络爬虫是否必须使用 Ruby on Rails?

不需要。爬虫可以是一个通过 Bundler 管理的纯 Ruby 项目,并可通过命令行、调度程序或后台工作进程运行。只有当数据采集任务属于需要使用 Rails 数据库、任务队列、配置或管理界面的现有应用程序时,Rails 才有用。 对于功能专注的脚本,Rails 会增加启动成本和依赖关系,却无法提升数据提取效率。独立脚本也更容易进行调度,无需启动应用程序框架。

在 Ruby 中进行 HTTP 请求时,应该使用 Faraday 还是 HTTParty?

若需要适配器、中间件以及可随重试、性能监控或测试模拟而扩展的显式连接对象,请选择 Faraday。 当紧凑的类级 API 适合小型脚本,且团队已熟悉该 API 时,请选择 HTTParty。这两种客户端都无法自动确保抓取的可靠性。超时、状态处理、头部信息、速率控制和测试比表面语法更为重要。如果两者都适用,请优先选择团队能够一致地进行仪器化测试和模拟的客户端。

Nokogiri 能执行 JavaScript 吗,还是需要 Selenium?

Nokogiri 无法执行 JavaScript。它仅解析您提供的 HTML 或 XML 字符串。只有当允许的数据完全依赖于浏览器执行或交互后才能生成时,才需要 Selenium 或其他浏览器引擎。在添加这些组件之前,请先检查嵌入的 JSON 和经过授权的网络响应,因为这些来源可以在更短的时间内且不增加基础设施复杂性的情况下,提供相同的记录。 将浏览器捕获功能封装在接口之后,以便解析器测试仍可使用静态测试数据。

Kimurai 在现代 Ruby 爬虫技术栈中扮演什么角色?

当脚本发展为包含多种页面类型、队列、浏览器集成及生命周期规范的爬虫时,Kimurai 便可作为更高层次的框架进行评估。 对于小型数据提取任务,直接使用 Faraday 和 Nokogiri 代码更易于检查和维护。当维护工作主要集中在导航和协调而非解析时,其框架级别的便利性才最为重要。在为新项目选择 Kimurai 之前,请对照其文档确认其当前的兼容性和行为。

如何在不反复向真实网站发送请求的情况下测试 Ruby 爬虫?

在解析器测试中使用保存的 HTML 测试数据,然后对请求行为的 HTTP 边界进行模拟。WebMock 等库可以返回受控的状态码、头部和正文,而响应录制工具则可支持经过审查的集成测试数据。 保留少量定时在线检查以监测数据漂移,但应确保常规单元测试具有确定性、运行迅速,且不依赖目标系统的可用性。像审查代码一样审查测试数据更新,以防止页面变更悄然重新定义预期的数据契约。

结论

《使用 Ruby 进行 Web 爬取》的核心经验是:可靠的爬虫始于诊断,而非 gem 列表。 需验证记录是否存在于原始响应、嵌入状态、允许的后台响应中,还是仅存在于渲染后的 DOM 中。对于大多数静态页面,Faraday 和 Nokogiri 提供了从 HTTP 响应到规范化且经过验证的 CSV 的最简路径。

可维护性的提升源于该路径的边界控制。数据获取应强制执行超时和响应检查;解析应基于提供的 HTML 并生成一个稳定的模式;规范化应保留原始证据;验证应拒绝不完整的行;而 fixture 测试应使选择器漂移显而易见。 重试、限速、受限并发以及经过处理的日志,进而将脚本转变为可重复的任务,而非乐观循环。在安排任务之前,应定义可接受的输出条数和失败阈值,以便监控系统能够区分真正空白的页面与数据收集失败的情况。明确的小规模限制比隐藏的默认值更容易在生产环境中进行监控,且更改起来也更安全。

仅在确实需要 JavaScript 或交互操作时才使用 Selenium 和 Capybara,并在使用时复用相同的下游模式。 如果访问失败、验证码、代理轮换或请求层维护成为瓶颈,请考虑是否将 WebScrapingAPI 的 Scraper API 作为受管原始 HTML 传输方案,用于收集允许访问的页面。 确认当前产品是否适用后,请保持您的 Ruby 解析和 CSV 代码完整无损。这是一种切实可行的升级方案,因为它在不丢弃您已经测试过的爬虫程序的情况下,仅更改了传输方式。

关于作者

Raluca Penciuc, 全栈开发工程师 @ WebScrapingAPI

Raluca Penciuc

全栈开发工程师

Raluca Penciuc 是 WebScrapingAPI 的全栈开发工程师,主要负责开发爬虫、优化规避机制,并探索可靠的方法以降低在目标网站上的被检测概率。

开始构建

准备好扩展您的数据收集规模了吗?

加入2,000多家企业,使用WebScrapingAPI在无需任何基础设施开销的情况下,以企业级规模提取网络数据。