设计模式(二):工厂模式

一,什么是工厂模式

工厂模式(Factory Pattern) 是一种创建型设计模式,它定义了一个用于创建对象的接口,而不需要显式地指定对象所属的具体类。换句话说,工厂模式将对象的实例化过程延迟到子类或其他工厂方法中,通过工厂方法来创建对象而不是直接调用构造函数。

工厂模式的核心思想是将对象创建的细节隐藏在工厂方法中,从而让代码具有更好的扩展性和可维护性

  • 解耦对象的创建和使用:当你想要将对象的创建与其使用分离时,工厂模式很有用。这样可以提高代码的灵活性和可维护性。
  • 处理复杂的创建逻辑:当创建一个对象需要复杂的逻辑时,工厂模式可以将该逻辑封装在工厂方法中,从而简化代码。
  • 创建不同类型的对象:当你需要创建不同类型的对象,并且代码不需要知道这些对象的具体类型时,工厂模式很有用。
  • 支持多态:工厂模式可以通过返回抽象产品类型的对象来支持多态,从而使代码更加通用和灵活。

工厂模式在实际开发中有很多应用场景,下面是一些例子:

  1. 数据库连接工厂:在Web应用程序中,我们可能需要连接到不同类型的数据库(MySQL、PostgreSQL、MongoDB等)。使用工厂模式创建一个工厂类来封装创建数据库连接的逻辑,从而使客户端代码独立于具体的数据库实现。
  2. UI组件工厂:在GUI应用程序中,可以使用工厂模式来创建不同类型的UI组件,如按钮、文本框、下拉列表等。工厂类可以根据用户的输入或配置,返回合适的UI组件实例。
  3. 日志记录工厂:在应用程序中,我们可能需要使用不同的日志记录机制,如控制台输出、文件日志、 Syslog 等。使用工厂模式可以创建一个日志工厂,根据配置返回合适的日志记录器实例。
  4. 支付网关工厂:在电子商务应用程序中,可能需要支持多种支付方式,如信用卡、PayPal、Apple Pay等。使用工厂模式可以创建一个支付网关工厂,根据用户选择的支付方式返回相应的支付网关实例。
  5. 文件读写工厂:在处理文件I/O操作时,可以使用工厂模式创建不同类型的文件读写器,如CSV读写器、XML读写器、PDF读写器等。这样可以将文件格式的处理逻辑封装在工厂类中,使客户端代码更加灵活和可扩展。
  6. 游戏角色工厂:在游戏开发中,可以使用工厂模式来创建不同类型的游戏角色,如战士、法师、弓箭手等。每种角色都有不同的属性和技能,工厂类可以根据需求创建合适的角色实例。
  7. 报告生成器工厂:在企业应用程序中,可能需要生成各种类型的报告,如销售报告、财务报告、库存报告等。使用工厂模式可以创建一个报告生成器工厂,根据报告类型返回合适的报告生成器实例。

工厂模式的分类:

  • 简单工厂模式(Simple Factory Pattern):通过一个工厂类来决定实例化哪个类的对象,通常使用一个静态方法。

  • 工厂方法模式(Factory Method Pattern):将对象的实例化延迟到子类,通过子类重写的工厂方法来创建具体对象。

  • 抽象工厂模式(Abstract Factory Pattern):提供一个接口,用于创建一系列相关或依赖的对象,而无需指定它们的具体类。抽象工厂模式通常用来生产一系列产品,这些产品通常是关联在一起的,比如一整套家具中的桌子和椅子。

为什么会出现这三种工厂模式?

  1. 简单性与灵活性的权衡:
    - 简单工厂模式提供了一种简单易用的创建对象的方式,但当需求变化时,修改工厂类会很困难。
    - 工厂方法模式通过将对象的创建推迟到子类,提供了更大的灵活性,但增加了代码量。

  2. 产品种类的增加:
    - 当产品种类较少时,简单工厂模式可以很好地满足需求。
    - 但当产品种类不断增加时,工厂方法模式可以更好地应对这种变化,通过引入工厂子类来创建不同类型的产品。

  3. 产品之间的依赖关系:
    - 当产品之间存在相互依赖或相关的关系时,简单工厂模式工厂方法模式可能无法很好地处理这种情况。
    - 抽象工厂模式通过提供一个创建相关产品家族的接口,更好地解决了这个问题。

二,python代码

(一)简单工厂模式

class Car:def __init__(self, model):self.model = modeldef drive(self):passclass Sedan(Car):def drive(self):return f"驾驶 {self.model} 轿车"class SUV(Car):def drive(self):return f"驾驶 {self.model} SUV"class CarFactory:@staticmethoddef create_car(car_type, model):if car_type == "sedan":return Sedan(model)elif car_type == "suv":return SUV(model)else:raise ValueError("不支持的车型")# 使用简单工厂
factory = CarFactory()
sedan = factory.create_car("sedan", "Toyota Camry")
suv = factory.create_car("suv", "Honda CR-V")print(sedan.drive())  # 输出: 驾驶 Toyota Camry 轿车
print(suv.drive())    # 输出: 驾驶 Honda CR-V SUV

(二)工厂方法模式

from abc import ABC, abstractmethodclass Car(ABC):@abstractmethoddef drive(self):passclass Sedan(Car):def drive(self):return "驾驶轿车"class SUV(Car):def drive(self):return "驾驶SUV"class CarFactory(ABC):@abstractmethoddef create_car(self):passclass SedanFactory(CarFactory):def create_car(self):return Sedan()class SUVFactory(CarFactory):def create_car(self):return SUV()# 使用工厂方法
sedan_factory = SedanFactory()
suv_factory = SUVFactory()sedan = sedan_factory.create_car()
suv = suv_factory.create_car()print(sedan.drive())  # 输出: 驾驶轿车
print(suv.drive())    # 输出: 驾驶SUV

(三)抽象工厂模式

from abc import ABC, abstractmethod# 抽象产品
class Car(ABC):@abstractmethoddef drive(self):passclass Engine(ABC):@abstractmethoddef start(self):pass# 具体产品
class SedanCar(Car):def drive(self):return "驾驶轿车"class SUVCar(Car):def drive(self):return "驾驶SUV"class GasolineEngine(Engine):def start(self):return "启动汽油发动机"class ElectricEngine(Engine):def start(self):return "启动电动发动机"# 抽象工厂
class CarFactory(ABC):@abstractmethoddef create_car(self):pass@abstractmethoddef create_engine(self):pass# 具体工厂
class SedanGasolineFactory(CarFactory):def create_car(self):return SedanCar()def create_engine(self):return GasolineEngine()class SUVElectricFactory(CarFactory):def create_car(self):return SUVCar()def create_engine(self):return ElectricEngine()# 使用抽象工厂
sedan_gasoline_factory = SedanGasolineFactory()
suv_electric_factory = SUVElectricFactory()sedan = sedan_gasoline_factory.create_car()
sedan_engine = sedan_gasoline_factory.create_engine()suv = suv_electric_factory.create_car()
suv_engine = suv_electric_factory.create_engine()print(sedan.drive())        # 输出: 驾驶轿车
print(sedan_engine.start()) # 输出: 启动汽油发动机
print(suv.drive())          # 输出: 驾驶SUV
print(suv_engine.start())   # 输出: 启动电动发动机

三,JavaScript代码

(一)简单工厂模式

class Car {constructor(model) {this.model = model;}
}class SimpleCatFactory {createCar(model) {switch (model) {case 'SUV':return new Car('SUV');case 'Sedan':return new Car('Sedan');default:throw new Error('Unknown car model');}}
}// 使用简单工厂
const simpleFactory = new SimpleCatFactory();
const suv = simpleFactory.createCar('SUV');
console.log(suv.model); // 输出: SUV

(二)工厂方法模式

class CarFactory {createCar() {throw new Error('This method should be overridden');}
}class SUVFactory extends CarFactory {createCar() {return new Car('SUV');}
}class SedanFactory extends CarFactory {createCar() {return new Car('Sedan');}
}// 使用工厂方法
const suvFactory = new SUVFactory();
const sedanFactory = new SedanFactory();
const suv2 = suvFactory.createCar();
const sedan = sedanFactory.createCar();
console.log(suv2.model); // 输出: SUV
console.log(sedan.model); // 输出: Sedan

(三)抽象工厂模式

