我正在使用Python运行一个脚本,使用urllib2从天气API获取数据并在屏幕上显示。我遇到的问题是,在查询服务器时,出现“没有与主机名关联的地址”错误。我可以通过Web浏览器查看api的输出,并且可以使用wget下载文件,但我必须强制IPv4才能使其正常工作。在使用urllib2.urlopen时,是否可能强制使用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)
的操作。
并不是一个正式的答案,但是有个替代方案:调用 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)
socket
是一个模块,而socket.socket
是socket
模块的一个类。 - abarnert