scrapy

- 请求和响应
    - 请求
        'GET / http1.1\r\nHost:www.baidu.com\r\nUser-Agent:Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.100 Safari/537.36\r\nreferer:www.baidu.com;\r\n\r\n'
    - 响应 :
        'HTTP/1.1 200 \r\nSet-Cookie:k1=v1;k2=v2,Connection: keep-alive\r\nContent-Encoding: gzip\r\n\r\n<html>...</html>'
- 携带常见请求头
    - User-Agent
    - referer
    - host
    - content-type
    - cookie
- csrf 
    - 原因1:
        - 需要浏览器+爬虫先访问登录页面,获取token,然后再携带token去访问。                 
    - 原因2:
        - 两个tab打开的同时,其中一个tab诱导你对另外一个tab提交非法数据
web知识点
1. OSI 7层模型  **
    1. 应用层
        OSI参考模型中最靠近用户的一层,是为计算机用户提供应用接口,也为用户直接提供各种网络服务。我们常见应用层的网络服务协议有:HTTP,HTTPS,FTP,POP3、SMTP等
    2. 表示层
        表示层提供各种用于应用层数据的编码和转换功能,确保一个系统的应用层发送的数据能被另一个系统的应用层识别。如果必要,该层可提供一种标准表示形式,用于将计算机内部的多种数据格式转换成通信中采用的标准表示形式。数据压缩和加密也是表示层可提供的转换功能之一。
    3. 会话层
        会话层就是负责建立、管理和终止表示层实体之间的通信会话。该层的通信由不同设备中的应用程序之间的服务请求和响应组成。    
    4. 传输层
        传输层建立了主机端到端的链接,传输层的作用是为上层协议提供端到端的可靠和透明的数据传输服务,包括处理差错控制和流量控制等问题。该层向高层屏蔽了下层数据通信的细节,使高层用户看到的只是在两个传输实体间的一条主机到主机的、可由用户控制和设定的、可靠的数据通路。我们通常说的,TCP UDP就是在这一层。端口号既是这里的“端”
    5. 网络层
        本层通过IP寻址来建立两个节点之间的连接,为源端的运输层送来的分组,选择合适的路由和交换节点,正确无误地按照地址传送给目的端的运输层。就是通常说的IP层。这一层就是我们经常说的IP协议层。IP协议是Internet的基础。
    6. 数据链路层 
        将比特组合成字节,再将字节组合成帧,使用链路层地址 (以太网使用MAC地址)来访问介质,并进行差错检测。
        数据链路层又分为2个子层:逻辑链路控制子层(LLC)和媒体访问控制子层(MAC)。
    7. 物理层     
        实际最终信号的传输是通过物理层实现的。通过物理介质传输比特流。规定了电平、速度和电缆针脚。常用设备有(各种物理设备)集线器、中继器、调制解调器、网线、双绞线、同轴电缆。这些都是物理层的传输介质。                  
2. 三次握手、四次握手?  **
    所谓三次握手(Three-way Handshake),是指建立一个TCP连接时,需要客户端和服务器总共发送3个包。
    三次握手的目的是连接服务器指定端口,建立TCP连接,并同步连接双方的序列号和确认号并交换 TCP 窗口大小信息.在socket编程中,客户端执行connect()时。将触发三次握手。
        第一次握手:
            客户端发送一个TCP的SYN标志位置1的包指明客户打算连接的服务器的端口,以及初始序号X,保存在包头的序列号(Sequence Number)字段里。
        第二次握手:
            服务器发回确认包(ACK)应答。即SYN标志位和ACK标志位均为1同时,将确认序号(Acknowledgement Number)设置为客户的I S N加1以.即X+1。
        第三次握手.
            客户端再次发送确认包(ACK) SYN标志位为0,ACK标志位为1.并且把服务器发来ACK的序号字段+1,放在确定字段中发送给对方.并且在数据段放写ISN的+1
    四次握手:
    TCP的连接的拆除需要发送四个包,因此称为四次挥手(four-way handshake)。客户端或服务器均可主动发起挥手动作,在socket编程中,任何一方执行close()操作即可产生挥手操作。
3. TCP和UDP区别?  **
    1. TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接
    2、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付
    3、TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的
    4.UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)
    5、每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信
    6、TCP首部开销20字节;UDP的首部开销小,只有8个字节
    7、TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道
