Python爬虫之Scrapy框架系列(24)——分布式爬虫scrapy_redis完整实战【XXTop250完整爬取】

目录:

  • 每篇前言:
  • 1.使用分布式爬取豆瓣电影信息
    • (1)settings.py文件中的配置:
    • (2)spider文件的更改:
    • (3)items.py文件(两个项目一致!):
    • (4)pipelines.py文件:
    • 分布式实现效果:
      • ①直接运行项目,发现在等待:
      • ②再开一个终端,做如下操作:
    • 总结:
    • 效果:
  • 2.解决一些小问题:
    • 2.1 解决爬空问题:(在两个项目中都进行以下操作!)
      • ①使用拓展程序(这个文件就是为了解决爬空而生的):
      • ②在settings.py文件中设置这个拓展程序:
  • 3. 关于分布式(Scrapy_redis)的总结:

每篇前言:

  • 🏆🏆作者介绍:【孤寒者】—CSDN全栈领域优质创作者、HDZ核心组成员、华为云享专家Python全栈领域博主、CSDN原力计划作者

  • 🔥🔥本文已收录于Scrapy框架从入门到实战专栏:《Scrapy框架从入门到实战》
  • 🔥🔥热门专栏推荐:《Python全栈系列教程》、《爬虫从入门到精通系列教程》、《爬虫进阶+实战系列教程》、《Scrapy框架从入门到实战》、《Flask框架从入门到实战》、《Django框架从入门到实战》、《Tornado框架从入门到实战》、《前端系列教程》。
  • 📝​📝本专栏面向广大程序猿,为的是大家都做到Python全栈技术从入门到精通,穿插有很多实战优化点。
  • 🎉🎉订阅专栏后可私聊进一千多人Python全栈交流群(手把手教学,问题解答); 进群可领取Python全栈教程视频 + 多得数不过来的计算机书籍:基础、Web、爬虫、数据分析、可视化、机器学习、深度学习、人工智能、算法、面试题等。
  • 🚀🚀加入我一起学习进步,一个人可以走的很快,一群人才能走的更远!

在这里插入图片描述

1.使用分布式爬取豆瓣电影信息

  • (此处做了限制,只爬取四页电影数据共计100条,可去除限制爬取全部10页250条数据!)

**项目源码:
链接:https://pan.baidu.com/s/13akXDxNbtBeRTUzUB_2SNQ
提取码:bcuy
**

目标:在本机上使用两个完全一模一样的豆瓣项目,去使用分布式下载豆瓣电影top250电影信息!
在这里插入图片描述
其实,我们要进行修改的就只有settings.py文件以及爬虫文件,别的文件都不需要进行改动。

(1)settings.py文件中的配置:

  • (两个项目都做此配置)
#设置scrapy-redis
#1.启用调度将请求存储进redis
from scrapy_redis.scheduler import Scheduler
SCHEDULER="scrapy_redis.scheduler.Scheduler"

#2.确保所有spider通过redis共享相同的重复过滤
from scrapy_redis.dupefilter import RFPDupeFilter
DUPEFILTER_CLASS="scrapy_redis.dupefilter.RFPDupeFilter"

#3.指定连接到Redis时要使用的主机和端口     目的是连接上redis数据库
REDIS_HOST="localhost"
REDIS_PORT=6379

# 不清理redis队列,允许暂停/恢复抓取    (可选)    允许暂停,redis数据不丢失     可以实现断点续爬!!!
SCHEDULER_PERSIST = True


# 第二步:开启将数据存储进redis公共区域的管道!
# Configure item pipelines
# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
    'scrapy_redis.pipelines.RedisPipeline': 100,    # 开启数据交给redis公共区域的管道
    'douban.pipelines.DoubanPipeline': 200,         # 存储本地txt文件的管道
}

(2)spider文件的更改:

  • (两个项目略有不同!)

总共四步:

  1. 导入RedisSpider类:(既然要使用它,肯定首先要导入!)
    from scrapy_redis.spiders import RedisSpider

  2. 继承使用RedisSpider类:(既然要使用它,就要继承去使用这个类)
    class DbSpider(RedisSpider):

  3. 既然将请求都放进了Redis里,那爬虫文件中就不再需要start_urls这个初始请求了:
    #start_urls = ['https://movie.douban.com/top250']

  4. 设置一个键,寻找起始的url:(这个键就会在redis中寻找初始的url,所以后面我们只需往redis里放请求即可!)
    redis_key="db:start_urls"

