45 行 Python 代码实现 TCP 二级代理

2018-07-22 00:45:29 +08:00
 nozer

迫于种种原因,有时候我们不得不做一些流量转发的操作。

比如写爬虫。

写爬虫时,时常要与目标网站的“运维”、“程序员”斗智斗勇,而“代理”作为行走江湖必备的入门级杀手锏,自然是要逢场必上。

有匪君子,如切如磋,如琢如磨。 有匪君子,如切如磋,如琢如磨。

而这个“杀手锏”是不是那么好用却与代理的数量、质量息息相关。

我时常苦恼于“维护代理”和“切换代理”的麻烦,我堂堂一代“爬虫大王”,冉冉升起的“东方新星”,万千少女的。。。呃,好像有点扯远了。总之,怎能沉溺于区区“代理切换”这种微不足道的小事中。

那么,如果可以用一个二级代理来封装这些事情岂不美哉!

所谓空想不如实干,不仅要实干,更要撸起袖子加油干!于是抓起 Python 就撸了一个流量转发程序。

程序不长,去掉空行只有 45 行,但完整的实现了流量转发的功能,基于 ssclient 实现了二级代理,完整代码如下:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import socket
import select
from concurrent.futures import ThreadPoolExecutor


def process(conn, addr):
    proxy_conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    proxy_conn.connect(('127.0.0.1', 1080))
    conn.setblocking(socket.MSG_DONTWAIT)
    proxy_conn.setblocking(socket.MSG_DONTWAIT)
    closed = False
    while not closed:
        rlist, _, _ = select.select([conn, proxy_conn], [], [])
        for r in rlist:
            w = proxy_conn if r is conn else conn
            try:
                d = r.recv(1024)
                if not d:
                    closed = True
                    break
                w.sendall(d)
            except:
                closed = True
                break

    try:
        proxy_conn.shutdown(socket.SHUT_RDWR)
        proxy_conn.close()
    except:
        pass

    try:
        conn.shutdown(socket.SHUT_RDWR)
        conn.close()
    except:
        pass


def start():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind(('', 1991))
    s.listen(10)
    pool = ThreadPoolExecutor(128)
    print('Server listen on 0.0.0.0:1991')
    while True:
        conn, addr = s.accept()
        pool.submit(process, conn, addr)


if __name__ == '__main__':
    start()
    

流量转发既已实现,剩下的事情就很好解决了,无非是维护一个可用的“代理池”,在转发流量的过程中随机选取一个可用代理,之后进行流量转发即可。

完整的功能将在我开发完成之后在博客中跟大家分享,并同步发布到我的微信公众号上。有兴趣的朋友可以关注我的公众号哟~

本帖同步更新在我的博客上,原文在此

6891 次点击
所在节点    Python
32 条回复
AngelCriss
2018-07-22 00:57:29 +08:00
互联网的冗余信息又多了点而已
429839446
2018-07-22 01:01:34 +08:00
居然不用 coroutine
AngelCriss
2018-07-22 01:06:35 +08:00
@429839446 很奇怪
ericls
2018-07-22 04:39:26 +08:00
@429839446 我也想说 搞一个 asyncio 的对比一下
nbndco
2018-07-22 08:14:05 +08:00
这代码连逻辑都不对……
lychnis
2018-07-22 09:26:17 +08:00
我不懂 python 的人乍一看这是同步的吧 这能用?
naiba
2018-07-22 09:54:00 +08:00
nozer
2018-07-22 10:15:14 +08:00
@nbndco 还请不吝赐教。
wwqgtxx
2018-07-22 10:27:23 +08:00
从性能上更推荐这个
https://github.com/wangyu-/tinyPortMapper
而且就算用 Python 也应该用 Selector 或者是 asyncio 吧,这种用 ThreadPoolExecutor 的不觉得有点效率太低了么
nozer
2018-07-22 10:29:10 +08:00
@lychnis 兄弟你可能对同步异步有点误解,至于能不能用,当然是能的,至于好不好用当然是比不上键盘的。
nozer
2018-07-22 10:33:33 +08:00
@429839446 您这建议挺不错,您要不说,我还真不知道有这玩意儿。。。。当然这东西只是初步用来实现我的想法,一切从简单、易于理解的角度出发,在完善后续功能的过程中会考虑性能问题。
nozer
2018-07-22 10:37:01 +08:00
@wwqgtxx 用 Python 主要是考虑到后面要增加自己爬取、维护共享代理的功能,全部用 Python 实现会更加方便一些。
nbndco
2018-07-22 11:02:55 +08:00
@nozer
你虽然在标题里强调简单(行数少),但简单的主要原因只是因为你没写最重要的东西。当前的实现基本上就是同步的,虽然看起来代码是异步的,但是 IO 几乎没有复用,和同步唯一的区别就是一个线程处理两个方向而不是一个方向。并发只有 128 实在是少了一点。
不过你就算自己做,不用 asyncio,完全 IO 复用,也不会长太多。
misaka19000
2018-07-22 11:06:38 +08:00
既然都用了 select 了,就不需要再用多线程了啊。。。
Cbdy
2018-07-22 11:10:07 +08:00
source.on('data', data => destination.write(data))
destination.on('data', data => source.write(data))

流量转发不是两行代码的事情吗?至于这么麻烦
nozer
2018-07-22 11:19:41 +08:00
@misaka19000 select 是用在单个请求的两端进行流量转发,线程是用来处理新的请求。
nozer
2018-07-22 11:26:46 +08:00
@misaka19000 我明白您的意思了。
nozer
2018-07-22 11:34:34 +08:00
@nbndco 兄弟回答的很中肯,一看就是仔细读过程序的,直接点出了要害。异步处理的却不够好,大家也提出了很多很有用的意见,不过兄弟能不能说下到底哪里连逻辑都不对,我好修改免得让别人看了误入歧途。。。
nozer
2018-07-22 11:43:57 +08:00
@Cbdy 大神,不需要两行,一行就够了。

您看我帮您改写一下:s.on('data', data => d.write(data)) ;d.on('data', data => s.write(data)) ;。

? java 怎能少了分号
aheadlead
2018-07-22 12:01:41 +08:00
流量转发我都用 shell 写的… nc 足够解决很多问题了

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/473005

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX