python-scrapy

Scrapy框架介绍

1. 基本介绍

官方文档:

Scrapy 2.11 documentation — Scrapy 2.11.2 documentation

整体架构大致如下:

877318-20180912103950792-472094990

Components:

1、引擎(EGINE)
引擎负责控制系统所有组件之间的数据流,并在某些动作发生时触发事件。

2、调度器(SCHEDULER)
用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL的优先级队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址

3、下载器(DOWLOADER)
用于下载网页内容, 并将网页内容返回给EGINE,下载器是建立在twisted这个高效的异步模型上的

4、爬虫(SPIDERS)
SPIDERS是开发人员自定义的类,用来解析responses,并且提取items,或者发送新的请求

5、项目管道(ITEM PIPLINES)
在items被提取后负责处理它们,主要包括清理、验证、持久化(比如存到数据库)等操作
下载器中间件(Downloader Middlewares)位于Scrapy引擎和下载器之间,主要用来处理从EGINE传到DOWLOADER的请求request,已经从DOWNLOADER传到EGINE的响应response,

6、爬虫中间件(Spider Middlewares)
位于EGINE和SPIDERS之间,主要工作是处理SPIDERS的输入(即responses)和输出(即requests)

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM5NjExMjMw,size_16,color_FFFFFF,t_70

2. 命令行工具

* 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         #列出项目中所包含的爬虫名
        parse        #scrapy parse url地址 --callback 回调函数  #以此可以验证我们的回调函数是否正确
        bench        #scrapy bentch压力测试

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

3. 创建项目

scrapy startproject xxx
cd xxx
scrapy genspider xxxx xxx.com

目录

├── NewsPro
│   ├── __init__.py
│   ├── items.py
│   ├── middlewares.py
│   ├── pipelines.py
│   ├── settings.py
│   └── spiders
│       ├── __init__.py
│       └── wangyi.py
└── scrapy.cfg

4. 文件说明

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

5. 启动项目

scrapy crawl xxx

优雅的方法

在根目录下创建一个 bin.py 文件

from scrapy.cmdline import execute
execute(['scrapy', 'crawl', 'xxx', "--nolog"])

6. settings 配置文件

1. 为什么项目中需要配置文件

在配置文件中存放一些公共变量,在后续的项目中方便修改,如:本地测试数据库和部署服务器的数据库不一致

2. 配置文件中的变量使用方法
  1. 变量名一般全部大写
  2. 导入即可使用
3. settings.py中的重点字段和含义
- USER_AGENT 设置ua

- ROBOTSTXT_OBEY 是否遵守robots协议,默认是遵守

- CONCURRENT_REQUESTS 设置并发请求的数量,默认是16个

- DOWNLOAD_DELAY 下载延迟,默认无延迟 (下载器在从同一网站下载连续页面之前应等待的时间(以秒为单位)。这可以用来限制爬行速度,以避免对服务器造成太大影响)

- COOKIES_ENABLED 是否开启cookie,即每次请求带上前一次的cookie,默认是开启的

- DEFAULT_REQUEST_HEADERS 设置默认请求头,这里加入了USER_AGENT将不起作用

- SPIDER_MIDDLEWARES 爬虫中间件,设置过程和管道相同

- DOWNLOADER_MIDDLEWARES 下载中间件

- LOG_LEVEL 控制终端输出信息的log级别,终端默认显示的是debug级别的log信息

  - LOG_LEVEL = "WARNING"
    - CRITICAL  严重
    - ERROR  错误
    - WARNING  警告
    - INFO  消息
    - DEBUG   调试

- LOG_FILE 设置log日志文件的保存路径,如果设置该参数,终端将不再显示信息

  LOG_FILE = "./test.log"
  
- FEED_EXPORT_ENCODING='UTF-8' 保存的是unicode编码字符转为utf8

Spider类

Spiders是定义如何抓取某个站点(或一组站点)的类,包括如何执行爬行(即跟随链接)以及如何从其页面中提取结构化数据(即抓取项目)。换句话说,Spiders是您为特定站点(或者在某些情况下,一组站点)爬网和解析页面定义自定义行为的地方。 

1、 生成初始的Requests来爬取第一个URLS,并且标识一个回调函数
     第一个请求定义在start_requests()方法内默认从start_urls列表中获得url地址来生成Request请求,
     默认的回调函数是parse方法。回调函数在下载完成返回response时自动触发

