Python编程异步爬虫实战案例

aiohttp异步爬取实战

  1. 案例介绍

链接为https://spa5.scrape.center,页面如下图所示:

1

这是一个图书网站,整个网站包含数千本图书信息,网站数据是JavaScript渲染而得的,数据可以通过Ajax接口获取,并且接口没有设置反爬措施和加密参数。

完成目标:

  • 使用aiohttp爬取全站的图书数据;
  • 将数据通过异步的方式保存到MongoDB中。
  1. 准备工作

    实现MonogDB异步存储,离不开异步实现的MongoDB存储卡motor,其安装命令为:

    pip3 install motor
    
  2. 页面分析

    这个页面加载方式都是Ajax,分析如下信息:

    • 列表页的Ajax请求接口格式https://spa5.scrape.center/api/book/?limit=18&offset={offset}。其中limit的值为每页包含多少本书;offset的值为每一页的偏移量,计算公式为offset。limit*(page - 1),如第一页的offset值为0,第2页offset的值为18,依此类推。
    • 在列表页Ajax接口返回的数据里,results字段包含当前页里18本图书的信息,其中每本书的数据里包含一个id字段,这个id就是图书本身的ID,可以用来进一步请求详情页。
    • 详情页的Ajax请求接口格式为https://spa5.scrape.center/api/book/{id}。其中的id即为详情页对应图书的ID,可以从列表页Ajax接口的返回结果中获取此内容。
  3. 实现思路

    一个完善的异步爬虫应该能够充分利用资源进行全速爬取,其实现思路是维护一个动态变化的爬取队列,每产生一个新的task,就将其放入爬取队列中,有专门的爬虫消费者从此队列中获取task并执行,能做到在最大并发量的前提下充分利用等待时间进行额外的爬取处理。

    我们将爬取逻辑拆分成两部分,第一部分爬取列表页,第二部分为爬取详情页。因为异步爬虫的关键点在于并发执行,所以可以将爬取拆分为如下两个阶段。

    • 第一阶段是异步爬取所有列表页,我们可以将所有列表页的爬取任务集合在一起,并将其声明为由task组成的列表,进行异步爬取。
    • 第二阶段则是拿到上一步列表页的所有内容并解析,将所有图书的id信息组合为所有详情页的爬取任务集合,并将其声明为task组成的列表,进行异步爬取,同时爬取结果也以异步方式存储到MongoDB里面。
    1. 基本配置

    首先,先配置一些基本的变量并引入一些必需的库,代码如下:

    import asyncio
    import aiohttp
    import logging
    
    logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s:%(message)s')
    
    INDEX_URL = 'https://spa5.scrape.center/api/book/?limit=18&offset={offset}'
    DETAIL_URL = 'https://spa5.scrape.center/api/book/{id}'
    PAGE_SIZE = 18
    PAGE_NUMBER = 100
    CONCURRENCY = 5
    

    这里导入asyncio、aiohttp、logging这3个库,然后定义了logging的基本配置。接着定义了URL、爬取页码数量PAGE_NUMBER、并发量CONCURRENCY等信息。

    1. 爬取列表页面

    第一阶段来爬取列表页,还是和之前一样,先定义一个通用的爬取方法,代码如下:

    semaphore = asyncio.Semaphore(CONCURRENCY)
    session = None
    
    async def scrape_api(url):
        async with semaphore:
            try:
                logging.info('scraping %s', url)
                async with session.get(url) as response:
                    return await response.json()
            except aiohttp.ClientError:
                logging.error('error occurred while scraping %s', url, exc_info=True)
    

    这里声明一个信号量,用来控制最大并发量。接着定义scrape_api方法,接受一个参数url,该方法首先使用async with 语句引入信号量作为上下文,接着调用session的get方法请求这个url,然后返回响应的JSON格式的结果。另外,这里还进行了异常处理,捕获了ClientError,如果出现错误,就会输出异常信息。

    然后,爬取列表页,实现代码如下:

    async def scrape_index(page):
        url = INDEX_URL.format(offset=PAGE_SIZE * (page-1))
        return await scrape_api(url)
    

    这里定义了scrape_index方法用于爬取列表页,它接受一个参数page。随后构造一个列表页的URL,将其传给scrape_api调用之后本身会返回一个协程对象。另外,由于scrape_api的返回结果就是JSON格式,因此这个结果已经是我们想要爬取的信息,不需要再额外解析了。

    接下来定义main方法,将上面的方法串联起来调用,实现如下:

    import json
    
    async def main():
        global session
        session = aiohttp.ClientSession()
        scrape_index_tasks = [asyncio.ensure_future(scrape_index(page)) for page in range(1, PAGE_NUMBER + 1))]
        results = await asyncio.gather(*scrape_index_tasks)
        logging.info('results %s', json.dumps(results, ensure_ascii=False, indent=2))
    
        
    if __name__ == '__main__':
        asyncio.get_event_loop().run_until_complete(main())
    

    这里首先声明了session对象,即最初声明的全局变量。这样的话,就不需要在各个方法里面都传递session了,实现起来比较简单。

    接着定义了scrape_index_tasks,这就是用于爬取列表页的所有task组成的列表。然后调用asyncio的gather方法,并将task列表传入其参数,将结果赋值为results,它是由所有task返回结果组成的列表。

    最后,调用main方法,使用事件循环启动该main方法对应的协程即可。

    运行结果如下:

    2

    1. 爬取详情页

    第二阶段爬取详情页并保存数据。每个详情页对应一本书,每本书都需要一个ID作为唯一标识,而这个ID又正好在results里面,所以需将所有详情页的ID获取出来。

    在main方法里增加results的解析代码,如下:

        ids = []
        for index_data in results:
            if not index_data:continue
            for item in index_data.get('results'):
                ids.append(item.get('id'))
    

    这样ids就是所有书的id了,然后用所有的id构造所有详情页对应的task,进行异步爬取即可。

    这里再定义两个方法,用于爬取详情页和保存数据,实现如下:

    from motor.motor_asyncio import AsyncIOMotorClient
    
    MONGO_CONNECTION_STRING = 'mongodb://localhost:27017'
    MONGO_DB_NAME = 'books'
    MONGO_CONNECTION_NAME = 'books'
    
    client = AsyncIOMotorClient(MONGO_CONNECTION_STRING)
    db = client[MONGO_DB_NAME]
    collection = db[MONGO_CONNECTION_NAME]
    
    async def save_data(data):
        logging.info('saving data %s', data)
        if data:
            return await collection.update_one({
                'id':data.get('id')
            },{
                '$set':data
            },upsert=True)
    
    async def scrape_detail(id):
        url = DETAIL_URL.format(id=id)
        data = await scrape_api(url)
        await save_data(data)
    

    这里定义了scrape_detail方法用于爬取详情页数据,并调用save_data方法保存数据。save_data方法可以将数据保存到MongoDB里面。

    这里我们用到了支持异步的MongoDB存储库motor。motor的连接声明和pymongo是类似的,保存数据的调用方法也基本一致,不过整个都换成了异步方法。

    接着在main方法里增加对scrape_detail方法的调用即可爬取详情页,实现如下:

    scrape_detail_tasks = [asyncio.ensure_future(scrape_detail(id)) for id in ids]
    await asyncio.wait(scrape_detail_tasks)
    await session.close()
    

    运行结果如下:

    /usr/bin/python3 /Users/bruce_liu/PycharmProjects/崔庆才--爬虫/6章异步爬虫/aiohttp示例.py
    2024-03-24 22:19:09,706 - INFO:scraping https://spa5.scrape.center/api/book/?limit=18&offset=0
    2024-03-24 22:19:09,719 - INFO:scraping https://spa5.scrape.center/api/book/?limit=18&offset=18
    2024-03-24 22:19:09,720 - INFO:scraping https://spa5.scrape.center/api/book/?limit=18&offset=36
    ...
    2024-03-24 22:19:13,645 - INFO:results [
      {
        "count": 9040,
        "results": [
          {
            "id": "7952978",
            "name": "Wonder",
            "authors": [
              "R. J. Palacio"
            ],
            "cover": "https://cdn.scrape.center/book/s27252687.jpg",
            "score": "8.8"
          },
          
          }
        ]
      },
      {
        "count": 9040,
        "results": [
          {
            "id": "6814760",
            "name": "一個人暖呼呼",
            "authors": [
              "高木直子"
            ],
            "cover": "https://cdn.scrape.center/book/s32265782.jpg",
            "score": "8.3"
          },
          {
            "id": "6813394",
            "name": "曼珠沙华·彼岸花",
            "authors": [
              "\n            沧月",
              "鼎剑阁系列·沧月十周年珍藏版"
            ],
            "cover": "https://cdn.scrape.center/book/s6903111.jpg",
            "score": "7.6"
          },
          
          {
            "id": "6802423",
            "name": "哦!爸爸们",
            "authors": [
              "\n                [日]\n            伊坂幸太郎",
              "乐读文库"
            ],
            "cover": "https://cdn.scrape.center/book/s8353972.jpg",
            "score": "7.6"
          },
          {
            "id": "6802393",
            "name": "大漠荒颜·帝都赋",
            "authors": [
              "\n            沧月",
              "鼎剑阁系列·沧月十周年珍藏版"
            ],
            "cover": "https://cdn.scrape.center/book/s6902785.jpg",
            "score": "7.9"
          },
          {
            "id": "6802373",
            "name": "那些忧伤的年轻人",
            "authors": [
              "\n            许知远",
              "理想国",
              "理想国·许知远作品"
            ],
            "cover": "https://cdn.scrape.center/book/s6884382.jpg",
            "score": "7.5"
          },
          
          {
            "id": "6784039",
            "name": "你若安好便是晴天",
            "authors": [
              "\n            白落梅"
            ],
            "cover": "https://cdn.scrape.center/book/s6877731.jpg",
            "score": "5.7"
          }
        ]
      },
      
          {
            "id": "6758677",
            "name": "汉文学史纲要",
            "authors": [
              "鲁迅"
           
          }
        ]
      }
    ]
    .....
    2024-03-24 22:19:13,648 - INFO:scraping https://spa5.scrape.center/api/book/7952978
    2024-03-24 22:19:13,649 - INFO:scraping https://spa5.scrape.center/api/book/7916054
    2024-03-24 22:19:13,650 - INFO:scraping https://spa5.scrape.center/api/book/7698729
    2024-03-24 22:19:13,651 - INFO:scraping https://spa5.scrape.center/api/book/7658805
    56772', 'comments': [{'id': '1151612381', 'content': '嗯。有全套的书。碟。还有英文书。什么时候北京的某个电影院一天放完一遍的话,还是会去看。'}, {'id': '1151222346', 'content': '小时候书和电影是分开看的,这次每读完一本就看一部电影,就跟找不同样。'}, {'id': '1238066995', 'content': '太棒了~总有一天要读英文原版试试~'}, {'id': '1002081826', 'content': '包括那些番外'}, {'id': '1697423417', 'content': '结束   还挺失落'}, {'id': '2247572685', 'content': '哈哈哈小柠檬让我想起了小学时候熬夜读书的热情还有因为熬夜被爸爸大半夜批评的记忆'}, {'id': '2186418302', 'content': '终于没有经受住诱惑,虽更适合青少年,但也是大众读物。'}, {'id': '2140636956', 'content': '还需要犹豫几星?不给五星的人是什么心态?'}, {'id': '2122408930', 'content': '老版读了几遍'}, {'id': '2031971050', 'content': '第一本是从张一洋那里借的,还有一本五三班的坏小子。'}], 'name': '哈利·波特精装全集(套装全7册)', 'authors': ['\n                []\n            J·K·罗琳'], 'translators': ['苏农', '马爱农', '马爱新'], 'publisher': '人民文学出版社', 'tags': ['哈利波特', 'J.K.罗琳', '魔幻', '小说', '奇幻', '英国', '外国文学', 'HarryPotter'], 'url': 'https://book.douban.com/subject/6856772/', 'isbn': '9787020086627', 'cover': 'https://cdn.scrape.center/book/s6951249.jpg', 'page_number': 1687, 'price': '430.00', 'score': '9.4', 'introduction': '', 'catalog': None, 'published_at': '2011-01-20T16:00:00Z', 'updated_at': '2020-03-21T16:54:59.503224Z'}
    2024-03-24 22:19:57,523 - INFO:scraping https://spa5.scrape.center/api/book/6847760
    2024-03-24 22:19:58,009 - INFO:saving data {'id': '6854620', 'comments': [{'id': '905750233', 'content': '太拖沓,浪费时间'}, {'id': '932574876', 'content': '故事太拖沓,其实就是误会+错过,然后误会+错过,循环往复...囧'}, {'id': '676773490', 'content': '受不了女主。。。'}, {'id': '757656144', 'content': '就名字好一点。'}, {'id': '47香,直透人心扉。原来这就是爱。莫失莫忘的青春年华里,这样孤勇的爱,有生之年不会重来。当他的身边站着可堪比肩的校花,当他远在万里重洋之外,这份爱,她还有没有持续下去的希望?《你曾住在我心上(套装共2册)》倾情打造绵延数十年唯美纠结的虐心之作。书签:', 'catalog': '\n        楔子\t1\n        卷一 童年\t2\n        记得当时年纪小\t3\n        不是冤家不聚头\t11\n        韶华不为少年留\t20\n        西出阳关无故人\t30\n        卷二 花季\t40\n        新朋缘来也可庆\t40\n        又到绿杨曾折处\t50\n        黄花时节碧云天\t59\n        年少抛人容易去\t68\n        银汉红墙入望遥\t77\n        卷三 雨季\t88\n        未若柳絮因风起\t88\n        花明柳暗绕天愁\t98\n        不知迷路为花开\t107\n        风波不信菱枝弱\t116\n        桂花吹断月中香\t125\n        自今岐路各西东\t135\n        清声不远行人去\t143\n        一片幽情冷处浓\t153\n        又误心期到下弦\t162\n        卷四 大学\t1\n        春城何处不飞花\t1\n        良辰未必有佳期\t9\n        红楼隔雨相望冷\t19\n        不辞冰雪为卿热\t27\n        纵逢晴景如看雾\t35\n        不语还应彼此知……………………………………………………………………… ……43\n        遥听弦管暗看花\t1\n        行云归北又归南\t9\n        谁言千里自今夕\t17\n        一任南飞又北飞\t25\n        不将颜色托春风\t33\n        劳劳谁是怜君者\t40\n        萤在荒芜月在天\t49\n        卷五 工作\t59\n        归时休放烛花红\t59\n        长教碧玉藏深处\t67\n        无情有恨何人见\t76\n        又见桐花发旧枝\t84\n        别来几度春风换\t93\n        十一年前梦一场\t101\n        人生若只如初见\t109\n        十年一觉扬州梦\t119\n        当时只道是寻常\t127\n        盈盈自此隔银湾\t136\n        持向今朝照别离\t144\n        急雪乍翻香阁絮\t153\n        春云吹散湘帘雨\t161\n        莫向横塘问旧游\t170\n        旧时明月照扬州\t179\n        后记\t           191\n        精彩书评\t           191\n     · · · · · ·    ', 'published_at': '2011-10-20T16:00:00Z', 'updated_at': '2020-03-21T17:29:20.321848Z'}
    2024-03-24 22:19:58,011 - INFO:scraping https://spa5.scrape.center/api/book/6835758
    2024-03-24 22:19:58,546 - INFO:saving data {'id': '6854525', 'comments': [{'id': '958876319', 'content': '塔勒布的书,超5星。需要再多研究其它资料来好好理解,因为它太「实用」了:避免负面黑天鹅、抓住正面黑天鹅的机会是一辈子都需要考虑的事情。'},\n        第三部分\n        极端斯坦的灰天鹅\n        第十四章 从平均斯坦到极端斯坦,再回到平均斯坦\n        在极端斯坦,没有人是安全的。反过来也一样:也没人受到完全失败的威胁。我们现在的环境允许小人物在成功的希望前等待时机—活着就有希望。\n        第十五章 钟形曲线—智力大骗局\n        由于钟形曲线的不确定性计量方法忽视了跳跃性或者不连续变化发生的可能性及影响,因此无法适用于极端斯应对办法是在思维中避免从众。但在避免上当之外,这种态度受制于一种行为方式,不是思维方式,而是如何将知识转化为行动,并从中找出那些有价值的知识。\n        第十九章 一半对一半—如何与黑天鹅打成平手\n        当我受到正面黑天鹅事件的影响时,我会非常冒险,这时失败只有很小的影响;当我有可能受到负面黑天鹅事件的袭击时,我会非常保守。\n        后记1 从白天鹅到黑天鹅\n        后记2 强大与脆弱—更深层次的哲学与经验的反思\n     · · · · · ·    ', 'published_at': '2011-09-30T16:00:00Z', 'updated_at': '2020-03-21T17:40:30.176461Z'}
    2024-03-24 22:19:58,548 - INFO:scraping https://spa5.scrape.center/api/book/6834237
    ....
    Process finished with exit code 0
    
    

    3

    至此,我们就使用aiohttp完成了对图书网站的异步爬取。

    以上涉及的相关库的操作可以在[小蜜蜂AI网站][https://zglg.work]获取更多体验。

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

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

相关文章

深度学习 - PyTorch基本流程 (代码)

直接上代码 import torch import matplotlib.pyplot as plt from torch import nn# 创建data print("**** Create Data ****") weight 0.3 bias 0.9 X torch.arange(0,1,0.01).unsqueeze(dim 1) y weight * X bias print(f"Number of X samples: {len(…

ZYNQ学习之Ubuntu环境下的Shell与APT下载工具

基本都是摘抄正点原子的文章&#xff1a;<领航者 ZYNQ 之嵌入式Linux 开发指南 V3.2.pdf&#xff0c;因初次学习&#xff0c;仅作学习摘录之用&#xff0c;有不懂之处后续会继续更新~ 一、Ubuntu Shell操作 简单的说Shell 就是敲命令。国内把 Linux 下通过命令行输入命令叫…

代码随想录算法训练营第三十二天 | 122.买卖股票的最佳时机II ,55. 跳跃游戏 , 45.跳跃游戏II

贪心&#xff1a;只要把每一个上升区间都吃到手&#xff0c;就能一直赚 class Solution { public:int maxProfit(vector<int>& prices) {int res 0;for(int i 1;i< prices.size();i){int diff prices[i] - prices[i-1];if(prices[i] > prices[i-1]){res d…

WSL使用

WSL使用 WSL安装和使用 Termianl和Ubuntu的安装 打开Hype-V虚拟化配置Microsoft Store中搜索Window Terminal并安装Microsoft Store中搜索Ubuntu, 选择安装Ubuntu 22.04.3 LTS版本打开Window Terminal选择Ubuntu标签栏, 进入命令行 中文输入法安装 查看是否安装了fcitx框架…

【官方】操作指南,附代码!银河麒麟服务器迁移运维管理平台V2.1中间件及高可用服务部署(4)

1.RocketMQ集群模式 主机配置示例&#xff1a; IP 角色 架构模式 对应配置文件 1.1.1.1 nameserver1 master broker-n0.conf 2.2.2.2 nameserver2 salve1 broker-n1.conf 3.3.3.3 nameserver3 salve2 broker-n2.conf 1.1.安装rocketmq 在服务器上安装rocket…

第14篇:2线-4线译码器

Q&#xff1a;有编码器那对应的就会有译码器&#xff0c;本期我们来设计实现2线-4线二进制译码器 。 A&#xff1a;基本原理&#xff1a;译码器是编码器的逆过程&#xff0c;其功能是将具有特定含义的二进制码转换为对应的输出信号。2线-4线二进制译码器有2个输入共4种不同的组…

java目标和(力扣Leetcode106)

目标和 力扣原题 问题描述 给定一个正整数数组 nums 和一个整数 target&#xff0c;向数组中的每个整数前添加 ‘’ 或 ‘-’&#xff0c;然后串联起所有整数&#xff0c;可以构造一个表达式。返回可以通过上述方法构造的、运算结果等于 target 的不同表达式的数目。 示例 …

【MySQL】11. 复合查询(重点)

4. 子查询 子查询是指嵌入在其他sql语句中的select语句&#xff0c;也叫嵌套查询 4.1 单行子查询 返回一行记录的子查询 显示SMITH同一部门的员工 mysql> select * from emp where deptno (select deptno from emp where ename SMITH); -----------------------------…

小目标检测篇 | YOLOv8改进之添加BiFormer注意力机制

前言:Hello大家好,我是小哥谈。BiFormer是一种具有双层路由的动态稀疏注意力机制,它通过查询自适应的方式关注一小部分相关标记,从而提供了更灵活的计算分配和内容感知。它在多个计算机视觉任务中表现出了良好的性能和高计算效率。BiFormer注意力机制比较适合处理小尺度目标…

聚类算法之高斯混合模型聚类 (Gaussian Mixture Model, GMM)

注意&#xff1a;本文引用自专业人工智能社区Venus AI 更多AI知识请参考原站 &#xff08;[www.aideeplearning.cn]&#xff09; 高斯混合模型&#xff08;GMM&#xff09;是统计模型中的一颗璀璨之星&#xff0c;它为数据提供了一种复杂而又强大的表示方法。在机器学习的许多…

大数据基础:Linux基础详解

课程介绍 本课程主要通过对linux基础课程的详细讲解&#xff0c;让大家熟练虚拟机的安装使用&#xff0c;Linux系统的安装配置&#xff0c;学习掌握linux系统常用命令的使用&#xff0c;常用的软件安装方法&#xff0c;制作快照&#xff0c;克隆&#xff0c;完成免密登录&…

linux系统--------------mysql数据库管理

目录 一、SQL语句 1.1SQL语言分类 1.2查看数据库信息 1.3登录到你想登录的库 1.4查看数据库中的表信息 1.5显示数据表的结构&#xff08;字段&#xff09; 1.5.1数据表的结构 1.5.2常用的数据类型: 二、关系型数据库的四种语言 2.1DDL&#xff1a;数据定义语言&am…

跨域与Spring Boot中CORS的应用

摘要&#xff1a;前后端独立开发期间&#xff0c;交互主要通过接口文档&#xff0c;前端Mock数据&#xff0c;后端使用Postman都不会发现跨域问题。当联调时前端尝试调用后端接口&#xff0c;这往往就需要需要处理的跨域问题…… 下面总结下跨域问题产生的前因后果以及如何通过…

day03_mysql_课后练习 - 参考答案

文章目录 day03_mysql_课后练习mysql练习题第1题第2题第3题第4题第5题 day03_mysql_课后练习 mysql练习题 第1题 案例&#xff1a; 1、创建一个数据库&#xff1a;day03_test01_school 2、创建如下表格 表1 Department表的定义 字段名字段描述数据类型主键外键非空唯一D…

Java学习笔记 | JavaSE基础语法 | 04 | 数组

文章目录 0.前言1.数组2.数组声明2.1 数组定义2.2 数组初始化1.静态初始化2.动态初始化3.区别4.数组的默认初始化值&#xff1a; 2.3 数组名 3.访问数组3.1 索引3.2 访问数组3.3 length属性 4.数组常见问题5.数组内存分析5.1 内存分配5.2 数组内存分配 6.数组的练习练习1&#…

用Springboot(java程序)访问Salesforce RestAPI

本文讲一下&#xff0c;如何从0构建一个Springboot的应用程序&#xff0c;并且和Salesforce系统集成&#xff0c;取得Salesforce里面的数据。 一、先在Salesforce上构建一个ConnectApp。 有了这个&#xff0c;SF才允许你和它集成。手顺如下&#xff1a; 保存后&#xff0c;…

华为ensp中vrrp虚拟路由器冗余协议 原理及配置命令

CSDN 成就一亿技术人&#xff01; 作者主页&#xff1a;点击&#xff01; ENSP专栏&#xff1a;点击&#xff01; CSDN 成就一亿技术人&#xff01; ————前言————— VRRP&#xff08;Virtual Router Redundancy Protocol&#xff0c;虚拟路由器冗余协议&#xff0…

使用 CSS 预处理器的优缺点

使用CSS预处理器在前端开发中已经成为一种流行的趋势&#xff0c;它们提供了一种更灵活、更高效的方式来编写和管理样式表。然而&#xff0c;就像任何工具一样&#xff0c;CSS预处理器也有其优点和缺点。本文将深入探讨使用CSS预处理器的优缺点&#xff0c;并讨论如何在项目中明…

Luminar Neo:让每一张照片都散发独特魅力 mac/win版

Luminar Neo是一款引领摄影艺术新纪元的智能影像处理软件。它融合了先进的算法和人工智能技术&#xff0c;为摄影师提供了前所未有的创作自由度和影像处理能力。 Luminar Neo软件获取 作为一款强大的后期处理工具&#xff0c;Luminar Neo不仅具备丰富的调整选项和滤镜效果&…

MES管理系统生产调度模块的工作原理是什么

在现代制造业中&#xff0c;MES管理系统发挥着举足轻重的作用&#xff0c;其中的生产调度模块更是整个生产流程的核心。它集成了自动排产和手动排产的功能&#xff0c;能够精确安排每个工单在各个工序的具体生产线体、计划开始时间和计划结束时间&#xff0c;从而确保生产的高效…