爬虫之Scrapy架构

news2024/11/23 4:10:39

目录

Scrapy架构介绍

Scrapy下载

Scrapy基本使用

Scrapy目录结构

Scrapy解析数据

settings相关配置

基础配置

增加爬虫的爬取效率

去重规则(布隆过滤器)

持久化方案(数据保存)

request和response传递参数

网页解析下一页继续爬取

爬虫和下载中间件

加代理,cookie,header,集成selenium

scrapy-redis实现分布式爬虫


Scrapy架构介绍

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

引擎(EGINE)

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

调度器(SCHEDULER)

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

下载器(DOWLOADER)

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

爬虫(SPIDERS)

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

项目管道(ITEM PIPLINES)

在items被提取后负责处理它们,主要包括清理、验证、持久化(比如存到数据库)等操作。

下载器中间件(Downloader Middlewares)

位于Scrapy引擎和下载器之间,主要用来处理从EGINE传到DOWLOADER的请求request,已经从DOWNLOADER传到EGINE的响应response。

爬虫中间件(Spider Middlewares)

位于EGINE和SPIDERS之间,主要工作是处理SPIDERS的输入(即responses)和输出(即requests)。

Scrapy下载

安装:

pip install scrapy

如果安装失败,就需要走下面的流程:

1.安装wheel,之后通过wheel文件安装软件,wheel文件官网

pip install wheel
pip install lxml
pip install pyopenssl

3.下载并安装pywin32:Python for Windows Extensions - Browse /pywin32 at SourceForge.net

4.下载twisted的wheel文件:http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted

5.twisted的wheel文件通过pip安装

pip install 下载目录/Twisted-17.9.0-cp36-cp36m-win_amd64.whl

6.安装scrapy

pip install scrapy

Scrapy基本使用

创建项目

通过cmd窗口:在当前目录下创建项目

scrapy startproject 项目名

爬虫创建

在项目目录下,打开cmd窗口:

scrapy genspider 爬虫名 爬虫地址

比如创建爬博客园的爬虫:

scrapy genspider cnblogs cnblogs.com

此时会在项目spiders文件夹下创建cnblogs.py

import scrapy

class CnblogsSpider(scrapy.Spider):
    name = 'cnblogs'  # 爬虫名字
    allowed_domains = ['cnblogs.com']  # 允许爬取的地址
    start_urls = ['http://cnblogs.com/']  # 起始爬取的地址
    def parse(self, response):  # response就是爬取结果
        # 爬取完之后的代码,解析代码放在这里
        print(response.text)

爬虫启动方式一

--nolog代表不要日志输出

scrapy crawl 爬虫名
scrapy crawl 爬虫名 --nolog

比如启动刚刚创建的博客园爬虫:

scrapy crawl cnblogs --nolog

爬虫启动方式二

项目路径下新建py文件:

from scrapy.cmdline import execute
execute(['scrapy','crawl','爬虫名','--nolog'])

启动爬虫运行这个py文件即可。

Scrapy目录结构

项目名
 ├── 项目同名文件夹
 ├    ├── spiders -- 文件夹,专门存放爬虫文件
 ├    ├    ├── 爬虫1.py -- 其中一个爬虫,重点写代码的地方
 ├    ├    └── 爬虫2.py -- 其中一个爬虫,重点写代码的地方
 ├    ├── items.py -- 类比djagno的models,表模型--->类
 ├    ├── middlewares.py -- 爬虫中间件和下载中间件都在里面
 ├    ├── pipelines.py -- 管道,做持久化需要在这写代码
 ├    └── settings.py -- 配置文件
 └── scrapy.cfg -- 上线配置,开发阶段不用

Scrapy解析数据

创建爬虫时会自动在spiders下创建对应的爬虫名py文件,里面一个类,类中有一个解析数据用的方法parse(self, response):

class CnblogsSpider(scrapy.Spider):
    name = 'cnblogs'  # 爬虫名字
    allowed_domains = ['cnblogs.com']  # 允许爬取的地址
    start_urls = ['http://cnblogs.com/']  # 起始爬取的地址

    def parse(self, response):  # response就是爬取结果
        # 爬取完之后的代码,解析代码放在这里
        print(type(response))

