你有没有遇到过这种场景:想批量收集电商价格、招聘信息、房源列表或新闻内容,手动复制粘贴到崩溃。**网页抓取(Web Scraping)**就是专门解决这类“重复劳动”的——把网页上的结构化信息自动提取出来,速度更快、覆盖更广、成本也更可控。
这篇文章用“朋友带你走一遍”的方式,带你从 0 写一个可用的 Ruby 爬虫:能请求页面、解析 HTML、处理翻页,并把数据落到 CSV,最后再补一套更稳定的反爬与容错思路。
Ruby 做网页抓取有个很朴素的优势:写起来顺手,改起来也不痛苦。具体体现在:
语法表达力强:爬虫最怕“写完自己都看不懂”,Ruby 在可读性上确实省心。
标准库够用:处理 HTTP、HTML、JSON、CSV 这类常见需求,Ruby 生态很成熟。
工具多、路径多:从“轻量解析 HTML”到“驱动浏览器抓 JS 页面”,都有对应的 gem 可选。
适合快速迭代:网站结构一变就得改选择器,Ruby 这种“改两行就能跑”的体验很友好。
不同网站“难度”差别很大,选库要看目标站点的特点:
Nokogiri:解析 HTML/XML 的主力,提取文本、属性、列表很高效。
HTTParty / Net::HTTP:发请求用,适合抓静态页面或接口返回的 HTML/JSON。
Mechanize:需要“像人一样点链接、带 cookie、提交表单”的站点更合适。
Watir / Selenium:页面强依赖 JavaScript、需要等待异步渲染时,用浏览器自动化更稳。
Kimurai:偏框架化的方案,适合更工程化的 Ruby 爬虫项目。
很多人第一次写 Ruby 爬虫,代码没问题,卡在的是:403、429、验证码、IP 被封。这通常不是你解析写错了,而是请求环境被识别为“机器”。
你可以自己搭代理池、做 UA 轮换、处理重试与封禁;也可以把这些交给专门的抓取接口来省时间。
👉 用 ScraperAPI 把代理轮换和封禁处理交给服务端
下面我们先把基础的网页抓取流程跑通,再补稳定性细节。
下面用“抓取一个列表页(比如招聘/商品列表)”为例。你只需要把 URL 和选择器换成自己的目标站点即可。
新建项目并安装依赖:
bash
mkdir ruby_scraper_demo
cd ruby_scraper_demo
bundle init
编辑 Gemfile:
ruby
source "https://rubygems.org"
gem "httparty"
gem "nokogiri"
安装:
bash
bundle install
打开目标网页,按 F12(或右键“检查”):
列表项的外层容器是什么?(例如 .item、.job、li.result)
标题、公司、价格等字段分别在哪个节点?
翻页“下一页”的链接在哪里?是 <a> 还是按钮?链接是相对路径还是完整 URL?
这一步做扎实,后面写代码会轻松很多。
新建 scraper.rb:
ruby
require "httparty"
url = "https://example.com/list?page=1"
response = HTTParty.get(url, headers: { "User-Agent" => "Mozilla/5.0" })
puts "status=#{response.code}"
puts response.body[0, 300]
如果状态码不是 200,优先排查:
是否需要加请求头(User-Agent、Accept-Language 等)
是否被限流(429)或封禁(403)
是否需要登录或带 cookie
当你发现“本地能跑、线上就挂”,大概率就是反爬策略开始生效了。想快速提升成功率,通常需要更稳定的代理与请求指纹策略:
👉 ScraperAPI:提升抓取成功率的更省心方式
继续编辑 scraper.rb:
ruby
require "httparty"
require "nokogiri"
url = "https://example.com/list?page=1"
html = HTTParty.get(url, headers: { "User-Agent" => "Mozilla/5.0" }).body
doc = Nokogiri::HTML(html)
items = doc.css(".item") # 把 .item 换成你的列表容器选择器
items.each do |item|
title = item.at_css(".title")&.text&.strip
company = item.at_css(".company")&.text&.strip
location = item.at_css(".location")&.text&.strip
puts [title, company, location].inspect
end
几个小技巧能让 Ruby 网页抓取更稳定:
用 at_css + &. 处理“节点不存在”的情况,减少崩溃概率
.text.strip 做清洗,避免空格和换行污染数据
选择器尽量“短且稳”,别过度依赖层级结构
下面用循环写法更直观,也方便加“最大页数”控制:
ruby
require "httparty"
require "nokogiri"
BASE_URL = "https://example.com"
def fetch_doc(url)
html = HTTParty.get(url, headers: { "User-Agent" => "Mozilla/5.0" }).body
Nokogiri::HTML(html)
end
url = "#{BASE_URL}/list?page=1"
page = 1
max_pages = 5
while url && page <= max_pages
doc = fetch_doc(url)
doc.css(".item").each do |item|
title = item.at_css(".title")&.text&.strip
puts title
end
next_link = doc.at_css('a[aria-label="Next"], a.next')
url = next_link ? "#{BASE_URL}#{next_link["href"]}" : nil
page += 1
sleep 1
end
这里的关键点是:找到“下一页”链接并拼接成可访问的 URL。实际项目里,建议加上:
最大页数/最大条数限制(防止无限循环)
请求失败重试(避免偶发网络抖动导致整批任务报废)
把抓到的字段写入 jobs.csv(文件名随意):
ruby
require "httparty"
require "nokogiri"
require "csv"
BASE_URL = "https://example.com"
def fetch_doc(url)
html = HTTParty.get(url, headers: { "User-Agent" => "Mozilla/5.0" }).body
Nokogiri::HTML(html)
end
CSV.open("jobs.csv", "w") do |csv|
csv << ["Title", "Company", "Location"]
url = "#{BASE_URL}/list?page=1"
page = 1
max_pages = 5
while url && page <= max_pages
doc = fetch_doc(url)
doc.css(".item").each do |item|
title = item.at_css(".title")&.text&.strip
company = item.at_css(".company")&.text&.strip
location = item.at_css(".location")&.text&.strip
csv << [title, company, location]
end
next_link = doc.at_css('a[aria-label="Next"], a.next')
url = next_link ? "#{BASE_URL}#{next_link["href"]}" : nil
page += 1
sleep 1
end
end
当你的网页抓取规模从“几十页”变成“几千页”,稳定性比功能更重要。建议优先做到这些:
看 robots.txt 与站点规则:别一上来就全站扫,先确认抓取范围与频率。
控制抓取频率:固定 sleep 或随机延迟都行,目标是“更像正常用户”。
加重试与超时:对超时、502、429 做有限次数重试,别无限重试拖垮任务。
记录日志与失败样本:把失败 URL 存下来,方便二次补抓与定位变化点。
准备代理与 UA 策略:遇到封禁时,单纯加 sleep 不够,通常要“换出口”。
如果你不想自己维护代理池、封禁恢复、请求指纹这些琐碎但费时间的东西,可以考虑把抓取请求交给更专业的接口层处理:
👉 用 ScraperAPI 让大规模 Web Scraping 更稳更省事
写一个能跑的 Ruby 爬虫不难:HTTParty 拿 HTML,Nokogiri 抠字段,循环处理翻页,再导出 CSV,就能把网页抓取流程跑通。真正拉开差距的,是你能不能在规模化抓取时保持更稳定、更快、失败率更低。