在Ruby中检查URL是否存在

60

我该如何使用 Ruby 检查 URL 是否存在?

例如,对于以下 URL:

https://google.com

结果应该是真值,但对于URL来说

https://no.such.domain
或者
https://stackoverflow.com/no/such/path

结果应为falsey


你应该阅读这篇文章:在Ruby on Rails中验证URL/URI - Sandro Munda
9
问题足够好,与我在谷歌搜索的匹配度很高,答案非常有价值。 - kranzky
我同意。这个问题很有用。 - Dessa Simpson
1
我认为这是一个有用答案的好问题。它被关闭的原因(“必须表现出最基本的理解”)在SO上已经不再有效。我编辑了问题并添加了一些示例。因此,我认为现在可以重新打开这个问题了。 - Wayne Conrad
如果您认为这个问题很好,请投票“重新开放”。还需要4个人才能重新开放这个问题。我想发布一个考虑重定向的答案。 - ironsand
4个回答

74

使用Net::HTTP库。

require "net/http"
url = URI.parse("http://www.google.com/")
req = Net::HTTP.new(url.host, url.port)
res = req.request_head(url.path)

此时,res是一个包含请求结果的Net::HTTPResponse对象。您可以检查响应代码:

do_something_with_it(url) if res.code == "200"

注意: 要检查基于 https 的URL,use_ssl 属性应该设置为 true,如下所示:

require "net/http"
url = URI.parse("https://www.google.com/")
req = Net::HTTP.new(url.host, url.port)
req.use_ssl = true
res = req.request_head(url.path)

在生产环境中,每个URL都返回200代码..我解析了http://www.http:/这个URL并给出了200 OK..但是这是错误的...问题在哪里?有任何想法吗?注意:本地环境下这个工作正常。 - Jay_Pandya
为了检查查询部分,例如 YouTube URL,可以使用 address = [url.path, url.query].compact.split('').flatten.join('?') 或者在 Rails 中使用 [url.path.presence || '/', url.query.presence].compact.join('?'),然后执行 req.request_head(address) - Nic Nilov

62
抱歉回复晚了,但我认为这个问题值得更好的回答。
有三种方法来看待这个问题:
1. 严格检查URL是否存在 2. 检查您是否正确请求URL 3. 检查您能否正确请求并且服务器能够正确响应
1. 严格检查URL是否存在
虽然200意味着服务器响应了该URL(因此URL已经存在),但是其他状态码的响应并不意味着URL不存在。例如,响应302 - 重定向意味着URL存在并且正在重定向到另一个URL。在浏览时,302很多时候对最终用户的行为与200相同。如果URL存在,则可以返回其他状态代码,如500 - 内部服务器错误。毕竟,如果URL不存在,那么它怎么会导致应用程序服务器处理您的请求而不是简单地返回404 - 未找到呢?
因此,当URL不存在时,实际上只有两种情况:服务器不存在或服务器存在但无法找到给定的URL路径。因此,检查URL是否存在的唯一方法是检查服务器是否有响应且返回代码不是404。以下代码就是如此。
require "net/http"
def url_exist?(url_string)
  url = URI.parse(url_string)
  req = Net::HTTP.new(url.host, url.port)
  req.use_ssl = (url.scheme == 'https')
  path = url.path if url.path.present?
  res = req.request_head(path || '/')
  res.code != "404" # false if returns 404 - not found
rescue Errno::ENOENT
  false # false if can't find the server
end

2. 检查您是否正确请求URL

然而,大多数情况下我们并不关心URL是否存在,而是能否访问它。幸运的是,通过查看HTTP状态码中的4xx系列,即客户端错误(因此,在您的一侧出现错误,这意味着您没有正确请求页面,没有权限或其他问题)。这是检查是否可以访问此页面的错误之一。来自维基百科:

4xx状态码旨在处理客户端似乎出错的情况。除了响应HEAD请求时,服务器应包含一个实体,其中包含错误情况的说明以及它是临时还是永久条件。这些状态码适用于任何请求方法。用户代理应向用户显示任何包含的实体。

因此,以下代码确保URL存在且您可以访问它