response对象的方法:

方法作用
response.css('css选择器')根据css选择器选择标签
response.css('css选择器::text')获取标签文本内容
response.css('css选择器::attr(class)')获取标签class属性
response.xpath('xpath')根据xpath选择标签
response.xpath('xpath/text()')获取标签文本内容
response.xpath('xpath/@class')获取标签class属性
取个数
response.css().extract_first()根据css选择器选择标签,取一个
response.css().extract()根据css选择器选择标签,取全部

举例:获取博客园首页博客标题

class CnblogsSpider(scrapy.Spider):
    name = 'cnblogs'  # 爬虫名字
    allowed_domains = ['cnblogs.com']  # 允许爬取的地址
    start_urls = ['http://cnblogs.com/']  # 起始爬取的地址

    def parse(self, response):  
        print(response.css('.post-item-title::text').extract())

settings相关配置

基础配置

# 是否遵循爬虫协议
ROBOTSTXT_OBEY = False
# 输出日志级别
LOG_LEVEL='ERROR'
# 客户端类型
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36'
# 默认请求头
DEFAULT_REQUEST_HEADERS = {
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
    'Accept-Language': 'en',
}
# 爬虫中间件
SPIDER_MIDDLEWARES = {
    '项目名.middlewares.项目名SpiderMiddleware': 543,
}
# 下载中间件
DOWNLOADER_MIDDLEWARES = {
    '项目名.middlewares.项目名DownloaderMiddleware': 543,
}
# 持久化配置
ITEM_PIPELINES = {
    '项目名.pipelines.项目名Pipeline': 300,
}

增加爬虫的爬取效率

1.增加并发:

默认scrapy开启的并发线程为32个,可以适当进行增加。在settings配置文件中修改:并发设置成了为100

CONCURRENT_REQUESTS = 100

2.降低日志级别:

在运行scrapy时,会有大量日志信息的输出,为了减少CPU的使用率。可以设置log输出信息为INFO或者ERROR即可。在配置文件中编写:

LOG_LEVEL = 'ERROR'

3.禁止cookie:

如果不是真的需要cookie,则在scrapy爬取数据时可以禁止cookie从而减少CPU的使用率,提升爬取效率。在配置文件中编写:

COOKIES_ENABLED = False

4 禁止重试:

对失败的HTTP进行重新请求(重试)会减慢爬取速度,因此可以禁止重试。在配置文件中编写:

RETRY_ENABLED = False

5.减少下载超时:

如果对一个非常慢的链接进行爬取,减少下载超时可以能让卡住的链接快速被放弃,从而提升效率。在配置文件中进行编写:

DOWNLOAD_TIMEOUT = 10  # 超时时间为10s

去重规则(布隆过滤器)

scrapy中 实现了去重,爬过的网址不会再爬。

原理:每次根据爬取的地址对象request生成一个指纹(唯一),判断是否在集合中,如果在集合中,就不爬取了,如果不在,就爬取并且把生成的指纹(唯一)放到集合中。

源码剖析

在scrapy自己的配置文件中配置了一个过滤器:

DUPEFILTER_CLASS = 'scrapy.dupefilters.RFPDupeFilter'

RFPDupeFilter类中有一个方法request_seen:集合去重

def request_seen(self, request: Request) -> bool:
    # 生成指纹
    fp = self.request_fingerprint(request)
    # self.fingerprints是一个集合
    # 判断指纹是否存在
    if fp in self.fingerprints:
        # 返回True就说明重复了,不爬取这个地址了
        return True
    self.fingerprints.add(fp)
    if self.file:
        self.file.write(fp + '\n')
    return False

布隆过滤器

极小内存校验是否重复

01-Python实现布隆过滤器 - 知乎

可以自己写一个类,替换掉内置的去重:

class MyRFPDupeFilter(RFPDupeFilter):
    fingerprints=布隆过滤器

持久化方案(数据保存)

方式一:保存到文件

解析函数parse,需要返回 [{}, {}] 的格式:

class CnblogsSpider(scrapy.Spider):
    name = 'cnblogs'  # 爬虫名字
    allowed_domains = ['cnblogs.com']  # 允许爬取的地址
    start_urls = ['http://cnblogs.com/']  # 起始爬取的地址

    def parse(self, response):  # response就是爬取结果
        # 爬取完之后的代码,解析代码放在这里
        return [{'title': v} for v in response.css('.post-item-title::text').extract()]

然后终端命令:

scrapy crawl 爬虫名 -o 文件名(json,pkl,csv等结尾)

方式二:pipline模式

1.在items.py中写一个类,继承scrapy.Item

2.在类中写属性:

import scrapy

class CnblogsItem(scrapy.Item):
    title = scrapy.Field()

3.在爬虫中导入类,实例化得到对象,把要保存的数据放到对象中

class CnblogsSpider(scrapy.Spider):
    name = 'cnblogs'  # 爬虫名字
    allowed_domains = ['cnblogs.com']  # 允许爬取的地址
    start_urls = ['http://cnblogs.com/']  # 起始爬取的地址

    def parse(self, response):  # response就是爬取结果
        from ..items import CnblogsItem

        item = CnblogsItem()
        for title in response.css('.post-item-title::text').extract():
            item['title'] = title
            # 变成生成器
            yield item

4.修改配置文件,指定pipline,数字表示优先级,越小越大

ITEM_PIPELINES = {
    '项目名.pipelines.CrawlCnblogsPipeline': 300,
}

5.在pipelines.py中写一个pipline,和配置文件中的名字对应

class CrawlCnblogsPipeline:
    # 数据初始化,打开文件,打开数据库链接
    def open_spider(self, spider):
        # spider: 即爬虫对象
        # 打开文件
        self.f = open('cnblogs.txt', 'w', encoding='utf-8')

    # 真正存储的地方,一定不要忘了return item,交给后续的pipline继续使用
    def process_item(self, item, spider):
        # 存储
        self.f.write('标题:' + item['title'] + '\n')
        return item

    # 销毁资源,关闭文件,关闭数据库链接
    def close_spider(self, spider):
        # spider: 即爬虫对象
        # 关闭文件
        self.f.close()

request和response传递参数

传递参数需要使用Request方法。

from scrapy.http import Request
Request(url=爬虫, callback=调用哪个爬虫函数, meta=参数)

举例:博客园爬取首页标题和文章详情

爬虫代码:

import scrapy
from scrapy.http import Request

class CnblogsSpider(scrapy.Spider):
    name = 'cnblogs'  # 爬虫名字
    allowed_domains = ['cnblogs.com']  # 允许爬取的地址
    start_urls = ['http://cnblogs.com/']  # 起始爬取的地址

    def parse(self, response):  # response就是爬取结果
        from ..items import CnblogsItem

        for title in response.css('.post-item-title'):
            # 要写在里面
            item = CnblogsItem()
            # 标题
            item['title'] = title.css('::text').extract_first()
            # 文章详情地址
            url = title.css('::attr(href)').extract_first()
            # 爬取文章详情地址,把item对象传过去修改
            yield Request(url=url, callback=self.parser_detail, meta={'item': item})

    def parser_detail(self, response):
        item = response.meta.get('item')
        # 文章内容
        content = response.css('#post_detail').extract_first()
        item['content'] = content
        print('一篇文章完成')
        yield item

pipelines.py:(记得添加到配置文件中)

class CrawlCnblogsPipeline:
    # 数据初始化,打开文件,打开数据库链接
    def open_spider(self, spider):
        # spider: 即爬虫对象
        # 打开文件
        self.f = open('cnblogs.txt', 'w', encoding='utf-8')

    # 真正存储的地方,一定不要忘了return item,交给后续的pipline继续使用
    def process_item(self, item, spider):
        # 存储
        self.f.write('标题:' + item['title'] + '\n')
        self.f.write('内容:' + item['content'] + '\n')
        return item

    # 销毁资源,关闭文件,关闭数据库链接
    def close_spider(self, spider):
        # spider: 即爬虫对象
        # 关闭文件
        self.f.close()

items.py:

import scrapy

class CnblogsItem(scrapy.Item):
    title = scrapy.Field()  # 标题
    content = scrapy.Field()  # 文章内容