完整版爬虫文件:
第一个项目下的爬虫文件:

# -*- coding: utf-8 -*-
import scrapy
import re

from ..items import DoubanItem

from scrapy_redis.spiders import RedisSpider        # 1.导出RedisSpider类

class DbSpider(RedisSpider):                        # 2.使用RedisSpider类
    name = 'db'
    allowed_domains = ['movie.douban.com']
    # start_urls = ['https://movie.douban.com/top250']   # 3.将请求放进redis里

    redis_key = "db:start_urls"                          # 4.设置一个键,寻找起始的url

    page_num = 0  # 类变量

    def parse(self, response):  # 解析和提取数据
        print('第一个项目:', response.url)
        print('第一个项目:', response.url)
        print('第一个项目:', response.url)
        # 获取电影信息数据
        # films_name=response.xpath('//div[@class="info"]/div/a/span[1]/text()').extract()
        node_list = response.xpath('//div[@class="info"]')  # 25个
        if node_list:  # 此判断的作用:在爬取到10页之后,就获取不到了!判断每次是否获取到数据,如果没有则返回空(即停止了)
            for node in node_list:
                # 电影名字
                film_name = node.xpath('./div/a/span[1]/text()').extract()[0]
                # 主演   拿标签内容,再正则表达式匹配
                con_star_name = node.xpath('./div/p[1]/text()').extract()[0]
                if "主" in con_star_name:
                    star_name = re.findall("主演?:? ?(.*)", con_star_name)[0]
                else:
                    star_name = "空"
                # 评分
                score = node_list.xpath('./div/div/span[@property="v:average"]/text()').extract()[0]

                # 使用字段名  收集数据
                item = DoubanItem()
                item["film_name"] = film_name
                item["star_name"] = star_name
                item["score"] = score

                # 形式:{"film_name":"肖申克的救赎","star_name":"蒂姆","score":"9.7"}
                detail_url = node.xpath('./div/a/@href').extract()[0]
                yield scrapy.Request(detail_url,callback=self.get_detail,meta={"info":item})

            # 此处几行的代码配合yield里传的参数meta={"num":self.page_num},共同作用实现:
            # 两个项目的共享变量page_num能正确变化,不导致冲突!!!
            if response.meta.get("num"):
                self.page_num = response.meta["num"]
            self.page_num += 1
            if self.page_num == 4:
                return
            print("page_num:", self.page_num)
            page_url = "https://movie.douban.com/top250?start={}&filter=".format(self.page_num * 25)
            yield scrapy.Request(page_url, callback=self.parse, meta={"num": self.page_num})
            # 注意:各个模块的请求都会交给引擎,然后经过引擎的一系列操作;但是,切记:引擎最后要把得到的数据再来给到
            # spider爬虫文件让它解析并获取到真正想要的数据(callback=self.parse)这样就可以再给到自身。
        else:
            return

    def get_detail(self, response):
        item = DoubanItem()
        # 获取电影简介信息
        # 1.meta会跟随response一块返回  2.可以通过response.meta接收   3.通过updata可以添加到新的item对象
        info = response.meta["info"]  # 接收电影的基本信息
        item.update(info)  # 把电影基本信息的字段加进去

        # 将电影简介信息加入相应的字段里
        description = response.xpath('//div[@id="link-report-intra"]//span[@property="v:summary"]/text()').extract()[0]\
            .strip()
        item['description'] = description
        yield item

第二个项目下的爬虫文件:

# -*- coding: utf-8 -*-
import scrapy
import re

from ..items import DoubanItem

from scrapy_redis.spiders import RedisSpider        # 1.导出RedisSpider类

