告诉urllib2使用自定义DNS

18 投票
3 回答
15608 浏览
提问于 2025-04-15 19:06

我想让 urllib2.urlopen(或者一个自定义的打开器)使用 127.0.0.1(或者 ::1)来解析地址。不过,我不想去改我的 /etc/resolv.conf 文件。

一个可能的解决办法是使用像 dnspython 这样的工具来查询地址,然后用 httplib 来构建一个自定义的 URL 打开器。不过,我更希望能直接告诉 urlopen 使用一个自定义的域名服务器。有没有什么建议呢?

3 个回答

0

你需要自己实现一个 DNS 查询客户端(或者像你说的那样使用 dnspython)。glibc 中的名称查找过程相当复杂,主要是为了确保能和其他非 DNS 的名称系统兼容。例如,在 glibc 库中根本没有办法指定一个特定的 DNS 服务器。

23

另一种(不太正规的)方法是对 socket.getaddrinfo 进行猴子补丁。

比如这段代码为 DNS 查询添加了一个(无限制的)缓存。

import socket
prv_getaddrinfo = socket.getaddrinfo
dns_cache = {}  # or a weakref.WeakValueDictionary()
def new_getaddrinfo(*args):
    try:
        return dns_cache[args]
    except KeyError:
        res = prv_getaddrinfo(*args)
        dns_cache[args] = res
        return res
socket.getaddrinfo = new_getaddrinfo
24

看起来名字解析最终是由 socket.create_connection 来处理的。

-> urllib2.urlopen
-> httplib.HTTPConnection
-> socket.create_connection

不过,一旦设置了“Host:”这个头信息,你就可以解析主机名,并把IP地址传递给打开连接的部分。

我建议你可以创建一个 httplib.HTTPConnection 的子类,然后重写 connect 方法,在把主机名传给 socket.create_connection 之前,先修改 self.host

接着,你可以再创建一个 HTTPHandler(还有 HTTPSHandler)的子类,把 http_open 方法替换成一个可以传递你自定义的 HTTPConnection,而不是使用 httplib 自带的,去调用 do_open

像这样:

import urllib2
import httplib
import socket

def MyResolver(host):
  if host == 'news.bbc.co.uk':
    return '66.102.9.104' # Google IP
  else:
    return host

class MyHTTPConnection(httplib.HTTPConnection):
  def connect(self):
    self.sock = socket.create_connection((MyResolver(self.host),self.port),self.timeout)
class MyHTTPSConnection(httplib.HTTPSConnection):
  def connect(self):
    sock = socket.create_connection((MyResolver(self.host), self.port), self.timeout)
    self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file)

class MyHTTPHandler(urllib2.HTTPHandler):
  def http_open(self,req):
    return self.do_open(MyHTTPConnection,req)

class MyHTTPSHandler(urllib2.HTTPSHandler):
  def https_open(self,req):
    return self.do_open(MyHTTPSConnection,req)

opener = urllib2.build_opener(MyHTTPHandler,MyHTTPSHandler)
urllib2.install_opener(opener)

f = urllib2.urlopen('http://news.bbc.co.uk')
data = f.read()
from lxml import etree
doc = etree.HTML(data)

>>> print doc.xpath('//title/text()')
['Google']

显然,如果你使用 HTTPS,会有证书的问题,你还需要填写 MyResolver...

撰写回答