class Engine {constructor(type) {this.type = type;}
}class Tire {constructor(type) {this.type = type;}
}class AbstractCarFactory {createEngine() {throw new Error('This method should be overridden');}createTire() {throw new Error('This method should be overridden');}
}class SUVFactory extends AbstractCarFactory {createEngine() {return new Engine('SUV Engine');}createTire() {return new Tire('SUV Tire');}
}class SedanFactory extends AbstractCarFactory {createEngine() {return new Engine('Sedan Engine');}createTire() {return new Tire('Sedan Tire');}
}// 使用抽象工厂
const suvFactory2 = new SUVFactory();
const sedanFactory2 = new SedanFactory();const suvEngine = suvFactory2.createEngine();
const suvTire = suvFactory2.createTire();
console.log(suvEngine.type); // 输出: SUV Engine
console.log(suvTire.type); // 输出: SUV Tireconst sedanEngine = sedanFactory2.createEngine();
const sedanTire = sedanFactory2.createTire();
console.log(sedanEngine.type); // 输出: Sedan Engine
console.log(sedanTire.type); // 输出: Sedan Tire

四,实际应用

(一)python代码

1,配置管理系统

问题:在不同环境(开发、生产)中需要不同的配置设置。
解决方案:使用简单工厂来创建适合特定环境的配置对象。

# 场景:配置管理系统class Configuration:def __init__(self, settings):self.settings = settingsdef get_setting(self, key):return self.settings.get(key)class ConfigurationFactory:@staticmethoddef create_configuration(env):if env == "development":return Configuration({"debug": True})elif env == "production":return Configuration({"debug": False})else:raise ValueError("Invalid environment")# 使用简单工厂
dev_config = ConfigurationFactory.create_configuration("development")
print(dev_config.get_setting("debug"))  # 输出: True

优点:

- 集中管理配置创建逻辑,便于维护。
- 客户端代码不需要知道具体的配置实现细节。

缺点:

- 如果需要添加新的环境配置,需要修改工厂类。

适用场景:

- 配置项相对固定,不经常变动。
- 配置逻辑相对简单,不需要复杂的继承结构。

2,支付系统

问题:需要支持多种支付方式(如Stripe、PayPal),且可能需要经常添加新的支付方式。
解决方案:为每种支付处理器创建一个工厂类,通过工厂方法创建具体的支付处理器。

# 场景:支付系统from abc import ABC, abstractmethodclass PaymentProcessor(ABC):@abstractmethoddef process_payment(self, amount):passclass StripeProcessor(PaymentProcessor):def process_payment(self, amount):print(f"Processing ${amount} payment via Stripe")class PayPalProcessor(PaymentProcessor):def process_payment(self, amount):print(f"Processing ${amount} payment via PayPal")class PaymentProcessorFactory(ABC):@abstractmethoddef create_processor(self):passclass StripeProcessorFactory(PaymentProcessorFactory):def create_processor(self):return StripeProcessor()class PayPalProcessorFactory(PaymentProcessorFactory):def create_processor(self):return PayPalProcessor()# 使用工厂方法
stripe_factory = StripeProcessorFactory()
stripe_processor = stripe_factory.create_processor()
stripe_processor.process_payment(100)  # 输出: Processing $100 payment via Stripe

优点:

- 易于扩展新的支付方式,只需添加新的处理器类和对应的工厂类。
- 符合开闭原则,不需要修改现有代码就可以添加新的支付方式。

缺点:

- 可能会导致类的数量增加,每种支付方式都需要一个工厂类。

适用场景:

- 系统需要经常添加新的产品类型。
- 产品的创建逻辑比较复杂,需要独立的工厂类来管理。

3,跨平台 UI 组件库

问题:需要创建一套完整的、风格一致的 UI 组件,且这些组件需要在不同的操作系统上有不同的外观。
解决方案:使用抽象工厂来创建一系列相关的 UI 组件,为每个平台提供一个具体的工厂。

# 场景:跨平台 UI 组件库class Button(ABC):@abstractmethoddef paint(self):passclass MacButton(Button):def paint(self):return "Rendering a button in macOS style"class WindowsButton(Button):def paint(self):return "Rendering a button in Windows style"class Checkbox(ABC):@abstractmethoddef paint(self):passclass MacCheckbox(Checkbox):def paint(self):return "Rendering a checkbox in macOS style"class WindowsCheckbox(Checkbox):def paint(self):return "Rendering a checkbox in Windows style"class GUIFactory(ABC):@abstractmethoddef create_button(self):pass@abstractmethoddef create_checkbox(self):passclass MacFactory(GUIFactory):def create_button(self):return MacButton()def create_checkbox(self):return MacCheckbox()class WindowsFactory(GUIFactory):def create_button(self):return WindowsButton()def create_checkbox(self):return WindowsCheckbox()# 使用抽象工厂
def create_ui(factory):button = factory.create_button()checkbox = factory.create_checkbox()print(button.paint())print(checkbox.paint())mac_factory = MacFactory()
create_ui(mac_factory)
# 输出:
# Rendering a button in macOS style
# Rendering a checkbox in macOS style