class DbSpider(RedisSpider):                        # 2.使用RedisSpider类
    name = 'db'
    allowed_domains = ['movie.douban.com']
    # start_urls = ['https://movie.douban.com/top250']   # 3.将请求放进redis里

    redis_key = "db:start_urls"                          # 4.设置一个键,寻找起始的url

    page_num = 0  # 类变量

    def parse(self, response):  # 解析和提取数据
        print('第二个项目:', response.url)
        print('第二个项目:', response.url)
        print('第二个项目:', response.url)
        # 获取电影信息数据
        # films_name=response.xpath('//div[@class="info"]/div/a/span[1]/text()').extract()
        node_list = response.xpath('//div[@class="info"]')  # 25个
        if node_list:  # 此判断的作用:在爬取到10页之后,就获取不到了!判断每次是否获取到数据,如果没有则返回空(即停止了)
            for node in node_list:
                # 电影名字
                film_name = node.xpath('./div/a/span[1]/text()').extract()[0]
                # 主演   拿标签内容,再正则表达式匹配
                con_star_name = node.xpath('./div/p[1]/text()').extract()[0]
                if "主" in con_star_name:
                    star_name = re.findall("主演?:? ?(.*)", con_star_name)[0]
                else:
                    star_name = "空"
                # 评分
                score = node_list.xpath('./div/div/span[@property="v:average"]/text()').extract()[0]

                # 使用字段名  收集数据
                item = DoubanItem()
                item["film_name"] = film_name
                item["star_name"] = star_name
                item["score"] = score

                # 形式:{"film_name":"肖申克的救赎","star_name":"蒂姆","score":"9.7"}
                detail_url = node.xpath('./div/a/@href').extract()[0]
                yield scrapy.Request(detail_url,callback=self.get_detail,meta={"info":item})

            # 此处几行的代码配合57行yield里传的参数meta={"num":self.page_num},共同作用实现:
            # 两个项目的共享变量page_num能正确变化,不导致冲突!!!
            if response.meta.get("num"):
                self.page_num = response.meta["num"]
            self.page_num += 1
            if self.page_num == 4:
                return
            print("page_num:", self.page_num)
            page_url = "https://movie.douban.com/top250?start={}&filter=".format(self.page_num * 25)
            yield scrapy.Request(page_url, callback=self.parse, meta={"num": self.page_num})
            # 注意:各个模块的请求都会交给引擎,然后经过引擎的一系列操作;但是,切记:引擎最后要把得到的数据再来给到
            # spider爬虫文件让它解析并获取到真正想要的数据(callback=self.parse)这样就可以再给到自身。
        else:
            return

    def get_detail(self, response):
        item = DoubanItem()
        # 获取电影简介信息
        # 1.meta会跟随response一块返回  2.可以通过response.meta接收   3.通过updata可以添加到新的item对象
        info = response.meta["info"]  # 接收电影的基本信息
        item.update(info)  # 把电影基本信息的字段加进去

        # 将电影简介信息加入相应的字段里
        description = response.xpath('//div[@id="link-report-intra"]//span[@property="v:summary"]/text()').extract()[0]\
            .strip()
        item['description'] = description
        yield item

(3)items.py文件(两个项目一致!):

# -*- coding: utf-8 -*-

# Define here the models for your scraped items
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.html

import scrapy


class DoubanItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    #需要定义字段名  就像数据库那样,有字段名,才能插入数据(即存储数据)
    # films_name=scrapy.Field()   #定义字段名
    film_name=scrapy.Field()
    star_name=scrapy.Field()
    score=scrapy.Field()
    description = scrapy.Field()

(4)pipelines.py文件:

  • (两个项目存储本地txt文件名可改为不一样的,便于观察!)
# -*- coding: utf-8 -*-

# 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

import json
import pymysql

class DoubanPipeline(object):
    def open_spider(self,spider):   #爬虫文件开启,此方法就开启
        self.f=open("films.txt","w",encoding="utf-8")       #打开文件

    def process_item(self, item, spider):        #会来25次,就会调用25次这个方法  如果按常规来写,文件就会被操作25次打开关闭
        #为了能写进text  json.dumps将dic数据转换为str
        json_str=json.dumps(dict(item),ensure_ascii=False)+"\n"
        self.f.write(json_str)                              #爬虫文件开启时,文件就已经打开,在此直接写入数据即可!
        return item

    def close_spider(self,spider):  #爬虫文件关闭,此方法就开启
        self.f.close()                                      #爬虫文件关闭时,引擎已经将全部数据交给管道,关闭文件

分布式实现效果:

①直接运行项目,发现在等待:

分别在两个终端中开启两个scrapy项目:(注意:之前要开启redis数据库)

在这里插入图片描述
会发现,这俩项目都在等待,不会继续执行。这是因为没有给redis这个公共区域一个初始的请求,这俩项目都在周而复始的向redis要初始url,结果一直要不到!

在两个项目的settings.py文件中设置两个的日志不显示在控制台,而是存储到.log文件中。为了便于观察:

LOG_FILE="db.log"
LOG_ENABLED=False

②再开一个终端,做如下操作:

lpush db:start_urls https://movie.douban.com/top250

