0%

  1. yum update 查看yum源,替换国内源
  2. 确认centos-extras repository 开启
  3. 卸载旧版
  4. 安装包管理辅助工具
  5. 添加docker源
  6. 安装
  7. 启动
  8. 测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# step 3 to 8
sudo yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine

sudo yum install -y yum-utils \
device-mapper-persistent-data \
lvm2

sudo yum-config-manager \
--add-repo \
https://download.docker.com/linux/centos/docker-ce.repo

sudo yum install docker-ce docker-ce-cli containerd.io

sudo systemctl start docker

sudo docker run hello-world

linux
curl -sSL https://get.daocloud.io/daotools/set_mirror.sh | sh -s http://f1361db2.m.daocloud.io

mac & windows
http://f1361db2.m.daocloud.io 添加到registry-mirrors数组里

改完重启docker sudo systemctl restart docker

参考:

我的问题:

公司数据部门频繁清洗数据中

  1. 脚本繁多, 前后依赖
  2. 监控缺失,脚本执行时间管理几乎为0,脚本优化无针对性目标
  3. 重试机制缺失,任务超时而下个周期又被触发

什么是airflow

​ 基于python, 联合其他工具实现的的分布式任务调度解决方案。主要涉及工具:airflow, celery, db, mq

优点:

  • DAG(有向无环图)任务依赖清晰,任务统计信息明确,UI展示出众🌟
  • 配合celery实现分布式调度,并发控制、队列隔离task。。。且无需关注celery实现
  • 丰富的任务定义组件SSHOperator,PostgresOperator 等等 简单易用
  • 重试、超时机制
  • 连接池

缺点:

  • 官方UI,只支持UTC时间。展示不直观,非常难看

有经验的人也许会奇怪,celery本身就是个分布式任务队列了,那么airflow又干了啥。

举个例子吧,就像springboot与spring,发动机与汽车。你干的不错,但是我可以做到更丰富易用。

快捷搭建

DAG

任务依赖 一览无余

执行时间 监控

并发控制(concurrency)

retry

web security

官方自带方案,不过暂时没管。超级用户一时爽,一直超级一直爽!!!

⚠️注意

任务实际开始时间 = start_date + sheduler_interval

最优方案

结合celery特性,worker模式

在实际执行命令的机器,部署worker。消费执行队列的消息。

