• Zero

    GuaikOrg/gtask

    GTask-任务系统

    简介

    在某些场景下我们可能需要定时去处理一些任务,我们需要编写服务端的代码让它周期性的去工作,例如检查客户端的在线状态、定期的去获取接口的数据等,在一个系统中可能会存在多种不同类型的定时任务需要开发者去开发和维护。

    GTask支持远程管理任务,执行任务脚本(目前仅支持lua),开发者可以创建任务实例并为实例添加处理器,在处理器中配置执行脚本就可以轻松的管理任务。

    编译与使用

    Linux

    ./build/linux.sh
    

    MaxOS

    ./build/macos.sh
    

    编译成功后在release目录下会生成两个文件,gtaskclient,分别是服务端程序和客户端程序。

    服务端运行

    config/config.json中保存着服务端监听的端口号和登陆的密钥,在客户端连接服务器的时候将会用到该密钥。启动服务端时需要指定配置参数。

    ./release/gtask -cfg ./config/config.json
    

    客户端连接服务器

    ./release/client -h [host] -p 1126
    

    这时会要求输入密钥,该密钥是初始化服务器时候的密钥。

    客户端指令说明介绍

    指令 参数 参数介绍 示例
    create job key 任务key create job test
    use key 该key必须已经被创建 use test
    create processor [filePath trigger bReset bLoop bExit] (必须先use job) [脚本文件 触发时间(秒) 能否被重置 是否循环 是否退出] create processor ./example/lua/test_json.lua 3 0 1 0
    run (必须先use job)启动任务 run
    delete (必须先use job)停止并删除任务 delete

    客户端演示

    test.gif

    客户端测试

    使用默认配置文件并将服务端运行在本机(localhost)

    ./release/client -h localhost -p 1126
    secretKey:647851f2fcf6101aefa4a2c59a329a11c60300a4
    
    # 创建任务
    > create job test
    create job [test] success
    
    # 选择任务
    > use test
    select job [test]
    
    # 为任务创建执行器,解析json并打印相关数据,3秒执行一次,循环执行
    test > create processor ./example/lua/test_json.lua 3 0 1 0
    create processor success
    
    # 运行任务
    test > run
    run job [test] success
    
    # 为任务补充一条执行器,用来发送GET请求获取网站数据,5秒执行一次,循环执行
    test > create processor ./example/lua/test_http_get.lua 5 0 1 0
    create processor success
    
    # 停止并删除当前任务
    test > delete
    >
    

    LUA执行代码介绍

    // 函数名必须是processor
    // key(string): 任务key
    // count(number): 时间计数
    function processor(key,count)
        data = {}
        data["hello"]="world"
        data["a"] = {}
        data["a"]["b"] = "b"
        data["a"]["c"] = {1,2,3,4,5,6}
        res = jsonMarshal(data)
        res = jsonUnMarshal(res)
        for k,v in ipairs(res["a"]["c"]) do
            print(k,v)
        end
        return true // 如果返回false,当前执行器将会退出
    end
    

    LUA支持函数

    加密

    函数 参数 返回值 描述
    md5 string string 获取字符串的MD5值
    base64 string string 获取字符串的base64编码
    base64UrlSafe string string 获取字符串url安全的base64编码
    base64 string string 获取字符串的base64编码
    hmac [key:string, str:string] string 获取hmac值
    sha1 string string 获取字符串的SHA1值

    时间

    函数 参数 返回值 描述
    now number 获取当前时间戳(毫秒)

    Json

    函数 参数 返回值 描述
    jsonMarshal table string 将table转换成json字符串
    jsonUnMarshal string table 将json字符串转转换成table

    Http

    函数 参数 返回值 描述
    httpGet [url:string header:table] [res:string ok:bool] 发送GET请求
    httpPost [url:string header:table body:string] [res:string ok:bool] 发送POST请求

    posted in GuaiK实验室 read more
  • Zero

    go get gonum.org/v1/gonum

    posted in GuaiK实验室 read more
  • Zero

    wget wget http://mirror.bit.edu.cn/apache/kafka/2.4.0/kafka_2.13-2.4.0.tgz
    tar -zxvf kafka_2.13-2.4.0.tgz
    cd kafka_2.13-2.4.0/bin
    # 运行zookeeper服务
    ./zookeeper-server-start.sh ../config/zookeeper.properties 1>/dev/null 2>&1 &
    # 运行kafka服务
    ./kafka-server-start.sh ../config/server.properties 1>/dev/null 2>&1 &
    

    安装KafkaManager

    yum install java-devel
    git clone https://github.com/yahoo/kafka-manager.git
    cd kafka-manager
    ./sbt clean dist
    cd /root/kafka-manager/target/universal/kafka-manager-x.x.x.x
    

    修改配置文件:vi conf/application.conf
    WX20200216-131314@2x.png

    运行KafkaManager

    cd bin
    ./kafka-manager -Dconfig.files=…/config/application.conf 1>/dev/null 2>&1 &
    

    常用指令:

    创建一个新的Topic:

     ./kafka-topics.sh --create --zookeeper hadoop1:2181,hadoop2:2181,hadoop3:2181 --replication-factor 1 --partitions 3 --topic TopicSession
    

    查询Topic列表:

    ./kafka-topics.sh --list --zookeeper hadoop1:2181,hadoop2:2181,hadoop3:2181
    

    查看Topic详细信息:

    ./kafka-topics.sh --describe --zookeeper hadoop1:2181,hadoop2:2181,hadoop3:2181 --topic TopicSession
    

    建立订阅者console-consumer:

    ./kafka-console-consumer.sh --bootstrap-server hadoop1:9092,hadoop2:9092,hadoop3:9092 --topic TopicSession
    

    建立发布者console-producer:

    ./kafka-console-producer.sh --broker-list hadoop1:9092,hadoop2:9092,hadoop3:9092 --topic TopicSession
    

    posted in GuaiK实验室 read more
  • Zero

    # coding:utf-8
    from selenium import webdriver
    from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
    from selenium.webdriver.chrome.options import Options
    import json
    
    if __name__ == "__main__":
        chrome_options=Options()
        chrome_options.add_argument('--headless')
        browser = webdriver.Chrome(chrome_options=chrome_options)
        browser.get('https://m.baidu.com/s?word=%E6%AD%A6%E6%B1%89%E8%82%BA%E7%82%8E')
        browser.implicitly_wait(20)
        scripts = browser.find_elements_by_xpath('//script[@type="application/json"]')
        for item in scripts:
            j = json.loads(item.get_attribute('innerHTML'))
            if 'data' in j:
                if 'title' in j["data"] and j['data']['title'] == "事件脉络":
                    for es in j['data']['items']:
                        print(es['item_time_date']," ",es['eventDescription'])
        print(len(scripts))
        print(browser.title)
    

    数据来源:百度搜索【关键字:武汉肺炎】

    WX20200121-191448@2x.png

    posted in GuaiK实验室 read more
  • Zero

    TABLE

    id p1
    1 888
    2 888
    3 888
    4 888
    5 888
    6 888
    select array[1,2,3] <@ (select array_agg(id) from mid where p1=888);
    // result: true
    select array[1,2,3,7] <@ (select array_agg(id) from mid where p1=888);
    // result: false
    select array[8,9,10] <@ (select array_agg(id) from mid where p1=888);
    // result: false
    select array[1,2] <@ (select array_agg(id) from mid where p1=888);
    // result: true
    

    ARRAY常用操作符

    Operator Description Example Result
    = equal ARRAY[1.1,2.1,3.1]::int[] = ARRAY[1,2,3] T
    <> not equal ARRAY[1,2,3] <> ARRAY[1,2,4] T
    < less than ARRAY[1,2,3] < ARRAY[1,2,4] T
    > greater than ARRAY[1,4,3] > ARRAY[1,2,4] T
    <= less than or equal ARRAY[1,2,3] <= ARRAY[1,2,3] T
    >= greater than or equal ARRAY[1,4,3] >= ARRAY[1,4,3] T
    @> contains ARRAY[1,4,3] @> ARRAY[3,1] T
    <@ is contained by ARRAY[2,7] <@ ARRAY[1,7,4,2,6] T
    && overlap (have elements in common) ARRAY[1,4,3] && ARRAY[2,1] T
    || array-to-array concatenation ARRAY[1,2,3] || ARRAY[4,5,6] {1,2,3,4,5,6}
    || array-to-array concatenation ARRAY[1,2,3] || ARRAY[[4,5,6],[7,8,9]] {{1,2,3},{4,5,6},{7,8,9}}
    || element-to-array concatenation 3 || ARRAY[4,5,6] {3,4,5,6}
    || array-to-element concatenation ARRAY[4,5,6] || 7 {4,5,6,7}

    posted in GuaiK实验室 read more
  • Zero

    ☣️ 错误码:dyld: malformed mach-o image: segment __DWARF has vmsize < filesize

    🌀 现象:使用go run xxx.go不报错,使用go build xxx.go后运行./xxx会出现错误

    参数 版本
    OS macOS Catalina 10.15.2
    Glfw v3.3
    OpenGL v4.1-core

    ✅修复方式

    // -ldflags: 表示将后面的参数传给连接器
    // -s: 去掉符号信息
    // -w: 去掉DWARF调试信息
    go build -ldflags "-w" ./xxx.go
    

    posted in GuaiK实验室 read more
  • Zero

    如果最近一次commit的信息填写错误需要修改,可以使用以下指令。

    git commit --amend
    

    posted in GuaiK实验室 read more
  • Zero

    -- 安装uuid扩展函数
    create extension "uuid-ossp"
    
    -- 使用uuid生成
    select uuid_generate_v4() as uuid;
    

    WX20200115-123456.png

    posted in GuaiK实验室 read more
  • Zero

    我现在在使用Angular2 + Semantic UI,比较喜欢Semanntic UI的风格。😸

    posted in GuaiK实验室 read more
  • Zero

    最后我清理了下浏览器缓存,重新打开社区网站,发现头像已经被更新成功了。

    posted in GuaiK故障处理 read more
  • Zero

    上传成功后点击“保存更改”,发现头像并没有变化,于是我开始从Nginx日志开始查,一直查到NodeBB的配置文件,都没有发现问题。👨‍💻 👨‍💻 👨‍💻

    posted in GuaiK故障处理 read more
  • Zero

    Python时间字符串转时间戳

    import time
    
    dt = '2020-01-01 00:00:00'
    ts = int(time.mktime(time.strptime(dt, "%Y-%m-%d %H:%M:%S")))
    print(ts)
    

    posted in GuaiK实验室 read more
  • Zero

    GuaikOrg/go-snowflake

    • Github传送门

    https://github.com/GUAIK-ORG/go-snowflake


    Build Status

    ❄️ GO-Snowflake

    Snowflake简介

    在单机系统中我们会使用自增id作为数据的唯一id,自增id在数据库中有利于排序和索引,但是在分布式系统中如果还是利用数据库的自增id会引起冲突,自增id非常容易被爬虫爬取数据。在分布式系统中有使用uuid作为数据唯一id的,但是uuid是一串随机字符串,所以它无法被排序。

    Twitter设计了Snowflake算法为分布式系统生成ID,Snowflake的id是int64类型,它通过datacenterId和workerId来标识分布式系统,下面看下它的组成:

    1bit 41bit 5bit 5bit 12bit
    符号位(保留字段) 时间戳(当前时间-纪元时间) 数据中心id 机器id 自增序列

    算法简介

    在使用Snowflake生成id时,首先会计算时间戳timestamp(当前时间 - 纪元时间),如果timestamp数据超过41bit则异常。同样需要判断datacenterId和workerId不能超过5bit(0-31),在处理自增序列时,如果发现自增序列超过12bit时需要等待,因为当前毫秒下12bit的自增序列被用尽,需要进入下一毫秒后自增序列继续从0开始递增。


    快速开始

    安装

    git clone https://github.com/GUAIK-ORG/go-snowflake.git

    运行

    go run main.go

    测试

    本机测试:

    参数 配置
    OS MacBook Pro (13-inch, Late 2016, Four Thunderbolt 3 Ports)
    CPU 2.9 GHz 双核Intel Core i5
    RAM 8 GB 2133 MHz LPDDR3

    测试代码

    func TestLoad() {
        var wg sync.WaitGroup
        s, err := snowflake.NewSnowflake(int64(0), int64(0))
        if err != nil {
            glog.Error(err)
            return
        }
        var check sync.Map
        t1 := time.Now()
        for i := 0; i < 200000; i++ {
            wg.Add(1)
            go func() {
                defer wg.Done()
                val := s.NextVal()
                if _, ok := check.Load(val); ok {
                    // id冲突检查
                    glog.Error(fmt.Errorf("error#unique: val:%v", val))
                    return
                }
                check.Store(val, 0)
                if val == 0 {
                    glog.Error(fmt.Errorf("error"))
                    return
                }
            }()
        }
        wg.Wait()
        elapsed := time.Since(t1)
        glog.Infof("generate 20k ids elapsed: %v", elapsed)
    }
    

    运行结果

    load

    使用说明

    创建Snowflake对象

    // NewSnowflake(datacenterid, workerid int64) (*Snowflake, error)
    // 参数1 (int64): 数据中心ID (可用范围:0-31)
    // 参数2 (int64): 机器ID    (可用范围:0-31)
    // 返回1 (*Snowflake): Snowflake对象 | nil
    // 返回2 (error): 错误码
    s, err := snowflake.NewSnowflake(int64(0), int64(0))
    if err != nil {
        glog.Error(err)
        return
    }
    

    生成唯一ID

    s, err := snowflake.NewSnowflake(int64(0), int64(0))
    // ......
    // (s *Snowflake) NextVal() int64
    // 返回1 (int64): 唯一ID
    id := s.NextVal()
    // ......
    

    通过ID获取数据中心ID与机器ID

    // ......
    // GetDeviceID(sid int64) (datacenterid, workerid int64)
    // 参数1 (int64): 唯一ID
    // 返回1 (int64): 数据中心ID
    // 返回2 (int64): 机器ID
    datacenterid, workerid := snowflake.GetDeviceID(id))
    

    通过ID获取时间戳(创建ID时的时间戳 - epoch)

    // ......
    // GetTimestamp(sid int64) (timestamp int64)
    // 参数1 (int64): 唯一ID
    // 返回1 (int64): 从epoch开始计算的时间戳
    t := snowflake.GetTimestamp(id)
    

    通过ID获取生成ID时的时间戳

    // ......
    // GetGenTimestamp(sid int64) (timestamp int64)
    // 参数1 (int64): 唯一ID
    // 返回1 (int64): 唯一ID生成时的时间戳
    t := snowflake.GetGenTimestamp(id)
    

    通过ID获取生成ID时的时间(精确到:秒)

    // ......
    // GetGenTime(sid int64)
    // 参数1 (int64): 唯一ID
    // 返回1 (string): 唯一ID生成时的时间
    tStr := snowflake.GetGenTime(id)
    

    查看时间戳字段使用占比(41bit能存储的范围:从epoch开始往后69年)

    // ......
    // GetTimestampStatus() (state float64)
    // 返回1 (float64): 时间戳字段使用占比(范围 0.0 - 1.0)
    status := snowflake.GetTimestampStatus()
    

    posted in GuaiK实验室 read more
  • Zero

    GuaikOrg/go-restful


    🚀GO-Restful框架

    快速开始

    安装

    git clone https://github.com/GUAIK-ORG/go-restful.git

    运行

    go run main.go -log_dir=log -alsologtostderr

    测试

    ./test/session.html提供了一个js的登陆测试用例,请双击运行。测试用的邮箱和密码为:email:demo@guaik.org passwd:hello!

    负载测试

    新建文件:abtest.txt,内容如下:

    {
     "email": "demo@guaik.org",
     "passwd": "hello!"
    }
    

    ab测试指令:ab -n 10000 -c 100 -p "./abtest.txt" -T "application/json" -H "Content-Type: application/json" http://localhost:8080/session

    本机测试:(有条件的可用服务器测试)

    参数 配置
    OS MacBook Pro (13-inch, Late 2016, Four Thunderbolt 3 Ports)
    CPU 2.9 GHz 双核Intel Core i5
    RAM 8 GB 2133 MHz LPDDR3

    测试结果:

    ab-load


    框架介绍

    arch

    框架代码在pkg/restful目录下

    go-restful标准化了Restful接口开发,提供了post delete put get四种操作方式。

    ./cmd目录下session.go实现了一个标准的Restful处理者,可参考使用。

    框架提供了标准的返回数据:当status为0时代表操作成功,并且可在body中获取返回数据。

    在handler中设置成功状态:

    resp.Success(map[string]interface{} {
        "uid":   uid,
        "token": token,
    })
    

    客户端接收到的数据为:

    {"status": 0, "error_code": 0, "error_msg": null, "body": {"token": "xxxxxxxx", "uid": "10001"}}
    

    框架提供了多语言的错误信息,可通过配置的形式注册错误信息:

    restful.HandlerOpts{
        // 配置接口错误信息
        MakeErrorFunc: func(err *restful.Errors){
            err.NewError(1000, "email or passwd error")
            err.Translate(1000, "cn", "邮箱或密码错误") // 中文翻译
        },
    },
    

    客户端接收到的数据为:

    {"status": -1, "error_code": 1000, "error_msg": {"cn": "邮箱或密码错误", "en": "email or passwd error"}, "body": null}
    

    框架可自定义请求解析器,默认提供了json格式解析在./parser/json-parser.go中。

    框架支持过滤器队列,对请求数据进行预处理,在目录./filters目录下默认提供了两个过滤器。

    check.go : 负责参数格式校验,支持string,float64,int64,bool,[]interface{},正则表达式校验。

    token.go : 用来校验访问令牌信息。(需结合缓存和数据库进行修改)。

    将过滤器用于处理者:只要有任何一个过滤器error != nil,之后的过滤器将不会被执行,请求将被丢弃。

    restful.HandlerOpts{
        Filters: []restful.Filter{
            // 1、该接口需要验证token,如果token无效将不被执行
            &filter.CheckToken{},
            // 2、校验参数
            &filter.CheckParams{
                // 参数检查
                Params: map[string]interface{}{
                    // 正则校验
                    "email": filter.FieldRegexp(`^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$`),
                    // 6~12长度字符串校验
                    "passwd": filter.FieldString().SetLength(6, 12),
                },
            },
        },
    },
    

    posted in GuaiK实验室 read more
  • Zero

    Golang允许跨站Http请求


    func (s *Service) Handler(w http.ResponseWriter, request *http.Request) {
        w.Header().Add("Access-Control-Allow-Origin", "*")
        w.Header().Add("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,PATCH,OPTIONS")
        w.Header().Add("Access-Control-Allow-Headers", "Content-Type, Accept, Authorization")
    }
    

    posted in GuaiK实验室 read more
  • Zero

    “Type ‘EventEmitter’ is not generic” ERROR in angular


    产生原因:
    在使用VSCode添加EventEmitter时,IDE会自动导入imports EventEmitter from Node.js

    import { EventEmitter } from "events";
    

    修改成Angular中的EventEmitter即可:

    import { EventEmitter } from "@angular/core";
    

    posted in GuaiK实验室 read more
  • Zero

    Angular2安装Http模块


    默认情况下Angular2没有自带Http模块,需要安装才能使用。


    npm install @angular/http
    

    posted in GuaiK实验室 read more
  • Zero

    Golang基于Redis的分布式锁


    我理解的分布式锁:

    1、为了避免单持有锁的进程奔溃而无法释放锁,所以必须能够为锁设定过期时间自动释放锁资源
    2、使用TTL检查锁是否成功的被设置过期时间,如果返回-1(未被设置)的话,使用Expire为其设定过期时间
    3、在释放锁的时候需要使用Watch命令,确保监测的值在事务执行时时未被改变,如果其他进程修改了锁,会触发事务异常,然后重新执行Watch。


    分享一个我写的Redis封装类,仅实现了连接和锁

    package dao
    
    import (
    	"time"
    
    	"github.com/go-redis/redis/v7"
    	uuid "github.com/satori/go.uuid"
    )
    
    type Rds struct {
    	Client         *redis.Client
    	AcquireTimeout int32
    	LockTimeout    int32
    }
    
    func NewRds(host, passwd string, db int) (rds *Rds, err error) {
    	rdsOpt := &redis.Options{
    		Addr:     host,
    		Password: passwd,
    		DB:       db,
    	}
    	client := redis.NewClient(rdsOpt)
    	_, err = client.Ping().Result()
    	if err != nil {
    		return
    	}
    	rds = &Rds{Client: client, AcquireTimeout: 10, LockTimeout: 10}
    	return
    }
    
    func (r *Rds) AcquireLockWithTimeout(key string) (identifier string, b bool) {
    	identifier = uuid.NewV4().String()
    	lockname := "lock:" + key
    	end := time.Now().Add(time.Second * time.Duration(r.AcquireTimeout))
    	for time.Now().Before(end) {
    		if r.Client.SetNX(lockname, identifier, time.Second*time.Duration(r.LockTimeout)).Val() {
    			// 如果key不存在,并成功设置了key
    			b = true
    			return
    		} else if r.Client.TTL(lockname).Val() == -1 {
    			// 如果key存在,但是没有剩余时间
    			r.Client.Expire(lockname, time.Second*time.Duration(r.LockTimeout))
    		}
    		time.Sleep(time.Microsecond)
    	}
    	return
    }
    
    func (r *Rds) ReleaseLock(key, identifier string) (b bool) {
    	lockname := "lock:" + key
    	txf := func(tx *redis.Tx) error {
    		v, err := tx.Get(lockname).Result()
    		if err != nil {
    			return err
    		}
    		_, err = tx.Pipelined(func(pipe redis.Pipeliner) error {
    			if v == identifier {
    				pipe.Del(lockname)
    				b = true
    			}
    			return nil
    		})
    		return err
    	}
    	for {
    		err := r.Client.Watch(txf, lockname)
    		if err == nil {
    			break
    		} else if err == redis.TxFailedErr {
    			glog.Error(err)
    		}
    	}
    	return
    }
    

    posted in GuaiK实验室 read more
  • Zero

    使用Golang对MongoDB创建索引


    1、下载MongoDB驱动

    go get github.com/mongodb/mongo-go-driver 
    

    2、连接MongoDB

    import (
    	"context"
    	"log"
    
    	"go.mongodb.org/mongo-driver/mongo"
    	"go.mongodb.org/mongo-driver/mongo/options"
    	"go.mongodb.org/mongo-driver/mongo/readpref"
    )
    
    client, err := mongo.NewClient(options.Client().ApplyURI("mongodb://127.0.0.1:27017"))
    if err != nil {
        log.Fatal(err)
    }
    err = client.Connect(context.TODO())
    if err != nil {
        log.Fatal(err)
    }
    

    3、测试连接是否成功

    if err := client.Ping(context.TODO(), readpref.Primary()); err != nil {
        log.Fatal(err)
    }
    

    4、创建唯一索引

    // Email可能为空(注册时被必填项),为了防止
    // 空值的唯一冲突,所以设置成稀疏索引
    opEmail := options.Index()
    opEmail.SetName("email_index")
    opEmail.SetUnique(true)
    opEmail.SetSparse(true)
    
    // 默认唯一索引配置
    opDef := options.Index()
    opDef.SetName("def_index")
    opDef.SetUnique(true)
    
    // 使用配置生成索引对象
    modDef := mongo.IndexModel{
    	Keys: bson.M{
    		"nickname": 1,
    		"phone":    1,
    	}, Options: opDef,
    }
    modEmail := mongo.IndexModel{
    	Keys: bson.M{
    		"email": 1,
    	}, Options: opEmail,
    }
    
    // 为Collection配置索引,Indexes().CreateMany()可以一次
    // 指定多个索引对象
    col := client.Database("db_name").Collection("User")
    col.Indexes().CreateMany(context.TODO(), []mongo.IndexModel{modDef, modEmail})
    

    5、测试

    type RecUserI struct {
    	Nickname string             `bson:"nickname"`
    	Phone    string             `bson:"phone"`
    	Email    string             `bson:"email"`
    }
    // 邮箱为空值,并且使电话冲突
    result, err := col.InsertOne(context.TODO(), RecUserINickname: "guaik0", Phone: "18888888888"})
    if err != nil {
    	log.Fatal(err)
    	return
    }
    result, err = col.InsertOne(context.TODO(), RecUserINickname: "guaik1", Phone: "18888888888"})
    if err != nil {
    	log.Fatal(err)
    	return
    }
    

    posted in GuaiK实验室 read more
  • Zero

    优酷视频提取


    优酷使用的是m3u形式的媒体播放格式,通过拦截Http Response并且判断Content-Type是否等于application/vnd.apple.mpegurl可以很容易的拿到m3u文件,那么就可以批量下载ts格式的视频文件了,按照m3u列表进行播放即可。

    土豆视频提取


    土豆视频采用了定长格式的MP4格式视频,每播放完一段才会缓存下一段,所以相对于优酷来说并不能一次性的拿到所有的视频,在测试的时候发现在广告等待期间它不会加载MP4视频。

    posted in GuaiK实验室 read more