如何让xml.sax在DTD请求中使用HTTP代理?

3 投票
1 回答
617 浏览
提问于 2025-04-16 07:29

大家都知道,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 的部分,让它使用你安装了代理的 urllib2UrlOpener

不幸的是,我觉得这是你能实现你想要的功能的唯一方法,而不需要改变系统的全局设置或者创建一个可以缓存结果的 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,
    )

撰写回答