SpringBoot實戰筆記:28_Spring Batch
28_Spring Batch
什麼是Spring Batch
Spring Batch 是用來處理大量資料操作的一個框架,主要用來讀取大量資料,然後進行一定處理後輸出指定的形式。
Spring Batch 主要組成
名稱 | 用途 |
---|---|
JobRepository | 用來註冊Job的容器 |
JobLauncher | 用來啟動Job的介面 |
Job | 我們要實際執行的任務,包含一個或多個Step |
Step | Step-步驟,包含ItemReader,ItemProcessor,ItemWriter |
ItemReader | 用來讀取資料的介面 |
ItemProcessor | 用來處理資料的介面 |
ItemWriter | 用來輸出資料的介面 |
1,Spring Boot 的支援
Spring Boot
對 Spring Batch
提供了自動配置,為我們自動初始化了 Spring Batch
儲存批處理記錄的資料庫,且當我們程式啟動時,會自動執行我們定義的 Job
的Bean
相關配置
#啟動時要執行的job,預設會執行全部job
spring.batch.job.names=job1,job2
#是否自動執行job,預設為是
spring.batch.job.enabled=true
#是否初始化Spring Batch 的資料庫,預設為是
spring.batch.initializer.enabled=true
#設定資料庫
#spring.batch.schema=
#設定 Spring Batch 資料庫表的字首
#spring.batch.table-prefix=
2,新建Spring Boot專案
依賴:JDBC,Batch,Web,Oracle驅動,hibernate-validator(資料校驗)
**注:**Spring Batch 會自動載入 hsqldb
驅動,要去除
<dependency>
<groupId>org.springframework.boot</groupId >
<artifactId>spring-boot-starter-batch</artifactId>
<exclusions>
<exclusion>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc6</artifactId>
<version>11.2.0.2.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
3,準備用來測試的csv資料位於 src/main/resources/people.csv
4,資料表定義,位於 src/main/resources/schema.sql
5,資料來源配置
spring.datasource.username=root
spring.datasource.password=123
spring.datasource.driver-class-name=oracle.jdbc.OracleDriver
spring.datasource.url=jdbc:oracle:thin:@localhost:1521:xe
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jackson.serialization.indent-output=true
6,Person類
package com.zyf.domain;
import javax.validation.constraints.Size;
/**
* Created by zyf on 2018/3/15.
*/
public class Person {
/**
* 使用JSR-303校驗資料
*/
@Size(max = 4,min = 2)
private String name;
private int age;
private String nation;
private String address;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getNation() {
return nation;
}
public void setNation(String nation) {
this.nation = nation;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
7,資料處理及校驗
處理
package com.zyf.batch;
import com.zyf.domain.Person;
import org.springframework.batch.item.validator.ValidatingItemProcessor;
import org.springframework.batch.item.validator.ValidationException;
/**
* Created by zyf on 2018/3/15.
*/
public class CsvItemProcessor extends ValidatingItemProcessor<Person> {
@Override
public Person process(Person item) throws ValidationException {
//呼叫自定義的校驗器
super.process(item);
//處理資料
if(item.getNation().equals("蜀國")){
item.setNation("01");
}else {
item.setNation("02");
}
return item;
}
}
校驗
package com.zyf.batch;
import org.springframework.batch.item.validator.ValidationException;
import org.springframework.batch.item.validator.Validator;
import org.springframework.beans.factory.InitializingBean;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.ValidatorFactory;
import java.util.Set;
/**
* Created by zyf on 2018/3/15.
*/
public class CsvBeanValidator<T> implements Validator<T>,InitializingBean {
private javax.validation.Validator validator;
@Override
public void validate(T t) throws ValidationException {
//校驗資料,得到驗證不通過的約束
Set<ConstraintViolation<T>> constraintViolations = validator.validate(t);
if(constraintViolations.size() > 0){
StringBuilder message = new StringBuilder();
for (ConstraintViolation<T> constraintViolation : constraintViolations) {
message.append(constraintViolation.getMessage()+"\n");
}
throw new ValidationException(message.toString());
}
}
@Override
public void afterPropertiesSet() throws Exception {
//使用JSR-303 的 Validator 來校驗我們的資料
//此處初始化JSR-303 的 Validator
ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
validator = validatorFactory.usingContext().getValidator();
}
}
8,Job監聽
package com.zyf.batch;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobExecutionListener;
/**
* Created by zyf on 2018/3/15.
*/
public class CsvJobListener implements JobExecutionListener {
long startTime;
long endTime;
@Override
public void beforeJob(JobExecution jobExecution) {
startTime = System.currentTimeMillis();
System.out.println("任務開始");
}
@Override
public void afterJob(JobExecution jobExecution) {
endTime = System.currentTimeMillis();
System.out.println("任務結束");
System.out.println("耗時:"+(endTime - startTime) + "ms");
}
}
9,配置
package com.zyf.batch;
import com.zyf.domain.Person;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.core.launch.support.SimpleJobLauncher;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.database.BeanPropertyItemSqlParameterSourceProvider;
import org.springframework.batch.item.database.JdbcBatchItemWriter;
import org.springframework.batch.item.file.FlatFileItemReader;
import org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper;
import org.springframework.batch.item.file.mapping.DefaultLineMapper;
import org.springframework.batch.item.file.transform.DelimitedLineTokenizer;
import org.springframework.batch.item.validator.Validator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
/**
* Created by zyf on 2018/3/15.
*/
@Configuration
@EnableBatchProcessing//開啟批處理的支援
public class CsvBatchConfig {
@Bean
public ItemReader<Person> reader(){
//使用FlatFileItemReader讀取檔案
FlatFileItemReader<Person> reader = new FlatFileItemReader<>();
//設定目標檔案的路徑,通過ClassPathResource找到類路徑
reader.setResource(new ClassPathResource("p.csv"));
//將檔案中的資料與Person類中的屬性一一對映
reader.setLineMapper(new DefaultLineMapper<Person>(){
//程式碼塊
{
setLineTokenizer(new DelimitedLineTokenizer(){
//程式碼塊
{
setNames(new String[]{"name","age","nation","address"});
}
});
setFieldSetMapper(new BeanWrapperFieldSetMapper<Person>(){
{
setTargetType(Person.class);
}
});
}
});
return reader;
}
@Bean
public ItemProcessor<Person,Person> processor(){
//使用我們自定義的CsvItemProcessor
CsvItemProcessor processor = new CsvItemProcessor();
//為處理器指定校驗器
processor.setValidator(csvBeanValidator());
return processor;
}
/**
*
* @param dataSource spring boot 已經為我們定義了 DataSource,Spring能讓容器中已有的Bean,以引數的形式注入
* @return
*/
@Bean
public ItemWriter<Person> writer(DataSource dataSource){
//使用jdbc批處理的JdbcBatchItemWriter將資料寫入到資料庫
JdbcBatchItemWriter<Person> writer = new JdbcBatchItemWriter<>();
writer.setItemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<>());
String sql = "insert into PERSON (id,name,age,nation,address) values(PERSON_BATCH.nextval, :name, :age, :nation, :address)";
//設定要執行批處理的SQL語句
writer.setSql(sql);
//配置資料來源
writer.setDataSource(dataSource);
return writer;
}
/**
*
* @param dataSource 自動注入
* @param transactionManager 自動注入
* @return
* @throws Exception
*/
@Bean
public JobRepository jobRepository(DataSource dataSource, PlatformTransactionManager transactionManager) throws Exception {
JobRepositoryFactoryBean jobRepositoryFactoryBean = new JobRepositoryFactoryBean();
jobRepositoryFactoryBean.setDataSource(dataSource);
jobRepositoryFactoryBean.setTransactionManager(transactionManager);
jobRepositoryFactoryBean.setDatabaseType("oracle");
return jobRepositoryFactoryBean.getObject();
}
@Bean
public SimpleJobLauncher jobLauncher(DataSource dataSource,PlatformTransactionManager transactionManager) throws Exception {
SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
jobLauncher.setJobRepository(jobRepository(dataSource,transactionManager));
return jobLauncher;
}
@Bean
public Job importJob(JobBuilderFactory jobs,Step s1){
return jobs.get("importJob")
.incrementer(new RunIdIncrementer())
.flow(s1)//為job指定step(要執行什麼)
.end()
.listener(csvJobListener())//繫結監聽器
.build();
}
/**
*
* @param stepBuilderFactory
* @param reader
* @param writer
* @param processor
* @return
*/
@Bean
public Step step1(StepBuilderFactory stepBuilderFactory,ItemReader<Person> reader,ItemWriter<Person> writer,ItemProcessor<Person,Person> processor){
return stepBuilderFactory.get("step1")
.<Person,Person>chunk(65000)//批處理每次提交65000條資料
.reader(reader)//給step繫結reader
.processor(processor)//給step繫結processor
.writer(writer)//給step繫結writer
.build();
}
@Bean
public CsvJobListener csvJobListener(){
return new CsvJobListener();
}
@Bean
public Validator<Person> csvBeanValidator(){
return new CsvBeanValidator<Person>();
}
}
10,執行測試
11,通過訪問某路徑,執行job
新的配置
別忘了吧CsvBatchConfig類的@Configuration註釋掉
package com.zyf.batch;
import com.zyf.domain.Person;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.core.launch.support.SimpleJobLauncher;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.database.BeanPropertyItemSqlParameterSourceProvider;
import org.springframework.batch.item.database.JdbcBatchItemWriter;
import org.springframework.batch.item.file.FlatFileItemReader;
import org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper;
import org.springframework.batch.item.file.mapping.DefaultLineMapper;
import org.springframework.batch.item.file.transform.DelimitedLineTokenizer;
import org.springframework.batch.item.validator.Validator;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
/**
* Created by zyf on 2018/3/15.
*/
@Configuration
@EnableBatchProcessing
public class TriggerBatchConfig {
@Bean
@StepScope
public FlatFileItemReader<Person> reader(@Value("#{jobParameters['input.file.name']}") String pathToFile){
//使用FlatFileItemReader讀取檔案
FlatFileItemReader<Person> reader = new FlatFileItemReader<>();
//設定目標檔案的路徑,通過ClassPathResource找到類路徑
reader.setResource(new ClassPathResource(pathToFile));
//將檔案中的資料與Person類中的屬性一一對映
reader.setLineMapper(new DefaultLineMapper<Person>(){
//程式碼塊
{
setLineTokenizer(new DelimitedLineTokenizer(){
//程式碼塊
{
setNames(new String[]{"name","age","nation","address"});
}
});
setFieldSetMapper(new BeanWrapperFieldSetMapper<Person>(){
{
setTargetType(Person.class);
}
});
}
});
return reader;
}
@Bean
public ItemProcessor<Person,Person> processor(){
//使用我們自定義的CsvItemProcessor
CsvItemProcessor processor = new CsvItemProcessor();
//為處理器指定校驗器
processor.setValidator(csvBeanValidator());
return processor;
}
/**
*
* @param dataSource spring boot 已經為我們定義了 DataSource,Spring能讓容器中已有的Bean,以引數的形式注入
* @return
*/
@Bean
public ItemWriter<Person> writer(DataSource dataSource){
//使用jdbc批處理的JdbcBatchItemWriter將資料寫入到資料庫
JdbcBatchItemWriter<Person> writer = new JdbcBatchItemWriter<>();
writer.setItemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<>());
String sql = "insert into PERSON (id,name,age,nation,address) values(PERSON_BATCH.nextval, :name, :age, :nation, :address)";
//設定要執行批處理的SQL語句
writer.setSql(sql);
//配置資料來源
writer.setDataSource(dataSource);
return writer;
}
/**
*
* @param dataSource 自動注入
* @param transactionManager 自動注入
* @return
* @throws Exception
*/
@Bean
public JobRepository jobRepository(DataSource dataSource, PlatformTransactionManager transactionManager) throws Exception {
JobRepositoryFactoryBean jobRepositoryFactoryBean = new JobRepositoryFactoryBean();
jobRepositoryFactoryBean.setDataSource(dataSource);
jobRepositoryFactoryBean.setTransactionManager(transactionManager);
jobRepositoryFactoryBean.setDatabaseType("oracle");
return jobRepositoryFactoryBean.getObject();
}
@Bean
public SimpleJobLauncher jobLauncher(DataSource dataSource, PlatformTransactionManager transactionManager) throws Exception {
SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
jobLauncher.setJobRepository(jobRepository(dataSource,transactionManager));
return jobLauncher;
}
@Bean
public Job importJob(JobBuilderFactory jobs, Step s1){
return jobs.get("importJob")
.incrementer(new RunIdIncrementer())
.flow(s1)//為job指定step(要執行什麼)
.end()
.listener(csvJobListener())//繫結監聽器
.build();
}
/**
*
* @param stepBuilderFactory
* @param reader
* @param writer
* @param processor
* @return
*/
@Bean
public Step step1(StepBuilderFactory stepBuilderFactory, ItemReader<Person> reader, ItemWriter<Person> writer, ItemProcessor<Person,Person> processor){
return stepBuilderFactory.get("step1")
.<Person,Person>chunk(65000)//批處理每次提交65000條資料
.reader(reader)//給step繫結reader
.processor(processor)//給step繫結processor
.writer(writer)//給step繫結writer
.build();
}
@Bean
public CsvJobListener csvJobListener(){
return new CsvJobListener();
}
@Bean
public Validator<Person> csvBeanValidator(){
return new CsvBeanValidator<Person>();
}
}
12,建立Controller
package com.zyf.web.controller;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.JobParametersInvalidException;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException;
import org.springframework.batch.core.repository.JobRestartException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* Created by zyf on 2018/3/15.
*/
@RestController
public class DemoController {
@Autowired
JobLauncher jobLauncher;
@Autowired
Job importJob;
JobParameters jobParameters;
@RequestMapping("/imp")
public String imp(String fileName) throws JobParametersInvalidException, JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException {
String path = fileName +".csv";
jobParameters = new JobParametersBuilder().addLong("time",System.currentTimeMillis())
.addString("input.file.name",path)
.toJobParameters();
jobLauncher.run(importJob,jobParameters);
return "ok";
}
}
13,更改自動執行job的配置
14,訪問 /imp?fileName=p
相關推薦
SpringBoot實戰筆記:28_Spring Batch
28_Spring Batch 什麼是Spring Batch Spring Batch 是用來處理大量資料操作的一個框架,主要用來讀取大量資料,然後進行一定處理後輸出指定的形式。 Spring Batch 主要組成 名稱 用途
SpringBoot實戰筆記:03_Spring常用配置
Spring常用配置 1,Bean的Scope //預設是Singleton,相當於@Scope("singleton"),單例 @Service public class DefaultScopeService{ } @Service @Scop
SpringBoot實戰筆記:15_SpringBoot 自定義starter pom
15_SpringBoot 自定義starter pom 我們在 pom.xml 中的那些springboot依賴,就是starter 目的:當某個類存在的時候,自動配置這個類的Bean,並可將Bean的書寫在 application.properties
SpringBoot實戰筆記:24_Spring Boot 的事務支援
24_Spring Boot 的事務支援 所有的資料訪問技術都有事務處理機制,這些技術提供了API用來開啟事務,提交事務來完成資料操作,或者在發生錯誤的時候回滾資料。 Spring 的事務機制是用統一的機制來處理不同的資料訪問技術的事務處理。 Spring
SpringBoot實戰筆記:02_使用註解與Java配置的Aop示例
使用註解與Java配置的Aop示例 0,新增所需依賴 <!--02_新的依賴--> <!--匯入spring的aop支援--> <dependency>
Spring實戰筆記:Spring核心(一)
spring bean spring core一.簡介: 1.依賴註入(DI) 優點:解耦 Spring 通過應用上下文(Application Context)裝載bean的定義,並把它們組裝起來。 Spring應用上下文負責對象的創建和組裝。 ClassPathXm
Spring實戰筆記:Web中的Spring
web spring一.構建Spring Web應用1.Spring MVC中用戶請求處理 上圖展示了請求使用Spring MVC所經歷的所有站點。 1:在請求離開瀏覽器時,會帶有所有請求內容的信息,至少會包含請求的URL。 請求通過Spring的DispatcherServlet前
Spring實戰筆記:Spring集成
Spring RPC REST 消息 一.使用遠程服務 遠程調用是客戶端應用和服務端之間的會話。 1.Spring通過多種遠程調用技術支持RPC(remote procedure call,遠程過程調用)RPC模型使用場景RMI不考慮網絡限制時(例如防火墻),訪問/發布基於Java
Spring實戰筆記:後端中的Spring
spring 數據庫 緩存 安全 一.使用對象-關系映射持久化數據 對象/關系映射(object-relational mapping,ORM)。 在持久層使用ORM工具可以節省術前行代碼和大量開發時間。 Spring對ORM框架的支持提供了與這些框架的集成點以及一些附加的服
springboot學習筆記:01Building a RESTful Web Service
我學習的時候使用eclipse Jee 作為開發工具。另外再推薦idea community版本。 學習資料地址 我使用的是maven構建專案方式 按照文章裡介紹的,依次修改活新建: pom.xml src/main/java/hello/Greeting.ja
實戰筆記:Jenkins打造強大的前端自動化工作流
背景 公司的前端構建及部署工作都是人工去做,隨著業務擴大,專案迭代速度變快,人員增多,各種問題都暴露出來,本文是對前端自動化工作流進行探索後的一篇經驗分享,將通過一個簡單案例分享一下基於Jenkins的前端自動化工作流搭建的過程,搭建完這套工作流,我們只需要在本地發起一個g
【JAVAWEB學習筆記】網上商城實戰4:訂單模塊
接收 筆記 網上商城 詳情 src head 分頁查詢 cnblogs logs 今日任務 完成訂單模塊的功能 1.1 訂單 模塊的功能 1.1.1 我的訂單: 【我的訂單的查詢】 * 在header.jsp中點擊我的訂單. * 提交到Servlet:
【JAVAWEB學習筆記】網上商城實戰5:後臺的功能模塊
form 所有 實現 返回 .com 訂單管理 模塊 集合 後臺 今日任務 完成後臺的功能模塊 1.1 網上商城的後臺功能的實現: 1.1.1 後臺的功能的需求: 1.1.1.1 分類管理: 【查詢所有分類】 * 在左側菜單
SpringBoot學習筆記(5):處理前端JSON返回的日期的格式
處理 date() ring row 學習筆記 post 直觀 val rtt SpringBoot學習筆記(4):處理前端JSON返回的日期的格式 問題描述 前端頁面顯示的時間為毫秒格式,不利於直觀顯示! 解決方法1——後端解決 public class Flow
SpringBoot學習筆記(1):配置Mybatis
target oca run class .com gpo connect auto users SpringBoot學習筆記(1):配置Mybatis 參考資料: 1.AndyLizh的博客 2.xiaolyuh123的博客 快速開始 添加Mybatis依賴(
SpringBoot學習筆記(10):使用MongoDB來訪問數據
and exclude 包含 思維導圖 .org args 表示 告訴 http SpringBoot學習筆記(10):使用MongoDB來訪問數據 快速開始 本指南將引導您完成使用Spring Data MongoDB構建應用程序的過程,該應用程序將數據存儲在Mong
SpringBoot學習筆記(11):使用WebSocket構建交互式Web應用程序
-- 文件 基於 ping pan lan tin eas return SpringBoot學習筆記(11):使用WebSocket構建交互式Web應用程序 快速開始 本指南將引導您完成創建“hello world”應用程序的過程,該應用程序在瀏覽器和服務器之間來回發
SpringBoot學習筆記(12):計劃任務
當前 frame mode oot odin 構建java項目 ati pre string SpringBoot學習筆記(12):計劃任務 快速開始 本指南將指導您完成使用Spring安排任務的步驟。 參考教程: https://spring.io/guides/g
SpringBoot學習筆記(13):日誌框架
配置 沒有 alt clas load dep 技術 bubuko col SpringBoot學習筆記(13):日誌框架——SL4J 快速開始 說明 SpringBoot底層選用SLF4J和LogBack日誌框架。 SLF4J的使用 SpringBoot的底層依
20180813視頻筆記 深度學習基礎上篇(1)之必備基礎知識點 深度學習基礎上篇(2)神經網絡模型視頻筆記:深度學習基礎上篇(3)神經網絡案例實戰 和 深度學習基礎下篇
計算 概念 人臉識別 大量 png 技巧 表現 lex github 深度學習基礎上篇(3)神經網絡案例實戰 https://www.bilibili.com/video/av27935126/?p=1 第一課:開發環境的配置 Anaconda的安裝 庫的安裝 Windo