4. 路由器和交换机的区别?  **
    1. 路由器可以给局域网自动分配IP,虚拟拨号。交换机则只是用来分配网络数据的。
    2. 路由器可以把一个IP分配给很多个主机使用,这些主机对外只表现出一个IP。交换机可以把很多主机连起来,这些主机对外各有各的IP。
    3. 交换机工作在中继层,根据MAC地址寻址,不能处理TCP/IP协议。路由器工作在网络层,根据IP地址寻址,可以处理TCP/IP协议。
    4. 路由器提供防火墙服务,交换机不能提供该功能
5. ARP协议?
    根据IP地址获取物理地址的一个TCP/IP协议
6. DNS解析?
    域名解析是把域名指向网站空间IP,让人们通过注册的域名可以方便地访问到网站的一种服务。
    IP地址是网络上标识站点的数字地址,为了方便记忆,采用域名来代替IP地址标识站点地址。域名解析就是域名到IP地址的转换过程。域名的解析工作由DNS服务器完成。
7. Http和Https
    1、https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。
    2、http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。
    3、http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
    4、http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。
    HTTP:是互联网上应用最为广泛的一种网络协议,是一个客户端和服务器端请求和应答的标准(TCP),用于从WWW服务器传输超文本到本地浏览器的传输协议,它可以使浏览器更加高效,使网络传输减少。
    HTTPS:是以安全为目标的HTTP通道,简单讲是HTTP的安全版,即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。
8. 进程、线程、协程区别?  **
    进程(Process)
        是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
        在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,
        进程是程序的实体。
            特征 
            动态性:进程的实质是程序在多道程序系统中的一次执行过程,进程是动态产生,动态消亡的。 
            并发性:任何进程都可以同其他进程一起并发执行 
            独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位; 
            异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进 
            结构特征:进程由程序、数据和进程控制块三部分组成。 
            多个不同的进程可以包含相同的程序:一个程序在不同的数据集里就构成不同的进程,能得到不同的结果;但是执行过程中,程序不能发生改变。
    线程
        有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。
        一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,
        线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。
        线程是程序中一个单一的顺序控制流程。进程内有一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指令运行时的程序的调度单位。
        在单个程序中同时运行多个线程完成不同的工作,称为多线程。   
    协程
        协程与子例程一样,协程(coroutine)也是一种程序组件。相对子例程而言,协程更为一般和灵活,但在实践中使用没有子例程那样广泛。 
        协程不是进程或线程,其执行过程更类似于子例程,或者说不带返回值的函数调用。 
        一个程序可以包含多个协程,可以对比与一个进程包含多个线程,                     
9. GIL锁 **
    GIL保证同一时刻只有一个线程执行代码,每个线程在执行过程中都要先获取GIL
    python使用多进程是可以利用多核的CPU资源的。
    多线程爬取比单线程性能有提升,因为遇到IO阻塞会自动释放GIL锁
    GIL只对计算密集型的程序有作用,对IO密集型的程序并没有影响,因为遇到IO阻塞会自动释放GIL锁
