zoukankan      html  css  js  c++  java
  • Web 请求之--性能相关

    本博客代码运行环境

         
         python :     Python 3.7.1rc1   version
         pip :        pip 19.1.1        version
         Scrapy:      scrapy 1.6.0      version
         asyncio:     asyncio 3.4.3     version
         aiohttp:     aiohttp 3.5.4     version
         gevent:      gevent 1.4.0      version
    grequests: grequests 0.3.0 version greenlet: greenlet 0.4.15 version requests: requests 2.21.0 version urllib3: urllib3 1.24.1 version tornado: tornado 6.0.2 version twisted: twisted 19.2.0 version

    以下所有代码都以 爬虫 为例, 发送 http 请求,获取页面源码。

    概念回顾

    操作系统(OS):
    是一个用来协调、管理和控制计算机硬件和软件资源的系统程序,它位于硬件和应用程序之间。
    进程(Process):------------》》是 OS 进行资源分配和调度的一个基本独立单位
        进程就是一个程序在一个数据集上的一次动态执行过程。
        进程一般由程序、数据集、进程控制块三部分组成。
        程序:       由我们编写用来描述进程要完成哪些功能以及如何完成,是指令、数据及其组织形式的描述,进程是程序的实体
        数据集:     则是程序在执行过程中所需要使用的资源;
        进程控制块: 用来记录进程的外部特征,描述进程的执行变化过程,系统可以利用它来控制和管理进程,它是系统感知进程存在的唯一标志。
    多进程:
    指操作系统能同时运行多个任务(程序)。
    总结来说:
         进程是线程的容器

    每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程。(进程是资源分配的最小单位
    适用于: 计算密集型

    线程(Thread):------------》》是cpu调度和分派的最小单位
      有时被称为轻量级进程(Lightweight Process,LWP)

    为了降低上下文切换的消耗,提高系统的并发性,并突破一个进程只能干一样事的缺陷,使到进程内并发成为可能。
    是程序执行流的最小单元。 一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成
    线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。
    一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。
    由于线程之间的相互制约,致使线程在运行中呈现出间断性。
    线程也有就绪、阻塞和运行三种基本状态
    就绪状态是指线程具备运行的所有条件,逻辑上可以运行,在等待 CPU 调度;
    阻塞状态是指线程在等待一个事件(如某个信号量),逻辑上不可执行。每一个程序都至少有一个线程,若程序只有一个线程,那就是程序本身。
    运行状态是指线程占有 CPU 正在运行;
    多线程:
    在单个程序中同时运行多个线程完成不同的工作,即具有多个顺序流在执行。
      总结来说:
    线程是程序中一个单一的顺序控制流程。进程内一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指运行中的程序的调度单位。
    同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。(线程是cpu调度的最小单位)
    适用于: IO 密集型
    GIL全局解释器锁(Global Interpreter Lock):
    无论你启多少个线程,你有多少个cpu, Python在执行一个进程的时候会淡定的在同一时刻只允许一个线程运行
    机制,一个基本的要求就是需要实现不同线程对共享资源访问的互斥,所以引入了GIL。
    GIL缺点:多处理器退化为单处理器;优点:避免大量的加锁解锁操作
    协程(Coroutine):
    又称微线程,纤程。协程是一种用户态的轻量级线程。
    线程/进程:操作系统提供的一种并发处理任务的能力,操作是由程序触发系统接口, 操作者:系统;
    协程:程序员通过高超的代码能力,在代码执行流程中人为的实现多任务并发,是单个线程内的任务调度技巧。操作者:程序员;

    没有切换的开销。协程不需要多线程的锁机制,因为都是在同一个线程中运行,所以没有同时访问数据的问题,执行效率比多线程高很多
    协程存在的意义:对于多线程应用,CPU通过切片(轮询)的方式来切换线程间的执行,线程切换时需要耗时(保存状态,下次继续)。
    协程切换时,因为只使用一个线程,是由程序自身控制的,体现的是程序员的流程控制能力,通过一定的语法规定在一个线程中所有代码块的执行顺序。
    协程的适用场景:当程序中存在大量不需要CPU的操作时(IO),适用于协程, 即: IO 密集型

    综上: 最佳方案: 协程 + 多进程

    线程与进程的共同点和区别 

    (1)共同点:
    
         线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。
    
    (2)区别:
    
              线程和进程的区别在于,子进程和父进程有不同的代码和数据空间,而多个线程则共享数据空间,每个线程有自己的执行堆栈和程序计数器为其执行上下文。多线程主要是为了充分利用 CPU,提高效率。
         线程与进程的区别可以归纳为以下几点:
               1)地址空间和其它资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
    
          2)通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
    
          3)调度和切换:线程上下文切换比进程上下文切换要快得多。故所消耗的资源比进程要少的多多。
    
          4)在多线程OS中,进程不是一个可执行的实体。
    (3)线程的属性(特点):
           1)轻型实体
          线程中的实体基本上不拥有系统资源,只是有一点必不可少的、能保证独立运行的资源,比如:
              在每个线程中都具有一个用于控制线程运行的线程控制块TCB,用于指示被执行指令序列的程序计数器、保留局部变量、少数状态参数和返回地址等的一组寄存器和堆栈。
    
        2)独立调度和分派的基本单位。
          在多线程OS中,线程是能独立运行的基本单位,因而也是独立调度和分派的基本单位。由于线程很“轻”,故线程的切换非常迅速且开销小。
    
        3)可并发执行。
          在一个进程中的多个线程之间,可以并发执行,甚至允许在一个进程中所有线程都能并发执行;同样,不同进程中的线程也能并发执行。
    
        4)共享进程资源。
          在同一进程中的各个线程,都可以共享该进程所拥有的资源,这首先表现在:          
              所有线程都具有相同的地址空间(进程的地址空间),这意味着,线程可以访问该地址空间的每一个虚地址;此外,还可以访问进程所拥有的已打开文件、定时器、信号量机构等。
    

    HTTP 协议

    HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于从万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的传送协议。
    
    HTTP是一个基于TCP/IP通信协议来传递数据(HTML 文件, 图片文件, 查询结果等)。
    
    HTTP是一个属于应用层的面向对象的协议,目前在WWW中使用的是 HTTP/1.0的第六版,
    
    HTTP协议工作于客户端-服务端架构为上。基于 : 请求-----响应 ,模式
    

    HTTP 特点

    1、简单快速:客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有GET、HEAD、POST。                         
               每种方法规定了客户与服务器联系的类型不同。由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。
    
    2、灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记。
    
    3.无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。
    
    4.无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。
             缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。
    

    HTTP 协议 数据发送格式

    HTTP 请求协议

    请求首行;         // 请求方式 请求路径 协议和版本,例如:GET /index.html HTTP/1.1
    请求头信息;       // 请求头名称:请求头内容,即为key:value格式,例如:Host:localhost
    空行;            // 用与分隔 请求头 和 请求体(
    
    )
    请求体。          // GET没有请求体,只有POST有请求体。
    GET:
    HTTP默认的请求方法就是GET
          * 没有请求体
          * 数据量有限制!
          * GET请求数据会暴露在浏览器的地址栏中
    GET请求常用的操作:
           1. 在浏览器的地址栏中直接给出URL,那么就一定是GET请求
           2. 点击页面上的超链接也一定是GET请求
           3. 提交表单时,表单默认使用GET请求,但可以设置为POST
    例如:
    GET 请求: GET /338.jpg HTTP/1.1 Host:img.mukewang.com Content-Type:multipart/form-data Referer:http://www.imooc.com/ POST 请求:
    POST / HTTP/1.1 Host: img.mukewang.com Connection: Keep-Alive name=Professional&publisher=Wiley

    请求头:

    1、Host
    
    请求的web服务器域名地址
    
    2、User-Agent
    
    HTTP客户端运行的浏览器类型的详细信息。通过该头部信息,web服务器可以判断出http请求的客户端的浏览器的类型。
    
    3、Accept
    
    指定客户端能够接收的内容类型,内容类型的先后次序表示客户都接收的先后次序
    
    4、Accept-Lanuage
    
    指定HTTP客户端浏览器用来展示返回信息优先选择的语言
    
    5、Accept-Encoding
    
    指定客户端浏览器可以支持的web服务器返回内容压缩编码类型。表示允许服务器在将输出内容发送到客户端以前进行压缩,以节约带宽。
    而这里设置的就是客户端浏览器所能够支持的返回压缩格式。
    
    6、Accept-Charset
    
    HTTP客户端浏览器可以接受的字符编码集
    
    7、Content-Type
    
    显示此HTTP请求提交的内容类型。一般只有post提交时才需要设置该属性
    
    有关Content-Type属性值有如下两种编码类型:
    
    (1)“application/x-www-form-urlencoded”: 表单数据向服务器提交时所采用的编码类型,默认的缺省值就是“application/x-www-form-urlencoded”。
         然而,在向服务器发送大量的文本、包含非ASCII字符的文本或二进制数据时这种编码方式效率很低。
    
    (2)“multipart/form-data”: 在文件上载时,所使用的编码类型应当是“multipart/form-data”,它既可以发送文本数据,也支持二进制数据上载。
    
    当提交为表单数据时,可以使用“application/x-www-form-urlencoded”;当提交的是文件时,就需要使用“multipart/form-data”编码类型。
    

    HTTP 响应协议

    HTTP响应也由四个部分组成,分别是:状态行、消息报头、空行和响应正文。
    例如:
    HTTP/1.1 200 OK
        Date: Fri, 22 May 2009 06:07:21 GMT
        Content-Type: text/html; charset=UTF-8
    
       <html>
            <head></head>
               <body>
                  <!--body goes here-->
              </body>
        </html>

     例:

             HTTP/1.1 200 OK Date:Fri, 22 May 2009 06:07:21 GMT Content-Type: text/html; charset=UTF-8 <html><head</head><body>shiwei</body></html>

    第一部分:状态行,由HTTP协议版本号, 状态码, 状态消息 三部分组成。

                 第一行为状态行,(HTTP/1.1)表明HTTP版本为1.1版本,状态码为200,状态消息为(ok)

    第二部分:消息报头,用来说明客户端要使用的一些附加信息

                  第二行和第三行为消息报头,
                  Date:生成响应的日期和时间;Content-Type:指定了MIME类型的HTML(text/html),编码类型是UTF-8

    第三部分:空行,消息报头后面的空行是必须的
    第四部分:响应正文,服务器返回给客户端的文本信息。

    响应状态码:

    状态代码有三位数字组成,第一个数字定义了响应的类别,共分五种类别:
    1xx:指示信息--表示请求已接收,继续处理
    2xx:成功--表示请求已被成功接收、理解、接受
    3xx:重定向--要完成请求必须进行更进一步的操作
    4xx:客户端错误--请求有语法错误或请求无法实现
    5xx:服务器端错误--服务器未能实现合法的请求
    常见状态码:
    200 OK                        //客户端请求成功
    400 Bad Request               //客户端请求有语法错误,不能被服务器所理解
    401 Unauthorized              //请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用 
    403 Forbidden                 //服务器收到请求,但是拒绝提供服务
    404 Not Found                 //请求资源不存在,eg:输入了错误的URL
    500 Internal Server Error     //服务器发生不可预期的错误
    503 Server Unavailable        //服务器当前不能处理客户端的请求,一段时间后可能恢复正常
    

    准备好了吗?基础知识很重要!!

     编写爬虫程序,性能的消耗主要在 IO 请求中,当单进程单线程模式下请求URL时必然会引起等待,效率极低!

     1 import requests
     2 
     3 def task(url):
     4     response = requests.get(url)
     5     return response
     6 
     7 
     8 url_list = ['http://www.github.com', 'http://www.bing.com']
     9 
    10 for url in url_list:
    11     task(url)
    同步执行
     1 from concurrent.futures import ThreadPoolExecutor
     2 import requests
     3 
     4 def task(url):
     5     response = requests.get(url)
     6     print(response)
     7     # print(response.text)  # 获取 页面 源码 字符串数据
     8     print("33[1;30;47murl==%-70s----------response==%-s33[0m"%(url,response.status_code))
     9     return response
    10 
    11 url_list = [
    12     "http://tuijian.hao123.com/",
    13     "http://www.163.com/",
    14     "http://www.eastmoney.com/",
    15 ]
    16 
    17 pool = ThreadPoolExecutor(5)
    18 
    19 for url in url_list:
    20     v = pool.submit(task,url)
    21 
    22 pool.shutdown(wait=True)
    多线程执行
    from concurrent.futures import ThreadPoolExecutor
    import requests
    
    def task(url):
        """  返回页面 源码数据 """
        response = requests.get(url)
        print("33[1;30;47murl==%-70s----------response==%-s33[0m"%(url,response.status_code))
        return response
    
    def done(future,*args, **kwargs):
        """  解析页面 源码  """
        print(future.result())
    
    url_list = [
        "http://tuijian.hao123.com/",
        "http://www.163.com/",
        "http://www.eastmoney.com/",
    ]
    
    pool = ThreadPoolExecutor(5)
    
    for url in url_list:
        v = pool.submit(task,url)
        v.add_done_callback(done)
    
    pool.shutdown(wait=True)
    多线程 + 回调函数 
    from concurrent.futures import ProcessPoolExecutor
    import requests
    
    pool = ProcessPoolExecutor(5)
    def task(url):
        response = requests.get(url)
        print(response)
        print("33[1;30;47murl==%-70s----------response==%-s33[0m"%(url,response.status_code))
        return response
    
    url_list = [
        "http://tuijian.hao123.com/",
        "http://www.163.com/",
        "http://www.eastmoney.com/",
    ]
    
    if __name__ == "__main__":  # 必须要加 此 判断语句
        for url in url_list:
            v = pool.submit(task, url)
    
    pool.shutdown(wait=True)
    多进程执行
    from concurrent.futures import ProcessPoolExecutor
    import requests
    pool = ProcessPoolExecutor(5)
    
    def task(url):
        response = requests.get(url)
        # print("33[1;30;47murl==%-70s----------response==%-s33[0m"%(url,response.status_code))
        return response
    
    def done(*args, **kwargs):
        print(args[0].result())
        print("33[1;31;40m %s-----%s 33[0m"%(args, kwargs))
    url_list = [
        "http://tuijian.hao123.com/",
        "http://www.163.com/",
        "http://www.eastmoney.com/",
    ]
    
    if __name__ == "__main__":  # 必须要加 此 判断语句
        for url in url_list:
            v = pool.submit(task, url)
            v.add_done_callback(done)
    
    pool.shutdown(wait=True)
    多进程 + 回调函数

     以上代码 均 可提升请求性能, 但多线程 和 多进程 的缺点是 在 IO 阻塞时 依然会造成线程 和 进程的 浪费,

    故 异步 IO   是 首选:  

                      遇到 IO , 则 立即转而操作其他, IO 完成时,则自动回调运行处理 IO 的 函数。

    import asyncio
                                          # 不支持 HTTP 请求, 但支持 tcp 请求,socket
    @asyncio.coroutine                    # 不可缺少
    def task():
        print('before ......Task')
        yield from asyncio.sleep(5)      # 不可写为 time.sleep(5)
        print('after ...... Task')
    
    
    tasks = [task(), task(), task()]      # 任务列表
    loop = asyncio.get_event_loop()
    loop.run_until_complete(asyncio.gather(*tasks))
    loop.close()
    asyncio 示例

         asyncio + 模拟 http :

    import asyncio
    @asyncio.coroutine
    def task(host,url='/'):
        #  创建 链接
        reader, writer = yield from asyncio.open_connection(host, 80)
    
        request_header_content = """GET %s HTTP/1.0
    Host: %s
    
    """%(url,host,)
        request_header_content = bytes(request_header_content, encoding='utf-8')
    
        writer.write(request_header_content)
        yield from writer.drain()
        text = yield from reader.read()
        print('ending ',host, url, text)
        writer.close()
    
    tasks = [
        task('www.cnblogs.com', '/wupeiqi/'),
        task('www.cnblogs.com', '/shiwei1930/'),
        task('www.cnblogs.com', '/alex3714/'),
    ]
    
    loop = asyncio.get_event_loop()
    results = loop.run_until_complete(asyncio.gather(*tasks))
    loop.close()

        asyncio  + aiohttp :

    import aiohttp
    import asyncio
    
    @asyncio.coroutine
    def task(url):
        print(url)
        resp = aiohttp.ClientSession().get(url)
        response = yield from resp
        print(url, response)
        response.close()
    
    tasks = [task('http://www.baidu.com/'), task('http://www.chouti.com/')]
    
    loop = asyncio.get_event_loop()
    results = loop.run_until_complete(asyncio.gather(*tasks))
    loop.close()

         asyncio + requests

    import  requests
    import asyncio
    @asyncio.coroutine
    def task(func,*args):
        loop = asyncio.get_event_loop()
        future = loop.run_in_executor(None,func,*args)
        response = yield from future
        print('33[1;31;40m url=%-30s
    33[0mcontents=%-30s'%(response.url,response.content))
    
    tasks = [
        task(requests.get, 'http://www.baidu.com/'),
        task(requests.get, 'http://www.cnblogs.com/'),
        task(requests.get, 'http://autohome.com.cn'),
    ]
    
    loop = asyncio.get_event_loop()
    results = loop.run_until_complete(asyncio.gather(*tasks))
    print(results)
    loop.close()

         gevent + requests

    import requests
    import gevent
    from gevent import monkey
    monkey.patch_all()                 # 不可缺少
    
    def task(method,url,req_kwargs):
        print('method=%s,url=%s,kwargs=%s'%(method,url,req_kwargs))
        response = requests.request(method=method,url=url, **req_kwargs)
        print('url=%s
    		content=%s'%(response.url,response.content))
    
    gevent.joinall([
        gevent.spawn(task,method='get', url='https://www.github.com/', req_kwargs={}),
        gevent.spawn(task, method='get', url='https://www.yahoo.com/', req_kwargs={}),
        gevent.spawn(task, method='get', url='https://www.python.org/', req_kwargs={}),
    ])

          grequests  =    gevent + requests

    import grequests
    
    request_list = [
        grequests.get('http://www.cnblogs.com/'),
        grequests.get('https://www.yahoo.com/'),
        grequests.get('https://www.github.com/'),
    ]
    
    response_list = grequests.map(request_list)
    print('result=',response_list)
    for item  in response_list:
        print('content=',item.content)

       Twisted

    from twisted.web.client import getPage,defer
    from twisted.internet import reactor
    
    def callback(contents):
        print(contents)
    
    def all_done(arg):
        reactor.stop()  # 全部完成之后 stop
    
    @defer.inlineCallbacks
    def task(url):
        defered = getPage(bytes(url, encoding='utf8'))
        defered.addCallback(callback)
        yield defered
    defered_list = []
    
    url_list = ['http://www.cnblogs.com/', 'https://github.com/', 'http://baidu.com/']
    for url in url_list:
        defered = task(url)
        defered_list.append(defered)
    
    dlist = defer.DeferredList(defered_list)
    dlist.addBoth(all_done)
    
    # 事件循环
    reactor.run()  # 去 defered_list 中 检测 谁回来了, 是个死 循环

        Tornado

    from tornado.httpclient import AsyncHTTPClient
    from tornado.httpclient import HTTPRequest
    from tornado import ioloop
    
    COUNT = 0
    def handler_response(response):
        global COUNT
        COUNT -= 1
        if response.error:
            print('error:',response.error)
        else:
            print('body:',response.body)
        if COUNT == 0:
            ioloop.IOLoop.current().stop()
    
    def task():
        url_list = [
            'http://www.baidu.com/',
            'http://www.cnblogs.com/',
            'https://www.github.com',
        ]
        global COUNT
        COUNT = len(url_list)
        for url in url_list:
            print("url===",url)
            http_client = AsyncHTTPClient()
            http_client.fetch(HTTPRequest(url),handler_response)
    if __name__ == '__main__':
        ioloop.IOLoop.current().add_callback(task)
        ioloop.IOLoop.current().start()   #     # 事件循环, 是一个 死循环

    自定义 异步 IO 框架

     最基本的 网络编程的 本质:客户端

    import socket
    sk = socket.socket()
    # 链接
    sk.connect(('www.baidu.com',80))  # IO 阻塞
    
    # 设置 非阻塞
    # sk.setblocking(False)  # 请求是 发过去了
    
    print('链接成功!')
    
    sk.send(b'GET / HTTP/1.0
    Host: baidu.com
    
    ')
    # sk.send(b'POST / HTTP/1.0
    Host: baidu.com
    
     fdkfjdkjfkdfj')
    
    buffer_size = 1024
    # 等待 着 服务端响应
    data  = sk.recv(buffer_size)  # IO 阻塞
    
    print(data)
    # 关闭链接
    sk.close()

    自定义 异步 IO 框架-----》客户端

    import select
    import socket
    
    # IO  多路复用: 监听多个 Socket 对象 ,死循环, 事件循环
    #         - 利用此特性 可以开发 异步 IO 模块 等等
    
    # 异步 IO : 遇到 IO , 则 立即转而操作其他, IO 返回时,则自动回调处理 IO 的 函数。
    # 回答: 非阻塞的 socket(setblocking(False)) + IO 多路复用
    
    #                    此时 为客户端
    class HttpRequest:
        def __init__(self, sk, host, callback):
            self.socket = sk
            self.host = host
            self.callback = callback
        def fileno(self):
            return self.socket.fileno()
    
    Buffer_size = 4096
    
    class HttpResponse:
        def __init__(self, recv_data):
            self.recv_data = recv_data
            self.header_dict = {}
            self.body = ""
            self.initialize()
    
        def initialize(self):
            headers, body = self.recv_data.split(b'
    
    ', 1)
            header_list = headers.split(b'
    ')
            for item in header_list:
                item_str = str(item, encoding='utf-8')
                sp = item_str.split(':', 1)
                if len(sp) == 2:
                    self.header_dict[sp[0]] = sp[1]
    
    class AsyncRequest:
        def __init__(self):
            self.conn = []
            self.connection = []  # 用于检测 是否链接成功
    
        def add_request(self, host,callback):
            try:
                sk = socket.socket()
                sk.setblocking(False)
                sk.connect((host, 80))
            except BlockingIOError as e:
                pass
            request = HttpRequest(sk, host, callback)
            self.conn.append(request)
            self.connection.append(request)
    
        def run(self):
            # 事件 循环
            while True:
                rlist, wlist, elist = select.select(self.conn, self.connection, self.conn, 0.05)
                for w in wlist:
                    print(w.host,'链接成功')
                    # 表示 socket  和服务端链接 成功
                    txt = "GET / HTTP/1.0
    Host:%s
    
    "%(w.host)
                    w.socket.send(bytes(txt, encoding='utf8'))
                    self.connection.remove(w)
                print('write over')
                for r in rlist:
                    print(r.host, '有数据返回 ')
                    # r = HttpRequest object
                    recv_data = bytes()
                    while True:
                        try:
                            chunck = r.socket.recv(Buffer_size)
                            recv_data += chunck
                        except Exception as e:
                            break
    
                    response = HttpResponse(recv_data)
    
                    r.callback(response)
                    print('host=%s,返回数据为: recv_data=%s'%(r.host,recv_data))
                    self.conn.remove(r)
    
                if not self.conn:
                    break
    
                self.connection
                self.conn
    
    def f1(response):
        print('保存到文件,', response.header_dict)
    def f2(response):
        print('保存到数据库,', response.header_dict)
    url_list = [
        # 'cn.bing.com',
        {"host": 'www.baidu.com', 'callback': f1},
        {'host': 'www.cnblogs.com', 'callback': f2},
    ]
    
    req = AsyncRequest()
    for item in url_list:
        req.add_request(item['host'], item['callback'])
    
    req.run()
    
    # 等待 服务端的 链接成功 信号
  • 相关阅读:
    车厢重组
    军事机密
    士兵站队
    归并排序
    输油管道
    冒泡排序
    快排
    烦人的幻灯片(确实烦人啊)
    奖金(类拓扑排序)
    能量项链
  • 原文地址:https://www.cnblogs.com/shiwei1930/p/11142668.html
Copyright © 2011-2022 走看看