scrapy框架详解

一、介绍

Scrapy是一个开源和协作的框架,其最初是为了页面抓取(更确切地来说,网络抓取)所设计的,使用它可以快速、简单、可扩展的方式从网站中提取所需的数据。但目前Scrapy的用途十分广泛,可用于如数据挖掘、监测和自动化测试等领域,也可以应用在获取API所返回的数据(如Amaz Associates Web Services)或者通用的网络爬虫。

Scrapy是基于twisted框架开发而来的,twisted是一个流行的事件驱动python网络框架。因此Scrapy使用了一种异步非阻塞的代码来实现并发。

scrapy框架由以上几个部分组成。Engine、Scheduler、Downloader一般不用进行修改。我们只要专注于编写我们自己的Spiders、Pipelines程序、还有Middleware中间件即可。

二、安装

#Windows平台
    1、pip3 install wheel #安装后,便支持通过wheel文件安装软件,wheel文件官网:https://www.lfd.uci.edu/~gohlke/pythonlibs
    3、pip3 install lxml
    4、pip3 install pyopenssl
    5、下载并安装pywin32:https://sourceforge.net/projects/pywin32/files/pywin32/
    6、下载twisted的wheel文件:http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
    7、执行pip3 install 下载目录\Twisted-17.9.0-cp36-cp36m-win_amd64.whl
    8、pip3 install scrapy

#Linux平台
    1、pip3 install scrapy

三、命令行工具

#1 查看帮助
    scrapy -h
    scrapy <command> -h

#2 有两种命令:其中Project-only必须切到项目文件夹下才能执行,而Global的命令则不需要
    Global commands:
        startproject #创建项目
        genspider    #创建爬虫程序
        settings     #如果是在项目目录下,则得到的是该项目的配置
        runspider    #运行一个独立的python文件,不必创建项目
        shell        #scrapy shell url地址  在交互式调试,如选择器规则正确与否
        fetch        #独立于程单纯地爬取一个页面,可以拿到请求头
        view         #下载完毕后直接弹出浏览器,以此可以分辨出哪些数据是ajax请求
        version      #scrapy version 查看scrapy的版本,scrapy version -v查看scrapy依赖库的版本
    Project-only commands:
        crawl        #运行爬虫,必须创建项目才行,确保配置文件中ROBOTSTXT_OBEY = False
        check        #检测项目中有无语法错误
        list         #列出项目中所包含的爬虫名
        edit         #编辑器,一般不用
        parse        #scrapy parse url地址 --callback 回调函数  #以此可以验证我们的回调函数是否正确
        bench        #scrapy bentch压力测试

#3 官网链接
    https://docs.scrapy.org/en/latest/topics/commands.html

示范用法

#1、执行全局命令:请确保不在某个项目的目录下,排除受该项目配置的影响
scrapy startproject MyProject

cd MyProject
scrapy genspider baidu www.baidu.com

scrapy settings --get XXX #如果切换到项目目录下,看到的则是该项目的配置

scrapy runspider baidu.py

scrapy shell https://www.baidu.com
    response
    response.status
    response.body
    view(response)
    
scrapy view https://www.taobao.com #如果页面显示内容不全,不全的内容则是ajax请求实现的,以此快速定位问题

scrapy fetch --nolog --headers https://www.taobao.com

scrapy version #scrapy的版本

scrapy version -v #依赖库的版本

#2、执行项目命令:切到项目目录下
scrapy crawl baidu
scrapy check
scrapy list
scrapy parse http://quotes.toscrape.com/ --callback parse
scrapy bench

四、项目结构以及爬虫应用简介

project_name/
   scrapy.cfg
   project_name/
       __init__.py
       items.py
       pipelines.py
       settings.py
       spiders/
           __init__.py
           爬虫1.py
           爬虫2.py
           爬虫3.py

文件说明:

  • scrapy.cfg 项目的主配置信息,用来部署scrapy时使用,爬虫相关的配置信息在settings.py中。
  • items.py 设置数据存储模版,用于结构化数据。类似于django中的model。
  • pipelines 数据处理行为,一般结构化的数据持久化。
  • settings.py 配置文件,如:递归的层数、并发数、延迟下载等。强调:配置文件的选项必须大写否则视为无效。
  • spiders 爬虫目录

注意:一般创建爬虫文件时,以网站域名命名