在这里插入图片描述
会发现我们的两个项目都会成功的跑起来:(而且总共获取数据刚好是四页的电影信息,共计100条)

在这里插入图片描述在这里插入图片描述

总结:

会发现,第一个项目运行会显示使用了parse函数,这也就说明在redis这个公共区域的start_urls请求被第一个项目抢到了,然后就会运行这个项目,
但是,在这个项目的爬虫文件代码执行的过程中会在25次循环中给引擎发送共25次url请求,引擎得到这25个request请求后会将它们都交给scheduler调度器,再通过调度器交给redis数据库这个公共区域。
然后,两个项目的scheduler调度器就会一起抢这公共区域里的请求,并在各自的爬虫程序运行过程中提交给redis别的请求,两个项目继续抢,直到爬空。这就实现了咱爬虫的分布式爬取数据!!!

在这里插入图片描述

效果:

  • (因为没有解决爬空,所以项目运行完并不会自己关闭,而且,哪怕项目运行完了,也会一直无限的爬空,就导致两个项目爬取的保存本地的数据不够100条,所以,在两个项目运行完在爬空的时候,强制关闭两个项目,就会发现数据是完整的了!!!)

两个项目下的获取存储到本地的txt文本内的电影信息共计刚好我们所要爬取的所有目标数据:四页共100部电影的信息。

2.解决一些小问题:

2.1 解决爬空问题:(在两个项目中都进行以下操作!)

①使用拓展程序(这个文件就是为了解决爬空而生的):

两个项目进行防爬空设置后,如果数据爬取完成,在指定时间内就会自动停止爬虫!!!
(文件名:extensions.py,放到settings.py同级目录里)

加入此拓展之后完整的项目代码:
链接:https://pan.baidu.com/s/1Naie1HsWCxS-1ntorT3_RQ
提取码:e30p

# -*- coding: utf-8 -*-

# Define here the models for your scraped Extensions
import logging

from scrapy import signals
from scrapy.exceptions import NotConfigured

logging = logging.getLogger(__name__)


class RedisSpiderSmartIdleClosedExensions(object):

    def __init__(self, idle_number, crawler):
        self.crawler = crawler
        self.idle_number = idle_number
        self.idle_list = []
        self.idle_count = 0

    @classmethod
    def from_crawler(cls, crawler):
        # first check if the extension should be enabled and raise

        # NotConfigured otherwise

        if not crawler.settings.getbool('MYEXT_ENABLED'):
            raise NotConfigured

        if not 'redis_key' in crawler.spidercls.__dict__.keys():
            raise NotConfigured('Only supports RedisSpider')

        # get the number of items from settings

        idle_number = crawler.settings.getint('IDLE_NUMBER', 360)

        # instantiate the extension object

        ext = cls(idle_number, crawler)

        # connect the extension object to signals

        crawler.signals.connect(ext.spider_opened, signal=signals.spider_opened)

        crawler.signals.connect(ext.spider_closed, signal=signals.spider_closed)

        crawler.signals.connect(ext.spider_idle, signal=signals.spider_idle)

        return ext

    def spider_opened(self, spider):
        spider.logger.info("opened spider {}, Allow waiting time:{} second".format(spider.name, self.idle_number * 5))

    def spider_closed(self, spider):
        spider.logger.info(
            "closed spider {}, Waiting time exceeded {} second".format(spider.name, self.idle_number * 5))

    def spider_idle(self, spider):
        # 程序启动的时候会调用这个方法一次,之后每隔5秒再请求一次
        # 当持续半个小时都没有spider.redis_key,就关闭爬虫
        # 判断是否存在 redis_key
        if not spider.server.exists(spider.redis_key):
            self.idle_count += 1
        else:
            self.idle_count = 0

        if self.idle_count > self.idle_number:
            # 执行关闭爬虫操作
            self.crawler.engine.close_spider(spider, 'Waiting time exceeded')

②在settings.py文件中设置这个拓展程序:

# Enable or disable extensions                  #扩展程序
# See https://docs.scrapy.org/en/latest/topics/extensions.html
EXTENSIONS = {
   # 'scrapy.extensions.telnet.TelnetConsole': None,
    'film.extensions.RedisSpiderSmartIdleClosedExensions':500,					#开启extensions.py这个拓展程序
}
MYEXT_ENABLED = True      # 开启扩展
IDLE_NUMBER = 3           # 配置空闲持续时间单位为 3个 ,一个时间单位为5s

