21

我正在尝试用 python 编写一个网络代理。目标是访问一个 url,如:并像往常一样http://proxyurl/http://anothersite.com/查看他的内容。http://anothersite.com通过滥用 requests 库,我已经取得了不错的成绩,但这并不是 requests 框架的真正预期用途。我以前用twisted写过代理,但我不知道如何将它与我想做的事情联系起来。这就是我目前所处的位置......

import os
import urlparse

import requests

import tornado.ioloop
import tornado.web
from tornado import template

ROOT = os.path.dirname(os.path.abspath(__file__))
path = lambda *a: os.path.join(ROOT, *a)

loader = template.Loader(path(ROOT, 'templates'))


class ProxyHandler(tornado.web.RequestHandler):
    def get(self, slug):
        if slug.startswith("http://") or slug.startswith("https://"):
            if self.get_argument("start", None) == "true":
                parsed = urlparse.urlparse(slug)
                self.set_cookie("scheme", value=parsed.scheme)
                self.set_cookie("netloc", value=parsed.netloc)
                self.set_cookie("urlpath", value=parsed.path)
            #external resource
            else:
                response = requests.get(slug)
                headers = response.headers
                if 'content-type' in headers:
                    self.set_header('Content-type', headers['content-type'])
                if 'length' in headers:
                    self.set_header('length', headers['length'])
                for block in response.iter_content(1024):
                    self.write(block)
                self.finish()
                return
        else:
            #absolute
            if slug.startswith('/'):
                slug = "{scheme}://{netloc}{original_slug}".format(
                    scheme=self.get_cookie('scheme'),
                    netloc=self.get_cookie('netloc'),
                    original_slug=slug,
                )
            #relative
            else:
                slug = "{scheme}://{netloc}{path}{original_slug}".format(
                    scheme=self.get_cookie('scheme'),
                    netloc=self.get_cookie('netloc'),
                    path=self.get_cookie('urlpath'),
                    original_slug=slug,
                )
        response = requests.get(slug)
        #get the headers
        headers = response.headers
        #get doctype
        doctype = None
        if '<!doctype' in response.content.lower()[:9]:
            doctype = response.content[:response.content.find('>')+1]
        if 'content-type' in headers:
           self.set_header('Content-type', headers['content-type'])
        if 'length' in headers:
            self.set_header('length', headers['length'])
        self.write(response.content)


application = tornado.web.Application([
    (r"/(.+)", ProxyHandler),
])

if __name__ == "__main__":
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()

请注意,如果查询字符串中有 start=true,我设置了一个 cookie 来保留方案、netloc 和 urlpath。这样,然后命中代理的任何相对或绝对链接都使用该 cookie 来解析完整的 url。

有了这个代码,如果你去http://localhost:8888/http://espn.com/?start=true你会看到 ESPN 的内容。但是,在以下站点上它根本不起作用:http: //www.bottegaveneta.com/us/shop/。我的问题是,最好的方法是什么?我目前的实施方式是稳健的,还是这样做有一些可怕的陷阱?如果它是正确的,为什么像我指出的那样某些网站根本不起作用?

感谢您的任何帮助。

4

5 回答 5

8

我最近写了一个类似的网络应用程序。请注意,这是我的做法。我不是说你应该这样做。这些是我遇到的一些陷阱:

将属性值从相对更改为绝对

除了获取页面并将其呈现给客户端之外,还涉及更多内容。很多时候,您无法在没有任何错误的情况下代理网页。

为什么像我指出的那样某些网站根本不起作用?

许多网页依赖于资源的相对路径,以便以格式良好的方式显示网页。例如,这个图像标签:

<img src="/header.png" />

将导致客户端发出以下请求:

http://proxyurl/header.png

哪个失败了。' src ' 值应转换为:

http://anothersite.com/header.png.

因此,您需要使用BeautifulSoup之类的东西解析 HTML 文档,遍历所有标签并检查以下属性:

'src', 'lowsrc', 'href'

并相应地更改它们的值,使标签变为:

<img src="http://anothersite.com/header.png" />

此方法适用于更多标签,而不仅仅是图像标签。a , script , link , liframe也是一些你应该改变的。

HTML 恶作剧

先前的方法应该能让你走得更远,但你还没有完成。

两个都

<style type="text/css" media="all">@import "/stylesheet.css?version=120215094129002";</style>