# 在项目目录下新建:entrypoint.py
from scrapy.cmdline import execute
execute(['scrapy','crawl','amazon'])
import sys,os
sys.stdout=io.TextIOWrapper(sys.stdout.buffer,encoding='gbk')

五、Spiders

1.介绍
# 1.Spiders是由一系列类组成,具体包括如何执行爬取任务如何从页面中提取结构化数据
# 2.Spiders 是我们为了一个特定的网址或一组网址定义爬取和解析页面行为的地方。
2.Spiders的循环执行过程
# 1.生成初始的Request来爬取第一个URLS,并且标识一个回调函数
	第一个请求定义在start_request()方法内默认从start_urls列表中获得url地址来生成Request请求,默认的回调函数是parse。回调函数在下载完成返回response时自动触发。
    
# 2.在回调函数中,解析response并且返回值
可以有4种返回值,不做详述。

# 3.在回调函数中解析页面内容

# 4.针对返回的Items对象持久化到数据库
3.Spiders类下的各方法属性
class AmazonSpider(scrapy.Spider):
    # 1.定义爬虫名
    name = 'amazon'
    # 2.定义允许爬取的域名
    allowed_domains = ['www.amazon.cn']

    # 3.如果没有指定url,就从该列表中读取url来生成第一个请求
    start_urls = ['http://www.amazon.cn/']

    # 4.自定义配置信息(否则就使用setting.py中默认的配置)
    # self.settings['key'] 可以访问(优先)custom_settings或settings.py的配置
    custom_settings = {
        'BOT_NAME': "amazon",
        "REQUEST_HEADERS": {
            'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.87 Safari/537.36'
        }
    }
	
    # 重写__init__方法时记住要调用父类方法
    def __init__(self, keyword,*args,**kwargs):
        super().__init__(*args,**kwargs)
        self.keyword = keyword

    # 该方法用来发起第一个Request请求,必须返回可迭代对象。在爬虫程序打开时执行,只会被调用一次。
    def start_requests(self):
        pass
    
    # 如果Request没有指定回调函数,默认的回调函数
    def parse(self, response):
        pass
    
    # 自定义回调函数
    def parse_detail(self,response):
        pass
    
    # 爬虫程序关闭时执行的函数
    def close(spider, reason):
        pass

Request对象

Request(url='https://www.amazon.cn',call_back=self.parse,dont_filter=False)
# 1.url
# 2.call_back回调函数
# 3.是否参与去重,可自定义去重规则
自定义去重规则

去重规则用于多个爬虫共享,一个url如果已经被爬取则不需要重复爬取,实现方式如下:

# 方法1:
# 1.新增定义类属性
visited = set()

# 2.回调函数parse方法
def parse(self,response):
    # 如果被已经访问过
    if response.url in self.visited:
        return None
    
    # 没有被访问执行响应代码
    self.visited.add(response.url)

    
# 方法2:Scrapy自带去重功能
settings.py中
DUPEFILTER_CLASS = 'scrapy.dupefilter.RFPDupeFilter' # 默认的去重规则

# 方法3:可以仿照RFPDupeFilter自定义去重规则
#步骤一:在项目目录下自定义去重文件dup.py
from scrapy.dupefilter import RFPDupeFilter # 模仿RFPDupeFilter

class MyDupeFilter(object):
    def __init__(self):
        self.visited = set() #或者放到数据库

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

    def request_seen(self, request):
        if request.url in self.visited:
            return True
        self.visited.add(request.url)

    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
        pass


#步骤二:配置文件settings.py:
DUPEFILTER_CLASS = '项目名.dup.MydupeFilter'

在启动爬虫程序时传递参数

scrapy crawl myspidet -a keyword=hahah

六、Selectors

response.selector.css()
response.selector.xpath()
可简写为
response.css()
response.xpath()

#1 //与/
response.xpath('//body/a/')#
response.css('div a::text')