优点:

- 确保创建的 UI 组件之间风格一致。
- 易于切换整个产品族(如从 Windows 风格切换到 macOS 风格)。

缺点:

- 如果需要添加新的 UI 组件类型(如 RadioButton),需要修改所有的工厂类。

适用场景:

- 需要创建一系列相关或相互依赖的对象。
- 系统需要与多个产品族一起工作,但每次只使用其中一个。

(二)JavaScript代码

1,前端日志记录系统

前端:

function initDB() {return new Promise((resolve, reject) => {const request = indexedDB.open('LogDatabase', 1);request.onupgradeneeded = (event) => {const db = event.target.result;if (!db.objectStoreNames.contains('logs')) {db.createObjectStore('logs', {autoIncrement: true});}};request.onsuccess = (event) => {resolve(event.target.result);};request.onerror = (event) => {reject('Database error: ' + event.target.errorCode);};});
}class Logger {constructor(db) {this.db = db;}// 记录日志信息到 IndexedDBlog(message, level = 'info') {const logEntry = {timestamp: new Date().toISOString(),level: level.toUpperCase(),message,};const transaction = this.db.transaction(['logs'], 'readwrite');const store = transaction.objectStore('logs');store.add(logEntry);console.log(`[${logEntry.timestamp}] [${logEntry.level}]: ${logEntry.message}`);}info(message) {this.log(message, 'info');}warning(message) {this.log(message, 'warning');}error(message) {this.log(message, 'error');}debug(message) {this.log(message, 'debug');}// 从 IndexedDB 中读取所有日志并生成文本文件async getLogsAsText() {return new Promise((resolve, reject) => {const transaction = this.db.transaction(['logs'], 'readonly');const store = transaction.objectStore('logs');const request = store.getAll();request.onsuccess = (event) => {const logs = event.target.result;const logText = logs.map(log => `[${log.timestamp}] [${log.level}]: ${log.message}`).join('\n');resolve(logText);};request.onerror = (event) => {reject('Failed to retrieve logs: ' + event.target.errorCode);};});}// 上传日志到服务器并重试三次async uploadLogs(serverUrl, retryCount = 3) {try {const logText = await this.getLogsAsText();const logBlob = new Blob([logText], {type: 'text/plain'});const formData = new FormData();formData.append('file', logBlob, 'logs.txt');const response = await fetch(serverUrl, {method: 'POST',body: formData,});if (response.ok) {console.log('Logs uploaded successfully');this.clearLogs(); // 上传成功后清除日志} else {throw new Error(`Failed to upload logs: ${response.statusText}`);}} catch (error) {console.error(error);if (retryCount > 0) {console.log(`Retrying... (${3 - retryCount + 1}/3)`);await this.uploadLogs(serverUrl, retryCount - 1);} else {console.error('Failed to upload logs after 3 attempts.');}}}// 清空 IndexedDB 中的日志clearLogs() {const transaction = this.db.transaction(['logs'], 'readwrite');const store = transaction.objectStore('logs');store.clear();console.log('Logs cleared from IndexedDB');}// 每天定时上传scheduleUpload(serverUrl) {const now = new Date();const nextUploadTime = new Date();nextUploadTime.setHours(1, 0, 0, 0); // 设置为第二天的凌晨 1:00if (now > nextUploadTime) {// 如果当前时间已经超过今天的 1:00,则设置为明天的 1:00nextUploadTime.setDate(nextUploadTime.getDate() + 1);}const timeUntilNextUpload = nextUploadTime.getTime() - now.getTime();console.log(`Next upload scheduled in ${(timeUntilNextUpload / 1000 / 60).toFixed(2)} minutes`);setTimeout(() => {this.uploadLogs(serverUrl);setInterval(() => {this.uploadLogs(serverUrl);}, 24 * 60 * 60 * 1000); // 每天上传一次}, timeUntilNextUpload);}
}const config = {uploadLogs: true, // 配置项:是否上传日志serverUrl: 'https://example.com/upload', // 后端服务器地址
};initDB().then(db => {const logger = new Logger(db);// 记录不同级别的日志logger.info('This is an info log.');logger.warning('This is a warning log.');logger.error('This is an error log.');logger.debug('This is a debug log.');// 每天定时上传日志if (config.uploadLogs) {logger.scheduleUpload(config.serverUrl);}
}).catch(error => {console.error('Failed to initialize IndexedDB:', error);
});