注意:redis中存储的数据:

  • spidername:items
    list类型,保存爬虫获取到的数据item内容是json字符串。
  • spidername:dupefilter
    set类型,用于爬虫访问的URL去重内容是40个字符的url的hash字符串
  • spidername:start_urls
    list类型,用于接收redisspider启动时的第一个url
  • spidername:requests
    zset类型,用于存放requests等待调度。内容是requests对象的序列化字符串。

3. 关于分布式(Scrapy_redis)的总结:

()分布式爬虫
一.settings里的配置
# 启用调度将请求存储进redis
# 1.必须
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
#2. 必须
# 确保所有spider通过redis共享相同的重复过滤。
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"

# 3.必须
# 指定连接到Redis时要使用的主机和端口。
REDIS_HOST = 'localhost'
REDIS_PORT = 6379

二.spider文件更改

from scrapy_redis.spiders import  RedisSpider #1 导出 RedisSpider

class DbSpider(RedisSpider):  #2使用RedisSpider类

    # start_urls = ['https://movie.douban.com/top250/']  #3将要请求放在  公共区域 redis里面
    redis_key = "db:start_urls"#4  设置一个键  寻找起始url.redis数据库中 写入  start_urls      
lpush  db:start_urls   https://movie.douban.com/top250/


四.解决爬空的问题
1.解决爬空的文件    extensions.py  主要是RedisSpiderSmartIdleClosedExensions
2.设置
MYEXT_ENABLED = True      # 开启扩展
IDLE_NUMBER = 3           # 配置空闲持续时间单位为 3个 ,一个时间单位为5s

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

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

相关文章

c语言(数据在内存中的存储)

1. 整数在内存中的存储 整数的2进制表⽰⽅法有三种,即原码、反码和补码 三种表⽰⽅法均有符号位和数值位两部分,符号位都是⽤0表⽰“正”,⽤1表⽰“负”,⽽数值位最 ⾼位的⼀位是被当做符号位,剩余的都是数值位。 正整…

手写简易操作系统(十二)--实现时钟中断

前情提要 前面我们开启了中断,但是这些中断都对应着一个通用的中断处理函数,而且几乎都是处理器触发的中断,没有我们的外设中断,虽然我们提前预留了这些接口。 现在我们实现一个时钟中断 一、可编程计数器8253 计算机中的时钟…

基于SpringBoot+MyBatis-Plus的图书管理系统

基于SpringBoot的图书管理系统 图书管理系统开发技术功能模块代码结构数据库设计运行截图源码获取 图书管理系统 开发技术 技术:SpringBoot、MyBatis-Plus、MySQL、Beetl、Layui。 框架:基于开源框架Snowy-Layui开发。 工具:IDEA、Navicat等…

2078: [蓝桥杯2023初赛] 01 串的熵

对于一个长度为 n 的 01 串 S x1x2x3...xn. 香农信息熵的定义为: 。 其中 p(0), p(1) 表示在这个 01 串中 0 和 1 出现的占比。 比如,对于S 100 来说,信息熵 H(S ) - 1/3 log2(1/3) - 2/3 log2(2/3) - 2/3 log2(2/3) 1.3083。 对于一个…

QT--信号和槽机制

信号槽 信号槽是 Qt 框架引以为豪的机制之一。所谓信号槽,实际就是观察者模式。当某个事件发生之后,比如,按钮检测到自己被点击了一下,它就会发出一个信号(signal)。这种发出是没有目的的,类似广播。如果有对象对这个…

选择排序算法(Selection Sort)原理及实现

选择排序算法,运行效率不高,但是非常容易理解,算法复杂度为 。 原理: 假设要排序的数组的长度为n,将数组先分为两个部分,一个是有序区域部分,另一个为无序区域部分。初始时有序部分中没有元素…

Python学习:注释和运算符

python 注释 在Python中,注释用于在代码中添加解释、说明或者提醒,但并不会被解释器执行。Python中的注释以#开头,直到行末为止。下面是关于Python注释的详细解释和举例: 单行注释:使用#符号在行的开头添加注释&…

十四届蓝桥杯 冶炼金属(二分 / 公式)

二分代码1&#xff1a; #include<iostream> #include<cstdio> #include<cmath> using namespace std;int get(int a, int b){int l1;r1e91;while(l<r){int mid lr >>1;if(a / mid < b){r mid;}else l mid 1;}return l; } int main() {int n…

为什么技术人员副业赚钱那么难?

