Сообщите urllib2 использовать собственный DNS
Вопрос
Я хотел бы сказать urllib2.urlopen
(или пользовательская открывалка) использовать 127.0.0.1
(или ::1
) для разрешения адресов.я бы не стал менять свой /etc/resolv.conf
, однако.
Одним из возможных решений является использование такого инструмента, как dnspython
для запроса адресов и httplib
чтобы создать собственный инструмент для открытия URL-адресов.Я бы предпочел рассказать urlopen
однако использовать собственный сервер имен.Какие-либо предложения?
Решение
Похоже, разрешение имен в конечном итоге обрабатывается socket.create_connection
.
-> urllib2.urlopen
-> httplib.HTTPConnection
-> socket.create_connection
Хотя после того, как заголовок «Host:» установлен, вы можете определить хост и передать IP-адрес открывающему устройству.
Я бы предложил вам подкласс httplib.HTTPConnection
, и оберните connect
метод изменения self.host
прежде чем передать его socket.create_connection
.
Затем подкласс 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...
Другие советы
Другой (грязный) способ — исправление обезьян. 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
Вам нужно будет реализовать свой собственный клиент поиска DNS (или использовать dnspython, как вы сказали).Процедура поиска имени в glibc довольно сложна, чтобы обеспечить совместимость с другими системами имен, отличными от DNS.Например, вообще невозможно указать конкретный DNS-сервер в библиотеке glibc.