node.js 后端:

const express = require('express');
const multer = require('multer');
const app = express();
const port = 3000;const upload = multer({dest: 'uploads/'}); // 上传文件的目录app.post('/upload', upload.single('file'), (req, res) => {console.log('File uploaded:', req.file);res.status(200).send('File uploaded successfully');
});app.listen(port, () => {console.log(`Server running at http://localhost:${port}`);
});

http://www.ppmy.cn/news/1516868.html

相关文章

【论文阅读】NGD-SLAM: Towards Real-Time SLAM for Dynamic Environments without GPU

arxiv上一篇很新的视觉SLAM论文,能够在不使用GPU的情况下进行语义分割的辅助运算。 一、跟踪流程 作为一个语义结合的视觉SLAM,其基本的思路和以前看过的DynaSLAM基本类似,都是依赖语义分割模型对场景中动态的特征点进行剔除,这…

【jvm】栈是否存在垃圾回收

目录 一、栈的特点1.1 栈内存分配1.2 栈的生命周期1.3 垃圾回收不直接涉及 二、堆与栈的区别三、总结 一、栈的特点 1.1 栈内存分配 1.栈内存分配是自动的,不需要程序员手动分配和释放。 2.每当一个方法被调用时,JVM就会在这个线程的栈上创建一个新的栈…

C++ | Leetcode C++题解之第355题设计推特

题目&#xff1a; 题解&#xff1a; class Twitter {struct Node {// 哈希表存储关注人的 Idunordered_set<int> followee;// 用链表存储 tweetIdlist<int> tweet;};// getNewsFeed 检索的推文的上限以及 tweetId 的时间戳int recentMax, time;// tweetId 对应发送…

使用GDIView工具排查GDI对象泄漏案例的若干细节总结

目录 1、查看任务管理器,发现程序中有明显的GDI对象泄漏 2、使用GDIView工具查看发生泄漏的是哪一种GDI对象 3、尝试找到复现问题的方法,缩小排查范围,逐步地找到GDI对象的泄漏点 4、本案例中的相关细节点的思考与总结(有价值的细节点) 4.1、UI界面无法显示的原因分析…

TypeScript 面试题汇总

引言 TypeScript 是一种由微软开发的开源、跨平台的编程语言&#xff0c;它是 JavaScript 的超集&#xff0c;为 JavaScript 添加了静态类型系统和其他高级功能。随着 TypeScript 在前端开发领域的广泛应用&#xff0c;掌握 TypeScript 已经成为很多开发者必备的技能之一。本文…

Clickhouse集群化(六)clickhosue-operator学习

1. Custom Resource元素 apiVersion: "clickhouse.altinity.com/v1" kind: "ClickHouseInstallation" metadata:name: "clickhouse-installation-test" 这是clickhouse operator自定义的资源ClickHouseInstallation 1.1. .spec.defaults spe…

35次8.23(docker02)

#搜索拉取镜像 docker search centos docker pull centos #创建启动容器 docker run -it --namea0 centod:latest echo "abc" #如果容器中没有正在执行的指令&#xff0c;就会exit docker run -it --namea0 cenyos:latest /bin/bash #查看docker进程 docker ps #发现…

SQL,解析 json

Google BigQuery数据库的data表存储了若干多层的Json串&#xff0c;其中一条形如&#xff1a; [{"active":true,"key":"key1","values":[{"active":true,"value":"value1"}]},{"active":tru…

go 系列实现websocket

一、简介 websocket是个二进制协议&#xff0c;需要先通过Http协议进行握手&#xff0c;从而协商完成从Http协议向websocket协议的转换。一旦握手结束&#xff0c;当前的TCP连接后续将采用二进制websocket协议进行双向双工交互&#xff0c;自此与Http协议无关。 二、websocket…

