超详细的Scrapy框架的基本使用教程

Scrapy的介绍

scrapy的工作流程(重点!!!)

如下图所示:

爬虫

  1. 负责向引擎提供要爬取网页的URL,引擎会把这个URL封装成request对象并传递给调度器,
  2. 把引擎传递过来的response对象进行数据解析。数据解析有两种结果:
    1. 解析出具体的数据,那么通过引擎把这个具体的数据传递给管道,然后存入文件、数据库等
    2. 解析出一个新的URL,那么过程同作用1

管道:负责把引擎传递过来的数据进行存储,存入文件、数据库等。管道可以有多个,比如MySQL的管道,某个文件的管道,mango的管道等。

调度器:可以把调度器的存储结构看成一个优先队列,不同的request对象可能优先级不一样,按优先级的高低进行调度

  1. 把引擎传递过来的request对象放入队列进行排队,调度器可以实现去重的效果,即对两个相同的URL,只存储一个
  2. 向引擎提供队头的request对象(即优先级高的request对象),引擎把这个request对象传递给下载器进行请求

下载器:把引擎传递过来的request对象发送给服务器请求数据,并把服务器返回的内容封装成response对象, 然后把这个response对象传递给引擎,引擎再把这个response对象传递给爬虫进行数据解析

引擎:从上面的流程中可以看到,引擎负责控制数据流在所有组件流动,并在相应动作时触发事件,相当于爬虫的大脑

注意,在实际的代码编写过程中,我们只需要关注爬虫和管道部分的代码编写,而引擎、调度器、下载器都不需要我们实现   

scrapy的安装

在终端输入以下命令(包有点大,建议切换成国内的镜像源,如清华源等,据说安装可能会有很多问题,但是可能我人品比较好,没遇到,嘿嘿。如果有问题的百度一下吧)

pip install scrapy 

scrapy的基本使用

以爬取4399游戏网站的游戏名称为例,scrapy有以下几个步骤:

1、创建scrapy项目

首先在某个文件夹下打开终端,输入以下命令创建scrapy项目

scrapy startproject 项目名称

2、创建一个爬虫程序 

首先进入项目文件夹下,然后输入命令: 

scrapy genspider 爬虫程序的名称  要爬取网站的域名

3、编写爬虫程序

在game_4399.py文件中编写爬虫代码,代码如下

import scrapy


class Game4399Spider(scrapy.Spider):
    name = "game_4399"  # 爬虫程序的名称
    allowed_domains = ["4399.com"]  # 允许爬取的域名
    # 默认情况下是:https://4399.com
    # 但是我们不从首页开始爬取,所以改一下URL
    start_urls = ["https://4399.com/flash/"]  # 一开始爬取的URL

    def parse(self, response):  # 该方法用于对response对象进行数据解析
        # print(response)  # <200 http://www.4399.com/flash/>
        # print(response.text)  # 打印页面源代码
        # response.xpath()  # 通过xpath解析数据
        # response.css()  # 通过css解析数据

        # 获取4399小游戏的游戏名称
        # txt = response.xpath('//ul[@class="n-game cf"]/li/a/b/text()')
        # txt 列表中的每一项是一个Selector:
        # <Selector query='//ul[@class="n-game cf"]/li/a/b/text()' data='逃离克莱蒙特城堡'>]
        # 要通过extract()方法拿到data中的内容
        # print(txt)

        # txt = response.xpath('//ul[@class="n-game cf"]/li/a/b/text()').extract()
        # print(txt)  # 此时列表中的元素才是游戏的名字

        # 也可以先拿到每个li,然后再提取名字
        lis = response.xpath('//ul[@class="n-game cf"]/li')
        for li in lis:
            # name = li.xpath('./a/b/text()').extract()
            # # name 是一个列表
            # print(name)  # ['王城霸业']

            # 一般我们都会这么写:li.xpath('./a/b/text()').extract()[0]
            # 但是这样如果列表为空就会报错,所以换另一种写法
            # extract_first方法取列表中的第一个,如果列表为空,返回None
            name = li.xpath('./a/b/text()').extract_first()
            print(name)  # 王城霸业

            category = li.xpath('./em/a/text()').extract_first()  # 游戏类别
            date = li.xpath('./em/text()').extract_first()  # 日期
            print(category, date)

            # 通过yield向管道传输数据
            dic = {
                'name': name,
                'category': category,
                'date': date
            }
            # 可以认为这里是把数据返回给了管道pipeline,
            # 但是实际上是先给引擎,然后引擎再给管道,只是这个过程不用我们关心,scrapy会自动完成
            # 这里的数据会在管道程序中接收到
            yield dic

