V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
NxnXgpuPSfsIT
V2EX  ›  Python

Trip: 给 Requests 加上协程,一百份网络请求一份时间

  •  3
     
  •   NxnXgpuPSfsIT ·
    littlecodersh · 2017-11-02 09:43:51 +08:00 · 6938 次点击
    这是一个创建于 2586 天前的主题,其中的信息可能已经有所发展或是发生改变。

    Trip 是一个协程的网络库,如 Requests 一般简单的操作,程序不再被网络阻塞。

    兼容 Python 2.7+ 的所有版本,主流三大操作系统。

    基于两大依赖包:TRIP: Tornado & Requests In Pair

    感谢 Tornado 与 Requests 让想法可以快速变成现实,坦诚的说,这个项目我只做了一些简单的工作。

    其实上半年就写好了这个项目,结果文档拖到了今天才大致写了一些,不得不感谢一下同类项目对我的督促。

    让协程变的简单

    这是一个让协程变的简单的项目,你只需要这样:

    import trip
    
    @trip.coroutine
    def main():
        r = yield trip.get('https://httpbin.org/get', auth=('user', 'pass'))
        print(r.content)
    
    trip.run(main)
    

    一百份请求一份时间

    基于 Tornado 的协程让网络阻塞不再成为问题:(这里演示兼容用法)

    import time, functools
    
    import requests, trip
    
    def timeit(fn):
        start_time = time.time()
        fn()
        return time.time() - start_time
    
    url = 'http://httpbin.org/get'
    times = 10 # 100 changed for inland network delay
    
    def fetch():
        r = [requests.get(url) for i in range(times)]
        return r
    
    @trip.coroutine
    def async_fetch():
        r = yield [trip.get(url) for i in range(times)]
        raise trip.Return(r)
    
    print('Non-trip cost: %ss' % timeit(fetch))
    print('Trip cost: %ss' % timeit(functools.partial(trip.run, async_fetch)))
    
    # Result:
    # Non-trip cost: 17.90799999237s
    # Trip cost: 0.172300004959s
    

    由于协程的特性,所有的等待时间重合在了一起。

    你不需要每个请求开一个线程,主线程中一切也可以井然有序的进行。

    可以想象如果你在写一个爬虫,这将节省多少时间!

    让协程服务人类

    基于 Requests 的操作方式让协程 HTTP 从未如此简单:(这里使用 Python3 演示 async/await )

    >>> async def main():
    ...     global r
    ...     r = await trip.get('https://httpbin.org/basic-auth/user/pass', auth=('user', 'pass'))
    ...
    >>> trip.run(main)
    >>> r.status_code
    200
    >>> r.headers['content-type']
    'application/json'
    >>> r.encoding
    None
    >>> r.text
    u'{"authenticated": true,...'
    >>> r.json()
    {u'authenticated': True, u'user': u'user'}
    

    只要你有一些 Requests 基础就可以轻松使用 Trip,协程不再遥不可及。

    重现了几乎所有 Requests 的操作,最大限度的减少了你的学习成本。

    以一个爬虫为例

    为了不打扰正常网站的运行,这里以httpbin.org作为目标网站。 设置 Cookies 模拟登陆,get 请求模拟爬取内容。

    那么普通 Requests 是这样的:

    import requests
    
    url = 'http://httpbin.org'
    s = requests.Session()
    
    def fetch(times=10):
        s.get('%s/cookies/set?name=value' % url)
        r = [s.get('%s/get' % url) for i in range(times)]
        print r
    
    fetch()
    

    使用 Trip 以后就会变成这样:

    import trip
    
    url = 'http://httpbin.org'
    s = trip.Session()
    
    @trip.coroutine
    def fetch(times=10):
        yield s.get('%s/cookies/set?name=value' % url)
        r = yield [s.get('%s/get' % url) for i in range(times)]
        print r
    
    trip.run(fetch)
    

    几乎不需要修改代码,爬虫就获得了协程的特性!

    最后

    爬虫耗时太久优化困难吗? 各种协程网络框架难以使用吗? 大型爬虫框架臃肿无法灵活定制吗?

    试试 Trip,你不会后悔的!

    38 条回复    2017-12-29 20:49:15 +08:00
    Chyroc
        1
    Chyroc  
       2017-11-02 10:09:39 +08:00
    厉害
    golmic
        2
    golmic  
       2017-11-02 10:38:31 +08:00
    wwqgtxx
        3
    wwqgtxx  
       2017-11-02 10:40:28 +08:00 via iPhone
    mark
    jyf
        4
    jyf  
       2017-11-02 10:46:20 +08:00
    跟 grequests 比如何呢
    CSM
        5
    CSM  
       2017-11-02 10:59:15 +08:00 via Android
    跟 aiohttp 相比有什么优势?
    leavic
        6
    leavic  
       2017-11-02 11:01:32 +08:00
    noli
        7
    noli  
       2017-11-02 11:05:59 +08:00
    我是不是唯一一个想起 gevent 的 ?
    NxnXgpuPSfsIT
        8
    NxnXgpuPSfsIT  
    OP
       2017-11-02 11:41:50 +08:00
    @jyf @noli gevent 就我的体验,感受最深的就是总有一些根本意想不到的问题因为 patch 出现。
    我很难定位问题,有的还是完全无法修复问题。
    NxnXgpuPSfsIT
        9
    NxnXgpuPSfsIT  
    OP
       2017-11-02 11:48:24 +08:00
    @CSM 兼容更多版本,学习成本更少
    est
        10
    est  
       2017-11-02 11:49:14 +08:00
    居然最后没有广告链接。
    NxnXgpuPSfsIT
        11
    NxnXgpuPSfsIT  
    OP
       2017-11-02 11:55:49 +08:00
    @est 毕竟自己随便写写,有广告位也没有广告发
    noli
        12
    noli  
       2017-11-02 11:57:13 +08:00
    @NxnXgpuPSfsIT

    是有遇到过,但写爬虫的时候很难遇到。
    确实也很致命,在 python 的范围内很难修复。

    所以现在不用 python 写爬虫 ;P
    jyf
        13
    jyf  
       2017-11-02 12:25:04 +08:00
    @NxnXgpuPSfsIT 我只是想了解下性能方面的比较数据 :D
    cheesea
        14
    cheesea  
       2017-11-02 13:08:23 +08:00
    据说 aiohttp 对 http 的解析拖慢了它的速度,不知道这个怎么样?
    janxin
        15
    janxin  
       2017-11-02 13:10:12 +08:00
    @cheesea 那个是 2.0 以前的事了,之后重写优化过
    mdzz
        16
    mdzz  
       2017-11-02 13:17:24 +08:00
    想起之前的一个帖子 /t/401575
    timonwong
        17
    timonwong  
       2017-11-02 13:39:57 +08:00   ❤️ 1
    给用户提个醒,resp.text 以及 resp.json 相关,如果 Content-Type 没有 charset,比如一个裸的 application/json,会导致 chardet 执行,chardet 执行速度比较慢(其实可以说很慢,比如一个接口一个 roundtrip 不到 1ms,跑个 chardet 都要 4-5 毫秒,就不可接受了),因此会降低整体性能(当然这个问题在 requests 里面也存在)。
    ideascf
        18
    ideascf  
       2017-11-02 13:40:22 +08:00
    还是 gevent 来得爽,真正的无侵入支持协程
    wsxka
        19
    wsxka  
       2017-11-02 14:29:14 +08:00
    666 !
    takanasi
        20
    takanasi  
       2017-11-02 15:21:17 +08:00
    看到兼容 3.7 还以为 3.7 出了
    tikazyq
        21
    tikazyq  
       2017-11-02 15:40:29 +08:00
    太给力了!给作者点个赞
    NxnXgpuPSfsIT
        22
    NxnXgpuPSfsIT  
    OP
       2017-11-02 17:44:17 +08:00
    @timonwong 是的,这是有研究过的。
    wjm2038
        23
    wjm2038  
       2017-11-03 01:29:00 +08:00 via Android
    qsnow6
        24
    qsnow6  
       2017-11-03 09:53:24 +08:00
    great
    qsnow6
        25
    qsnow6  
       2017-11-03 09:54:54 +08:00
    看起来学习成本很低啊;刚好在找 aiohttp 的低成本解决方案,aiohttp 学习成本太高了
    zhengxiaowai
        26
    zhengxiaowai  
       2017-11-03 10:30:01 +08:00
    先 star 一下,有空看看源码学习一下。

    ----

    简单看了一下,trip 基于 Tornado 的封装。所以充斥的大量 Tronado 类似的语法。

    Tronado 最为人诟病之一就是语法比较恶心, 不够 pythonic...

    我觉得可以把这些恶心的语法封装成 API 的形式,比如 raise Return() 这种。
    zhengxiaowai
        27
    zhengxiaowai  
       2017-11-03 10:48:39 +08:00
    我去,大兄弟。。我就说看源码怎么那么熟悉呢。。基本都是 requests 的 copy 啊。基本就把 send 方法改成了 Tornado 版本的啦
    NxnXgpuPSfsIT
        28
    NxnXgpuPSfsIT  
    OP
       2017-11-03 11:49:10 +08:00
    @zhengxiaowai
    说到的“把这些恶心的语法封装成 API 的形式,比如 raise Return() 这种”具体是指什么,
    我没太理解你的意思,可以具体和我解释一下吗?

    外部 api 遵从 Requests 就是功能之一呀,但这不是 copy。
    我把不需要更改不必要的内容都用了 import,留下来的肯定都是必要的、文档或是必须重新整合修改的。

    区别的话,最显而易见的,你有没有发现 adapter 里面完全就不是一个东西了?
    对于协程和非协程有区别的 api,比如 iter_content 也完全不一样了。
    另外,就是把 send 方法改成 Tornado 版本,其实也不是特别简单的一件事情啦。
    fxxkgw
        29
    fxxkgw  
       2017-11-03 11:51:52 +08:00
    = = 报错,requests 版本兼容有问题。。。

    >>> import trip
    Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    File "/usr/local/lib/python2.7/site-packages/trip/__init__.py", line 12, in <module>
    from .api import (
    File "/usr/local/lib/python2.7/site-packages/trip/api.py", line 10, in <module>
    from . import sessions
    File "/usr/local/lib/python2.7/site-packages/trip/sessions.py", line 21, in <module>
    from requests.sessions import (
    ImportError: cannot import name preferred_clock
    NxnXgpuPSfsIT
        30
    NxnXgpuPSfsIT  
    OP
       2017-11-03 11:59:27 +08:00
    @zhengxiaowai
    其实我也就是看了些 Requests 和 Tornado 源码的水平,看源码有问题的话可以 qq 咱们多交流。
    加个群吧我来加你,462703741,写的不行的地方发现了就一点一点改。
    NxnXgpuPSfsIT
        31
    NxnXgpuPSfsIT  
    OP
       2017-11-03 12:00:22 +08:00
    @fxxkgw 你更新一下版本呀,python -m pip install requests -U
    bluesky139
        32
    bluesky139  
       2017-11-03 13:27:03 +08:00 via Android
    大概看了一眼,实际上是把 requests 里的参数取出来再用 tornado 的 http 进行请求?
    NxnXgpuPSfsIT
        33
    NxnXgpuPSfsIT  
    OP
       2017-11-03 13:34:21 +08:00
    @bluesky139 tornado 拆到 TCPClient 这层
    guyskk0x0
        34
    guyskk0x0  
       2017-11-03 18:06:51 +08:00
    上周也开源了一个 Curio + Requests: Async HTTP for Humans https://www.v2ex.com/t/401739
    哈哈
    NxnXgpuPSfsIT
        35
    NxnXgpuPSfsIT  
    OP
       2017-11-03 18:59:28 +08:00 via iPhone
    @guyskk0x0 哈哈,其实 Curequests 就是我文章开头说到的同类项目,不是你写完了,我的项目再过半年我也抽不出足够时间来写完文档。倒是提醒我了,下次提交我把你的友链加上去!
    guyskk0x0
        36
    guyskk0x0  
       2017-11-03 19:10:33 +08:00
    @NxnXgpuPSfsIT #35 共勉,我也加个友链!
    Marsss
        37
    Marsss  
       2017-11-07 09:18:18 +08:00
    实际写爬虫的时候,同一个 ip,这样的速度,是会被封锁的,使用代理的话,一般代理商能提供的 ip 切换速度是有限的,基本跟不上这种速度。或许大量静态代理能用得上这种协程。不知道我理解的对不对。
    wangycc
        38
    wangycc  
       2017-12-29 20:49:15 +08:00
    我司两个后台在评论里面
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2560 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 10:22 · PVG 18:22 · LAX 02:22 · JFK 05:22
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.