网页解析下一页继续爬取

import scrapy
from scrapy.http import Request

class CnblogsSpider(scrapy.Spider):
    name = 'cnblogs'  # 爬虫名字
    allowed_domains = ['cnblogs.com']  # 允许爬取的地址
    start_urls = ['http://cnblogs.com/']  # 起始爬取的地址

    def parse(self, response):  # response就是爬取结果
        from ..items import CnblogsItem

        item = CnblogsItem()
        for title in response.css('.post-item-title::text').extract():
            item['title'] = title
            yield item
        # 下一页的标签的href属性获取
        next_url = response.css('div.pager>a:last-child::attr(href)').extract_first()
        next_url = 'https://www.cnblogs.com' + next_url
        print('准备爬取 ' + next_url)
        # 把下一页的爬取继续交给当前self.parse方法
        yield Request(url=next_url, callback=self.parse)

爬虫和下载中间件

配置文件中:

# 爬虫中间件
SPIDER_MIDDLEWARES = {
    '项目名.middlewares.项目名SpiderMiddleware': 543,
}
# 下载中间件
DOWNLOADER_MIDDLEWARES = {
    '项目名.middlewares.项目名DownloaderMiddleware': 543,
}

用的比较多的是下载中间件,里面的两个方法:

class DownloaderMiddleware:
    # 请求来的时候
    def process_request(self, request, spider):
        # - return None: 继续执行下一个中间件的process_request
        # - return a Response object :直接返回给engin,去解析
        # - return a Request object :给engin,再次被放到调度器中
        # - raise IgnoreRequest: 执行 process_exception()方法
        return None
    # 响应走的时候
    def process_response(self, request, response, spider):
        # - return a Response :继续走下一个中间件的process_response,给engin,进爬虫解析
        # - return a Request :给engin,进入调度器,等待下一次爬取
        # - raise IgnoreRequest:抛异常
        return response

加代理,cookie,header,集成selenium

代理、cookie、header都是在下载中间件中添加

代理

def process_request(self, request, spider):
        print('下载中间件:',request)
        # 代理
        request.meta['proxy'] = 'http://221.6.215.202:9091'
        return None

cookie

def process_request(self, request, spider):
        print('下载中间件:',request)
        # cookie
        request.cookies= {}
        # 或者
        request.cookies['name']='value'
        return None

请求头:

def process_request(self, request, spider):
        print('下载中间件:',request)
        # 请求头
        request.headers['Auth']='asdfasdfasdfasdf'
        request.headers['USER-AGENT']='ssss'
        return None

随机生成user-agent

pip install fake_useragent
from fake_useragent import UserAgent

ua = UserAgent()
print(ua.ie)  # 随机打印ie浏览器任意版本
print(ua.firefox)  # 随机打印firefox浏览器任意版本
print(ua.chrome)  # 随机打印chrome浏览器任意版本
print(ua.random)  # 随机打印任意厂家的浏览器

集成selenium

在爬虫类中集成:

import scrapy
from selenium import webdriver

class CnblogsSpider(scrapy.Spider):
    name = 'cnblogs'
    allowed_domains = ['cnblogs.com'] 
    start_urls = ['http://cnblogs.com/'] 
    # 添加属性
    driver = webdriver.Chrome()
    def parse(self, response): 
        print(response)
    # 关闭触发
    def close(self, reason):
        self.driver.close()

下载中间件中:

def process_request(self, request, spider):
    """spider就是爬虫类对象"""
    from scrapy.http import HtmlResponse
    spider.driver.get(url=request.url)
    response = HtmlResponse(url=request.url, body=spider.driver.page_source.encode('utf-8'), request=request)
    return response

scrapy-redis实现分布式爬虫

第一步:安装scrapy-redis

pip install scrapy-redis

第二步:改造爬虫类

from scrapy_redis.spiders import RedisSpider
class CnblogSpider(RedisSpider):
    name = 'cnblog_redis'
    allowed_domains = ['cnblogs.com']
    # 写一个key:redis列表的key,起始爬取的地址
    redis_key = 'myspider:start_urls'

第三步:配置文件配置