2、 在回调函数中,解析response并且返回值
     返回值可以4种:
          包含解析数据的字典
          Item对象
          新的Request对象(新的Requests也需要指定一个回调函数)
          或者是可迭代对象(包含Items或Request)

3、在回调函数中解析页面内容
   通常使用Scrapy自带的Selectors,但很明显你也可以使用Beutifulsoup,lxml或其他你爱用啥用啥。

4、最后,针对返回的Items对象将会被持久化到数据库
   通过Item Pipeline组件存到数据库:https://docs.scrapy.org/en/latest/topics/item-pipeline.html)
   或者导出到不同的文件(通过Feed exports:https://docs.scrapy.org/en/latest/topics/feed-exports.html)
   

Item

抓取的主要目标是从非结构化源(通常是网页)中提取结构化数据。Scrapy爬虫可以像Python一样返回提取的数据。虽然方便和熟悉,但很容易在字段名称中输入拼写错误或返回不一致的数据,尤其是在具有许多爬虫的较大项目中。

为了定义通用输出数据格式,Scrapy提供了Item类。 Item对象是用于收集抓取数据的简单容器。它们提供类似字典的 API,并具有用于声明其可用字段的方便语法。

1、声明项目

使用简单的类定义语法和Field 对象声明项。这是一个例子:

import scrapy
 
class Product(scrapy.Item):
    name = scrapy.Field()
    price = scrapy.Field()
    stock = scrapy.Field()
    last_updated = scrapy.Field(serializer=str)

注意那些熟悉Django的人会注意到Scrapy Items被宣告类似于Django Models,除了Scrapy Items更简单,因为没有不同字段类型的概念。

Field对象用于指定每个字段的元数据。例如,last_updated上面示例中说明的字段的序列化函数。

您可以为每个字段指定任何类型的元数据。Field对象接受的值没有限制。出于同样的原因,没有所有可用元数据键的参考列表。

Field对象中定义的每个键可以由不同的组件使用,只有那些组件知道它。您也可以根据Field自己的需要定义和使用项目中的任何其他键。

Field对象的主要目标是提供一种在一个地方定义所有字段元数据的方法。通常,行为取决于每个字段的那些组件使用某些字段键来配置该行为。

Item PipeLine

在一个项目被spider抓取之后,它被发送到项目管道,该项目管道通过顺序执行的几个组件处理它。

每个项目管道组件(有时简称为“项目管道”)是一个实现简单方法的Python类。他们收到一个项目并对其执行操作,同时决定该项目是否应该继续通过管道或被丢弃并且不再处理。

项目管道的典型用途是:

  • cleansing HTML data
  • validating scraped data (checking that the items contain certain fields)
  • checking for duplicates (and dropping them)
  • storing the scraped item in a database

1 编写自己的项目管道

'''
每个项管道组件都是一个必须实现以下方法的Python类:

process_item(self,项目,spider)
为每个项目管道组件调用此方法。process_item() 

必须要么:返回带数据的dict,返回一个Item (或任何后代类)对象,返回Twisted Deferred或引发 DropItem异常。丢弃的项目不再由其他管道组件处理。

此外,他们还可以实现以下方法:

open_spider(self,spdier)
打开蜘蛛时会调用此方法。

close_spider(self,spider)
当蜘蛛关闭时调用此方法。

from_crawler(cls,crawler )
如果存在,则调用此类方法以从a创建管道实例Crawler。它必须返回管道的新实例。Crawler对象提供对所有Scrapy核心组件的访问,
如设置和信号; 它是管道访问它们并将其功能挂钩到Scrapy的一种方式。
'''

2 项目管道示例

(1) 价格验证和丢弃物品没有价格

让我们看看下面的假设管道,它调整 price那些不包含增值税(price_excludes_vat属性)的项目的属性,并删除那些不包含价格的项目:

from scrapy.exceptions import DropItem
 
class PricePipeline(object):
 
    vat_factor = 1.15
 
    def process_item(self, item, spider):
        if item['price']:
            if item['price_excludes_vat']:
                item['price'] = item['price'] * self.vat_factor
            return item
        else:
            raise DropItem("Missing price in %s" % item)

(2) 将项目写入JSON文件

以下管道将所有已删除的项目(来自所有spider)存储到一个items.jl文件中,每行包含一个以JSON格式序列化的项目:

注意JsonWriterPipeline的目的只是介绍如何编写项目管道。如果您确实要将所有已删除的项目存储到JSON文件中,则应使用Feed导出。

import json
 
class JsonWriterPipeline(object):
 
    def open_spider(self, spider):
        self.file = open('items.jl', 'w')
 
    def close_spider(self, spider):
        self.file.close()
 
    def process_item(self, item, spider):
        line = json.dumps(dict(item)) + "\n"
        self.file.write(line)
        return item

(3) 将项目写入数据库(网易新闻应用)

在这个例子中,我们将使用pymongo将项目写入MongoDB。MongoDB地址和数据库名称在Scrapy设置中指定; MongoDB集合以item类命名。

from scrapy.exceptions import DropItem
import pymongo


class NewsproPipeline:
    def process_item(self, item, spider):
        print("item:::", item)
        if not item["content"]:
            raise DropItem(f"{item['title']}content为空")

        return item


class MongoPipeline(object):
    mongo_db = "news"
    collection_name = 'wangyiNews'

    def __init__(self, mongo):
        self.mongo = mongo

    @classmethod
    def from_crawler(cls, crawler):
        # Scrapy会先通过getattr判断我们是否自定义了from_crawler,有则调它来完成实例化

        return cls(crawler.settings.get('MONGO'))

    def open_spider(self, spider):
        self.client = pymongo.MongoClient(host=self.mongo[0], port=self.mongo[1])
        self.db = self.client[self.mongo_db]

    def close_spider(self, spider):
        self.client.close()

    def process_item(self, item, spider):
        self.db[self.collection_name].insert_one(dict(item))
        print(f"news数据库的{self.collection_name}表中插入{item['title']}记录")
        return item

这个例子的要点是展示如何使用from_crawler() 方法以及如何正确地清理资源。

补充知识点(from_crawler):

当您使用 settings 对象获取全局变量时,可以在 Scrapy 配置文件中设置该变量的值,然后通过 settings.get('MY_SETTING') 方法来访问它。这样做很方便,但也有一些限制:

  • 您无法根据特定的 Crawler 实例动态地调整全局变量的值。例如,当您在多个爬虫之间共享同一个 Scrapy 项目时,某些设置可能需要在每个爬虫之间进行微调。
  • 您无法轻松地将其他参数传递给中间件。如果您想基于环境变量、命令行参数或其他外部因素配置中间件,那么使用 settings 对象可能很麻烦。

为了解决这些问题,Scrapy 引入了 from_crawler 方法。这个方法可以让您根据具体的 Crawler 实例创建中间件实例,并动态地向中间件传递其他参数。

具体来说,from_crawler 方法接收一个 crawler 参数,这是当前 Crawler 实例的引用。您可以使用 crawler.settings 访问 Scrapy 的全局配置变量,还可以使用 crawler.signals 注册信号处理程序,以及使用 crawler.enginecrawler.spider 访问其他 Scrapy 子系统和资源。

因此,在 from_crawler 中,您可以使用更多的 Scrapy 资源,并根据需要动态地配置中间件。这使得中间件更加灵活和可扩展,可以适应多种场景和需求。

(4) 重复过滤

一个过滤器,用于查找重复项目,并删除已处理的项目。假设我们的项目具有唯一ID,但我们的蜘蛛会返回具有相同ID的多个项目:

from scrapy.exceptions import DropItem
 
class DuplicatesPipeline(object):
 
    def __init__(self):
        self.ids_seen = set()
 
    def process_item(self, item, spider):
        if item['id'] in self.ids_seen:
            raise DropItem("Duplicate item found: %s" % item)
        else:
            self.ids_seen.add(item['id'])
            return item

3 激活项目管道组件

要激活Item Pipeline组件,必须将其类添加到settings.pyITEM_PIPELINES设置中,如下例所示:

ITEM_PIPELINES = {
    'myproject.pipelines.PricePipeline': 300,
    'myproject.pipelines.JsonWriterPipeline': 800,
    "NewsPro.pipelines.MongoPipeline": 500,
}

您在此设置中为类分配的整数值决定了它们运行的顺序:项目从较低值到较高值类进行。习惯上在0-1000范围内定义这些数字。

下载中间件

image-20230411154516741

class MyDownMiddleware(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
github