<div style="position:absolute;right:8px;background-image:url('/Portals/_default/Skins/BE/images/top_img.gif');height:200px;width:427px;background-repeat:no-repeat;background-position:right top;" >

是使用BeautifulSoup难以访问和修改的代码示例。

在第一个示例中,有一个相对 uri 的 css @Import。第二个涉及内联 CSS 语句中的 ' url() ' 方法。

在我的情况下,我最终编写了糟糕的代码来手动修改这些值。您可能想为此使用正则表达式,但我不确定。

重定向

使用 Python-Requests 或 Urllib2,您可以轻松地自动跟踪重定向。只要记住保存新的(基本)uri 是什么;您将需要它来执行“将属性值从相对更改为绝对”操作。

您还需要处理“硬编码”重定向。比如这个:

<meta http-equiv="refresh" content="0;url=http://new-website.com/">

需要改为:

<meta http-equiv="refresh" content="0;url=http://proxyurl/http://new-website.com/">

基础标签

base 标记指定文档中所有相对 URL 的基本 URL/目标。您可能想要更改该值。

终于完成了?

没有。一些网站严重依赖 javascript 在屏幕上绘制内容。这些网站是最难代理的。我一直在考虑使用PhantomJSGhost 之类的东西来获取和评估网页并将结果呈现给客户端。

也许我的源代码可以帮助你。你可以以任何你想要的方式使用它。

于 2013-11-01T15:10:50.580 回答
3

如果要制作真正的代理,可以使用:

龙卷风代理

或者

基于 Twisted 的简单代理

但我认为根据您的情况调整它们并不难。

于 2013-08-26T14:57:37.427 回答
0

您可以使用标准库中的 socket 模块,如果您使用的是 Linux epoll 也可以。

您可以在此处查看简单异步服务器的示例代码:https ://github.com/aychedee/octopus/blob/master/octopus/server.py

于 2013-08-03T18:14:56.987 回答
0

我认为您不需要最后一个 if 块。这似乎对我有用:

class ProxyHandler(tornado.web.RequestHandler):
    def get(self, slug):
        print 'get: ' + str(slug)

        if slug.startswith("http://") or slug.startswith("https://"):
            if self.get_argument("start", None) == "true":
                parsed = urlparse.urlparse(slug)
                self.set_cookie("scheme", value=parsed.scheme)
                self.set_cookie("netloc", value=parsed.netloc)
                self.set_cookie("urlpath", value=parsed.path)
            #external resource
            else:
                response = requests.get(slug)
                headers = response.headers
                if 'content-type' in headers:
                    self.set_header('Content-type', headers['content-type'])
                if 'length' in headers:
                    self.set_header('length', headers['length'])
                for block in response.iter_content(1024):
                    self.write(block)
                self.finish()
                return
        else:

            slug = "{scheme}://{netloc}/{original_slug}".format(
                scheme=self.get_cookie('scheme'),
                netloc=self.get_cookie('netloc'),
                original_slug=slug,
            )
            print self.get_cookie('scheme')
            print self.get_cookie('netloc')
            print self.get_cookie('urlpath')
            print slug
        response = requests.get(slug)
        #get the headers
        headers = response.headers
        #get doctype
        doctype = None
        if '<!doctype' in response.content.lower()[:9]:
            doctype = response.content[:response.content.find('>')+1]
        if 'content-type' in headers:
           self.set_header('Content-type', headers['content-type'])
        if 'length' in headers:
            self.set_header('length', headers['length'])
        self.write(response.content)
于 2013-05-13T16:53:50.047 回答
0

显然我回答这个问题已经很晚了,但不久前偶然发现了它。我自己一直在写与您的要求类似的东西。

它更像是一个 HTTP 中继器,但它的第一个任务是代理本身。它还没有完全完成,目前还没有阅读我的内容——但这些都在我的待办事项清单上。

我已经使用 mitmproxy 来实现这一点。它可能不是最优雅的代码,我在这里和那里使用了很多技巧来实现转发器功能。我知道 mitmproxy 默认有办法轻松实现中继器,但在我的情况下有一些特定的要求,我无法使用 mitmproxy 提供的那些功能。

您可以在https://github.com/c0n71nu3/python_repeater/找到该项目。 当有任何进展时,我仍在更新 repo。

希望它能够为您提供一些帮助。

于 2015-09-01T11:28:59.640 回答