优势:

  • BashOperator 直接调用(小声逼逼:尽量使用bash直接操作,避免bash调用http等外部服务
  • 限制worker并发数
  • 限制任务内存占用

QA

  1. UI时间不能换时区问题

    A:修改源码首页,登录后台页

  2. log过大,导致磁盘空间不足

    A:写个job定时清理log目录,log目录在airflow.cfg中有定义

  3. timeout作用

    A:超时kill掉任务,在operator的on_kill方法实现。目前只支持bashOperator(其他task始终保持running)

    A: 超时结束工作流,这个始终有效。但是会出现一种情况,工作流结束,子任务是中running

  4. BashOperator 自动结束逻辑

    A:开启子进程执行脚本,保存子进程组id,根据id kill

  5. sql timeout实现

    A: 修改conn hook源码

  6. alter告警发送到钉钉

    A:默认发送邮件,自定义operator发送到钉钉

1. 主流MQ

rabbitMQ activeMQ rocketMQ kafka mqtt
授权方式
开发语言 Erlang java java Java&scala

除此之外,redis的发布订阅模式,mysql等也常常用作队列

2. 重试机制

消息可靠性

  • 至少消费一次
    • 收到一次确认成功为止
  • 至多消费一次
    • 成功或者未收到确认消息为止
  • 恰好消费一次
    • 至少消费一次 + 业务验证幂等

2.1 producer重试

2.2 consumer重试

3. 事物消息

4. 有序消息

ETL的困扰

​ ETL过程涉及数据的抽取、转换、加载。某些业务中ETL甚至涉及报表工作。

​ 宽表处理、细分纬度、报表中间表……ETL中间流程急剧膨胀。

为此,我们越来越难描绘清楚ETL流程。

桑基图

echarts的桑基图示例:

从左到右,我们可以清晰的看到流转过程

ETL与桑基图

设计如下:

  • 表名 – 主体
  • 抽取字段数 — 图宽

观察某个表数据来源:

高亮的清晰展示

快速展示

临时方案哈

毕竟我就画这一张

  1. 打开预览网页

  2. 准备ETL流转数据

    1. {"nodes":[
      {"name":"crm_om_order"},
      {"name":"cas_ca_app_approval"},
      {"name":"ods_employee_org_struct_info"},
      {"name":"table A"},
      {"name":"table B"},
      {"name":"ods_order_info_detail"},
      {"name":"castopg"},
      {"name":"crmtopg"}
      ],
      "links":[
      {"source": "castopg", "target": "cas_ca_app_approval", "value": 6},
      {"source": "castopg", "target": "table A", "value": 18},
      {"source": "crmtopg", "target": "crm_om_order", "value": 20},  
      {"source": "crmtopg", "target": "table B", "value": 8},
      {"source": "crmtopg", "target": "table C", "value": 13},
      
      {"source": "crm_om_order", "target": "ods_order_info_detail", "value": 18},
      {"source": "cas_ca_app_approval", "target": "ods_order_info_detail", "value": 3},
      {"source": "ods_employee_org_struct_info", "target": "ods_order_info_detail", "value": 5},
      {"source": "table A", "target": "ods_employee_org_struct_info", "value": 5},
      {"source": "table B", "target": "ods_employee_org_struct_info", "value": 10}
      ]}
      
  3. 替换网页默认加载的数据

    1. 添加一行
    2. 复制准备好的数据(下图)


如果发生版本升级:https://echarts.baidu.com/

请打开链接,寻找【实例】-【桑基图】

还原现场

现象:

  • sonar关闭后重启失败
  • 尝试重新启动,依旧失败;
  • es报错,对某个目录lock拿不到
  • 怀疑进程未关闭;但是sonar/es相关进程均无
  • 开始查java进程,如下
1
ps -ef | grep defunct
1
2
3
UID          PID     PPID       C    STIME      TTY          TIME              CMD
1000 637 27872 0 Oct12 ? 00:00:04 [chrome] <defunct>
1000 1808 1 0 Oct04 ? 00:00:00 [java] <defunct>

顾名思义,这是一个弃用的进程。

既然存在进程,那么很可能占用了某些资源。

继续排查:

  • 机器上无其他服务
  • 查资料 - defunct

一般产生原因:

使用jdk是预编译,与本地环境不兼容。例如,某个.so动态链接库文件不一致等等。

解决方式:

下载jdk src源码,本地编译

处理方式:

  • 尝试kill -9 pid
  • 重启(必杀)

1. 概述

WebRTC是“网络实时通信”(Web Real Time Communication)的缩写。它最初是为了解决浏览器上视频通话而提出的,即两个浏览器之间直接进行视频和音频的通信,不经过服务器。后来发展到除了音频和视频,还可以传输文字和其他数据。

Google是WebRTC的主要支持者和开发者,它最初在Gmail上推出了视频聊天,后来在2011年推出了Hangouts,语序在浏览器中打电话。它推动了WebRTC标准的确立。

WebRTC主要让浏览器具备三个作用。

  • 获取音频和视频
  • 进行音频和视频通信
  • 进行任意数据的通信

WebRTC共分成三个API,分别对应上面三个作用。

  • MediaStream (又称getUserMedia)
  • RTCPeerConnection
  • RTCDataChannel

2. 集成三方即时通讯服务遇到的问题

  1. 本地可用,发布到服务器不可用
  2. 发不到服务器后,多次尝试。部分firefox可用,chrome不可用
  3. 生产符合1、2条件,chrome还是不可用

2.1 问题一处理

表象

  • 首先排除网络问题
  • 其次网络是否有错误
  • 至此,熟悉的领域没有问题
  • webRtc
  • 至此,可以解释问题一

2.2 问题二处理

  1. firfox不要求origins检查
  2. 公司web服务firefox支持不好,部分firefox版本不可用

2.3 问题三处理

初步结论:三方js代码问题,错误的调用获取midea方法

查看三方js,可见

查看我方web可见

所以,三方代码没有走这个方法获取midea

但是,该方法是可用的

3. 其他问题

  • 媒体不可同时被多个浏览器获得
  • webRtc存在IP泄漏问题,故chrome存在origins要求

java解决了跨平台

http解决了跨应用

jni解决了java跨语言

姑且,就当它是java跨语言适配器吧。使能像调用操作系统一样,调用别的语言

1. jdk目录说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.
├── bin
├── COPYRIGHT
├── db
├── include
├── javafx-src.zip
├── jre
├── lib
├── LICENSE
├── man
├── README.html
├── release
├── src.zip
├── THIRDPARTYLICENSEREADME-JAVAFX.txt
└── THIRDPARTYLICENSEREADME.txt

可见其中有个include目录

1
2
3
4
5
6
7
8
9
10
include/
├── classfile_constants.h
├── jawt.h
├── jdwpTransport.h
├── jni.h
├── jvmticmlr.h
├── jvmti.h
└── linux
├── jawt_md.h
└── jni_md.h

这里的文件,都是.h结尾的c语言头文件

C-language header files that support native-code programming using the Java Native Interface and the Java Virtual Machine Debugger Interface.

解释:c语言的头文件,支持使用jni和jvm debug interface调用本地代码。

2. 开发流程

  1. 定义java native方法

    1. ```java
      public native boolean leaveChannel(long nativeHandle);
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27

      2. javac

      1. `javac *.java -d dest`
      2. 确认文件无误、生成java可执行文件`.class`

      3. javah

      1. `javah -jni *.java`
      2. 根据定义的native方法类,生成`.h`文件

      4. 据h实现c

      1. 完成`.cpp`文件

      5. g++

      1. `g++ xxx.cpp -o xx.so -shared -fPIC -I. -Ixxx -I/usr/java/include -I/usr/java/include/linux xxx.o xxx.o -pthread -lpthread -L`pwd`/../../libs -lrecorder -lrt -I.`
      2. 根据`.cpp` `jvm jni.h` `c其他编译后文件`生成动态链接库`.so`

      6. Java加载动态链接库(.so or .dll)

      1. ```java
      static {
      // System.loadLibrary("recording");
      System.load("xxx/librecording.so");
      }
  2. ……没了

3. 最后的麻烦

java调用c报错

出错信息为c,java接到signal直接崩溃

  • c异常捕获,防止java程序继续执行,引发不可预知异常
    • c中处理
  • c问题排除(对于没玩过c的,太难!!!)
    • Core dump
    • gdb

1. spring bean生命周期

可见beanPostProcesser执行位置

2. 注册过程

第一轮注册

第二轮注册

image-20190119233758534

image-20190119233841024

第三轮注册(上面已有)

第四轮注册

第五轮注册

自定义的在第四或第五轮,具体忘记了

3. 常见实现

从上面可以看到很多我们熟悉的注解,require、autoAwire等等。spring的很多便捷功能,都在这一层实现的。包括springcloud的一些组建,也做了这一层拦截.

spring-boot有三种启动类

  • JarLauncher
  • PropertiesLauncher
  • WarLauncher

通常,我们会以第一种方式部署项目

spring-boot提供了插件spring-boot-maven-plugin用于把程序打包成一个可执行的jar包。在pom文件里加入这个插件即可:

1
2
3
4
5
6
7
8
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

打包后的jar文件结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
├── META-INF
│ ├── MANIFEST.MF
│ └── maven
│ └── org.xxx
│ └── executable-jar
│ ├── pom.properties
│ └── pom.xml
├── org
│ └── springframework
│ └── boot
│ └── loader
│ ├── ExecutableArchiveLauncher$1.class
│ ├── ...
└── BOOT-INFO

然后,启动程序

1
java -jar xxx.jar

打包出来fat jar内部有4种文件类型:

  1. META-INF文件夹:程序入口,其中MANIFEST.MF用于描述jar包的信息

  2. BOOT-INFO 下 lib目录:放置第三方依赖的jar包,比如springboot的一些jar包

  3. spring boot loader相关的代码

  4. BOOT-INFO 下 classes目录模块自身的代码

MANIFEST.MF文件的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Manifest-Version: 1.0
Implementation-Title: appname
Implementation-Version: 1.0.0
Archiver-Version: Plexus Archiver
Built-By: xxx
Implementation-Vendor-Id: cn.xxx.xxx.xxx
Spring-Boot-Version: 1.5.15.RELEASE
Implementation-Vendor: Pivotal Software, Inc.
Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: cn.victorplus.car.cas.xxx
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Created-By: Apache Maven 3.5.2
Build-Jdk: 1.8.0_162
Implementation-URL: http://projects.spring.io/spring-boot/xxx-xxx-xxx/

我们看到,它的Main-Class是org.springframework.boot.loader.JarLauncher,当我们使用java -jar执行jar包的时候会调用JarLauncher的main方法,而不是我们编写的SpringApplication。

那么JarLauncher这个类是的作用是什么的?

它是SpringBoot内部提供的工具Spring Boot Loader提供的一个用于执行Application类的工具类(fat jar内部有spring loader相关的代码就是因为这里用到了)。相当于Spring Boot Loader提供了一套标准用于执行SpringBoot打包出来的jar

JarLauncher的执行过程

JarLauncher的main方法:

1
2
3
4
public static void main(String[] args) {
// 构造JarLauncher,然后调用它的launch方法。参数是控制台传递的
new JarLauncher().launch(args);
}

JarLauncher被构造的时候会调用父类ExecutableArchiveLauncher的构造方法。

ExecutableArchiveLauncher的构造方法内部会去构造Archive,这里构造了JarFileArchive。构造JarFileArchive的过程中还会构造很多东西,比如JarFile,Entry …

JarLauncher的launch方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
protected void launch(String[] args) {
try {
// 在系统属性中设置注册了自定义的URL处理器:org.springframework.boot.loader.jar.Handler。如果URL中没有指定处理器,会去系统属性中查询
JarFile.registerUrlProtocolHandler();
// getClassPathArchives方法在会去找lib目录下对应的第三方依赖JarFileArchive,同时也会项目自身的JarFileArchive
// 根据getClassPathArchives得到的JarFileArchive集合去创建类加载器ClassLoader。这里会构造一个LaunchedURLClassLoader类加载器,这个类加载器继承URLClassLoader,并使用这些JarFileArchive集合的URL构造成URLClassPath
// LaunchedURLClassLoader类加载器的父类加载器是当前执行类JarLauncher的类加载器
ClassLoader classLoader = createClassLoader(getClassPathArchives());
// getMainClass方法会去项目自身的Archive中的Manifest中找出key为Start-Class的类
// 调用重载方法launch
launch(args, getMainClass(), classLoader);
}
catch (Exception ex) {
ex.printStackTrace();
System.exit(1);
}
}

// Archive的getMainClass方法
// 这里会找出spring.study.executablejar.ExecutableJarApplication这个类
public String getMainClass() throws Exception {
Manifest manifest = getManifest();
String mainClass = null;
if (manifest != null) {
mainClass = manifest.getMainAttributes().getValue("Start-Class");
}
if (mainClass == null) {
throw new IllegalStateException(
"No 'Start-Class' manifest entry specified in " + this);
}
return mainClass;
}

// launch重载方法
protected void launch(String[] args, String mainClass, ClassLoader classLoader)
throws Exception {
// 创建一个MainMethodRunner,并把args和Start-Class传递给它
Runnable runner = createMainMethodRunner(mainClass, args, classLoader);
// 构造新线程
Thread runnerThread = new Thread(runner);
// 线程设置类加载器以及名字,然后启动
runnerThread.setContextClassLoader(classLoader);
runnerThread.setName(Thread.currentThread().getName());
runnerThread.start();
}

MainMethodRunner的run方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Override
public void run() {
try {
// 根据Start-Class进行实例化
Class<?> mainClass = Thread.currentThread().getContextClassLoader()
.loadClass(this.mainClassName);
// 找出main方法
Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
// 如果main方法不存在,抛出异常
if (mainMethod == null) {
throw new IllegalStateException(
this.mainClassName + " does not have a main method");
}
// 调用
mainMethod.invoke(null, new Object[] { this.args });
}
catch (Exception ex) {
UncaughtExceptionHandler handler = Thread.currentThread()
.getUncaughtExceptionHandler();
if (handler != null) {
handler.uncaughtException(Thread.currentThread(), ex);
}
throw new RuntimeException(ex);
}
}

Start-Class的main方法调用之后,内部会构造Spring容器,启动内置Servlet容器等过程。 这些过程我们都已经分析过了。

关于自定义的类加载器LaunchedURLClassLoader

LaunchedURLClassLoader重写了loadClass方法,也就是说它修改了默认的类加载方式(先看该类是否已加载这部分不变,后面真正去加载类的规则改变了,不再是直接从父类加载器中去加载)。LaunchedURLClassLoader定义了自己的类加载规则:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private Class<?> doLoadClass(String name) throws ClassNotFoundException {

// 1) Try the root class loader
try {
if (this.rootClassLoader != null) {
return this.rootClassLoader.loadClass(name);
}
}
catch (Exception ex) {
// Ignore and continue
}

// 2) Try to find locally
try {
findPackage(name);
Class<?> cls = findClass(name);
return cls;
}
catch (Exception ex) {
// Ignore and continue
}

// 3) Use standard loading
return super.loadClass(name, false);
}

加载规则:

  1. 如果根类加载器存在,调用它的加载方法。这里是根类加载是ExtClassLoader
  2. 调用LaunchedURLClassLoader自身的findClass方法,也就是URLClassLoader的findClass方法
  3. 调用父类的loadClass方法,也就是执行默认的类加载顺序(从BootstrapClassLoader开始从下往下寻找)

LaunchedURLClassLoader自身的findClass方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
protected Class<?> findClass(final String name)
throws ClassNotFoundException
{
try {
return AccessController.doPrivileged(
new PrivilegedExceptionAction<Class<?>>() {
public Class<?> run() throws ClassNotFoundException {
// 把类名解析成路径并加上.class后缀
String path = name.replace('.', '/').concat(".class");
// 基于之前得到的第三方jar包依赖以及自己的jar包得到URL数组,进行遍历找出对应类名的资源
// 比如path是org/springframework/boot/loader/JarLauncher.class,它在jar:file:/Users/Format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar!/lib/spring-boot-loader-1.3.5.RELEASE.jar!/中被找出
// 那么找出的资源对应的URL为jar:file:/Users/Format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar!/lib/spring-boot-loader-1.3.5.RELEASE.jar!/org/springframework/boot/loader/JarLauncher.class
Resource res = ucp.getResource(path, false);
if (res != null) { // 找到了资源
try {
return defineClass(name, res);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
} else { // 找不到资源的话直接抛出ClassNotFoundException异常
throw new ClassNotFoundException(name);
}
}
}, acc);
} catch (java.security.PrivilegedActionException pae) {
throw (ClassNotFoundException) pae.getException();
}
}

下面是LaunchedURLClassLoader的一个测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 注册org.springframework.boot.loader.jar.Handler URL协议处理器
JarFile.registerUrlProtocolHandler();
// 构造LaunchedURLClassLoader类加载器,这里使用了2个URL,分别对应jar包中依赖包spring-boot-loader和spring-boot,使用 "!/" 分开,需要org.springframework.boot.loader.jar.Handler处理器处理
LaunchedURLClassLoader classLoader = new LaunchedURLClassLoader(
new URL[] {
new URL("jar:file:/Users/Format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar!/lib/spring-boot-loader-1.3.5.RELEASE.jar!/")
, new URL("jar:file:/Users/Format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar!/lib/spring-boot-1.3.5.RELEASE.jar!/")
},
LaunchedURLClassLoaderTest.class.getClassLoader());

// 加载类
// 这2个类都会在第二步本地查找中被找出(URLClassLoader的findClass方法)
classLoader.loadClass("org.springframework.boot.loader.JarLauncher");
classLoader.loadClass("org.springframework.boot.SpringApplication");
// 在第三步使用默认的加载顺序在ApplicationClassLoader中被找出
classLoader.loadClass("org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration");

Spring Boot Loader的作用

SpringBoot在可执行jar包中定义了自己的一套规则,比如第三方依赖jar包在/lib目录下,jar包的URL路径使用自定义的规则并且这个规则需要使用org.springframework.boot.loader.jar.Handler处理器处理。它的Main-Class使用JarLauncher,如果是war包,使用WarLauncher执行。这些Launcher内部都会另起一个线程启动自定义的SpringApplication类。

包外指定配置文件

修改pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 不拷贝资源文件
<resources>
<resource>
<directory>src/main/resources</directory>
<excludes>
<exclude>*</exclude>
</excludes>
<filtering>true</filtering>
</resource>
</resources>

// 修改打包方式
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<layout>ZIP</layout>
</configuration>
</plugin>

打包结果比对

配置前

1
2
Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: com.chinaunicom.gateway.GatewayApplication

配置后

1
2
Main-Class: org.springframework.boot.loader.PropertiesLauncher
Start-Class: com.chinaunicom.gateway.GatewayApplication

发现是类加载器变了,此时启动类为PropertiesLauncher。文章开头可见

同时,启动方式变更

1
java -Dloader.path=your_config_path -jar xxx.jar

用途:

  • 搜索引擎索引与索引拒绝
  • 冒充百度,优化爬虫效果

1. 百度爬虫UA汇总

Name of Products User-agent
PC search Baiduspider
Mobile search Baiduspider
Image search Baiduspider-image
Video search Baiduspider-video
News search Baiduspider-news
Baidu bookmark Baiduspider-favo
Union baidu Baiduspider-cpro
Business search Baiduspider-ads
other search Baiduspider

如下:

1
“Mozilla/5.0 (compatible; Baiduspider/2.0;+http://www.baidu.com/search/spider.html)”

设置user-agent是标识百度爬虫的一种基础方式。

如果平台以该方法验证爬虫,trick it !

2. 反向DNS查找

例(linux):

1
2
3
4
$ host 123.125.66.120

120.66.125.123.in-addr.arpa domain name pointer
Baiduspider-123-125-66-120.crawl.baidu.com.

百度爬虫来源IP,域名*.baidu.com or *.baidu.jp

如果平台以该方法验证爬虫,基本无解。

除非你能做到DNS污染

3. 关于robots.txt优化

君子协定

正规军的爬虫,会先爬去服务根路径下,该文件。

根据文件定义的规则,爬取网站

1
2
3
4
5
6
7
## 拒绝Baiduspider的所有访问
User-agent: Baiduspider
Disallow: /

## 允许Baiduspider-image访问路径/image/
User-agent: Baiduspider-image
Allow: /image/