require "net/http"
def url_exist?(url_string)
  url = URI.parse(url_string)
  req = Net::HTTP.new(url.host, url.port)
  req.use_ssl = (url.scheme == 'https')
  path = url.path if url.path.present?
  res = req.request_head(path || '/')
  if res.kind_of?(Net::HTTPRedirection)
    url_exist?(res['location']) # Go after any redirect and make sure you can access the redirected URL 
  else
    res.code[0] != "4" #false if http code starts with 4 - error on your side.
  end
rescue Errno::ENOENT
  false #false if can't find the server
end

3. 检查是否可以正确请求并且服务器能够正确响应

就像 4xx 系列检查您是否可以访问URL一样,5xx 系列检查服务器是否有任何问题回答您的请求。这个系列的错误大多数情况下都是由于服务器本身出现问题,希望他们正在努力解决。如果您需要立即访问页面并获得正确的答案,您应该确保答案不来自 4xx5xx 系列,并且如果您被重定向,重定向的页面正确地回答了。与(2)非常相似,您可以简单地使用以下代码:

require "net/http"
def url_exist?(url_string)
  url = URI.parse(url_string)
  req = Net::HTTP.new(url.host, url.port)
  req.use_ssl = (url.scheme == 'https')
  path = url.path if url.path.present?
  res = req.request_head(path || '/')
  if res.kind_of?(Net::HTTPRedirection)
    url_exist?(res['location']) # Go after any redirect and make sure you can access the redirected URL 
  else
    ! %W(4 5).include?(res.code[0]) # Not from 4xx or 5xx families
  end
rescue Errno::ENOENT
  false #false if can't find the server
end

2
如果您使用https-url进行操作,可能会出现“Net :: HTTPBadResponse:错误的状态行”错误。这是因为您需要告诉Net:HTTP使用ssl。为了使其也适用于https,请在调用“request_head”之前加入一行“req.use_ssl =(url.scheme =='https')”。 - Yo Ludke
1
另外一件事:如果您请求(或重定向到)“http://www.example.com”(没有尾随斜杠),则会收到“ArgumentError:HTTP请求路径为空”的错误。这可以通过将`res = req.request_head(url.path)行更改为path = url.path if url.path.present?req.request_head(path || '/')`来解决。 - Yo Ludke
我在gist.github.com上创建了一个代码片段,它对我很有效。链接为https://gist.github.com/tb/8787397。 - tomaszbak
6
我不得不添加更多的救援以处理其他情况: rescue Errno::ENOENT false #如果找不到服务器,则返回false rescue URI::InvalidURIError false #如果URI无效,则返回false rescue SocketError false #如果无法打开TCP连接,则返回false rescue Errno::ECONNREFUSED false #如果无法打开TCP连接,则返回false rescue Net::OpenTimeout false #如果执行超时,则返回false rescue OpenSSL::SSL::SSLError false - Camille
1
@Tashows 只有在恶意用户可以利用 URI.parse 时才会不安全,据我所知,它没有已知的漏洞。 - fotanus
显示剩余3条评论

32

Net::HTTP 可以使用,但如果你可以不依赖于标准库的话,Faraday 更好。

Faraday.head(the_url).status == 200

(假设"存在"是指200状态码表示成功。)


8
你认为为什么这样做更好? - Dennis
2
您还可以使用RestClient库require 'rest_client'; RestClient.head(url).code != 404 - Dennis
如果您只想检查一般的“成功”,那么您也可以使用.success?。这将返回任何状态码从200299true,并对所有其他状态码返回false。https://github.com/lostisland/faraday/search?q=SuccessfulStatuses - Joshua Pinter

3

Simone的回答对我很有帮助。

这里有一个版本,根据URL有效性返回true/false,并处理重定向:

require 'net/http'
require 'set'

def working_url?(url, max_redirects=6)
  response = nil
  seen = Set.new
  loop do
    url = URI.parse(url)
    break if seen.include? url.to_s
    break if seen.size > max_redirects
    seen.add(url.to_s)
    response = Net::HTTP.new(url.host, url.port).request_head(url.path)
    if response.kind_of?(Net::HTTPRedirection)
      url = response['location']
    else
      break
    end
  end
  response.kind_of?(Net::HTTPSuccess) && url.to_s
end

如果服务器不支持HEAD请求怎么办? - Slava Nikulin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接