# 分布式爬虫配置
# 去重规则使用redis
REDIS_HOST = 'localhost'  # 主机名
REDIS_PORT = 6379  # 端口
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 持久化:文件,mysql,redis
ITEM_PIPELINES = {
    'scrapy_redis.pipelines.RedisPipeline': 400,
}

第四步:在多台机器上启动scrapy项目

第五步:把起始爬取的地址放到redis的列表中

lpush myspider:start_urls http://www.cnblogs.com/

 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java安全之深入了解SQL注入

深入了解Java中的SQL注入 本文以代码实例复现了Java中JDBC及Mybatis框架采用预编译和非预编译时可能存在SQL注入的几种情况,并给予修复建议。 JDBC 首先看第一段代码,使用了远古时期的JDBC并且并没有使用预编译。这种简单的字符串拼接就存在SQL注入 …

信息化带来的制造业生产管理系统究竟有哪些作用呢?

制造业是一个现代国家的经济基础,决定着着国家的兴衰存亡。长期以来人们和国家都对制造业给予高度重视,无论是资金投入还是管理的手段和方法的提高,制造业在生产行业中的优势越来越明显。尤其是随着科学技术的快速发展,制造业的现…

uniapp easycom

easycom 是 uniapp 的一种组件自动引入的规则,使用这种规则可以使满足规则的组件无需注册直接使用。 接下来我们来看一眼效果 这里可以看到我并没有进行组件注册而是直接使用了组件,这样的效果就是通过 easycom 的自定义规则来实现的。 来看一眼我的自…

庐山真面目之——LWIP初探

目录 LWIP简介 网络层协议分层模型介绍 LWIP源代码结构 lwip源码文件说明 lwip的contrib包文件说明 以太网接入MCU方案 LWIP结构框图 LWIP简介 lwIP 是 Light Weight(轻型)IP 协议,有无操作系统的支持都可以运行。lwIP 实现的重点 是在保…

Android Studio compose的简单使用与案例实现

Compose是Android团队与JetBrain大力推动的新一代UI框架,它能够简化安卓界面的开发,让本来繁琐的xml文件写法变为简便的kt文件写法。 其声明式 UI、更简单的自定义、实时且带交互的预览功能更是让安卓开发锦上添花 android compose框架的使用一.前置知识…

vue+elementUI 使用腾讯地图

效果如下 引入地图qqmap 刚开始我是直接用 npm install qqmap&#xff0c;但是好像只有v1版本的&#xff0c;我需要用v2版本的&#xff0c;所以直接使用script标签加载API服务。 文件&#xff1a;/public/index.html <script charset"utf-8" src"https:…

2023最新SSM计算机毕业设计选题大全(附源码+LW)之java双笙映画ou5oj

毕业设计也不需要做多高端的程序&#xff0c;毕业设计对于大多数同学来说&#xff0c;为什么感觉到难&#xff0c;最重要的一个原因&#xff0c;那就是理论课到实践课的转变&#xff0c;很多人一下不适应&#xff0c;本能开始拒绝&#xff0c;如果是一个考试&#xff0c;大家都…

spring boot基于Java的电影院售票与管理系统毕业设计源码011449

电影院售票与管理系统的设计与实现 摘 要 信息化社会内需要与之针对性的信息获取途径&#xff0c;但是途径的扩展基本上为人们所努力的方向&#xff0c;由于站在的角度存在偏差&#xff0c;人们经常能够获得不同类型信息&#xff0c;这也是技术最为难以攻克的课题。针对电影院售…

SpringBoot —— 整合RabbitMQ常见问题及解决方案

前言 企业中最常用的消息中间件既不是RocketMQ&#xff0c;也不是Kafka&#xff0c;而是RabbitMQ。 RocketMQ很强大&#xff0c;但主要是阿里推广自己的云产品而开源出来的一款消息队列&#xff0c;其实中小企业用RocketMQ的没有想象中那么多。 至于Kafka&#xff0c;主要还是…

常见的推荐算法原理介绍