4、运行scrapy爬虫程序

在终端输入命令,就可以看到爬虫程序运行结果。

scrapy crawl 爬虫程序名称

5、总结scrapy的基本使用

关于第6、7步,在下面的scrapy管道中会说到。

Scrapy中的管道

基本介绍

我们接着看上述4399中创建的scrapy项目,管道的默认情况如下:

管道程序默认是不生效的,需要在settings文件进行配置,如下:

pipelines.py文件中的代码

# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html


# useful for handling different item types with a single interface
from itemadapter import ItemAdapter


# 默认情况下管道是不开启的,需要在settings文件中进行设置
class GamePipeline:
    def process_item(self, item, spider):
        """
        接收爬虫通过引擎传递过来的数据
        :param item: 具体的数据内容
        :param spider: 对应传递数据的爬虫程序
        :return:
        """
        print(item)  # {'name': '武器升级', 'category': '休闲类', 'date': '2024-01-06'}
        print(spider)  # <Game4399Spider 'game_4399' at 0x22867dafc70>
        return item  # 把数据传递给下一个管道

 settings文件中关于管道的代码

ITEM_PIPELINES = {
    # 管道程序的所在路径:优先级
    # 300 表示管道的优先级,数字越小优先级越高
    # 优先级高的管道会比优先级低的管道先拿到数据
   "game.pipelines.GamePipeline": 300,
}

运行命令:scrapy crawl game_4399,运行结果如下(只截取了一部分):

上述只有一个管道,如果有多个管道,比如我们自定义一个管道,代码如下:

from itemadapter import ItemAdapter


# 默认情况下管道是不开启的,需要在settings文件中进行设置
class GamePipeline:
    def process_item(self, item, spider):
        """
        接收爬虫通过引擎传递过来的数据
        :param item: 具体的数据内容
        :param spider: 对应传递数据的爬虫程序
        :return:
        """
        print(item)  # {'name': '武器升级', 'category': '休闲类', 'date': '2024-01-06'}
        print(spider)  # <Game4399Spider 'game_4399' at 0x22867dafc70>
        return item  # 把数据传递给下一个管道


# 自定义一个管道程序
# 记得在settings文件中配置,否则不生效
class OtherPipeline:
    def process_item(self, item, spider):
        # 比如这里给传递过来的数据添加一个新的字段
        item['new_field'] = 'hello'
        return item

settings文件关于管道的代码

ITEM_PIPELINES = {
    # 管道程序的所在路径:优先级
    # 300 表示管道的优先级,数字越小优先级越高
    # 优先级高的管道会比优先级低的管道先拿到数据
   "game.pipelines.GamePipeline": 300,
   # 优先级比GamePipeline高,可以通过运行结果看出
   "game.pipelines.OtherPipeline": 299
}

 运行结果如下,可见多了一个字段

 scrapy中的数据格式item

在上述的项目中,我们在爬虫程序里解析出来的数据组装成了字典然后使用yield传递给了管道,但是这实际上是不符合scrapy的规范的。在scrapy中,数据用item表示。

还是以上面的4399为例。game_4399.py中的代码如下:

import scrapy

from game.items import GameItem


class Game4399Spider(scrapy.Spider):
    name = "game_4399"  # 爬虫程序的名称
    allowed_domains = ["4399.com"]  # 允许爬取的域名
    # 默认情况下是:https://4399.com
    # 但是我们不从首页开始爬取,所以改一下URL
    start_urls = ["https://4399.com/flash/"]  # 一开始爬取的URL

    def parse(self, response):  # 该方法用于对response对象进行数据解析
        # 也可以先拿到每个li,然后再提取名字
        lis = response.xpath('//ul[@class="n-game cf"]/li')
        for li in lis:
            name = li.xpath('./a/b/text()').extract_first()
            category = li.xpath('./em/a/text()').extract_first()  # 游戏类别
            date = li.xpath('./em/text()').extract_first()  # 日期

            # 通过yield向管道传输数据
            # dic = {
            #     'name': name,
            #     'category': category,
            #     'date': date
            # }
            # 可以认为这里是把数据返回给了管道pipeline,
            # 但是实际上是先给引擎,然后引擎再给管道,只是这个过程不用我们关心,scrapy会自动完成
            # 如果只有一个数据,可以通过return返回,但是在scrapy中没人使用return,都是用yield的
            # 另外,在scrapy中,只希望yield返回三个类型之一的数据:item、request、None
            # 这里可以yield dic 返回字典,但是实际上并不希望这么干
            # 而且如果换成了 yield [] 返回雷暴,就会报错:
            # ERROR: Spider must return request, item, or None, got 'list'
            # yield dic

            # 我们现在不返回字典,而是返回真正推荐我们返回的格式之一:item
            # 先导入GameItem类:from game.items import GameItem
            # 然后创建它的实例,使用起来和字典类似
            # 区别就是GameItem类里没有定义的字段,就不能使用,比如不能item['某个没有定义的字段']
            item = GameItem()
            # item['xxx'] 里的xxx要在类GameItem里定义有,否则就会报错
            item['name'] = name
            item['category'] = category
            item['date'] = date
            yield item

 items.py中的代码

import scrapy


class GameItem(scrapy.Item):
    # 这里定义了三个字段,分别表示游戏的名称、类别和日期
    name = scrapy.Field()
    category = scrapy.Field()
    date = scrapy.Field()


# 可以定义其他字段来表示不同的信息
class OtherItem(scrapy.Item):
    pass

管道程序pipelines.py中的代码

from itemadapter import ItemAdapter


# 默认情况下管道是不开启的,需要在settings文件中进行设置
class GamePipeline:
    def process_item(self, item, spider):
        """
        接收爬虫通过引擎传递过来的数据
        :param item: 具体的数据内容
        :param spider: 对应传递数据的爬虫程序
        :return:
        """
        print(item)  # {'name': '武器升级', 'category': '休闲类', 'date': '2024-01-06'}
        print(spider)  # <Game4399Spider 'game_4399' at 0x22867dafc70>
        return item  # 把数据传递给下一个管道


# 自定义一个管道程序
# 记得在settings文件中配置,否则不生效
class OtherPipeline:
    def process_item(self, item, spider):
        # 比如这里给传递过来的数据添加一个新的字段
        # 此时传递过来的item不再是字典,而是GameItem类对象
        # 由于GameItem类里没有定义字段new_field,所以不能使用,否则报错
        # item['new_field'] = 'hello'
        return item

数据存储

我们一直说通过管道存储数据,但是上面的例子一直未涉及,现在来讲解怎么把数据进行持久化存储。在上面的例子中,我们已经在管道程序里拿到了引擎传递过来的数据,现在就可以把这些数据存储起来。

首先,先来说一说数据存储的几种方案:

  • 存入.csv文件,这类数据一般用于数据分析
  • 存入MySQL数据库
  • 存入mangodb数据库
  • 写入文件,如图片、视频、文字等数据

下面以存入csv文件为例(存入MySQL的也列举了一个模板,mango数据库的操作和MySQL基本一致,但是由于我对mango不熟悉,所以不写了,需要的可以百度一下)

settings文件中的代码

ITEM_PIPELINES = {
    # 管道程序的所在路径:优先级
    # 300 表示管道的优先级,数字越小优先级越高
    # 优先级高的管道会比优先级低的管道先拿到数据
   "game.pipelines.GamePipeline": 300,
   "game.pipelines.GameMySqlPipeline": 300,
   # 优先级比GamePipeline高,可以通过运行结果看出
   "game.pipelines.OtherPipeline": 299
}
# 配置MySQL
MYSQL = {
    "host": "localhost",  # 主机
    "port": 3306,  # 端口
    "user": "xxx",  # 用户名
    "password": "xxx",  # 密码
    "database": "xxx"  # 数据库名称
}

pipelines.py的代码:

import pymysql
# 导入MySQL配置
from game.settings import MYSQL

# 默认情况下管道是不开启的,需要在settings文件中进行设置
class GamePipeline:
    def __init__(self):
        self.f = None

    def open_spider(self, spider):
        """"""
        print('爬虫开始了...')
        self.f = open('./game_data.csv', mode='a', encoding='utf-8')

    def process_item(self, item, spider):
        """
        接收爬虫通过引擎传递过来的数据
        :param item: 具体的数据内容
        :param spider: 对应传递数据的爬虫程序
        :return:
        """
        print('爬虫进行中...')
        # 把数据写入文件
        # 写入模式是mode='a',表示在文件里追加,不能是w,否则文件原本的内容会被覆盖
        # 以下的这种方式效率不高,因为每传递一次数据,就要进行一次文件的打开的关闭操作
        # with open('./game_data.csv', mode='a', encoding='utf-8') as f:
        #     f.write(f'{item["category"]}, {item["name"]}, {item["date"]}\n')

        # 采取另一种方式
        # scrapy 提供了两个方法open_spider()、close_spider(),分别会在爬虫开始时和爬虫结束后调用
        self.f.write(f'{item["category"]}, {item["name"]}, {item["date"]}\n')
        return item  # 把数据传递给下一个管道

    def close_spider(self, spider):
        print('爬虫结束了...')
        if self.f:
            self.f.close()


# 默认情况下管道是不开启的,需要在settings文件中进行设置
class GameMySqlPipeline:
    def __init__(self):
        self.conn = None

    def open_spider(self, spider):
        print('爬虫开始了...')
        # self.conn = pymysql.connect(  # 创建数据库连接
        #     host='localhost',  # 主机
        #     port=3306,  # 端口
        #     user='xxx',  # 用户名
        #     password='xxx',  # 密码
        #     database='xxx'  # 数据库名称
        # )

        # 可以向上面那样写,但是更好的办法是写在settings文件中
        # 然后从settings文件中导入:from game.settings import MYSQL
        self.conn = pymysql.connect(  # 创建数据库连接
            host=MYSQL['host'],  # 主机
            port=MYSQL['port'],  # 端口
            user=MYSQL['user'],  # 用户名
            password=MYSQL['password'],  # 密码
            database=MYSQL['database']  # 数据库名称
        )

    def process_item(self, item, spider):
        """
        接收爬虫通过引擎传递过来的数据
        :param item: 具体的数据内容
        :param spider: 对应传递数据的爬虫程序
        :return:
        """
        print('爬虫进行中...')
        # 把数据写入mysql数据库
        # 下载数据库包并导入:pip install pymysql
        # 确定自己的数据库中准备好了相应的数据表
        try:
            cursor = self.conn.cursor()
            # 插入的sql语句
            # (%s, %s, %s) 对应相应的字段类型,%s表示字符串类型
            insert_sql = 'insert into 数据库表名 (字段1, 字段2, 字段3, ...) values (%s, %s, %s)'
            # execute()的第二个参数是一个元祖,里面的每一个元素对应sql语句中的字段值
            cursor.execute(insert_sql, (item['category'], item['name'], item['date']))
            self.conn.commit()  # 提交事务
        except:
            self.conn.rollback()  # 出现异常,执行回滚操作
        finally:
            if cursor:
                cursor.close()

        return item  # 把数据传递给下一个管道

    def close_spider(self, spider):
        print('爬虫结束了...')
        if self.conn:
            self.conn.close()


# 自定义一个管道程序
# 记得在settings文件中配置,否则不生效
class OtherPipeline:
    def process_item(self, item, spider):
        # 比如这里给传递过来的数据添加一个新的字段
        # 此时传递过来的item不再是字典,而是GameItem类对象
        # 由于GameItem类里没有定义字段new_field,所以不能使用,否则报错
        # item['new_field'] = 'hello'
        return item

结尾

关于scrapy的基本使用,好像还有中间件这个内容,但是我看的那个视频教程这个部分好像漏掉了,反正就是没有笔记,需要了解的自行百度一下吧,或者看官方文档也行。

本人也是初学者,所以文章中有什么错误的地方,欢迎指正。

有一个对应的综合练习:scrapy框架爬取图片

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/431580.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【送书活动1】基于React低代码平台开发:构建高效、灵活的应用新范式

【送书活动1】基于React低代码平台开发&#xff1a;构建高效、灵活的应用新范式 写在最前面一、React与低代码平台的结合优势二、基于React的低代码平台开发挑战三、基于React的低代码平台开发实践四、未来展望《低代码平台开发实践&#xff1a;基于React》编辑推荐内容简介作者…

逻辑代数基础(一)(逻辑符号)

目录 三种基本运算 与运算 或运算 非运算 复合运算 与非运算 或非运算 与或非运算 异或运算 同或运算 逻辑代数的基本定律和常用公式 逻辑代数的基本定律 常量-常量的运算 常量-变量的运算 特殊定律 逻辑代数的常用公式 逻辑函数 逻辑函数的定义 逻辑函数的约束条件 逻…

140.乐理基础-音程的转位

上一个内容&#xff1a;139.乐理基础-一四五八度为何用纯?-CSDN博客 上一个内容里练习的答案&#xff1a; 本次内容并不会感觉到有多大用处&#xff0c;但要牢牢掌握。 音程的转位&#xff1a; 改变音的高低顺序 比如c-e&#xff0c;c低、e高 音程转位&#xff1a;也就是变成…

为什么有了HTTP协议,还要有WebSocket协议?

文章目录 使HTTP不断轮询长轮询WebSocket是什么&#xff1f;怎么建立WebSocket连接WebSocket抓包WebSocket的消息格式WebSocket的使用场景总结 平时我们打开网页&#xff0c;比如购物网站某宝。都是点一下列表商品&#xff0c;跳转一下网页就到了商品详情。 从HTTP协议的角度来…

【C语言】编程题专项练习+答案

目录 1.删除有序数组中重复的数 2.用除二取余的方法&#xff0c;把任意一个十进制正数的二进制序列输出&#xff08;不考虑溢出&#xff09; 2.1如果是把任意一个十进制整数的二进制序列输出呢&#xff1f; 3.输出一个六行六列的整形矩阵&#xff0c;并输出其转置矩阵。矩阵…

C++之map

1、map介绍 map是C STL的一个关联容器&#xff0c;它提供一对一的数据处理能力。其中&#xff0c;各个键值对的键和值可以是任意数据类型&#xff0c;包括 C 基本数据类型&#xff08;int、double 等&#xff09;、使用结构体或类自定义的类型。 第一个可以称为关键字(key)&…

盘点国内大厂的10个AI创作工具,看看你都用过哪些?

国内大厂的 AI 创作工具&#xff0c;目前已经非常多了&#xff0c;而且有很多都是大家耳熟能详的。 下面整理了一些&#xff0c;包含 AI 绘画、AI 视频、AI 智能体、AI 大模型等多个方向的国内大厂 AI 创作工具。 发现有几款 AI 工具&#xff0c;还真的非常好用。看看这些 AI…

H5小游戏,斗地主

H5小游戏源码、JS开发网页小游戏开源源码大合集。无需运行环境,解压后浏览器直接打开。有需要的,私信本人,发演示地址,可以后再订阅,发源码,含60+小游戏源码。如五子棋、象棋、植物大战僵尸、开心消消乐、扑鱼达人、飞机大战等等 <!DOCTYPE html> <html> <…

算法46:动态规划专练(力扣198: 打家劫舍 力扣740:删除并获取点数)

打家劫舍问题&#xff1a; 你是一个专业的小偷&#xff0c;计划偷窃沿街的房屋。每间房内都藏有一定的现金&#xff0c;影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统&#xff0c;如果两间相邻的房屋在同一晚上被小偷闯入&#xff0c;系统会自动报警。 给定…

每日五道java面试题之mysql数据库篇(五)

目录&#xff1a; 第一题. 联合索引是什么&#xff1f;为什么需要注意联合索引中的顺序&#xff1f;第二题. 什么是数据库事务&#xff1f;第三题. 事物的四大特性(ACID)介绍一下?第四题. 按照锁的粒度分数据库锁有哪些&#xff1f;锁机制与InnoDB锁算法?第五题. 从锁的类别上…

Linux 防火墙 操作命令【实用】

防火墙操作&#xff1a; 描述命令查看防火墙状态systemctl status firewalld、firewall-cmd --state暂时关闭防火墙systemctl stop firewalld永久关闭防火墙systemctl disable firewalld开启防火墙systemctl start firewalld开放指定端口firewall-cmd --zonepublic --add-port…

Docker常见命令使用

Docker命令是使用Docker的基础。这里记录下Docker日常运维过程中经常使用到的一些命令&#xff0c;更全面的命令还请参考Docker官网。 docker用法概述 Docker命令可以通过CLI工具实现与服务器的交互。Docker命令的语法如下&#xff1a; docker [DOCKER-COMMAND] [OPTIONS] […

基于openKylin与RISC-V的MindSpore AI项目实践

项目目标&#xff1a; 在openKylin系统上安装和配置MindSpore框架。开发一个简单的图像分类模型&#xff0c;并在RISC-V平台上进行训练和推理。根据RISC-V的特性&#xff0c;对MindSpore框架进行必要的优化。 目录 项目目标&#xff1a; 训练模型 编写训练代码&#xff0c;设…

Mysql之存储过程与函数

Mysql之存储过程与函数 存储过程概述分类创建存储过程一般的语法格式完整的语法格式案例一案例二 调用存储过程调用语法格式 创建存储函数存储过程与函数的查看&#xff0c;修改和删除 存储过程概述 官网解释是&#xff1a;存储过程的英文是 Stored Procedure 。它的思想很简单…

微信小程序开发:循环定时删除阿里云oss上传的文件

上文有说到我们开发了定时删除阿里云oss的功能&#xff0c;但是一次只能删除10条。 本文我们做到一次删除全部过期的文件。 实现&#xff1a;使用while循环&#xff0c;在循环里获取是否还有已过期的&#xff0c;没有就break掉&#xff0c;有就走删除逻辑。 开始代码部分&am…

鸿蒙系统适配的流程

鸿蒙系统适配的流程通常涉及以下关键步骤&#xff0c;以下是鸿蒙系统适配的一般流程&#xff0c;具体流程可能会根据项目的具体需求和开发团队的情况进行调整和优化。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎交流合作。 1. 准备工作&#…

常见限流算法及其实现

一、背景 在分布式系统中&#xff0c;随着业务量的增长&#xff0c;如何保护核心资源、防止系统过载、保证系统的稳定性成为了一个重要的问题。限流算法作为一种有效的流量控制手段&#xff0c;被广泛应用于各类系统中。本文将详细介绍四种常见的限流算法、两种常用的限流器工…

贝叶斯优化双向门控循环单元BO-BIGRU时序预测的matlab实现【源代码】

贝叶斯优化双向门控循环单元简介&#xff1a; 贝叶斯优化双向门控循环单元&#xff08;BO-BIGRU&#xff09;是一种结合了贝叶斯优化和双向门控循环单元&#xff08;BIGRU&#xff09;的神经网络模型。BIGRU是一种改进的循环神经网络&#xff08;RNN&#xff09;&#xff0c;它…

ArcGIS学习(十三)多源数据下的城市街道功能评估

ArcGIS学习(十三)多源数据下的城市街道功能评估 本任务带来的内容是多元数据下的城市街道功能评估。本任务包括两个关卡: 城市街道空间中观解读 城市街道功能详细评价 首先,我们来看看本任务的分析思路。 1.城市街道空间中观解读 下面我们正式进入第一关的内容一- 城市…

学习Python类型和对象,看这篇文章足矣!

类型与对象 一点基础理论: 对象代表现实世界中像轿车、狗、自行车这些事物。对象具有数据和行为两个主要特征。 在面向对象编程中&#xff0c;我们把数据当作属性&#xff0c;把行为当作方法。即&#xff1a; 数据 → 属性 和 行为 → 方法 类型是创造单个对象实例的蓝本。…