2010-02-10 11 views
13

Ich mag urllib2.urlopen sagen (oder ein individueller Opener) zu verwenden 127.0.0.1 (oder ::1)-Adressen aufzulösen. Ich würde meine /etc/resolv.conf jedoch nicht ändern.Sagen urllib2 individuelle DNS verwenden

Eine mögliche Lösung besteht darin, ein Tool wie dnspython zum Abfragen von Adressen und httplib zu verwenden, um einen benutzerdefinierten URL-Öffner zu erstellen. Ich würde lieber urlopen sagen, einen benutzerdefinierten Nameserver zu verwenden. Irgendwelche Vorschläge?

Antwort

20

Sieht aus wie Namensauflösung wird letztlich von socket.create_connection behandelt.

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

Obwohl, sobald der „Host:“ Header festgelegt wurde, können Sie den Host und geben die IP-Adresse durch bis auf den Opener lösen.

Ich würde vorschlagen, dass Sie httplib.HTTPConnection Unterklasse, und wickeln Sie die connect Methode self.host zu ändern, bevor es zu socket.create_connection vorbei.

Unterklasse Dann HTTPHandler (und HTTPSHandler) die http_open Methode mit einer ersetzen, die Ihre HTTPConnection statt httplib eigenen zu do_open geht.

So:

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'] 

Offensichtlich gibt es Zertifikat Fragen, ob Sie die HTTPS verwenden, und Sie werden MyResolver füllen müssen aus ...

+0

Ich glaube nicht, dass ich HTTPS für jetzt brauche, also wird dies vollkommen ausreichen! Vielen Dank! –

+0

Es ist auch möglich, 'HTTPConnection._create_connection' zu überschreiben, das seit Python 2.7.7 und 3.5 aufgrund http://bugs.python.org/issue7776 verfügbar ist. –

0

Sie müssen Ihre eigenen DNS-Lookup implementieren Client (oder mit dnspython wie du gesagt hast). Die Namenssuchprozedur in glibc ist ziemlich komplex, um die Kompatibilität mit anderen Nicht-DNS-Namenssystemen zu gewährleisten. Es gibt zum Beispiel keine Möglichkeit, einen bestimmten DNS-Server in der glibc-Bibliothek überhaupt anzugeben.

16

Ein anderer (schmutziger) Weg ist Affe-Patching socket.getaddrinfo.

Zum Beispiel fügt dieser Code einen (unbegrenzten) Cache für DNS-Lookups hinzu.

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 
+2

Ein Vorteil dieses Hacks ist auch abfängt fast alle DNS-Lookups in Python, nicht nur über 'urlopen' –

+0

Dies ist eine bessere Lösung, wenn die Hosts in einer kleinen Anzahl beschränkt sind. Ich habe eine 10-fache Geschwindigkeit damit. :) –