常见的推荐算法原理介绍&#xff0c;随着互联网的发展短视频运营越来越精准化&#xff0c;我们身边常见的抖音、火山小视频等软件让你刷的停不下来&#xff0c;这些软件会根据你的浏览行为推荐你感兴趣的相关内容&#xff0c;这就用到了很多推荐算法在里面。 在淘宝购物&#…

Linux 负载均衡介绍之LVS工作模式-DR直接路由模式

Linux 负载均衡介绍之LVS工作模式-DR直接路由模式 图示&#xff1a; 工作原理&#xff1a; ①.客户端将请求发往前端的负载均衡器&#xff0c;请求报文源地址是CIP&#xff0c;目标地址为VIP。 ②.负载均衡器收到报文后&#xff0c;发现请求的是在规则里面存在的地址&#x…

[Java反序列化]—Shiro反序列化(二)

0x01 这篇利用CC链来进行RCE 利用分析 在shiro-web 中加上CC依赖 <dependency><groupId>commons-collections</groupId><artifactId>commons-collections</artifactId><version>3.2.1</version><scope>compile</scope>…

ZKP方案衍变及对比

1. 引言 2019年是ZKP方案创新井喷的一年。 2019年10月&#xff0c;Chiesa在#zk0x04上的分享 State of the SNARG-scape - Alessandro Chiesa (UC Berkeley, StarkWare, Zcash)&#xff0c;有&#xff1a; 根据reference string的类型&#xff0c;可将zk-SNARKs分类为&#…

1.集群环境搭建

1.集群信息概览 2.集群环境搭建 2.1第一台服务器 修改静态ipvim /etc/sysconfig/network-scripts/ifcfg-ens33修改主机名echo first-node /etc/hostname修改主机名映射echo 192.168.226.140 first-node >> /etc/hosts echo 192.168.226.141 second-node >> /…

Redis缓存 缓存穿透+缓存雪崩+缓存击穿的原因及其解决方案

Redis缓存 缓存穿透缓存雪崩缓存击穿的原因及其解决方案 文章目录Redis缓存 缓存穿透缓存雪崩缓存击穿的原因及其解决方案一、缓存穿透是什么&#xff1f;解决方案&#xff1a;二、缓存雪崩是什么&#xff1f;解决方案三、缓存击穿是什么&#xff1f;解决方案一、缓存穿透是什么…

【保姆级·创建对象】如何通过factory-method创建对象

这个步骤在createBeanInstance()方法中有使用&#xff0c;我们先来看下这个方法中都干了些啥(&#xff61;&#xff65;ω&#xff65;&#xff61;)&#xff89; 首先&#xff0c;方法开头确认了beanClass是否被加载&#xff08;因为只有被加载叻的对象才是可以实例化的&#…

深入浅出MySQL事务和锁定语句

https://dev.mysql.com/doc/refman/8.0/en/sql-transactional-statements.html 13.3事务和锁定语句 13.3.1启动事务、提交和回滚语句 开启事务 begin START TRANSACTION提交事务 COMMIT回滚事务 ROLLBACK查询自动提交 show SESSION VARIABLES where variable_name "…

深入浅出InnoDB Locking

https://dev.mysql.com/doc/refman/8.0/en/innodb-locking.html 本节讨论的所有锁都是在 InnoDB 引擎下 MySQL 实现行锁定主要使用共享锁和排他锁。也就是常说的读写锁。 A shared (S) lock permits the transaction that holds the lock to read a row. An exclusive (X) l…

若依多租户集成浅析(基于数据源隔离)

背景 这边有个做 saas 化应用的需求&#xff0c;要求做到数据源级别隔离&#xff0c;选了 RuoyiCRM: 基于若依Vue平台搭建的多租户独立数据库CRM系统&#xff0c; 项目不断迭代中。欢迎提BUG交流~ (gitee.com) 这个项目做分析 先放一下码云上作者画的图&#xff0c;后面我把整…

股票量化怎样分析股票数据精准选股?

在日常的股票量化交易过程中&#xff0c;通常有不少的交易者会借助股票数据接口来分析股票数据&#xff0c;并且经过一番股票量化分析之后&#xff0c;做到精准选股也是很有可能的事情。那么&#xff0c;普通投资者进行股票量化怎样分析股票数据选好股呢&#xff1f; 首先来了…