【发布时间】:2013-08-02 01:10:16
【问题描述】:
我正在使用 python 运行一个脚本,该脚本使用 urllib2 从天气 api 获取数据并将其显示在屏幕上。我遇到的问题是,当我查询服务器时,我收到“没有与主机名关联的地址”错误。我可以使用网络浏览器查看 api 的输出,也可以使用 wget 下载文件,但我必须强制 IPv4 才能使其工作。使用 urllib2.urlopen 时是否可以在 urllib2 中强制 IPv4?
【问题讨论】:
我正在使用 python 运行一个脚本,该脚本使用 urllib2 从天气 api 获取数据并将其显示在屏幕上。我遇到的问题是,当我查询服务器时,我收到“没有与主机名关联的地址”错误。我可以使用网络浏览器查看 api 的输出,也可以使用 wget 下载文件,但我必须强制 IPv4 才能使其工作。使用 urllib2.urlopen 时是否可以在 urllib2 中强制 IPv4?
【问题讨论】:
不直接,不。
那么,你能做什么呢?
一种可能性是自己将主机名显式解析为 IPv4,然后使用 IPv4 地址而不是名称作为主机。例如:
host = socket.gethostbyname('example.com')
page = urllib2.urlopen('http://{}/path'.format(host))
但是,某些虚拟服务器站点可能需要 Host: example.com 标头,而它们将获得 Host: 93.184.216.119。您可以通过覆盖标题来解决这个问题:
host = socket.gethostbyname('example.com')
request = urllib2.Request('http://{}/path'.format(host),
headers = {'Host': 'example.com'})
page = urllib2.urlopen(request)
或者,您可以提供自己的处理程序来代替标准处理程序。但标准处理程序大多只是httplib.HTTPConnection 的包装,真正的问题在于HTTPConnection.connect。
因此,干净的方法是创建您自己的httplib.HTTPConnection 子类,它会像这样覆盖connect:
def connect(self):
host = socket.gethostbyname(self.host)
self.sock = socket.create_connection((host, self.post),
self.timeout, self.source_address)
if self._tunnel_host:
self._tunnel()
然后创建您自己的 urllib2.HTTPHandler 子类,覆盖 http_open 以使用您的子类:
def http_open(self, req):
return self.do_open(my wrapper.MyHTTPConnection, req)
...HTTPSHandler 也是如此,然后按照urllib2 文档中所示正确连接所有内容。
做同样事情的快速而肮脏的方法是只猴子补丁httplib.HTTPConnection.connect到上面的函数。
最后,您可以使用不同的库来代替urllib2。据我记得,requests 并没有让这变得更容易(最终,您必须覆盖或猴子补丁略有不同的方法,但实际上是相同的)。但是,任何libcurl 包装器都将允许您执行与curl_easy_setopt(h, CURLOPT_IPRESOLVE, CURLOPT_IPRESOLVE_V4) 等效的操作。
【讨论】:
socket 是一个模块,socket.socket 是socket 模块的一个类。
不是一个正确的答案,而是一个替代方案:致电curl?
import subprocess
import sys
def log_error(msg):
sys.stderr.write(msg + '\n')
def curl(url):
process = subprocess.Popen(
["curl", "-fsSkL4", url],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
stdout, stderr = process.communicate()
if process.returncode == 0:
return stdout
else:
log_error("Failed to fetch: %s" % url)
log_error(stderr)
exit(3)
【讨论】: