欢迎光临
我们一直在努力

老板说“把系统升级到https”,我用一个脚本实现了,而且永久免费!​

lei阅读(2)

正文

现在很多站长都会考虑将自己的站点从http升级到https,不仅是基于安全的考虑,有的也是因为第三方平台的限制,如谷歌浏览器会将http站点标记为不安全的站点,微信平台要求接入的微信小程序必须使用https等。

那如何将一个http站点升级为https站点呢?

http与https的区别

为了数据传输的安全,https在http的基础上加入了ssl协议,ssl协议依靠证书来验证服务器的身份,并为浏览器和服务器之间的通信加密。要想将http升级为https,只需要给http站点增加一个CA证书即可。

目前获取CA证书有两种途径:

  1. 购买收费的CA证书
  2. 获取免费的证书

收费的CA证书各大服务提供商都有卖,如阿里云、腾讯云等。

image

【系统架构】如何升级到https?一个脚本帮你搞定,且永久免费

收费的证书不便宜,从阿里云官方网站看,它的价格可以从几千元到上万元不等。

image
《2020最新Java基础精讲视频教程和学习路线!》

【系统架构】如何升级到https?一个脚本帮你搞定,且永久免费

这对于小公司平台,甚至是个人站点来说,是一个不小的开支。

Letsencrypt是一个免费、自动化和开放的证书颁发机构,其颁发的证书一次有效期为三个月,但是只要能持续更新,基本可以永久使用。

今天推荐的这个脚本acme.sh,实现了 acme 协议, 可以帮你持续自动从Letsencrypt更新CA证书。下载地址如下:

github.com/Neilpang/ac…

安装 acme.sh

安装acme.sh很简单,一个命令即可:

curl get.acme.sh | sh

普通用户和 root 用户都可以安装使用。安装过程进行了以下几步:

1、把acme.sh安装到你的home目录下:

~/.acme.sh/

并创建 一个 bash 的 alias,方便你使用:alias acme.sh=~/.acme.sh/acme.sh

2、自动为你创建 cronjob,每天 0:00 点自动检测所有的证书。如果快过期了,需要更新,则会自动更新证书,安装过程不会污染已有的系统任何功能和文件,所有的修改都限制在安装目录中:~/.acme.sh/

生成证书

acme.sh 实现了 acme 协议支持的所有验证协议, 一般有两种方式验证:http 和 dns 验证。

1、http 方式需要在你的网站根目录下放置一个文件, 来验证你的域名所有权,完成验证,然后就可以生成证书了。

acme.sh –issue -d mydomain.com -d www.mydomain.com –webroot /home/wwwroot/mydomain.com/

acme.sh 会全自动的生成验证文件, 并放到网站的根目录,然后自动完成验证。最后会聪明的删除验证文件,整个过程没有任何副作用。

如果你用的是apache服务器,acme.sh 还可以智能的从 apache的配置中自动完成验证,你不需要指定网站根目录:

acme.sh –issue -d mydomain.com –apache

如果你用的是nginx服务器,或者反代,acme.sh还可以智能的从 nginx的配置中自动完成验证,你不需要指定网站根目录:

acme.sh –issue -d mydomain.com –nginx

注意:无论是 apache 还是 nginx 模式,acme.sh在完成验证之后,会恢复到之前的状态,都不会私自更改你本身的配置。好处是你不用担心配置被搞坏,但也有一个缺点,你需要自己配置 ssl 的配置,否则,只能成功生成证书,你的网站还是无法访问https。但是为了安全,你还是自己手动改配置吧。

如果你还没有运行任何 web 服务,80 端口是空闲的, 那么 acme.sh 还能假装自己是一个webserver, 临时听在80 端口,完成验证:

acme.sh –issue -d mydomain.com –standalone

2、dns 方式,在域名上添加一条 txt 解析记录,验证域名所有权。

这种方式的好处是,你不需要任何服务器,不需要任何公网 ip,只需要 dns 的解析记录即可完成验证。不过,坏处是,如果不同时配置 Automatic DNS API,使用这种方式 acme.sh 将无法自动更新证书,每次都需要手动再次重新解析验证域名所有权。

acme.sh –issue –dns -d mydomain.com

然后,acme.sh 会生成相应的解析记录显示出来,你只需要在你的域名管理面板中添加这条 txt 记录即可。

等待解析完成之后, 重新生成证书:

acme.sh –renew -d mydomain.com

注意:第二次这里用的是 –renew

dns 方式的真正强大之处在于可以使用域名解析商提供的 api 自动添加 txt 记录完成验证。

acme.sh 目前支持 cloudflare, dnspod, cloudxns, godaddy 以及 ovh 等数十种解析商的自动集成。

copy/安装 证书

前面证书生成以后,接下来需要把证书 copy 到真正需要用它的地方。

注意:默认生成的证书都放在安装目录下:~/.acme.sh/,请不要直接使用此目录下的文件。例如,不要直接让 nginx/apache 的配置文件使用这下面的文件。这里面的文件都是内部使用,而且目录结构可能会变化。

正确的使用方法是使用 –installcert 命令,并指定目标位置,然后证书文件会被copy到相应的位置,例如:

acme.sh --installcert -d <domain>.com 

--key-file /etc/nginx/ssl/<domain>.key 

--fullchain-file /etc/nginx/ssl/fullchain.cer 

--reloadcmd "service nginx force-reload"
复制代码

一个小提醒,这里用的是 service nginx force-reload,不是 service nginx reload,据测试, reload并不会重新加载证书,所以用的 force-reload。

Nginx 的配置 ssl_certificate 使用 /etc/nginx/ssl/fullchain.cer,而非 /etc/nginx/ssl/.cer ,否则 SSL Labs 的测试会报 Chain issues Incomplete 错误。

–installcert命令可以携带很多参数,来指定目标文件。并且可以指定 reloadcmd, 当证书更新以后,reloadcmd会被自动调用,让服务器生效。

值得注意的是,这里指定的所有参数都会被自动记录下来,并在将来证书自动更新以后,被再次自动调用。

更新证书

目前证书在 60 天以后会自动更新,你无需任何操作。今后有可能会缩短这个时间,不过都是自动的,你不用关心。

更新 acme.sh

目前由于 acme 协议和 Letsencrypt CA 都在频繁的更新,因此 acme.sh 也经常更新以保持同步。

升级 acme.sh 到最新版 :

acme.sh –upgrade

如果你不想手动升级, 可以开启自动升级:

acme.sh –upgrade –auto-upgrade

之后, acme.sh 就会自动保持更新了。

你也可以随时关闭自动更新:

acme.sh –upgrade –auto-upgrade 0

出错怎么办:

如果出错, 请添加 debug log:

acme.sh –issue ….. –debug

或者:

acme.sh –issue ….. –debug 2

链接:https://juejin.cn/post/686583…

https://segmentfault.com/a/1190000038331121

Java高级特性-注解:注解实现Excel导出功能

lei阅读(3)

注解是 Java 的一个高级特性,Spring 更是以注解为基础,发展出一套“注解驱动编程”。

这听起来高大上,但毕竟是框架的事,我们也能用好注解吗?

的确,我们很少有机会自己写注解,导致我们搞不清楚注解是怎么回事,更别提用好注解了。

既然这样,我们就从具体的工作出发,开发一个 Excel 导出功能。我相信,你在搞懂这个例子后,就能明白注解是怎么个用法。

Excel 导出-需求拆解

在后台管理系统中,常常需要把数据导出 Excel 表。

比如,在双十一过后,销售部要把商品订单录入到 Excel 表,财务部要把支付订单录入到 Excel 表,然后各部门汇总分析,最后找个时间讨论怎么改善公司的服务。

你想呀,双十一的订单成千上万,靠人工录入,少说也要花三四天,而且还特别容易出错。所以,你必须开发 Excel 导出功能。

那么,具体怎么做呢?

上次我们提到,注解想发挥作用,有三个要素:定义、使用、读取。这次,我们就利用注解的三个特性,来实现 Excel 导出功能,设计过程是这样的。

第一步,我们要创建不同的 Excel 模型。双十一过后,销售部要订单数据,财务部要支付数据,两个部门要的 Excel 表肯定也不一样,这就得帮每个部门创建不同的 Excel 模型,他们拿到想要的数据。

第二步,我们要根据 Excel 模型,来导出 Excel 表。

看到这,你应该明白 Excel 导出的设计过程了。接下来,我们就来一步步实现这个功能。

创建 Excel 模型

创建 Excel 模型,涉及到注解三要素中的定义、使用。

首先,定义 Excel 注解,我们直接看关键代码。

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelField {

    /**
     * 导出字段标题
     */
    String title();
    
    /**
     * 导出字段排序(升序)
     */
    int sort() default 0;
    
    /**
     * 对齐方式(0:自动;1:靠左;2:居中;3:靠右)
     */
    int align() default 0;    

}

这里用到了两个元注解@Retention@Target@Target代表这个注解只能放在成员变量上;@Retention代表这个注解要加载到 JVM 内存,我们可以用反射来读取注解。

此外,注解还有 3 个成员变量,分别对应:Excel 的字段标题、字段排序、对齐方式,方便大家微调表格。到了这,定义 Excel 注解就完成了。

接下来,使用注解,我们还是直接看代码。

public class OrderModel {
    @ExcelField(title = "订单号", align = 2, sort = 20)
    private String orderNo;

    @ExcelField(title = "金额", align = 2, sort = 20)
    private String amount;
    
    // 创建时间
    private Date createTime;
    
    // 省略 getter/setter 方法
}

订单模型有 3 个字段:订单号、金额、创建时间,但这里注解只加到订单号、金额上,表示这两个字段会导出 Excel 表,而创建时间会忽略,你可以看看这副图片。

excel模型导出-效果图

至此,我们完成了定义注解、使用注解,得到了一个 Excel 模型。但要想实现导出功能,还必须根据这个模型,生成出 Excel 表。

读取 Excel 模型

读取 Excel 模型,涉及到注解三要素中的读取。 我们需要读取注解,生成 Excel 表,这主要分成 3 个步骤:初始化 Excel 表对象—>写入数据到 Excel 表对象—>输出文件。

第一步,初始化 Excel 表对象。在这一步中,我们要根据 Excel 模型,生成一个 Excel 表对象,要创建这几个东西:标题、表头、样式等等。我们来看代码。

public class ExcelExporter {

    // ...省略无数代码

    /***************************** 初始化 Excel 表对象 ****************************/
    /**
     * 构造函数
     * @param title 表格标题,传“空值”,表示无标题
     * @param cls   excel模型对象
     */
    public ExcelExporter(String title, Class<?> cls) {
        // 获取注解list
        Field[] fs = cls.getDeclaredFields();
        for (Field f : fs) {
            ExcelField ef = f.getAnnotation(ExcelField.class);
            if (ef != null) {
                annotationList.add(new Object[]{ef, f});
            }
        }
        annotationList.sort(comparing(o -> ((ExcelField) o[0]).sort()));
        // 通过注解获取表头
        List<String> headerList = new ArrayList<>();
        for (Object[] os : annotationList) {
            String t = ((ExcelField) os[0]).title();
            headerList.add(t);
        }
        // 初始化excel表:创建excel表、添加表标题、创建表头等等
        initialize(title, headerList);
    }
}

在初始化的时候,我们先从 Excel 模型对象中读取注解,获得一个注解列表;然后,再从注解列表中,读取 title-字段标题;最后,再初始化 Excel 表对象,包括:创建 Excel 表对象、添加表标题、创建表头、添加样式。

第二步,写入数据到 Excel 表对象。在这一步中,我们要把 Java 的列表数据写到 Excel 表对象里,让这些数据能变成 Excel 表的一行行信息。还是来看代码。

public class ExcelExporter {

    /***************************** 初始化 Excel 表对象 ****************************/
    // ...省略无数代码

    /***************************** 写入数据到 Excel 表对象 ****************************/
    /**
     * 写入数据
     * @return list 数据列表
     */
    public <E> ExcelExporter setDataList(List<E> list) {
        for (E dataObj : list) {
            // 添加行
            Row row = this.addRow();

            // 获取数据,并写入单元格
            int cellNo = 0;
            for (Object[] os : annotationList) {
                // 获取成员变量的值
                Object value = null;
                try {
                    value = Reflections.invokeGetter(dataObj, ((Field) os[1]).getName());
                } catch (Exception ex) {
                    log.info(ex.toString());
                    value = "";
                }
                if (value == null) {
                    value = "";
                }

                // 写入单元格
                ExcelField ef = (ExcelField) os[0];
                this.addCell(row, cellNo++, value, ef.align());
            }
        }
        return this;
    }
}

我们先传入一个数据列表 dataList,然后用循环来遍历 dataList,在这个循环中,我们不断把数据写进 Excel 表对象里,具体操作是:创建了一个空白行,利用注解获取成员变量里的值,最后写进 Excel 表的单元格里。

第三步,输出文件。在这一步中,就是 Excel 表对象变成一个文件,来看下最后的代码吧。

public class ExcelExporter {

    /***************************** 初始化 Excel 表对象 ****************************/
    // ...省略无数代码

    /***************************** 写入数据到 Excel 表对象 ****************************/
    // ...省略无数代码

    /***************************** 输出相关 ****************************/
    /**
     * 输出到文件
     * @param fileName 输出文件名,加上绝对路径
     */
    public ExcelExporter writeFile(String fileName) throws IOException {
        FileOutputStream os = new FileOutputStream(fileName);
        this.write(os);
        return this;
    }
}

输出文件就没什么好说的了,就是指定文件名,然后把文件输出到指定的地方。

到了这,读取 Excel 模型就完成了。

当然,读取 Excel 模型涉及到注解的读取,这是最难理解的地方,因为读取注解要用到 Java 另一个高级特性—反射。而且,注解一般是用来简化业务,如果你对业务没有深刻的了解,是很难用好的。

限于篇幅,我只讲了最核心的代码,项目的完整代码放在文末的链接上,大家可以好好看看。

写在最后

注解想发挥作用,有三个要素:定义、使用、读取。这篇文章利用了注解的三要素,实现了 Excel 导出功能。

这分成两步。第一步,创建 Excel 模型,这涉及到注解三要素中的定义、使用;第二步,读取 Excel 模型,这涉及到注解三要素中的读取。

总之,注解一般用来简化业务,你要想用好注解,不但得熟练掌握 Java 的高级用法,还得对业务有深刻的理解。

文章演示代码:点击跳转

https://segmentfault.com/a/1190000038327072

php8注解详解

lei阅读(5)

注解语法

#[Route]
#[Route()]
#[Route("/path", ["get"])]
#[Route(path: "/path", methods: ["get"])]

其实语法跟实例化类非常相似,只是少了个 new 关键词而已。

要注意的是, 注解名不能是变量,只能是常量或常量表达式

//实例化类
$route = new Route(path: "/path", methods: ["get"]);

(path: "/path", methods: ["get"])php8 的新语法,在传参的时候可以指定参数名,不按照形参的顺序传参。

注解类作用范围

在定义注解类时,你可以使用内置注解类 #[Attribute] 定义注解类的作用范围,也可以省略,由 PHP 动态地根据使用场景自动定义范围

注解作用范围列表:

  • Attribute::TARGET_CLASS
  • Attribute::TARGET_FUNCTION
  • Attribute::TARGET_METHOD
  • Attribute::TARGET_PROPERTY
  • Attribute::TARGET_CLASS_CONSTANT
  • Attribute::TARGET_PARAMETER
  • Attribute::TARGET_ALL
  • Attribute::IS_REPEATABLE

在使用时, #[Attribute] 等同于 #[Attribute(Attribute::TARGET_ALL)],为了方便,一般使用前者。

1~7都很好理解,分别对应类、函数、类方法、类属性、类常量、参数、所有,前6项可以使用 | 或运算符随意组合,比如Attribute::TARGET_CLASS | Attribute::TARGET_FUNCTION。(Attribute::TARGET_ALL包含前6项,但并不包含 Attribute::IS_REPEATABLE)。

Attribute::IS_REPEATABLE 设置该注解是否可以重复,比如:

class IndexController
{
    #[Route('/index')]
    #[Route('/index_alias')]
    public function index()
    {
        echo "hello!world" . PHP_EOL;
    }
}

如果没有设置 Attribute::IS_REPEATABLERoute不允许使用两次。

上述提到的,如果没有指定作用范围,会由 PHP 动态地确定范围,如何理解?举例:

<?php

class Deprecated
{

}

class NewLogger
{
    public function newLogAction(): void
    {
        //do something
    }

    #[Deprecated('oldLogAction已废弃,请使用newLogAction代替')]
    public function oldLogAction(): void 
    {

    }
}

#[Deprecated('OldLogger已废弃,请使用NewLogger代替')]
class OldLogger
{

}

上述的自定义注解类 Deprecated 并没有使用内置注解类 #[Attribute] 定义作用范围,因此当它修饰类 OldLogger 时,它的作用范围被动态地定义为 TARGET_CLASS。当它修饰方法 oldLogAction 时,它的作用范围被动态地定义为 TARGET_METHOD一句话概括,就是修饰哪,它的作用范围就在哪

需要注意的是, 在设置了作用范围之后,在编译阶段,除了内置注解类 #[Attribute],自定义的注解类是不会自动检查作用范围的。除非你使用反射类 ReflectionAttributenewInstance 方法。

举例:

<?php

#[Attribute]
function foo()
{

}

这里会报错 Fatal error: Attribute "Attribute" cannot target function (allowed targets: class),因为内置注解类的作用范围是 TARGET_CLASS,只能用于修饰类而不能是函数,因为内置注解类的作用范围仅仅是 TARGET_CLASS,所以也不能重复修饰

而自定义的注解类,在编译时是不会检查作用范围的。

<?php 

#[Attribute(Attribute::TARGET_CLASS)]
class A1
{

}

#[A1] 
function foo() {}

这样是不会报错的。那定义作用范围有什么意义呢?看一个综合实例。

<?php 

#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_FUNCTION | Attribute::IS_REPEATABLE)]
class Route
{
    protected $handler;

    public function __construct(
        public string $path = '',
        public array $methods = []
    ) {}

    public function setHandler($handler): self
    {
        $this->handler = $handler;
        return $this;
    }

    public function run()
    {
        call_user_func([new $this->handler->class, $this->handler->name]);
    }
}

class IndexController
{
    #[Route(path: "/index_alias", methods: ["get"])]
    #[Route(path: "/index", methods: ["get"])]
    public function index(): void
    {
        echo "hello!world" . PHP_EOL;
    }

    #[Route("/test")]
    public function test(): void 
    {
        echo "test" . PHP_EOL;
    }
}

class CLIRouter
{
    protected static array $routes = [];

    public static function setRoutes(array $routes): void
    {
        self::$routes = $routes;
    }

    public static function match($path)
    {
        foreach (self::$routes as $route) {
            if ($route->path == $path) {
                return $route;
            }
        }

        die('404' . PHP_EOL);
    }
}

$controller = new ReflectionClass(IndexController::class);
$methods = $controller->getMethods(ReflectionMethod::IS_PUBLIC);

$routes = [];
foreach ($methods as $method) {
    $attributes = $method->getAttributes(Route::class);

    foreach ($attributes as $attribute) {
        $routes[] = $attribute->newInstance()->setHandler($method);
    }
}

CLIRouter::setRoutes($routes);
CLIRouter::match($argv[1])->run();
php test.php /index
php test.php /index_alias
php test.php /test

在使用 newInstance 时,定义的作用范围才会生效,检测注解类定义的作用范围和实际修饰的范围是否一致,其它场景并不检测。

注解命名空间

<?php

namespace {
    function dump_attributes($attributes) {
        $arr = [];
        foreach ($attributes as $attribute) {
            $arr[] = ['name' => $attribute->getName(), 'args' => $attribute->getArguments()];
        }
        var_dump($arr);
    }
}

namespace Doctrine/ORM/Mapping {
    class Entity {
    }
}

namespace Doctrine/ORM/Attributes {
    class Table {
    }
}

namespace Foo {
    use Doctrine/ORM/Mapping/Entity;
    use Doctrine/ORM/Mapping as ORM;
    use Doctrine/ORM/Attributes;

    #[Entity("imported class")]
    #[ORM/Entity("imported namespace")]
    #[/Doctrine/ORM/Mapping/Entity("absolute from namespace")]
    #[/Entity("import absolute from global")]
    #[Attributes/Table()]
    function foo() {
    }
}

namespace {
    class Entity {}

    dump_attributes((new ReflectionFunction('Foo/foo'))->getAttributes());
}

//输出:

array(5) {
  [0]=>
  array(2) {
    ["name"]=>
    string(27) "Doctrine/ORM/Mapping/Entity"
    ["args"]=>
    array(1) {
      [0]=>
      string(14) "imported class"
    }
  }
  [1]=>
  array(2) {
    ["name"]=>
    string(27) "Doctrine/ORM/Mapping/Entity"
    ["args"]=>
    array(1) {
      [0]=>
      string(18) "imported namespace"
    }
  }
  [2]=>
  array(2) {
    ["name"]=>
    string(27) "Doctrine/ORM/Mapping/Entity"
    ["args"]=>
    array(1) {
      [0]=>
      string(23) "absolute from namespace"
    }
  }
  [3]=>
  array(2) {
    ["name"]=>
    string(6) "Entity"
    ["args"]=>
    array(1) {
      [0]=>
      string(27) "import absolute from global"
    }
  }
  [4]=>
  array(2) {
    ["name"]=>
    string(29) "Doctrine/ORM/Attributes/Table"
    ["args"]=>
    array(0) {
    }
  }
}

跟普通类的命名空间一致。

其它要注意的一些问题

  • 不能在注解类参数列表中使用 unpack 语法。
<?php

class IndexController
{
    #[Route(...["/index", ["get"]])]
    public function index()
    {

    }
}

虽然在词法解析阶段是通过的,但是在编译阶段会抛出错误。

  • 在使用注解时可以换行
<?php 

class IndexController
{
    #[Route(
        "/index",
        ["get"]
    )]
    public function index()
    {

    }
}
  • 注解可以成组使用
<?php

class IndexController
{
    #[Route(
        "/index",
        ["get"]
    ), Other, Another]
    public function index()
    {

    }
}
  • 注解的继承

注解是可以继承的,也可以覆盖。

<?php

class C1
{
    #[A1]
    public function foo() { }
}

class C2 extends C1
{
    public function foo() { }
}

class C3 extends C1
{
    #[A1]
    public function bar() { }
}

$ref = new /ReflectionClass(C1::class);
print_r(array_map(fn ($a) => $a->getName(), $ref->getMethod('foo')->getAttributes()));

$ref = new /ReflectionClass(C2::class);
print_r(array_map(fn ($a) => $a->getName(), $ref->getMethod('foo')->getAttributes()));

$ref = new /ReflectionClass(C3::class);
print_r(array_map(fn ($a) => $a->getName(), $ref->getMethod('foo')->getAttributes()));

C3 继承了 C1foo 方法,也继承了 foo 的注解。而 C2 覆盖了 C1foo 方法,因此注解也就不存在了。

https://segmentfault.com/a/1190000038338121

用Python爬取英雄联盟(lol)全部皮肤

lei阅读(2)

小三:“怎么了小二?一副无精打采的样子!”

image

小二:“唉!别提了,还不是最近又接触了一个叫英雄联盟的游戏,游戏中很多皮肤都需要花钱买,但是我钱不够呀…”

image

小三:“咋得,钱攒够了你还要买呀?还吃不吃饭了?!要我说,你干脆将英雄的炫彩皮肤都爬下来欣赏一下得了,饭钱还给你省下了。”

小二:“你说的也对,毕竟吃饭更重要,那我还是爬取皮肤欣赏一下算了。”

image

首先,我们打开英雄联盟官网主页,网址为:https://lol.qq.com/main.shtml,然后向下拉,可以看到英雄列表,如图所示:

image

接着随意选一个英雄点击进入看一下,如图所示:

image

再点击鼠标右键,接着选择检查,看一下皮肤的 URL,如图所示:

image

通过观察,可以发现英雄皮肤 URL 组成方式为:https://game.gtimg.cn/images/lol/act/img/skin/big + 英雄id + 皮肤id.jpg

我们先看皮肤id,也就是看皮肤的个数,选择开发者工具的Network项,之后刷新一下页面,可以发现有一个17.js的请求,17实际就是英雄id,如图所示:

image

再选择Response项看一下相应数据,如图所示:

image

我们可以看到数据都显示在了一行,看着不太方便,我们将其格式化看一下,如图所示:

image

通过观察,可以发现获取指定英雄皮肤id的 URL 就是:https://game.gtimg.cn/images/lol/act/img/js/hero/ + 英雄id.js,获取皮肤id及下载皮肤图片的代码实现如下:

hero_skin_url = 'https://game.gtimg.cn/images/lol/act/img/js/hero/' + hero_id + '.js'
# 通过 url 获取英雄的皮肤数量
skin_text = requests.get(hero_skin_url).text
skin_json = json.loads(skin_text)
skin_list = skin_json['skins']
# 获取皮肤名
hero_skins.clear()
for skin in skin_list:
    hero_skins.append(skin['name'].replace('/', '').replace('//', '').replace(' ', ''))
# 皮肤数量
skins_num = len(hero_skins)
s = ''
for i in tqdm(range(skins_num), desc='【' + hero_name + '】皮肤下载'):
    if len(str(i)) == 1:
        s = '00' + str(i)
    elif len(str(i)) == 2:
        s = '0' + str(i)
    elif len(str(i)) == 3:
        pass
    try:
        # 拼接指定皮肤的 url
        skin_url = 'https://game.gtimg.cn/images/lol/act/img/skin/big' + hero_id + '' + s + '.jpg'
        img = requests.get(skin_url)
    except:
        # 没有炫彩皮肤 url 则跳过
        continue
    # 保存皮肤图片
    if img.status_code == 200:
        with open(hero_skins[i] + '.jpg', 'wb') as f:
            f.write(img.content)

现在就差英雄id参数的获取了,我们接着看如何获取全部的英雄id,返回到 https://lol.qq.com/main.shtml页面,打开开发者工具并选择Network,然后刷新页面,我们可以观察到有一个hero_list.js的请求,如图所示:

image

皮肤id的获取基本类似,通过这个请求就可以获取到英雄的全部英雄id,代码实现如下:

url = 'https://game.gtimg.cn/images/lol/act/img/js/heroList/hero_list.js'
hero_text = requests.get(url).text
# 转为 json 格式
hero_json = json.loads(hero_text)['hero']
path = os.getcwd()
# 获取当前文件夹路径
workspace = os.getcwd()
# 皮肤路径
skin_path = "{}//{}".format(workspace, 'skins')
# 遍历列表
for hero in hero_json:
    # 将每一个英雄的 id、name 放入一个字典中
    hero_dict = {'id': hero['heroId'], 'name': hero['name']}
    # 放入列表
    heros.append(hero_dict)

我们可以看出:代码中除了英雄id,还获取了英雄name,并将每一个英雄的idname放在了一个字典中,又将所有英雄对应的字典放在了列表中。

最后,我们看一下下载效果:

image

源码在公众号 Python小二 后台回复 201130 获取。

https://segmentfault.com/a/1190000038333555

太赞了,GitHub 上标星 115k+ 的 Java 教程!

lei阅读(2)

额,大家好啊,我是浑元形意门的学员小二哥(忍住别笑)。就在五个半小时前,有个小伙伴私信我说,“哥,能推荐一些高质量的 Java 教程吗?”

我回他,“教妹学 Java 不香吗?通俗易懂,风趣幽默,学起来多带劲……”

没等我说完,他“啪”的一下就回过来了新的信息,很快啊。“哥,你那教程才更新到第 17 讲,我前后看了不下 5 遍了,确实学到了很多。但我还想更进一步啊,赶紧点,有没有其他的教程,等不及了!”

我笑了,这位小伙伴也太心急了。我就回他,“小伙子,你得讲点武德啊,高质量的 Java 教程它需要时间啊,要知道‘慢工出细活’。”

“哥,你别啰嗦了行吗?你就说有没有吧!”他似乎急了,要对我发脾气。于是我耐心地劝这位小伙伴,要耗子尾汁啊,要讲武德啊,要以和为贵啊。。。。。。

扒拉扒拉说了半小时,终于安抚好了小伙伴的情绪。我打开 GitHub,输入 Java 关键字,开始寻找那些优质的 Java 项目了。

两个小时后,还真的找到了一份, GitHub 上标星 115k,内容还真的不错,来简单看一下目录。包括 Java 基础、Java 容器、Java 并发、Java 虚拟机和 Java IO,非常全面。

又花了三个小时把这份教程整理成 PDF 后,我发给了小伙伴,他连忙发私信给我说,“哥,对不起,对不起,是我不懂规矩,你也太用心了,这份教程的质量真的高,不服不行!”

如果你也对这份 PDF 感兴趣的话,可以通过下面的方式获取。

链接:https://pan.baidu.com/s/1rT0l5ynzAQLF–efyRHzQw 密码:dz95

我贴一下这份教程的 GitHub 地址:

https://github.com/CyC2018/CS…

小伙伴们方便的话,可以给作者点个 Star 鼓励一下。我自己也是 JavaBooks 项目的作者,深知开源的不易,需要耗费大量的心血。你们一个小小的鼓励,也许就是开源作者们创作的最强动力(逃。

https://segmentfault.com/a/1190000038336570

面试题:JS 获取某月的天数

lei阅读(2)

获取某个月的天数,这个题一般都是在学 switch(){} 一章的案例。

我们一直解题的方案是根据一个顺口溜一三五七八十腊,31天永不差,四六九十一,每月30天,惟有二月二十八,闰年要把日加一。(我搜的,具体怎么背我忘了

这里面还有个闰年的计算规则:四年一闰,百年不闰,四百年再闰。可以被4整除,但是不能被100整除,除非可以被400整除

image.png

是不是被上面的魔性概念征服了?接来下我们来实现一下

面试题:JS 获取某月的天数

先上测试用例

function getMonthCountDay(year, month){
    // year 为年份,month 为月份
}
[[2000,2],[2000,1],[2000,3],[2000,4],[2100,2],[2100,1],[2100,3],[2100,4],[2104,2],[2104,1],[2104,3],[2104,4],[2105,2],[2105,1],[2105,3],[2105,4],].map(v=>`${v} => ${getMonthCountDay.apply(null,v)}天`)

基础版本

根据我们的顺口溜我们来写一下

function getMonthCountDay (year, month) {
  switch (month){
    case 1:
    case 3:
    case 5:
    case 7:
    case 8:
    case 10:
    case 12:
        return 31
    case 4:
    case 6:
    case 9:
    case 11:
        return 30
    case 2:
        return year%400==0?(29):(year%4!=0||year%100==0?28:29)
  }
}

很好写完了,除了代码看上去多了点,没别的毛病。

测试截图

image.png

借助 Date API 处理日期溢出特性(进位)

接下来就开始骚了

function getMonthCountDay (year, month) {
  return 32 - new Date(year, month-1, 32).getDate()
}

是不是想不到,这种方法写的一下就很少了。

image.png

测试截图

image.png

方案原理

js 中 Date 在处理时间的时候会做进位退位操作

image.png

借助 Date API 处理日期溢出特性(退位方案)

刚才是用的进位,然后减去多余的。现在我们改用退位

function getMonthCountDay (year, month) {
  return new Date(year, month, 0).getDate()
}

image.png

测试截图

image.png

方案原理

js 中 Date 在处理时间的时候会做进位退位操作

image.png

利用这个特性还可以做什么?

image.png

获取月初是周几

image.png

获取月末是周几

image.png

https://segmentfault.com/a/1190000038295923

聊聊golang的error增强

lei阅读(5)

本文主要研究一下golang的error增强

Errors in Go 1.13

golang的1.13版本对error进行了增强,主要是

  • 引入了Unwrap方法
  • 增加Is和As方法
  • fmt.Errorf支持%w来包装error

实例

package main

import (
    "errors"
    "fmt"
    "os"
)

var DemoErr = errors.New("test error stack")

// https://itnext.io/golang-error-handling-best-practice-a36f47b0b94c
func main() {
    if err := methodA(false); err != nil {
        fmt.Printf("%+v/n", err)
    }

    if err := methodA(true); err != nil {
        fmt.Printf("%+v/n", err)
        fmt.Printf("%+v/n", errors.Unwrap(err))
        fmt.Printf("%+v/n", errors.Unwrap(errors.Unwrap(err)))

        fmt.Println("errors.Is(err, DemoErr)=", errors.Is(err, DemoErr))
        fmt.Println("errors.As(err, &DemoErr)=", errors.As(err, &DemoErr))

        var pe *os.PathError
        fmt.Println("errors.Is(err, pe)=", errors.Is(err, pe))
        fmt.Println("errors.As(err, &pe)=", errors.As(err, &pe))
    }
}

func methodA(wrap bool) error {
    if err := methodB(wrap); err != nil {
        if wrap {
            return fmt.Errorf("methodA call methodB error: %w", err)
        }
        return err
    }
    return nil
}

func methodB(wrap bool) error {
    if err := methodC(); err != nil {
        if wrap {
            return fmt.Errorf("methodB call methodC error: %w", err)
        }
        return err
    }
    return nil
}

func methodC() error {
    return DemoErr
}

输出

test error stack
methodA call methodB error: methodB call methodC error: test error stack
methodB call methodC error: test error stack
test error stack
errors.Is(err, DemoErr)= true
errors.As(err, &DemoErr)= true
errors.Is(err, pe)= false
errors.As(err, &pe)= false

小结

  • wrap对error进行了包装,不过没有包含堆栈
  • Is会挨个unwrap去对error进行判断errors.Is function behaves like a comparison to a sentinel error
  • As类似类型断言 errors.As function behaves like a type assertion

doc

https://segmentfault.com/a/1190000038335171

同步 MySQL 数据至 Elasticsearch/Redis/MQ 等的五种方式

lei阅读(5)

同步 MySQL 数据至 Elasticsearch/Redis/MQ 等的五种方式

在实际应用中,我们经常需要把 MySQL 的数据同步至其它数据源,也就是在对 MySQL 的数据进行了新增、修改、删除等操作后,把该数据相关的业务逻辑变更也应用到其它数据源,例如:

  • MySQL -> Elasticsearch ,同步 ES 的索引
  • MySQL -> Redis ,刷新缓存
  • MySQL -> MQ (如 Kafka 等) ,投递消息

本文总结了五种数据同步的方式。

1. 业务层同步

业务层同步

由于对 MySQL 数据的操作也是在业务层完成的,所以在业务层同步操作另外的数据源也是很自然的,比较常见的做法就是在 ORM 的 hooks 钩子里编写相关同步代码。

这种方式的缺点是,当服务越来越多时,同步的部分可能会过于分散从而导致难以更新迭代,例如对 ES 索引进行不兼容迁移时就可能会牵一发而动全身。

2. 中间件同步

中间件同步

当应用架构演变为微服务时,各个服务里可能不再直接调用 MySQL ,而是通过一层 middleware 中间件,这时候就可以在中间件操作 MySQL 的同时同步其它数据源。

这种方式需要中间件去适配,具有一定复杂度。

3. 定时任务根据 updated_at 字段同步

定时任务根据 updated_at 同步

在 MySQL 的表结构里设置特殊的字段,如 updated_at(数据的更新时间),根据此字段,由定时任务去查询实际变更的数据,从而实现数据的增量更新。

这种方式你可以使用开源的 Logstash 去完成。

当然缺点也很明显,就是无法同步数据的删除操作。

4. 解析 binlog 同步

解析 binlog 同步

比如著名的 canal

通过伪装成 slave 去解析 MySQL 的 binary log 从而得知数据的变更。

这是一种业界比较成熟的方案。

这种方式要求你将 MySQL 的 binlog-format 设置为 ROW 模式。

5. 解析 binlog — mixed / statement 格式

MySQL 的 binlog 有三种格式:

  • ROW 模式,binlog 按行的方式去记录数据的变更;
  • statement 模式,binlog 记录的是 SQL 语句;
  • mixed 模式时,混合以上两种,记录的可能是 SQL 语句或者 ROW 模式的每行变更;

某些情况下,可能你的 MySQL binlog 无法被设置为 ROW 模式,这种时候,我们仍然可以去统一解析 binlog ,从而完成同步,但是这里解析出来的当然还是原始的 SQL 语句或者 ROW 模式的每行变更,这种时候是需要我们去根据业务解析这些 SQL 或者每行变更,比如利用正则匹配或者 AST 抽象语法树等,然后根据解析的结果再进行数据的同步。

这种方式的限制也很明显,一是需要自己适配业务解析 SQL ,二是批量更新这种场景可能很难处理,当然如果你的数据都是简单的根据主键进行修改或者删除则能比较好的适用。

结语

最后列举几个 binlog 解析的开源库:

公众号

https://segmentfault.com/a/1190000038334381

2020年,值得收藏的50多种Kubernetes工具

lei阅读(5)

在过去几年,Kubernetes 在容器编排市场独占鳌头。自 2016 年以来,Docker Swarm 就退出了主要竞争者的行列,并且像 AWS 一样承诺对 K8s 进行支持和集成,换句话说,它承认了失败。

目前,由 Kubernetes 作为首选的容器解决方案已迅速普及,因此,这里列出了所有 K8s 增强工具的综合清单,以进一步提升您的开发工作。

Kubernetes 集群部署

Kubespray

Kubespray 为 Kubernetes 的部署和配置提供了一组 Ansible 角色。Kubespray 支持 AWS、GCE、Azure、OpenStack 或裸机 IaaS 平台。Kubespray 是具有开放开发模型的开源项目。由于无需使用其他工具进行配置和编排,因此对了解 Ansible 的人来说,该工具是一个不错的选择。Kubespray 基于 kubeadm 开发。

地址:https://github.com/kubernetes…
价格:免费

Minikube

Minikube 允许你在本地安装和试用 Kubernetes。该工具是探索 Kubernetes 的一个很好的起点,它可以让你在笔记本电脑上的虚拟机(VM)中轻松启动单节点 Kubernetes 集群。Minikube 在 Windows、Linux 和 OSX 上可用。只需 5 分钟,你就能探索 Kubernetes 的主要功能。只需一个命令即可直接启动 Minikube 控制台。

地址:https://github.com/kubernetes…
价格:免费

Kubeadm

自 1.4 版本以来,Kubeadm 成为 Kubernetes 的发行工具。该工具是在已有基础架构上搭建 Kubernetes 集群的最佳实践。但是,Kubeadm 无法为您提供基础架构。它的主要优势是能够在任何地方部署最小的可用 Kubernetes 集群。不过,Kubeadm 不包含其他附加组件和网络组件,因此你需要手动安装这些组件(或使用其他工具安装)。

地址:https://github.com/kubernetes…
价格:免费

Kops

Kops 可帮助你通过命令行创建、销毁、升级和维护生产级别的高可用 Kubernetes 集群。目前 Kops 正式版支持 Amazon Web Services(AWS),beta 版提供 GCE 支持,alpha 版提供 VMware vSphere 支持,其他平台的支持也在计划中。Kops 允许您控制整个 Kubernetes 集群生命周期——从基础架构配置到集群删除。

地址:https://github.com/kubernetes…
价格:免费

Bootkube

Bootkube 是一个用于启动自托管 Kubernetes 集群的优秀工具。它可以帮助您设置一个临时的 Kubernetes 控制平面,该临时控制平面将一直运行到自托管控制平面能够处理请求为止。

地址:https://github.com/kubernetes…
价格:免费

Kubernetes on AWS (Kube-AWS)

Kube-AWS 是 CoreOS 提供的控制台工具,可使用 AWS CloudFormation 部署功能齐全的 Kubernetes 集群。Kube-AWS 允许您部署传统的 Kubernetes 集群,并自动为每个 K8s 服务提供原生 AWS 功能(例如 ELB、S3 和自动扩展等)。

地址:https://github.com/kube-aws/k…
价格:免费

JAAS

JAAS(Juju 即服务)可简化目前复杂软件的配置、扩展和操作。Juju 可以部署在任何地方,包括公共云和私有云。JAAS 可将您的工作负载部署到您选择的云平台中。

地址:https://jaas.ai/

Conjure-up

Conjure-up 是另一种 Canonical 产品,它允许您使用一些简单的命令在 Ubuntu 上部署 Kubernetes 的 Canonical 发行版。它支持 AWS、GCE、Azure、Joyent、OpenStack、VMware、裸机和本地主机等部署场景。Conjure-up 基于 Juju、MAAS 和 LXD 等基础技术。

地址:https://conjure-up.io/
价格:免费

Amazon EKS

Amazon EKS(Amazon Elastic Container Service)是一项管理服务,你可以用它来轻松使用 Kubernetes 部署、管理和扩展容器化应用程序。Amazon EKS 跨多个 AWS 可用区管理您的 Kubernetes 基础架构,同时自动检测和替换不正常的控制节点,并提供按需升级和修补。你只需要配置工作节点并将它们连接到可用的 Amazon EKS 终端即可。

地址:https://aws.amazon.com/eks/
价格:按使用的资源量付费

监控工具

Kubebox

Kubebox 是一套用于 Kubernetes 集群的终端控制台,其能让用户通过美观且经典的界面对集群实时状态进行管理与监控。Kubebox 能显示容器资源的使用情况、集群监控以及容器日志等。除此之外,用户还可借助 Kubebox 轻松导航到目标名称空间,并在目标容器中执行相关操作,借此以快速排故 / 恢复。

地址:https://github.com/astefanutt…
价格:免费

Kubernetes Operational View (Kube-ops-view)

Kube-ops-view 是一款面向多个 Kubernetes 集群的系统监测面板。用户可通过 Kube-ops-view 导航到不同的 K8s 集群并监控节点以及 pod 健康状况,其还能够为部分 Kubernetes 过程提供动画效果,例如 pod 的创建与终止。Kube-ops-view 也将 Heapster 作为其数据源。

地址:https://github.com/hjacobs/ku…
价格:免费

Kubetail

Kubetail 是一个小型 bash 脚本,其能够将来自于多个 pod 的日志聚合到同一数据流中。Kubetail 的初始版本不提供过滤或高亮功能,但其目前已经在 GitHub 上添加了一个分支,该分支支持使用 multitail 工具构建日志并对日志着色。

地址:https://github.com/johanhaleb…

价格:免费

Kubewatch

Kubewatch 是一款 Kubernetes 监控工具,该产品可将 Kubernetes 事件(events)发布到团队协作应用 Slack。Kubewatch 以 Pod 形式运行于 Kubernetes 集群中,并监视系统中所发生的各种变化。此外,你可以通过编辑配置文件来指定需要接收的通知。

地址:https://github.com/bitnami-la…
价格:免费

Weave Scope

WeaveScope 是一款面向 Docker 与 Kubernetes 集群的故障排除与监控工具,该工具可自动生成应用程序与基础架构拓扑,借此帮助,用户能轻松识别应用程序的性能瓶颈。用户可在本地服务器或笔记本电脑上将 Weave Scope 部署为独立应用程序,或者选用 WeaveCloud 上的 Weave ScopeSaaS(软件即服务)解决方案。在 WeaveScope 的帮助下,用户可通过名称、标签或资源消耗量对容器执行分组、筛选或搜索。

地址:https://www.weave.works/oss/s…
价格:独立模式免费、标准模式每月 30 美元(免费试用期为 30 天)、企业模式每节点每月 150 美元

Prometheus

Prometheus 已迅速成为 Kubernetes 监控的必备工具。它提供了多维数据模型以及用户可访问的格式和协议。在 Kubernetes 中公开 Prometheus 指标非常容易,抓取的数据可解释性强且易于阅读,并使用标准 HTTP 服务方式发布。

地址:https://prometheus.io/
价格:免费

Searchlight

AppsCode 推出的 Searchlight 是一款面向 Icinga 的 Kubernetes 监控工具。Searchlight 会定期对 Kubernetes 集群执行各种检查,并会在发现问题后,通过电子邮件、短信或对话框发送警告信息。Searchlight 包含专为 Kubernetes 编写的默认检查套件。此外,其还能够通过联合外部黑盒子监控功能来增强 Prometheus 的监测性能,并在内部系统完全失效的情况下充当后备选项。

地址:https://github.com/appscode/s…
价格:免费

cAdvisor

CAdvisor 默认安装在所有集群节点中,它采集 Kubernetes 运行容器和节点的资源使用指标。CAdvisor Kubelet 通过 Kubelet API(默认值分辨率为一分钟)公开这些指标。Metrics Server 可识别所有可用节点并调用 Kubelet API 以获取容器和节点的资源使用情况,然后通过 Kubernetes 的聚合 API 公开这些指标。

地址:https://github.com/google/cad…
价格:免费

Kube-state-metrics

kube-state-metrics 通过监听 Kubernetes 的 API server 即可从 Kubernetes API 对象生成指标。它并不检查各个 Kubernetes 组件的运行状况,而是关注各种内部对象(如 deployment、节点和 Pod)的运行状况。

地址:https://github.com/kubernetes…
价格:免费

Sumo Logic App

Sumo Logic Kubernetes 应用提供对集群中的工作节点及其应用程序日志的完整监控。该应用程序允许用户监视容器健康状况、副本、负载均衡、Pod 状态和硬件资源分配并进行故障排除。该应用程序利用 Falco 事件来监视和检测异常的容器、应用程序、主机和网络活动。

地址:https://www.sumologic.com/app…
价格:专业版 108 美元每月,企业版 180 美元每月

Dynatrace

Dynatrace OneAgent 具有容器感知功能,并内置支持对 Kubernetes 集群的开箱即用监视。Dynatrace 为 Kubernetes 提供 全栈监视,即从应用程序到基础架构层的监视。但是,如果您无权访问基础架构层,则 Dynatrace 还提供仅监视应用程序的选项。

地址:https://www.dynatrace.com/sup…
价格:限时免费

测试工具

Kube-monkey

Kube-monkey 是 Netflix 公司旗下 Chaos Monkey 项目的 Kubernetes 版本。Kube-monkey 是一款遵循混沌工程原理的工具,其可以随机删除 Kubernetes pod,检查服务是否具备抗失效能力并帮助维持系统的健康运转。Kube-monkey 也可经由 TOML 文件完成配置,而 TOML 文件不仅能够终止指定的应用程序,还可以决定恢复策略的执行时间。

地址:https://github.com/asobti/kub…
价格:免费

K8s-testsuite

K8s-testsuite 由两个 Helm charts 组合而成,适用于网络带宽测试与单个 Kubernetes 集群的负载测试。负载测试模拟了带有 loadbots 的简单网页服务器,这些服务器可以基于 Vegeta 以 Kubernetes 微服务的形式运行。网络测试则在内部使用 iperf3 与 netperf-2.7.0 运行三次。这两项测试都会生成涵盖全部结果与指标的综合日志信息。

地址:https://github.com/mrahbar/k8…
价格:免费

Test-infra

Test-infra 是一套用于 Kubernetes 测试与结果验证的工具集合。Test-infra 包括多种仪表板,分别用于显示历史记录、汇总故障以及当前正在测试的内容。用户还可以使用 Test-infra 创建自定义测试任务。此外,Test-infra 可在使用 Kubetest 的不同供应商平台上,通过模拟完整的 Kubernetes 生命周期实现端到端的 Kubernetes 测试。

地址:https://github.com/kubernetes…
价格:免费

Sonobuoy

Sonobuoy 允许用户以易于访问和非破坏性的方式运行一组测试,从而对当前 Kubernetes 集群状态进行评估。Sonobuoy 可生成有关集群性能详细信息的报告。Sonobuoy 支持 3 个 Kubernetes 小版本,分别是当前发布版本和此前的两个小版本。SonobuoyScanner 是一款基于浏览器的工具,使用该工具,用户只需点击数下即可完成对 Kubernetes 集群的测试。当然,其 CLI 版本有更丰富的测试功能。

地址:https://sonobuoy.io/
价格:免费

PowerfulSeal

PowerfulSeal 类似于 Kube-monkey,同样遵循混沌工程原理。PowerfulSeal 不仅可终止 pod,还能够在集群中添加或删除虚拟机。不同于 Kube-monkey,PowerfulSeal 具有交互模式,从而允许用户以手动方式中断特定的集群组件。另外,除了 SSH 以外,PowerfulSeal 没有其它外部依赖。

地址:https://github.com/bloomberg/…
价格:免费

安全工具

Trireme

Trireme 是一项灵活且直接的 Kubernetes 网络策略实现方案,其适用于任何 Kubernetes 集群,并允许用户管理不同集群中的 pod 之间的流量。Tririme 的主要优势在于其无需任何集中式策略管理,能够轻松实现 Kubernetes 中所部署的两种资源的彼此交互,并且无需配合任何复杂的 SDN、VLAN 标签以及子网(Trireme 使用常规的 L3- 网络)。

地址:https://github.com/aporeto-in…
价格:免费

Aporeto

Aporeto 基于工作负载识别、加密和分布式策略为容器、微服务、云应用和遗留应用提供安全性。由于 Aporeto 策略独立于基础架构,因此可以在 Kubernetes 集群之间启用安全策略,甚至可以在包括 Kubernetes 和非 Kubernetes 的混合环境中使用。

地址:https://www.aporeto.com/

Twistlock

Twistlock 持续监视你在 K8 上部署的应用程序是否存在漏洞和合规性问题,包括底层主机以及容器和映像。此外,Twistlock 运行时防御会自动对容器行为进行建模,从而警报或阻止异常活动并允许已知的良好行为。最后,Twistlock 提供了第 3 层微分段(microsegmentation)和第 7 层防火墙,可以保护前端微服务免受常见的攻击。

地址:https://www.twistlock.com/

Falco

SysdigFalco 是一款行为活动监视器,旨在检测应用程序中的异常活动。Falco 基于 Sysdig 项目,Sysdig 是一款开源工具(现已转化为商业服务),可通过追踪内核系统调用来监控容器性能。Falco 允许用户通过一套规则来持续监控并检测容器、应用程序、主机以及网络活动。

地址:https://sysdig.com/opensource…

价格:独立工具免费、基础云每月 20 美元(可免费试用)、专业云每月 30 美元、专业版软件定制价格

Sysdig Secure

Sysdig Secure 是 Sysdig 容器智能平台的一部分,它开箱即用且具有无与伦比的容器可见性,并与容器编排工具深度集成。这些编排工具包括 Kubernetes、Docker、AWS ECS 和 Apache Mesos。借助 Sysdig Secure,你可以实现服务感知策略,阻止攻击,分析历史记录并监视群集性能。最后,Sysdig Secure 可作为云服务或内部部署软件产品。

地址:https://sysdig.com/product/se…
价格:独立工具免费、专业云和专业版软件定制价格

Kubesec.io

Kubesec.io 是一个能针对安全功能使用情况对 Kubernetes 资源进行评分的服务。Kubesec.io 可根据 Kubernetes 安全最佳实践来验证资源配置。你将拥有完全的控制权,结合它提供的额外建议,你将能够更好的提升系统的整体安全性。另外,该网站还包括大量与容器和 Kubernetes 安全相关的外部链接。

地址:https://kubesec.io/
价格:免费

实用 CLI 工具

Cabin

更新:该项目目前不在活跃开发状态

Cabin 可作为 Kubernetes 集群远程管理的移动仪表板。用户可通过 Cabin 快速管理应用程序、扩展部署,并通过 Android 或 iOS 设备对整个 Kubernetes 集群实施故障排查。对于 Kubernetes 集群的运营者而言,Cabin 无疑是一款强大的工具,其能够让管理者在故障发生时执行快速有效的补救措施。

地址:https://github.com/bitnami-la…
价格:免费

Kubectx/Kubens

更新:Kubectx 现在已经用 Go 语言重新编写,稳定的 bash 版本仍然可用

Kubectx 是一款小型开源实用工具,其不仅能够增强 Kubectl 的功能,还能够同时与多个 Kubernetes 集群实现连接。另外,Kubens 允许用户在 Kubernetes 命名空间之间进行导航。最后,这两款工具均可在 bash/zsh/fish shell 上提供自动补全功能。

地址:https://github.com/ahmetb/kub…
价格:免费

Kube-shell

Kube-shell 能在使用 Kubectl 操作 Kubernetes 集群时提高效率。Kube-shell 支持命令自动补全与自动建议。此外,Kube-shell 还能够提供相关命令的内嵌文档,其甚至还可以在输入错误时执行检索与纠正命令。因此,这是一款能够在 Kubernetes 终端使用时提升效率与生产力的工具。

地址:https://github.com/cloudnativ…
价格:免费

Kail

Kail 是 Kubernetes tail 的缩写,适用于 Kubernetes 集群。利用 Kail,用户可以在所有匹配的 pod 当中获取最新的 Docker 日志(tail)。Kail 还允许用户通过服务(service)、部署(deployment)、标签(labels)或其他功能对 pod 进行过滤。如果 pod 符合某种条件, Kail 启动后会自动获取该 pod 的日志(或不再获取该 pod 的日志)。

地址:https://github.com/boz/kail
价格:免费

部署工具

Telepresence

Telepresence 可将来自 Kubernetes 环境的数据转发至本地进程,进而在本地对 Kubernetes 集群进行调试。Telepresence 能够为你的本地代码提供对 Kubernetes 服务和 AWS/GCP 资源的访问权限,而本地代码将被部署在集群中。在 Telepresence 的帮助下,Kubernetes 会将本地代码视为集群中的普通 pod。

地址:https://www.telepresence.io/
价格:免费

Helm

Helm 是一款适用于 Kubernetes 的软件包管理器。其与 APT/Yum/Homebrew 类似,但适用于 Kubernetes。Helm 使用 Charts 进行操作,而 Charts 是一套 Kubernetes 资源清单文件,这些文件用于构建分布式应用程序。用户可通过创建 Helm Charts 来分享应用。Helm 使用户能够创建可复用的构建并轻松管理 Kubernetes 清单。

地址:https://github.com/kubernetes…
价格:免费

Jaeger

Jaeger Operator 是一个 Kubernetes Operator 扩展,它提供了另一种打包、部署和管理 Kubernetes 应用程序的方法。

地址:https://www.jaegertracing.io/
价格:免费

turbonomic

turbonomic 的 kubernetes 即服务(KaaS)管理功能包括对 Amazon EKS(Elastic Container Service for Kubernetes)、Microsoft AKS(Azure Kubernetes Service)、GKE(Google Kubernetes Engine)和 PKS(Pivotal Container Service)的支持。它可以提升自有 Kubernetes 集群的性能、效率和合规性,因此使 IT 组织可以扩展和加速云原生应用的发展。

地址:https://turbonomic.com/produc…
价格:定制价格

Supergiant

更新:Supergiant 目前为不活跃状态,但该项目仍然在 Github 上。

Supergiant 是一个实用程序的开源集合,可简化安装和管理 Kubernetes 集群的过程。Supergiant Kubernetes 工具箱中有三个独立的应用程序,分别为控制(Control)、分析(Analyze)和能力(Capacity)。本质上,Supergiant 作为一种微服务应用程序允许分别使用这三个工具。

地址:https://github.com/supergiant

Keel

Keel 使用户可以自动化 Kubernetes deployment 更新的过程,并且可以在专用命名空间中作为 Kubernetes 服务启动。通过这样的组织方式,Keel 在你的环境中引入的额外负载较小,但显著提升了鲁棒性。Keel 通过 labels、annotations 和 Charts 帮助部署 Kubernetes 服务,你只需要为每个 deployment 或由 Helm 部署的应用指定更新策略。一旦仓库中有新版本应用,Keel 会将其自动更新到你的环境中。

地址:https://keel.sh/
价格:免费

Apollo

Apollo 是一个开源应用程序,其为团队提供了自助式 UI,用于创建服务并将其部署到 Kubernetes。Apollo 使操作员只需点击一下即可查看日志并将 deployment 还原到任一时间点。对于 deployment,Apollo 有灵活的权限模型, 每个用户只能部署他需要部署的对象。

地址:https://github.com/logzio/apollo
价格:免费

Draft

Draft 是 Azure 团队提供的工具,可简化应用程序开发和部署到 Kubernetes 集群的过程。Draft 在代码部署和代码提交之间创建了“内部循环”,从而极大地加快了代码修改的验证过程。使用 Draft,开发人员可以使用两个命令准备应用程序的 Dockerfile 和 Helm Charts,并将应用程序部署到远程或本地 Kubernetes 集群。

地址:https://github.com/azure/draft
价格:免费

Kel

Kel 是 Eldarion 公司的开源 PaaS 平台,可帮助管理 Kubernetes 应用程序的整个生命周期。Kel 在 Kubernetes 之上提供了另外两层,这两层用 Python 和 Go 语言编写。级别 0 允许您提供 Kubernetes 资源,级别 1 可以帮助您在 K8 上部署任何应用程序。

地址:http://www.kelproject.com/
价格:免费

Kong

Kong 以前称为 Kong Community(CE),是由 Kong 公司发起的一种开源可扩展 API 网关技术,其具有不断发展的社区。Kong 允许开发人员在 Kubernetes 中使用 API 管理系统的标准功能,如身份验证、数据加密、日志、速率限制等等。所有这些都由一个简单的 RESTful API 提供支持,该平台本身基于 NGINX 代理服务器和 Apache Cassandra 数据库管理系统之上。

地址:https://konghq.com/
价格:Kong 云平台提供 15 天试用

持续集成和持续交付流水线工具

Cloud 66

Cloud 66 是完整的 DevOps 工具链,可用于生产级别的容器化应用程序,它通过专用的 Ops 工具自动执行许多繁重的 Devs 开发工作。该平台当前在 Kubernetes 上运行 4,000 个客户工作负载,并管理 2,500 行配置。通过提供端到端基础架构管理,Cloud 66 使工程师能够在任何云平台或服务器中构建、交付、部署和管理任何应用程序。

地址:https://www.cloud66.com/
Cost: Free for 14 days
价格:免费 14 天

无服务器 / 函数工具

Kubeless

Kubeless 是 Kubernetes 原生的无服务器框架,可让您部署少量代码而不必担心基础架构的问题。Kubeless 能够获取 Kubernetes 资源,并提供自动扩展、API 路由、监视和排故。Kubeless 完全依赖于 K8s 原语,因此 Kubernetes 用户也将能够使用原生 K8s API-server 和 API 网关。

地址:https://github.com/kubeless/k…
价格:免费

Fission

Fission 是 Kubernetes 的快速无服务器框架,专注于提高开发人员的生产力和提升性能。Fission 可在任何地方的 Kubernetes 集群上工作,如笔记本电脑、任何公共云或私有数据中心。你可以使用 Python、NodeJS、Go、C#或 PHP 编写函数,然后使用 Fission 将其部署在 K8s 集群上。

地址:https://fission.io/
价格:免费

Funktion

更新:该项目已沙盒化(不活跃)

长期以来,Kubernetes 仅有一个函数即服务(FaaS)的实现:Funktion。Funktion 是专为 Kubernetes 设计的开源事件驱动的 Lambda 风格编程模型,其与 fabric8 平台有紧密联系。使用 Funktion,你可以创建流程以从 200 多个事件源进行订阅以调用你的函数,这些事件源包括大多数数据库、消息系统、社交媒体以及其他中间件和协议。

地址:https://github.com/funktionio…
价格:免费

IronFunction

IronFunctions 是一个开源的无服务器平台或 FaaS 平台,你可以在任何地方运行 IronFunctions。该平台使用 Go 语言编写的,实际上可以支持任何语言的函数。IronFunction 的主要优点是它支持 AWS Lambda 风格。该平台允许你直接从 Lambda 导入函数,然后在任何需要的地方运行它们。

地址:https://github.com/iron-io/fu…
价格:免费

OpenWhisk

Apache OpenWhisk 是由 IBM 和 Adobe 驱动的强大的开源 FaaS 平台,其可以部署在本地设备或云上。Apache OpenWhisk 的设计意味着它充当异步且松耦合的运行环境,可以为外部触发器运行函数。OpenWhisk 在 Bluemix 上作为 SaaS 解决方案提供给用户,你也可以在本地部署基于 Vagrant 的虚拟机来运行它。

地址:https://openwhisk.apache.org/
价格:免费

OpenFaaS

OpenFaaS 框架用于在 Docker Swarm 或 Kubernetes 上管理无服务器函数,它将采集和分析这些平台中的各种指标。您可以将任何逻辑过程打包到函数中并使用它,而无需重复编码或执行任何其他常规操作。FaaS 内置了 Prometheus 采集器,这意味着它可以对你的函数实现按需缩放。FaaS 原生支持基于 Web 的界面,您可以在其中试用您的函数。

地址:https://github.com/openfaas/faas
价格:免费

Nuclio

Nuclio 是一个无服务器项目,旨在处理高性能事件和大量数据。Nuclio 可以作为独立库在本地设备上启动,也可以在虚拟机或 Docker 容器内启动。另外,Nuclio 开箱即用地支持 Kubernetes。Nuclio 提供具有最大并行度和最小开销的实时数据处理。你可以在 playground 页面上试用 Nuclio。

地址:https://github.com/nuclio/nuclio
价格:免费

Virtual-Kubelet

Virtual Kubelet 是开源的 Kubernetes Kubelet 实现,其为了将 Kubernetes 连接到其他 API 而伪装成 kubelet。Virtual Kubelet 允许节点接入其他服务(例如 ACI、Hyper.sh 和 AWS 等)。此连接器具有可插拔架构,可直接使用 Kubernetes 原语,从而使其更易于构建。

地址:https://virtual-kubelet.io/
价格:免费

Fnproject

Fnproject 是一个原生容器无服务器项目,它几乎支持任何编程语言,并且几乎可以在任何地方运行。Fn 是用 Go 语言编写的,因此性能较好且十分轻量。Fnproject 支持 AWS Lambda 风格,因此你可以轻松导入你的 Lambda 函数并通过 Fnproject 启动它。

地址:http://fnproject.io/
价格:免费

服务网格工具

Istio

Istio 是一个开源服务网格,它使在容器中运行的微服务之间的连接、管理和流量保护变得更容易,此外,它还可以对微服务指标进行遥测。Istio 是 IBM、Google 和 Lyft 之间的合作成果。

地址:https://istio.io/
价格:免费

Linkerd + Linkerd2

Linkerd 是一种开源的服务网格工具,可以使服务之间的通信可靠、快速且安全。通过拦截应用程序内的网络通信,服务网格能够提取度量指标(遥测),配置服务与服务之间通信的策略并进行通信加密。Linkerd2 是 Linkerd 的超轻量级服务网格,专门用于 Kubernetes。

地址:https://linkerd.io/
https://github.com/linkerd/li…

Hashicorp’s Consul

Consul 是一种服务网格解决方案,可跨任何运行时平台和公有或私有云连接并保护服务。与上述 Istio 和 Linkerd 服务网格技术一样,HashiCorp 的 Consul Connect 选择部署旁路代理。代理透明地保护微服务之间的通信,还支持通过 Intentions 的概念来定义策略。

地址:https://www.hashicorp.com/pro…
价格:免费

原生服务发现

CoreDNS

CoreDNS 是一组用 Go 编写的可执行 DNS 功能的插件。CoreDNS 和其他 Kubernetes 插件可以替换默认的 Kube-DNS 服务,并通过实现一套规范,完成基于 Kubernetes DNS 的服务发现。CoreDNS 还可以监听 UDP/TCP、TLS 和 gRPC 的 DNS 请求。

地址:https://coredns.io/
价格:免费

原生可视化与控制

Kubernetes Dashboard

Kubernetes Dashboard 是用于 Kubernetes 集群的基于 Web 的通用 UI。使用原生控制面板使 K8s 集群中的故障排除和监视变得更容易。你需要在你的机器和 Kubernetes API-server 之间创建一个安全的代理通道以访问该控制面板。该原生 Kubernetes 控制面板依赖 Heapster 数据收集器,因此 Heapster 也需要安装在系统中。

地址:https://github.com/kubernetes…
价格:免费

成本管理

Replex

Replex 是一个成本(cost)和治理(governance)管理平台,其工作于 Kubernetes 环境中。该工具通过统一用于云平台中 deployment 的成本(cost)和治理(governance)管理,解决了与 Kubernetes 动态特性相关的挑战。

地址:https://www.replex.io/
价格:定制价格

以上就是今天给大家分享的,2020年,值得收藏的50多种Kubernetes工具

原文链接:https://caylent.com/50-useful…
作者 | Stefan Thorpe 译者 | Rayden

image

https://segmentfault.com/a/1190000038331885

成千上万个站点,日数据过亿的大规模爬虫是怎么实现的?

lei阅读(7)

图怪兽_ae7f82ddf6d63b7f1aa3f3e208778c80_88454

分布式爬虫、智能解析、消息队列、去重和调度等技术点

我们身边接触最频繁、同时也是最大的爬虫莫过于几大搜索引擎。但是搜索引擎的爬取方式和我们爬虫工程师接触的方式差异比较大,没有太大的参考价值,我们今天要讲的是舆情方向的爬虫(架构以及关键技术原理),主要涉及:

  1. 网页文本智能提取;
  2. 分布式爬虫;
  3. 爬虫 DATA/URL 去重;
  4. 爬虫部署;
  5. 分布式爬虫调度;
  6. 自动化渲染技术;
  7. 消息队列在爬虫领域的应用;
  8. 各种各样形式的反爬虫;

请大家买好瓜子,搬好凳子坐下学习,并准备好争夺文末赠送的奖品!

一、网页文本智能提取

舆情其实就是舆论情况,要掌握舆情,那么就必须掌握足够多的内容资讯。除了一些开放了商业接口的大型内容/社交类平台(例如微博)之外,其他都需要依靠爬虫去采集。因此,舆情方向的爬虫工程师需要面对的是千千万万个内容和结构都不同的站点。我们用一个图来表示他们面对的问题:

CwHXGu

没错,他们的采集器必须要能够适配千千万万个站点的结构,从风格迥异的 HTML 文本中提取出主体内容——标题、正文、发布时间和作者。

如果是你,你会用什么样的设计来满足业务需求呢?

曾经我也设想过这样的问题,在技术群里也看到有群友提出类似的问题,但是很难得到满意的答案。有的人说:

  1. 用归类法,相似的内容归类到一起,然后给一类内容配置提取规则;
  2. 用正则,提取指定标签中的内容;
  3. 用深度学习,NLP 语义分析出哪里是有意义的内容,提取出来;
  4. 用计算机视觉,让人去点击,然后按照页面相似度分类提取(其实就是归类法的自动化版本);
  5. 用算法,计算出文本的密度,然后提取;

总之各种各样的想法层出不穷,但是最后都没有听到实际应用的消息。目前来说,大部分公司使用的都是人工配配置 XPATH 规则的方式,采集的时候通过网址来匹配对应的提取规则,然后调用规则来实现多站点的爬取。这种方法很有效,而且在企业中应用已久,比较稳定,但缺点也显而易见——费时间、费人工、费钱!

偶有一天,我在微信技术群里看到有人(优秀的 Python 工程师青南)发表了一个用于自动化提取文本的算法库,GeneralNewsExtractor (以下简称 GNE)。这个库参考了武汉邮电科学研究院洪鸿辉、丁世涛、黄傲、郭致远等人编写的论文——《基于文本及符号密度的网页正文提取方法》,并在论文的基础上用 Python 代码进行了具体实现,也就是 GNE。它的原理是通过提取网页 DOM 中的文本以及其中的标点符号,以文本中标点符号的密度作为基础,使用算法从一句话延伸到一段文字和一篇文章。

xS6M3T

GNE 能够有效排除正文以外的的广告、推荐栏、介绍栏等“噪声”内容,准确识别出网页正文,且识别率高达 99%(测试选用的内容为国内主流门户/媒体平台中的文章)。

GNE 的具体算法细节以及源码解析请翻阅《Python3 网络爬虫宝典》第 5 章。

有了它,基本上就可以解决 90% 以上的舆情方向爬虫解析的需求,剩下的 10% 可以基于提取规则进行针对性调整或者完全定制化开发,解放了一大波 XPATH 工程师。

二、爬虫 DATA/URL 去重

舆情业务必须紧盯网站是否有新的内容发布,要求是越快越好,但由于各项软硬件限制,通常会要求在 30 分钟内或者 15 分钟内监听到新内容。要实现对目标网站内容变化的监听,那么我们可以选择的比较好的方式就是轮询。不停地访问网页,并且判断是否有“新内容”出现,如果有的话就执行爬取,没有“新内容”就不爬取。

那么问题来了,应用程序如何知道哪些内容是“新的”、哪些内容又是“旧的”的呢?

问题拆解一下,“新内容”就是没有爬过的内容。这时候我们需要用一个东西来记录这篇文章是否被爬取过,每次有要爬取的文章时就比对一下,这就是解决这个问题的办法。

那又依靠什么来进行比对呢?

我们都知道文章的 URL 几乎都是不变且不会重复的,因此可以选择文章的 URL 作为判定的依据,也就是把爬取过的 URL 放到一个类似列表一样的容器里存储起来,每次有待爬取的 URL 就判断它是否已经被存在容器里,如果在就说明已经爬过了,直接丢弃,进入下一个 URL 的判断流程。整体逻辑就像下面这张图一样:

ac9WnD

这就是爬虫领域的“去重”。实际上去重可以粗略的分为内容(DATA)去重和链接(URL)去重,这里我们讲的只是舆情方向的去重需求,如果是电商方向的去重,那么就不能用 URL 作为判断依据,因为电商爬虫(例如比价软件)的目的主要是判断价格的变化,这时候判断变化的依据应该是商品的关键信息(例如价格、折扣),也就是 DATA 去重。

去重的原理明白了,那用什么东西作为存放去重依据的容器呢?MySQL?Redis?MongoDB?内存?实际上大部分工程师都选择 Redis 作为存放去重依据的容器,但实际上 MySQL、MongoDB 和内存都是可以充当容器的,至于为什么会选择 Redis,它又比其他数据存储好在哪里?你可以翻阅《Python3 网络爬虫宝典》的第 3 章。

三、分布式爬虫

无论是舆情方向的爬虫还是电商方向的爬虫,要承担的爬取量都是非常大的。少则每日百万数据,多则每日数十亿数据。以往大家了解的单机爬虫,在性能和资源方面都无法满足需求。既然 1 个满足不了,那就 10 个、100 个!这就是分布式爬虫出现的背景。

众所周知,分布式和单机要面对的问题是有差异的,除了业务目标是相同的之外,分布式还要考虑多个个体之间的协作,尤其是资源的共享和竞争。

Btwyu7

在只有 1 个爬虫应用的时候,也只有它 1 个读取待爬队列、只有 1 个存储数据、只有它 1 个判断 URL 是否重复。但有几十几百个爬虫应用的时候,就需要区分先后顺序,避免出现多个爬虫应用访问同一个 URL 的情况(因为这不仅浪费时间,还浪费资源)。而且,只有 1 个爬虫应用的时候只需要把它放在 1 台计算机(服务器)上运行就可以了,但是爬虫应用突然变得这么多,又应该如何部署到不同的计算机上呢?手动一个个上传,然后一个个启动吗?

资源问题

先说资源共享和竞争的情况,为了解决 URL 待爬队列和已经爬队列的共享,那么必须将队列(也就是上面提到的存放 URL 的容器)放到一个可以公开(多个爬虫应用)访问的地方,例如部署在服务器上的 Redis。

这时候又出现一个新状况,随着数据量越来越大,要存储的 URL 越来越多,后面很有可能出现因为存储空间需求过大而导致成本递增的问题。因为 Redis 是利用内存来存储数据的,所以存放的 URL 越多就需要越多的内存,而内存又是硬件设备里价格相对较高的硬件,所以不得不考虑这个问题。

好在一个叫做布隆的人发明了一种算法——Bloom Filter(布隆过滤器),这种算法通过哈希映射的方式来标记一个对象(这里是 URL)是否存在,这样可以将内存的占用率大大降低,按 1 亿条长度为 32 字符的 URL MD5 值来计算,使用 Bloom Filter 前后的差距大约在 30倍。关于 Bloom Filter 的算法原理和代码实现解读可翻阅《Python3 网络爬虫宝典》第 3 章 。

部署问题

一个个文件上传,一次次手动运行爬虫实在是太累了。你可以向运维同事寻求技术支持,但也可以自己探寻这些能够减轻你工作量的自动化部署方式。目前业内知名的持续集成和部署莫过于 GitLab 的 GitLab Runner 和 GitHub Action,又或者是借助 K8S 的容器化来实现。但它们只能帮助你实现部署和启动,而爬虫应用的一些管理功能就指望不上了。遂,今天要给大家介绍的是另一种实现方式——使用 Crawlab。

Crawlab 是一款由知名外企工程师开发的分布式爬虫管理平台,它不仅支持 Python 语言编写的爬虫,几乎可以兼容大部分编程语言和应用程序。借助 Crawlab,我们可以将爬虫应用分散到不同的计算机(服务器)上,而且能够在可视化界面设定定时任务、查看平台上爬虫应用的状态以及环境依赖等信息。具体如下图所示:

qEQqcK

面对一款如此实用的平台工具,作为工程师的我们不禁想问:

  1. 它是如何把文件分散到不同计算机的?
  2. 它如何实现不同计算机(多节点)之间通信的?
  3. 它是如何实现多语言兼容的?
  4. ……

其中我们比较关注的多节点通信是借助 Redis 实现的,文件分散同步是借助 MongoDB 实现的。更多细节可翻阅《Python3 网络爬虫宝典》 第 6 章。

除了这样的平台之外,Python 爬虫工程师常常接触的莫过于 Scrapy 框架以及相关衍生的库。Scrapy 团队官方开发了一个名为 Scrapyd 的库,它专门用来部署 Scrapy 框架开发的爬虫应用。在部署 Scrapy 应用时,我们通常只需要执行 1 行命令就可以把爬虫程序部署到服务器上。你想不想知道背后的逻辑:

  1. 程序以什么样的形式上传到服务器的?
  2. 程序在服务器上如何运行的?
  3. 为什么可以查看到每个任务运行的开始时间和结束时间?
  4. 中途取消任务执行的功能是怎么实现的?
  5. 它的版本控制是怎么实现的?
  6. 如果不是 Scrapy 框架编写的 Python 应用,能实现像上面几点那样的监控和操作吗?

实际上 Scrapy 应用会被打包成为一个后缀为“.egg” 的压缩包,以 HTTP 的形式上传到服务器上。当服务端程序需要执行这个程序时,先将它复制到操作系统的临时文件夹,执行时将其导入到当前 Python 环境,执行完毕后删除该文件。至于它的执行时间和中断操作,实际上借助了 Python 进程接口,具体细节翻阅《Python3 网络爬虫宝典》 第 6 章。

四、自动化渲染技术

为了实现炫酷的效果,或者说为了节省静态资源对带宽的占用,很多网站都是借助 JavaScript 来实现对页面内容的优化。Python 程序本身是无法解释 JavaScript 和 HTML 代码的,因此无法获得我们在浏览器中“看到”,但实际上并不是“真实存在”的内容,因为这些内容都是由浏览器渲染出来的,只存在于浏览器中,HTML 文档里面还是那些文本、JavaScript 文件中还是那些代码,图片、视频和那些特效并不会出现在代码中,我们看到的一切都是浏览器的功劳。

由于 Python 也无法获取浏览器渲染后的内容,所以当我们像往常一样写代码爬取上面的数据时,就会发现拿到的数据和看到的并不一样,任务它就失败了。

image-20201129203201261

这时候我们就需要用到自动化渲染技术了,实际上像 Chrome 和 FireFox 这样的浏览器都开放了接口,允许其他编程语言按照协议规范操控浏览器。基于这样的技术背景,有团队开发出了像 Selenium 和 Puppeteer 这样的工具,然后我们就可以用 Python (其他语言也可以)代码来操作浏览器了。让浏览器帮助我们做一些用户名密码输入、登录按钮点击、文本和图片渲染、验证码滑动等操作,从而打破 Python 与浏览器本身的差异壁垒,借助浏览器渲染内容后再返回给 Python 程序,然后拿到和我们在网页上看到的一样的内容。

除了浏览器,APP 也有类似的情况。具体操作实践和案例细节可翻阅《Python3 网络爬虫宝典》 第 2 章。

五、消息队列在爬虫领域的应用

之前的描述中,我们并没有提到爬取时候的细节。假设这样一个正常的爬虫场景:爬虫先访问网站的文章列表页,然后根据列表页的 URL 进入详情页进行爬取。这里要注意,文章详情页的数量一定是比列表页的数量多 N 倍的,如果列表展示的是 20 条内容,那么就是多 20 倍。

如果我们需要爬取的网站很多,那么就会用到分布式爬虫。如果分布式爬虫只是把 1 个爬虫程序复制出 N 份来运行,那么就会出现资源分配不均衡的情况,因为在上面提到的这种情况下,每 1 个爬虫都需要这么干活。实际上我们可以有更好的搭配方式,让它们的资源得到最大利用。例从列表页到详情页可以抽象为生产者和消费者模型:

image-20201129204509533

4 号和 5 号爬虫应用只负责将列表页中抽取详情页的 URL,然后推送到一个队列中,另外几个爬虫程序从队列中取出详情页的 URL 进行爬取。当列表页和详情页数量差距比较大的时候,我们可以增加右侧的爬虫程序数量,差距较小的时候就减少右侧的爬虫程序(或者增加左侧的爬虫程序,具体视情况定)。

左侧的爬虫程序相对于队列这条“数据采集生产线”来说,它就是生产者,右侧爬虫程序的就是消费者。有了这样的结构,我们就可以根据实际情况对生产者或者消费者的熟练进行调整,实现资源的最大化利用。另外一个好处是当生产者拿到的 URL 越来越多,但消费者一时消费不过来时,URL 会一直存放在队列中,等消费能力上升时就能够再次实现均衡。有了这样的生产线,我们就不用担心一下突然涌来很多的 URL 或者一下突然把队列的 URL 消费一空,队列这种削峰填谷的能力除了在后端应用中大放异彩之外,在爬虫应用中也发挥了很大的作用。

关于爬虫(以及分布式爬虫)程序接入消息队列的具体实现和细节可翻阅《Python3 网络爬虫宝典》 第 4 章。

六、各种各样形式的反爬虫

你想要我偏不给!

网站可不会轻易让你爬取站点上面的内容,它们往往会从网络协议、浏览器特征、编程语言差异、人机差异等方面给爬虫工程师设置障碍,常见的有滑块验证码、拼图验证码、封 IP、检查 COOKIE、要求登录、设定复杂的加密逻辑、混淆前端代码等。

水来土掩、兵来将挡!爬虫工程师与目标网站的工程师你来我往的过招就像兵家尔虞我诈一般精彩。《Python3 反爬虫原理与绕过实战》一书囊括了市面上 80% 以上的反爬虫手段和爬虫技巧,详细解读双方所用招术,使各位看客从中学到不少使用招式。具体细节可翻阅该书,领略技术领域的江湖!

小结

今天我们一起学习了日数据量过亿的大规模爬虫实践路上的关键技术点,包括文本智能提取、分布式爬虫、爬虫部署和调度、去重、自动化渲染等。学会了这些技术并融会贯通,那么实现日数据过亿的爬虫就不是问题了。

double-book

这些经验都来自一线爬虫工程师,同时这些技术和设计都经过了长期的工作验证,能够直接应用在工作当中。

https://segmentfault.com/a/1190000038323270