>>> response.xpath('//body/a') #开头的//代表从整篇文档中寻找,body之后的/代表body的儿子
[]
>>> response.xpath('//body//a') #开头的//代表从整篇文档中寻找,body之后的//代表body的子子孙孙
[<Selector xpath='//body//a' data='<a href="image1.html">Name: My image 1 <'>, <Selector xpath='//body//a' data='<a href="image2.html">Name: My image 2 <'>, <Selector xpath='//body//a' data='<a href="
image3.html">Name: My image 3 <'>, <Selector xpath='//body//a' data='<a href="image4.html">Name: My image 4 <'>, <Selector xpath='//body//a' data='<a href="image5.html">Name: My image 5 <'>]

#2 text
>>> response.xpath('//body//a/text()')
>>> response.css('body a::text')

#3、extract与extract_first:从selector对象中解出内容
>>> response.xpath('//div/a/text()').extract()
['Name: My image 1 ', 'Name: My image 2 ', 'Name: My image 3 ', 'Name: My image 4 ', 'Name: My image 5 ']
>>> response.css('div a::text').extract()
['Name: My image 1 ', 'Name: My image 2 ', 'Name: My image 3 ', 'Name: My image 4 ', 'Name: My image 5 ']

>>> response.xpath('//div/a/text()').extract_first()
'Name: My image 1 '
>>> response.css('div a::text').extract_first()
'Name: My image 1 '

#4、属性:xpath的属性加前缀@
>>> response.xpath('//div/a/@href').extract_first()
'image1.html'
>>> response.css('div a::attr(href)').extract_first()
'image1.html'

#4、嵌套查找
>>> response.xpath('//div').css('a').xpath('@href').extract_first()
'image1.html'

#5、设置默认值
>>> response.xpath('//div[@id="xxx"]').extract_first(default="not found")
'not found'

#4、按照属性查找
response.xpath('//div[@id="images"]/a[@href="image3.html"]/text()').extract()
response.css('#images a[@href="image3.html"]/text()').extract()

#5、按照属性模糊查找
response.xpath('//a[contains(@href,"image")]/@href').extract()
response.css('a[href*="image"]::attr(href)').extract()

response.xpath('//a[contains(@href,"image")]/img/@src').extract()
response.css('a[href*="imag"] img::attr(src)').extract()

response.xpath('//*[@href="image1.html"]')
response.css('*[href="image1.html"]')

#6、正则表达式
response.xpath('//a/text()').re(r'Name: (.*)')
response.xpath('//a/text()').re_first(r'Name: (.*)')

#7、xpath相对路径
>>> res=response.xpath('//a[contains(@href,"3")]')[0]
>>> res.xpath('img')
[<Selector xpath='img' data='<img src="image3_thumb.jpg">'>]
>>> res.xpath('./img')
[<Selector xpath='./img' data='<img src="image3_thumb.jpg">'>]
>>> res.xpath('.//img')
[<Selector xpath='.//img' data='<img src="image3_thumb.jpg">'>]
>>> res.xpath('//img') #这就是从头开始扫描
[<Selector xpath='//img' data='<img src="image1_thumb.jpg">'>, <Selector xpath='//img' data='<img src="image2_thumb.jpg">'>, <Selector xpath='//img' data='<img src="image3_thumb.jpg">'>, <Selector xpa
th='//img' data='<img src="image4_thumb.jpg">'>, <Selector xpath='//img' data='<img src="image5_thumb.jpg">'>]

#8、带变量的xpath
>>> response.xpath('//div[@id=$xxx]/a/text()',xxx='images').extract_first()
'Name: My image 1 '
>>> response.xpath('//div[count(a)=$yyy]/@id',yyy=5).extract_first() #求有5个a标签的div的id
'images'

七、items

import scrapy


class AmazonItem(scrapy.Item):
    # define the fields for your item here like:
    name = scrapy.Field()
    price = scrapy.Field()
    delivery_method = scrapy.Field()

八、Item Pipeline

pipeline是对item进行处理的过程

如果自定义需要在 settings.py下定义

ITEM_PIPELINES = {
   'Amazon.pipelines.CustomPipeline': 200,
}
from scrapy.exceptions import DropItem
from pymongo import MongoClient
class CustomPipeline(object):
    def __init__(self,host,port,user,pwd,db,table):
        self.host = host
        self.port = port
        self.user = user
        self.pwd = pwd
        self.db = db
        self.table = table

    @classmethod
    def from_crawler(cls, crawler):
        """
        Scrapy会先通过getattr判断我们是否自定义了from_crawler,有则调它来完
        成实例化
        """
        HOST = crawler.settings.get('HOST')
        PORT = crawler.settings.get('HOST')
        USER = crawler.settings.get('USER')
        PWD = crawler.settings.get('PWD')
        DB = crawler.settings.get('DB')
        TABLE = crawler.settings.get('COLLECTION')
        return cls(HOST,PORT,USER,PWD,DB,TABLE)

    def open_spider(self,spider):
        """
        爬虫刚启动时执行一次
        """
        # self.client = MongoClient('mongodb://%s:%s@%s:%s' %(self.user,self.pwd,self.host,self.port))
        self.client = MongoClient()

    def close_spider(self,spider):
        """
        爬虫关闭时执行一次
        """
        self.client.close()

    def process_item(self, item, spider):
        # 操作并进行持久化

        # return表示会被后续的pipeline继续处理
        d = dict(item)
        print(d)
        if all(d.values()):
            self.client[self.db][self.table].save(d)

        # 表示将item丢弃,不会被后续pipeline处理
        # raise DropItem()

九、下载中间件

下载中间件的用途
    1、在process——request内,自定义下载,不用scrapy的下载
    2、对请求进行二次加工,比如
        设置请求头
        设置cookie
        添加代理
            scrapy自带的代理组件:
                from scrapy.downloadermiddlewares.httpproxy import HttpProxyMiddleware
                from urllib.request import getproxies
class DownMiddleware1(object):
    def process_request(self, request, spider):
        """
        请求需要被下载时,经过所有下载器中间件的process_request调用
        :param request: 
        :param spider: 
        :return:  
            None,继续后续中间件去下载;
            Response对象,停止process_request的执行,开始执行process_response
            Request对象,停止中间件的执行,将Request重新调度器
            raise IgnoreRequest异常,停止process_request的执行,开始执行process_exception
        """
        pass
​```
    def process_response(self, request, response, spider):
        """
        spider处理完成,返回时调用
        :param response:
        :param result:
        :param spider:
        :return: 
            Response 对象:转交给其他中间件process_response
            Request 对象:停止中间件,request会被重新调度下载
            raise IgnoreRequest 异常:调用Request.errback
        """
        print('response1')
        return response

    def process_exception(self, request, exception, spider):
        """
        当下载处理器(download handler)或 process_request() (下载中间件)抛出异常
        :param response:
        :param exception:
        :param spider:
        :return: 
            None:继续交给后续中间件处理异常;
            Response对象:停止后续process_exception方法
            Request对象:停止中间件,request将会被重新调用下载
        """
        return None

配置代理池

​```python
#1、与middlewares.py同级目录下新建proxy_handle.py
import requests

def get_proxy():
    return requests.get("http://127.0.0.1:5010/get/").text

def delete_proxy(proxy):
    requests.get("http://127.0.0.1:5010/delete/?proxy={}".format(proxy))
    
#2、middlewares.py
from Amazon.proxy_handle import get_proxy,delete_proxy

class DownMiddleware1(object):
    def process_request(self, request, spider):
        """
        请求需要被下载时,经过所有下载器中间件的process_request调用
        :param request:
        :param spider:
        :return:
            None,继续后续中间件去下载;
            Response对象,停止process_request的执行,开始执行process_response
            Request对象,停止中间件的执行,将Request重新调度器
            raise IgnoreRequest异常,停止process_request的执行,开始执行process_exception
        """
        proxy="http://" + get_proxy()
        request.meta['download_timeout']=20
        request.meta["proxy"] = proxy
        print('为%s 添加代理%s ' % (request.url, proxy),end='')
        print('元数据为',request.meta)

​```
def process_response(self, request, response, spider):
    """
    spider处理完成,返回时调用
    :param response:
    :param result:
    :param spider:
    :return:
        Response 对象:转交给其他中间件process_response
        Request 对象:停止中间件,request会被重新调度下载
        raise IgnoreRequest 异常:调用Request.errback
    """
    print('返回状态吗',response.status)
    return response

​```

​```
def process_exception(self, request, exception, spider):
    """
    当下载处理器(download handler)或 process_request() (下载中间件)抛出异常
    :param response:
    :param exception:
    :param spider:
    :return:
        None:继续交给后续中间件处理异常;
        Response对象:停止后续process_exception方法
        Request对象:停止中间件,request将会被重新调用下载
    """
    print('代理%s,访问%s出现异常:%s' %(request.meta['proxy'],request.url,exception))
    import time
    time.sleep(5)
    delete_proxy(request.meta['proxy'].split("//")[-1])
    request.meta['proxy']='http://'+get_proxy()

    return request
​```
posted @ 2020-03-20 22:08  GhostAnt  阅读(1000)  评论(0编辑  收藏  举报