我有一个名为代理的模型,我在其中存储我的代理。
我希望Nokogiri每次打开网页时都能使用IP和端口获取随机代理。
这是我的代码:
total_proxies = @proxies.count
random_proxies = rand(total_proxies - total_proxies..total_proxies -1)
doc = Nokogiri::HTML(open(page.uri, :proxy => '#{random_proxies.ip}:#{random_proxies.port}'))
我在这里错过了什么?当我尝试运行它时,给我这个错误:
bad URI(is not URI?): #{random_proxies.ip}:#{random_proxies.port}
你正在使用单引号作为字符串参数来:p roxy。 您需要双引号,以便对#{}
进行插值。 这样:
doc = Nokogiri::HTML(open(page.uri, :proxy => "#{random_proxies.ip}:#{random_proxies.port}"))
我想让 nokogiri 获取一个随机代理...
你不明白Nokogiri是如何工作的。它什么也没得到。曾。它可以读取打开的文件,也可以接受字符串,但它不知道如何"获取"任何内容。
这项工作落在代码中的 OpenURI 上,它修补内核的 open
方法来理解 URL。OpenURI将打开的文件返回给Nokogiri,然后从中读取。因此,这使Nokogiri摆脱了获取任何东西的问题。读取错误
bad URI(is not URI?)
更仔细地,你会发现它来自OpenURI,而不是Nokogiri。
正如@PhilipHallstrom所说(给他功劳,而不是我),真正的问题是你使用单引号而不是双引号来包装你的内插字符串。考虑一下:
class RandomProxies
def ip
'127.0.0.1'
end
def port
42
end
end
random_proxies = RandomProxies.new
'#{random_proxies.ip}:#{random_proxies.port}'
# => "#{random_proxies.ip}:#{random_proxies.port}"
"#{random_proxies.ip}:#{random_proxies.port}"
# => "127.0.0.1:42"
。如果我没错,请纠正我 dosen open-uri 关闭连接。因为在它为每个页面打开代理后,我收到此错误"503 打开的连接太多"。
不,Nokogiri 不会关闭连接;我真的不认为这是它的责任。
您可以查看parse
并read_io
源代码来确认这一点,并且/或者,如果您使用的是Linux,则可以运行一些测试:
- 在磁盘上创建一个简单的 XML 文件。
启动 IRB 并加载 Nokogiri:
require 'nokogiri'
告诉 Nokogiri 使用以下方法解析文档:
Nokogiri::XML(open('path/to/file.xml'))
切换到另一个 shell 中的命令行并运行:
lsof | grep path/to/file.xml
lsof
命令将显示 Ruby 仍然打开该文件,尽管 Nokogiri 读取了它。("LSOF"代表"列出打开的文件")。退出 IRB 并再次运行
lsof
命令。您将看到它已关闭,因为文件未显示。
遇到问题的原因是因为您正在打开一堆文件而不是关闭它们。通常/通常我们可以不关闭它们,因为有大量可用的文件句柄,并且代码在退出之前不会耗尽这些句柄。但这并不是真正的好编程实践。我们应该显式关闭它们,或者依赖于在完成后关闭文件的方法/结构。
因为您正在使用 Nokogiri 将文件解析为 DOM,所以 Nokogiri 会将整个文件读入内存进行处理。在这种情况下,不要简单地使用 open
,请使用 open
后跟 read
,完成后将关闭文件。从文档中:
读取确保文件在返回之前已关闭。
或者,您可以将文档的分析(无论是文件还是从网络 URL 读取)放入块中,以便自动关闭。像这样未经测试的代码应该可以工作:
require 'open-air'
%w[urls to read].each do |url|
open(url) do |xml|
dom = Nokogiri::XML(xml)
# do something with the dom
end
end
请记住,使用带有open
的块可确保在块退出时自动关闭文件。
如果指定了块,则将以 IO 对象作为参数调用该块,并且当块终止时,IO 将自动关闭。调用返回块的值。
这应该可以防止"503 打开的连接过多"问题发生,因为您的代码一次只能打开一个与主机的连接。
如果您请求的页面数已经用完了,另一种解决方案是使用 Typhoeus 和 Hydra,它们可以并行抓取 URL,并且可以被告知如何限制连接。(您将请求限制在合理的价格,不是吗?如果没有,你应该得到503错误。