如何让xml.sax在DTD请求中使用HTTP代理?
大家都知道,XML解析器在处理文档时,常常会发送HTTP请求去获取文档中引用的DTD。具体来说,Python的解析器就是这样做的。这导致了www.w3.org网站的流量过大,因为这个网站上托管了很多这样的DTD文件。结果就是,XML解析的速度变得非常慢,有时候甚至会超时。这是个大问题,因为这让一个看似只和文本处理有关的任务,变得依赖于一个不可靠的第三方。
为了缓解这个问题(因为找到真正的解决方案非常困难),我想在本地安装一个缓存的网络代理,并让xml.sax通过这个代理发送请求。我特别不想让代理设置泄露到其他组件,所以系统范围的设置是不行的。
我该如何让xml.sax使用HTTP代理呢?
我已经有了:
handler = # instance of a subclass of xml.sax.handler.ContentHandler
parser = xml.sax.make_parser()
parser.setContentHandler(handler)
parser.parse(indata)
return handler.result()
一种方法是使用自定义的EntityResolver。不过,结果发现实现一个缓存的EntityResolver是不可能的,因为它没有足够的信息。
1 个回答
2
有一种快速而简单的方法可以做到这一点,那就是对 saxutils.prepare_input_source
进行“猴子补丁”。你几乎可以直接复制粘贴这段代码,然后调整一下调用 urllib.urlopen
的部分,让它使用你安装了代理的 urllib2
的 UrlOpener
。
不幸的是,我觉得这是你能实现你想要的功能的唯一方法,而不需要改变系统的全局设置或者创建一个可以缓存结果的 EntityResolver
。
问题在于 saxutils.prepare_input_source
明确地调用了 urllib.urlopen
,而且没有选项可以修改这个行为。所以你必须通过你的代理来处理这个请求,这样会影响到所有使用 urllib
的其他客户端。
来自Magnus Hoff: 一个有效的猴子补丁实现:
def make_caching_prepare_input_source(old_prepare_input_source, proxy):
def caching_prepare_input_source(source, base = None):
if isinstance(source, xmlreader.InputSource):
return source
full_uri = urlparse.urljoin(base or "", source)
if not full_uri.startswith('http:'):
args = (source,) if base == None else (source, base)
return old_prepare_input_source(*args)
r = urllib2.Request(full_uri)
r.set_proxy(proxy, 'http')
f = urllib2.urlopen(r)
i = xmlreader.InputSource()
i.setSystemId(source)
i.setByteStream(f)
return i
return caching_prepare_input_source
def enable_http_proxy(server):
saxutils.prepare_input_source = make_caching_prepare_input_source(
saxutils.prepare_input_source,
server,
)