公众号&#xff1a;小北技术圈。 34岁老程序员&#xff0c;长期探索副业项目&#xff0c;写过IDEA插件&#xff0c;搞过工具导航&#xff0c;做过出海网站&#xff0c;运营过自媒体。欢迎提前探索35岁程序员的第二赛道。 每周分享干货内容。寻找100个技术人员&#xff0c;聚在…

2000-2021年各省研发强度数据(原始数据+计算结果)(无缺失)

2000-2021年各省研发强度数据&#xff08;原始数据计算结果&#xff09;&#xff08;无缺失&#xff09; 1、时间&#xff1a;2000-2021年 2、指标&#xff1a;RD经费内部支出&#xff08;万元&#xff09;、国内生产总值、研发强度 3、范围&#xff1a;31省 4、来源&#…

人工智能技术应用笔记(九):大道至简!提示词学习入门,看这一篇就够了!

本篇为《人工智能技术应用》专栏的第九篇。希望以学习笔记的形式和大家一起了解和探索人工智能技术的实际应用。 现在关于提示词的武功秘笈已经多如牛毛&#xff0c;但是我相信这个就像练功一样&#xff0c;练到最后总是化繁为简&#xff0c;一招制胜&#xff01; 今天想说的这…

03python注释与输入函数

Python 注释的作用: 注释可用于解释 Python 代码。 注释可用于提高代码的可读性。 在测试代码时,可以使用注释来阻止执行。 注释可以放在一行的末尾,Python 将忽略该行的其余部分: 实例1 print("Hello, World!") #打印输出Hello,World print(9-3) #输出9…

C++_day6:2024/3/18

作业1&#xff1a;编程题&#xff1a; 以下是一个简单的比喻&#xff0c;将多态概念与生活中的实际情况相联系&#xff1a; 比喻&#xff1a;动物园的讲解员和动物表演 想象一下你去了一家动物园&#xff0c;看到了许多不同种类的动物&#xff0c;如狮子、大象、猴子等。现在…

发挥实力,引领游戏行业的未来——武汉灰京文化的成功之路

作为一家游戏发行商&#xff0c;武汉灰京文化的公司实力源于其强大的战略规划和专业团队。自成立以来&#xff0c;公司坚持以用户为中心&#xff0c;不断提升用户体验。通过深入了解玩家需求&#xff0c;武汉灰京文化在游戏运营和推广过程中&#xff0c;精益求精&#xff0c;力…

使用C#的winform控制数据库实例服务的运行状态

一、得到sqlserver的实例名 二、引用对应的程序集和命名空间 using System.ServiceProcess; C#操作服务要用的类 ServiceController 声明类 private ServiceController serviceController new ServiceController("MSSQLSERVER"); 三、判断服务状态 serviceCon…

Vue.js+SpringBoot开发企业项目合同信息系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 合同审批模块2.3 合同签订模块2.4 合同预警模块2.5 数据可视化模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 合同审批表3.2.2 合同签订表3.2.3 合同预警表 四、系统展示五、核心代码5.1 查询合同…

仿懂车帝的二手车交易平台功能介绍

二手车交易平台app是一款功能丰富的二手车交易平台&#xff0c;以下是其主要功能介绍&#xff1a; 二手车信息展示&#xff1a;APP首页展示各类二手车信息&#xff0c;包括车型、品牌、价格等&#xff0c;用户可以轻松浏览并选择自己感兴趣的车辆。搜索与筛选功能&#xff1a;…

AI智能客服系统的费用

实现智能客服所需的费用取决于多个因素&#xff0c;包括项目的规模、所选择的技术和服务提供商、数据的获取和处理方式等。以下是一些可能影响费用的因素&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎交流合作…

微服务day03 -- Docker

1.初识Docker 1.1.什么是Docker 微服务虽然具备各种各样的优势&#xff0c;但服务的拆分通用给部署带来了很大的麻烦。 分布式系统中&#xff0c;依赖的组件非常多&#xff0c;不同组件之间部署时往往会产生一些冲突。 在数百上千台服务中重复部署&#xff0c;环境不一定一致…

Android中Gradle的生命周期详解

前些天发现了一个蛮有意思的人工智能学习网站,8个字形容一下"通俗易懂&#xff0c;风趣幽默"&#xff0c;感觉非常有意思,忍不住分享一下给大家。 &#x1f449;点击跳转到教程 Gradle的生命周期分为三个阶段&#xff1a; 初始化阶段定义阶段(配置阶段)执行阶段 第…