uni-app 手记集。

1、uni-app 是一个使用 Vue.js 开发的前端应用的框架&#xff0c;所以不会Vue.js的小伙伴可以先去看看Vue.js的基础教学。 2、.vue文件结构 <template><div class"container"></div> </template><script type"text/ecmascript-6&q…

未来城市的科技展望

未来城市&#xff0c;‌将是科技与人文深度融合的产物&#xff0c;‌展现出一个全方位智能化、‌绿色生态且可持续发展的全新面貌。‌随着物联网、‌人工智能等技术的飞速发展&#xff0c;‌未来城市的轮廓逐渐清晰&#xff0c;‌它将为我们带来前所未有的生活体验。‌ 在未来…

吴光明为鱼跃集团指明方向 以用户为核心构建发展战略

鱼跃集团创始人吴光明&#xff0c;始终秉持着以用户需求为核心的发展理念&#xff0c;引领企业构建技术与产品的双轮驱动体系。 在他的远见卓识下&#xff0c;鱼跃集团明确了以呼吸治疗解决方案、糖尿病管理及POCT、感染控制为三大核心支柱的战略布局&#xff0c;同时保持家用…

SAP怎么查找系统全部的增强点呢?

1.在已有的BADI查找程序里面有点手无足措的样子&#xff0c;不知道该如何去找增强&#xff01; 2.这个时候刚刚接触系统还不熟悉&#xff0c;系统里面存在了什么增强&#xff0c;这个时候咋办捏&#xff1f;SE38 -SNIF 此时全部的增强点都在这里面啦&#xff01;&#xff01;&…

bitsandbytes使用错误:CUDA Setup failed despite GPU being available

参考:https://huggingface.co/docs/bitsandbytes/main/en/installation 报错信息 ======================

一文了解机器学习顶会ICML 2024的研究热点

对人工智能研究领域前沿方向的跟踪是提高科研能力和制定科研战略的关键。本文通过图文并茂的方式介绍了ICML 2024的研究热点&#xff0c;帮助读者了解和跟踪机器学习和人工智能的前沿研究方向。本推文的作者是许东舟&#xff0c;审校为邱雪和黄星宇。 1 会议介绍 ICML&#x…

java在实际开发中反常识bug

目录 1.背景 2.案例 1.包装类型拆箱导致空指针异常 2.switch传入null,导致空指针异常 3.Arrays.asList添加异常 4.转BigDecimal类型时精度丢失 5.除以0不一定抛异常 6.Steam filter后集合修改,会修改原数据 3.完美&评论 1.背景 这篇博客,将列举本人在实际开发中看…

复现ssrf漏洞

目录 一、pikachu靶场 1、靶场环境&#xff1a; 使用docker拉取&#xff1a; docker run -d -p 8765:80 8023/pikachu-expect:latest 2、使用dict 3、使用file读取文件 二、redis未授权访问 1、源码 2、使用bp探测端口 3、继续使用bp探测172.18.0.2的端口 4、使用go…

如何使用ssm实现基于VUE的新闻类网站+vue修改完的

TOC ssm272基于VUE的新闻类网站vue修改完的 系统概述 进过系统的分析后&#xff0c;就开始记性系统的设计&#xff0c;系统设计包含总体设计和详细设计。总体设计只是一个大体的设计&#xff0c;经过了总体设计&#xff0c;我们能够划分出系统的一些东西&#xff0c;例如文件…

这几天旅游去了,刚回来,有几点感想

这几天旅游去了&#xff0c;刚回来&#xff0c;有几点感想&#xff1a; 一、不要抱怨外部环境差&#xff0c;你无法适应&#xff0c;不代表别人无法适应 从疫情开始&#xff0c;就一直有一个声音&#xff0c;抱怨说自己不赚钱是因为外部环境差&#xff0c;有理由可以摆烂了。…

爬虫中使用第三方打码平台(识别验证码)

引入 在爬虫程序中,常常会遇到验证码校验的关卡,人工识别不仅慢而且很费精力,使用打码平台就成了高效,省时且便利的选择。 本次案例对于高清资源库1080zyk.com进行爬虫采集&#xff0c;使用超级鹰验证码识别(chaojiying.com)和python的requests库具体实现&#xff0c;类似的第…