10. 进程如何进程共享?  **
    1.无名管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
    2.高级管道(popen):将另一个程序当做一个新的进程在当前程序进程中启动,则它算是当前程序的子进程,这种方式我们成为高级管道方式。
    3.有名管道 (named pipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
    4.消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
    5.信号量( semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
    6.信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
    7.共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。
    8.套接字( socket ) : 套解字也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。
  
计算机网络

收发消息详解:

data:
    request.post(
        url='xxx',
        data={'k1':'v1', 'k2':'v2'}
    )
    # 数据: POST / http1.1\r\n...\r\n\r\nk1=v1&k2=v2
    
    request.post(
        url='xxx',
        data=json.dumps({'k1':'v1', 'k2':'v2'})
    )
    # 数据: POST / http1.1\r\n...\r\n\r\n{'k1':'v1', 'k2':'v2'}

    request.post(
        url='xxx',
        data=b'sdcasc'
    )
     # 数据: POST / http1.1\r\n...\r\n\r\n'sdcasc'  
     
json:
    request.post(
        url='xxx',
        json={'k1':'v1', 'k2':'v2'}
    )
    # 数据: POST / http1.1\r\nContent-Type:application/json...\r\n\r\n{'k1':'v1', 'k2':'v2'}
    
问题:
    post请求 发数据,django获取不到值?进行分析  request.POST
        发送数据格式:
            方式一:
                request.post(
                    url='xxx',
                    data={'k1':'v1', 'k2':'v2'}
                )
                # 数据: POST / http1.1\r\nContent-Type:urlencode-form...\r\n\r\nk1=v1&k2=v2
                request.POST一定会获取到值
                    - Content-Type:urlencode-form
                    - 数据格式: k1=v1&k2=v2
            方式二:
                request.post(
                    url='xxx',
                    json={'k1':'v1', 'k2':'v2'}
                )
                # 数据: POST / http1.1\r\nContent-Type:application/json...\r\n\r\n{'k1':'v1', 'k2':'v2'}
                request.POST一定不会获取到值
                在request.body中获取
                    - 字节={'k1':'v1', 'k2':'v2'}
                    - 字节转换成字符串
                    - 反序列化字符串  -> 字典
知识点:
    在chrome ->
        1. Form-Data类型的数据格式
            phone=31313413&password=45454
        
            用以下方式发送数据:
                request.post(
                    url=url,
                    data={
                       'phone': 31313413,
                       'password':45454
                    }
                )
                
        2. Request Payload类型的数据格式
            data_dict = {'BaseRequest':{
            'DeviceID':'165111',
            'Sid':wededxwec165,
            'Skey':ewfcdwec5456,
            'Uin':'42132'
        }}
            用以下方式发送数据:
                request.post(
                    url=url,
                    json = data_dict  # 适用于data_dict数据中不存在中文
                    )
                    
                # 适用于data_dict数据中存在中文
                request.post(
                    url=url,
                    data=bytes(json.dumps(data_dict, ensure_ascii=False), encoding='utf-8')

scrapy框架

使用:

Scrapy:
# 创建project
scrapy startproject cku
cd cku
# 创建爬虫
scrapy genspider chouti chouti.com
scrapy genspider cnblogs cnblogs.com
# 启动爬虫
scrapy crawl chouti

1. 创建project
    scrapy startproject 项目名称
    项目名称
        项目名称/
            __init__.py
            items.py  # 持久化
            pipelines.py  # 持久化
            settings.py  # 配置文件(爬虫)
            middlewares.py  # 中间件
            spiders/  # 爬虫文件
                - 项目名称
                - 
        scrapy.cfg    # 主配置文件 (部署)                                           
2. 创建爬虫
    cd 项目名称
    scrapy genspider chouti chouti.com
    scrapy genspider cnblogs cnblogs.com
3. 启动爬虫
    scrapy crawl chouti
    scrapy crawl cnblogs --nolog

示例:  

示例1(爬取汽车之家新闻和链接):
    import scrapy
    class CarSpider(scrapy.Spider):
        name = 'car'
        allowed_domains = ['www.autohome.com.cn/news/']
        start_urls = ['http://www.autohome.com.cn/news/']

        def parse(self, response):
            
            f = open('new.log', mode='a+')
            car_list = response.xpath('//div[@id="auto-channel-lazyload-article"]//li')
          
            for item in car_list:        
                href = item.xpath('.//a/@href').extract_first()
                text = item.xpath('.//a/p/text()').extract_first()
                print(href,text)
                if href and text:
                    f.write(href+'\n'+ text+'\n')          
            f.close()
示例2(爬取抽屉)
    # -*- coding: utf-8 -*-
    import scrapy
    # from scrapy.http.response.html import HtmlResponse
    # sys.stdout=io.TextIOWrapper(sys.stdout.buffer,encoding='gb18030')
    class ChoutiSpider(scrapy.Spider):
        name = 'chouti'
        allowed_domains = ['chouti.com']
        start_urls = ['http://chouti.com/']

        def parse(self, response):                                                         
            # 去子孙中找div并且id=content-list
            f = open('new.log', mode='a+')
            item_list = response.xpath('//div[@id="content-list"]/div[@class="item"]')
            for item in item_list:
                text = item.xpath('.//a/text()').extract_first()
                href = item.xpath('.//a/@href').extract_first()
                print(href, text.strip())
                f.write(href+'\n')
            f.close()

            page_list = response.xpath('//div[id="dig_lcpage"]//a/@href').extract()
            for page in page_list:
                from scrapy.http import Request
                page = 'https://dig.chouti.com'+page
                yield Request(url=page, callback=self.parse)

总结: 
- HTML解析:xpath
- 再次发起请求: yield Request对象

twisted

内部基于事件循环的机制实现爬虫的并发
    原来的你:
        url_list = ['http://www.baidu.com', 'http://www.baidu.com']
        
        for item in url_list:
            response = requests.get(item)
            print(response.text)
    现在的你:
        from twisted.web.client import getPage, defer
        from twisted.internet import reactor

        # 1. 代理开始接受任务
        def callback(contents):
            print(contents)

        deferred_list = []

        url_list = ['http://www.bing.com', 'https://segmentfault.com/', 'https://stackoverflow.com/']
        for url in url_list:
            deferred = getPage(bytes(url, encoding='utf8'))
            deferred.addCallback(callback)
            deferred_list.append(deferred)

        # 2. 代理执行完任务后停止
        dlist = defer.DeferredList(deferred_list)

        def all_done(arg):
            reactor.stop()
        dlist.addBoth(all_done)

        # 3. 代理开始处理
        reactor.run()
#scrapy命令的编写
 命令:
    scrapy startproject xx
    cd xx
    scrapy gender chouti chouti.com
    
    scrapy crawl chputi --nolog
编写:
    def parse(self, response):
        # 1. 响应
        # response封装了响应相关的所有数据
        - response.text
        - response.encoding
        - response.body
        - response.request # 当前响应是由哪个请求发起:请求中封装(要访问的url,下载完成后执行哪个函数)
        # 2.解析
        # response.xpath('//div[@href="x1"]/a').extract_first()  # 找到第一个
        # response.xpath('//div[@href="x1"]/a').extract()  # 找到所有
        # response.xpath('//div[@href="x1"]/a/text()').extract() 
        # response.xpath('//div[@href="x1"]/a/@href').extract() 
        tag_list = response.xpath('//div[@href="x1"]/a')
        for tag in tag_list:
            tag.xpath('.//p/text()').extract_first()  # .//表示从当前开始
            
        # 3. 再次发起请求
        yield Request(url='xxx', callback=self.parse)
        
代码分析:
    def parse(self, response):          
        f = open('new.log', mode='a+')
        item_list = response.xpath('//div[@id="content-list"]/div[@class="item"]')
        for item in item_list:
            text = item.xpath('.//a/text()').extract_first()
            href = item.xpath('.//a/@href').extract_first()
            print(href, text.strip())
            f.write(href+'\n')
        f.close()

        page_list = response.xpath('//div[id="dig_lcpage"]//a/@href').extract()
        for page in page_list:
            from scrapy.http import Request
            page = 'https://dig.chouti.com'+page
            yield Request(url=page, callback=self.parse)
        目前缺点:
            1. 无法完成爬虫刚开始打开连接,爬虫结束时候关闭连接
            2. 分工不明确
优化:
    pipelines/items
        a. 先写pipeline类
        class PchoutiPipeline(object):
            def process_item(self, item, spider):                           
                return item
        b. 写Item类                  
        class PchoutiItem(scrapy.Item):                       
            href = scrapy.Field()
            title = scrapy.Field()
        c. 配置
            ITEM_PIPELINES = {
               'pchouti.pipelines.PchoutiPipeline': 300,
            } 
        d.爬虫
            yield每执行一次,process_item就调用一次。    
            yield Item对象
编写pipelines:
    class PchoutiPipeline(object):
        def __init__(self,path):
            self.f = None
            self.path = path

        @classmethod
        def from_crawler(cls, crawler):
            """
            初始化时候,用于创建pipeline对象
            :param crawler:
            :return:
            """
            path = crawler.settings.getint('HREF_FILE_PATH')
            return cls(path)

        def open_spider(self, spider):
            """
            爬虫开始执行时,调用
            :param spider:
            :return:
            """
            if spider.name == 'chouti':
                self.f = open(self.path, 'a+')
                print('爬虫开始')

        def process_item(self, item, spider):
            self.f.write(item['href'])
            # return item # 交给下一个pipeline的process_item方法
            return DropItem()  # 后续的pipeline的process_item方法不再执行

        def close_spider(self, spider):
            """
            爬虫关闭时,被调用
            :param spider:
            :return:
            """
            self.f.close()
            print('爬虫结束')
注意:如何pipeline是所有爬虫公用的,如果想要给某个爬虫定制功能,需要使用spider参数自己进行处理                        
持久化
a. 编写类: pchouti/dupefilters.py:
                from scrapy.dupefilters import BaseDupeFilter
                from scrapy.utils.request import request_fingerprint

                class pchDupeFilter(BaseDupeFilter):
                    def __init__(self):
                        self.visteded_url = set()

                    @classmethod
                    def from_settings(cls, settings):
                        return cls()

                    def request_seen(self, request):
                        fd = request_fingerprint(request=request)
                        if fd in self.visteded_url:
                            return True
                        self.visteded_url.add(fd)

                    def open(self):  # can return deferred
                        pass

                    def close(self, reason):  # can return a deferred
                        pass

                    def log(self, request, spider):  # log that a request has been filtered
                        print('日志') 
          
            b. 修改默认的去除规则(配置文件):
                # DUPEFILTER_CLASS= 'scrapy.dupefilter.RFPDupeFilter'
                DUPEFILTER_CLASS = 'pchouti.dupefilters.pchDupeFilter'
                
            c. 爬虫使用
                class ChoutiSpider(scrapy.Spider):
                name = 'chouti'
                allowed_domains = ['chouti.com']
                start_urls = ['http://chouti.com/']

                def parse(self, response): 
                    print(response.request.url)
                    # 去子孙中找d iv并且id=content-list
                    # item_list = response.xpath('//div[@id="content-list"]/div[@class="item"]')
                    # for item in item_list:
                    #    text = item.xpath('.//a/text()').extract_first()
                    #    href = item.xpath('.//a/@href').extract_first()
                    #    print(href, text.strip())
                    #    yield PchoutiItem(title=text, href=href)

                    page_list = response.xpath('//div[id="dig_lcpage"]//a/@href').extract()
                    for page in page_list:
                        from scrapy.http import Request
                        page = 'https://dig.chouti.com'+page
                        yield Request(url=page, callback=self.parse, dont_filter=False)
            注意:
                - request_seen中编写正确逻辑
                - dont_filter=False
去重
# -*- coding: utf-8 -*-
from urllib.parse import urlencode

import scrapy
from scrapy.http.cookies import CookieJar
from scrapy.http import Request

class ChoutiSpider(scrapy.Spider):
    name = 'chouti'
    allowed_domains = ['chouti.com']
    start_urls = ['http://chouti.com/']
    cookie_dict = {}

    def parse(self, response):
        # 去响应头中获取cookie
        # cookie保存在cookie_jar对象中
        cookie_jar = CookieJar()
        cookie_jar.extract_cookies(response, response.request)
        # 去对象中将cookie解析到字典
        for k, v in cookie_jar._cookies.items():
            for i, j in v.items():
                for m, n in j.items():
                    self.cookie_dict[m] = n.value

        yield Request(
            url='https://dig.chouti.com/login',
            method='POST',
            headers={'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'},
            # body='phone=8615131255089&password=pppppppp&oneMonth=1',

            body=urlencode({
                'phone':'8615131255089',
                'password':'pppppppp'
             }),

            cookies=self.cookie_dict,
            callback=self.check_login
        )

    def check_login(self, response):
        print(response.text)
        yield Request(
            url='https://dig.chouti.com/all/hot/recent/1',
            cookies=self.cookie_dict,
            callback=self.index
        )

    def index(self, response):
        news_list = response.xpath('//div[@id="content-list"]/div[@class="item"]')
        for new in news_list:
            link_id = new.xpath('//div[@class="part2"]/@share-linkid').extract_first()
            yield Request(
                url='http://dig.chouti.com/link/vote?linksId=%s' % (link_id,),
                method='POST',
                cookies=self.cookie_dict,
                callback=self.check_result
            )
            page_list = response.xpath('//div[id="dig_lcpage"]//a/@href').extract()
            for page in page_list:
                from scrapy.http import Request
                page = 'https://dig.chouti.com' + page
                yield Request(url=page, callback=self.index, dont_filter=False)

    def check_result(self, response):
        print(response.text)         
点赞示例
requests是一个Python实现的可以伪造浏览器发送Http请求的模块
    - 封装socket发送请求
twisted是基于事件循环的异步非阻塞网络框架
    - 封装socket发送请求
    - 单线程完成并发请求
    ps:三个关键词
        - 非阻塞;不等待
        - 异步:回调
        - 事件循环:一直循环去检查状态
twisted是什么以及和requests的区别
    - 引擎找到要执行的爬虫,并执行爬虫的start_requests方法,并得到一个迭代器。
    - 迭代器循环时候会获取Request对象,而request对象中封装了要访问的URL和回调函数。
    - 将所有的request对象(任务)放到调度器中,用于以后被下载器下载。
       
    - 下载器去调度器中获取要下载的任务(就是Request对象),下载完成后执行回调函数
    - 回到spider的回调函数中
        yield Request()
        yield Item()
scrapy框架组件以及执行流程?
帮助开发分布式爬虫的组件
- 基于scrapy-redis的去重规则
    - 完全自定义:
        import redis
        from scrapy.dupefilters import BaseDupeFilter
        from scrapy.utils.request import request_fingerprint                

        class DupFilter(BaseDupeFilter):
            def __init__(self):
                # self.visited_url = set()
                self.conn = redis.Redis(host='localhost', port=6379, password='cxcz')

            def request_seen(self, request):
                """
                检测当前请求是否已经被访问过
                :param request:
                :return: True表示已经访问过;False表示未访问过
                """
                # if request.url in self.visited_url:
                #     return True
                # self.visited_url.add(request.url)
                # return False
                fid = request_fingerprint(request)
                res = self.conn.sadd('visited_urls', fid)
                if res == 1:
                    return False
                return True
    - 使用scrapy-redis
    - 继承scrapy-redis实现自定制
        from scrapy_redis.dupefilter import RFPDupeFilter
        from scrapy_redis import get_redis_from_settings, defaults
        class RedisDuperFilter(RFPDupeFilter):

            @classmethod
            def from_settings(cls, settings):
                server = get_redis_from_settings(settings)
                # XXX: This creates one-time key. needed to support to use this
                # class as standalone dupefilter with scrapy's default scheduler
                # if scrapy passes spider on open() method this wouldn't be needed
                # TODO: Use SCRAPY_JOB env as default and fallback to timestamp.
                key = defaults.DUPEFILTER_KEY % {'timestamp': 'ddb'}
                debug = settings.getbool('DUPEFILTER_DEBUG')
                return cls(server, key=key, debug=debug)
- 配置
    ######### scrapy_redis 连接 #########
    
    REDIS_HOST = 'localhost'                            # 主机名
    REDIS_PORT = 6379                                   # 端口
    REDIS_PARAMS = {'password':'cxcz'}           
    REDIS_ENCODING = "utf-8"                            # redis编码类型    默认:'utf-8'
    # REDIS_URL = 'redis://user:pass@hostname:6379'
    
    ######### scrapy_redis 去重 #########
    
    DUPEFILTER_KEY = 'dupefilter:%(timestamp)s'
    DUPEFILTER_CLASS = 'scrapy_redis.dupefilter.RFPDupeFilter'
- 调度器
    - redis相关操作
    - 调度器
- pipeline
- start_requests
scrapy_redis

scrapy-redis详细 

- 先进先出
    import scrapy_redis
    import redis
    class FifoQueue(object):
        """Per-spider FIFO queue"""
        def __init__(self):
            self.server = redis.Redis(host='localhost', port=6379, password='cxcz')
        def push(self, request):
            """Push a request"""
            self.server.lpush('USERS', request)

        def pop(self, timeout=0):
            """Pop a request"""
            data = self.server.rpop('USERS')
            return data
    # 先进先出
    # []
    q = FifoQueue()
    q.push(11)
    q.push(22)
    q.push(33)
    # [33,22,11]

    print(q.pop())  # 11
    print(q.pop())  # 22
    print(q.pop())  # 33
- 后进先出(栈)
    import redis
    class LifoQueue(object):

        def __init__(self):
            self.server = redis.Redis(host='localhost', port=6379, password='cxcz')

        def push(self, request):
            """Push a request"""
            self.server.lpush('USERS', request)
        def pop(self, timeout=0):
            """Pop a request"""
            data = self.server.lpop('USERS')
            return data

    # 后进先出
    q = LifoQueue()
    q.push(11)
    q.push(22)
    q.push(33)
    # [33,22,11]
    print(q.pop())  # 33
    print(q.pop())  # 22
    print(q.pop())  # 11
- 优先级队列
    import redis
    class PriorityQueue(object):

        def __init__(self):
            self.server = redis.Redis(host='localhost', port=6379, password='cxcz')

        def push(self, request, score):
          self.server.execute_command('ZADD', 'xxx', score, request)

        def pop(self, timeout=0):
            pipe = self.server.pipeline()
            pipe.multi()
            pipe.zrange('xxx', 0, 0).zremrangebyrank(self.key, 0, 0)
            results, count = pipe.execute()
            if results:
                return results[0]


    q = PriorityQueue()

    q.push('alex', 99)
    q.push('old', 89)
    q.push('sc', 89)

    v1 = q.pop()
    print(v1)
    v2 = q.pop()
    print(v2)
    v3 = q.pop()
    print(v3)
scrapy-redis提供了三种队列
基于python列表构建一个栈
# 1.
    class Stack(object):

        def __init__(self):
            self.data = []

        def push(self, val):
            return self.data.append(val)

        def pop(self):
            return self.data.pop()
        
        def top(self):
            return self.data[-1]
# 2.
    class MYQueue():
        def __init__(self):
            self.list = []
        def push(self, arg):
            self.list.append(arg)
        
        def pop(self):
            while True:
                try:
                    data = self.list.pop()
                    if data:
                        return data
                except Exception as e:
                    return e
基于redis列表构建一个栈
    import redis
    class Myqueque(object):

        def __init__(self):
            self.conn = redis.Redis(host='localhost', port=6379, password='cxcz')

        def push(self, request):
            """Push a request"""
            self.conn.lpush('USERS', request)
        def pop(self, timeout=0):
            """Pop a request"""
            data = self.conn.blpop('USERS')     # blpop是阻塞式
            return data.decode('utf-8')
构建一个栈
问题:给定10个图片的url,将其10张图片下载下来                             
方案一:串行
    import requests
    urls = [
        'https://www.baidu.com/',
        'https://www.cnblogs.com/',
        'https://news.cnblogs.com/',
        'https://cn.bing.com/',
        'https://stackoverflow.com/',
    ]
    
    for url in urls:
    response = requests.get(url)
    with open(url+'.png','wb') as f:
        f.write(response.content)
    缺点:费时间,一个一个url进行           
方案二:多线程
    import requests
    import threading

    urls = [
        'https://www.baidu.com/',
        'https://www.cnblogs.com/',
        'https://news.cnblogs.com/',
        'https://cn.bing.com/',
        'https://stackoverflow.com/',

    ]

    def task(url):
        response = requests.get(url)
        print(response)

    for url in urls:
        t = threading.Thread(target=task, args=(url,))
        t.start()

    缺点:线程利用率不高                   
方案三:协程 
    # 协程——IO切换
    # 内部调用了greenlet实现了协程    
    from gevent import monkey; monkey.patch_all()
    import gevent
    import requests

    def func(url):
        response = requests.get(url)
        print(response)

    urls = [
        'https://www.baidu.com/',
        'https://www.cnblogs.com/',
        'https://news.cnblogs.com/',
        'https://cn.bing.com/',
        'https://stackoverflow.com/',

    ]
    spawn_list = []
    for url in urls:
        spawn_list.append(gevent.spawn(func, url))

    gevent.joinall(spawn_list)  
方案四:基于事件循环的异步非阻塞模块
    from twisted.web.client import getPage, defer
    from twisted.internet import reactor


    def stop_loop(arg):
        reactor.stop()

    def get_response(contents):
        print(contents)


    deferred_list = []

    url_list = [
        'https://www.baidu.com/',
        'https://www.cnblogs.com/',
        'https://news.cnblogs.com/',
        'https://cn.bing.com/',
        'https://stackoverflow.com/',
    ]
    for url in url_list:
        print(111)
        deferred = getPage(bytes(url, encoding='utf8'))
        deferred.addCallback(get_response)
        deferred_list.append(deferred)

    dlist = defer.DeferredList(deferred_list)
    dlist.addBoth(stop_loop)

    reactor.run()
高性能相关

 

posted @ 2020-04-27 16:53  royal天  阅读(185)  评论(0)    收藏  举报