├── cmd ├── app └── main.go ├── .DS_Store ├── assert ├── alipay.jpg └── wepay.jpg ├── global ├── constant.go ├── notification_test.go ├── cfgs.go ├── notification.go └── helper.go ├── .gitignore ├── go.mod ├── Dockerfile ├── logs └── logs.go ├── README.md ├── go.sum ├── chromedpEngine └── allocator.go └── secKill └── jdSecKill.go /cmd/app: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karhow/SecondKill/HEAD/cmd/app -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karhow/SecondKill/HEAD/.DS_Store -------------------------------------------------------------------------------- /assert/alipay.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karhow/SecondKill/HEAD/assert/alipay.jpg -------------------------------------------------------------------------------- /assert/wepay.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karhow/SecondKill/HEAD/assert/wepay.jpg -------------------------------------------------------------------------------- /global/constant.go: -------------------------------------------------------------------------------- 1 | package global 2 | 3 | 4 | //这个包放置公共常量 5 | const ( 6 | DateTimeFormatStr = "2006-01-02 15:04:05" 7 | DateFormatStr = "2006-01-02" 8 | 9 | ) 10 | 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | .idea/ 15 | *.log -------------------------------------------------------------------------------- /global/notification_test.go: -------------------------------------------------------------------------------- 1 | package global 2 | 3 | import "testing" 4 | 5 | func TestNotifyUser(t *testing.T) { 6 | type args struct { 7 | v []interface{} 8 | } 9 | tests := []struct { 10 | name string 11 | args args 12 | }{ 13 | // TODO: Add test cases. 14 | {"pushplus 发送消息测试", 15 | args{ 16 | []interface{}{"测试信息1", "测试信息2", "测试信息3"}, 17 | }, 18 | }, 19 | } 20 | for _, tt := range tests { 21 | t.Run(tt.name, func(t *testing.T) { 22 | NotifyUser(tt.args.v...) 23 | }) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/WuhuGoldPilot/SecondKill 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394 7 | github.com/chromedp/cdproto v0.0.0-20201204063249-be40c824ad18 8 | github.com/chromedp/chromedp v0.5.4 9 | github.com/giant-stone/go v0.0.0-20201217075953-ae0ac5ecfb4b 10 | github.com/gobwas/httphead v0.1.0 // indirect 11 | github.com/gookit/color v1.3.6 12 | github.com/tidwall/gjson v1.6.7 13 | golang.org/x/sys v0.0.0-20201223074533-0d417f636930 // indirect 14 | ) 15 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:alpine AS builder 2 | 3 | LABEL stage=gobuilder 4 | 5 | ENV CGO_ENABLED 0 6 | ENV GOOS linux 7 | ENV GOPROXY https://goproxy.cn,direct 8 | 9 | WORKDIR /build 10 | 11 | ADD go.mod . 12 | ADD go.sum . 13 | RUN go mod download 14 | COPY . . 15 | RUN go build -ldflags="-s -w" -o /app/second-kill ./cmd/main.go 16 | 17 | FROM kasmweb/hunchly:develop-rolling 18 | 19 | ENV TZ Asia/Shanghai 20 | 21 | WORKDIR /app 22 | COPY --from=builder /app/second-kill /app/second-kill 23 | 24 | ENTRYPOINT [ "./second-kill" ] 25 | CMD [" "] -------------------------------------------------------------------------------- /global/cfgs.go: -------------------------------------------------------------------------------- 1 | package global 2 | 3 | var ( 4 | // PushToken 集成pushlus 一键免费推送消息 令牌 5 | // http://www.pushplus.plus/doc/guide/api.html#%E4%B8%80%E3%80%81%E5%8F%91%E9%80%81%E6%B6%88%E6%81%AF%E6%8E%A5%E5%8F%A3 6 | PushToken = "" 7 | // PushToken = "xx" 8 | PushplusReqURL = "http://www.pushplus.plus/send" 9 | ) 10 | 11 | // PushplusReq pushplus 消息发送请求体 12 | // 请求地址:http://www.pushplus.plus/send 13 | type PushplusReq struct { 14 | // Token 15 | // Required 16 | Token string `json:"token"` 17 | Title string `json:"title"` 18 | // Content 19 | // Required 20 | Content string `json:"content"` 21 | Topic string `json:"topic"` 22 | Template string `json:"template"` 23 | Channel string `json:"channel"` 24 | Webhook string `json:"webhook"` 25 | CallbackUrl string `json:"callbackUrl"` 26 | Timestamp string `json:"timestamp"` 27 | } 28 | -------------------------------------------------------------------------------- /global/notification.go: -------------------------------------------------------------------------------- 1 | package global 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "strings" 7 | "time" 8 | 9 | "github.com/giant-stone/go/ghttp" 10 | "github.com/giant-stone/go/gutil" 11 | 12 | "github.com/WuhuGoldPilot/SecondKill/logs" 13 | ) 14 | 15 | func NotifyUser(v ...interface{}) { 16 | if PushToken == "" { 17 | logs.PrintlnInfo("没有推送token,推送失败!") 18 | return 19 | } 20 | 21 | chunks := []string{} 22 | for _, chunk := range v { 23 | chunks = append(chunks, fmt.Sprintf("%v", chunk)) 24 | } 25 | msg := strings.Join(chunks, " ") 26 | 27 | fullurl := PushplusReqURL 28 | reqBody := PushplusReq{ 29 | Token: PushToken, 30 | Title: "秒杀程序通知", 31 | Content: msg, 32 | Channel: "wechat", 33 | } 34 | rqBody, _ := json.Marshal(&reqBody) 35 | rq := ghttp.New(). 36 | SetTimeout(time.Second * 3). 37 | SetRequestMethod("POST"). 38 | SetUri(fullurl). 39 | SetPostBody(&rqBody) 40 | err := rq.Send() 41 | if gutil.CheckErr(err) { 42 | logs.PrintErr("notify user fail", string(rq.RespBody)) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /global/helper.go: -------------------------------------------------------------------------------- 1 | package global 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/json" 6 | "fmt" 7 | "math/rand" 8 | "strconv" 9 | "time" 10 | ) 11 | 12 | func UnixMilli() int64 { 13 | return time.Now().UnixNano() / 1e6 14 | } 15 | 16 | func GenerateRangeNum(min, max int64) int64 { 17 | rand.Seed(time.Now().UnixNano()) 18 | randNum := rand.Int63n(max - min) + min 19 | return randNum 20 | } 21 | 22 | func Hour2Unix(hour string) (time.Time, error) { 23 | return time.ParseInLocation(DateTimeFormatStr, time.Now().Format(DateFormatStr) + " " + hour, time.Local) 24 | } 25 | 26 | func Md5(s string) string { 27 | data := []byte(s) 28 | has := md5.Sum(data) 29 | return fmt.Sprintf("%x", has) 30 | } 31 | 32 | func Json2Map(j string) map[string]interface{} { 33 | r := make(map[string]interface{}) 34 | _ = json.Unmarshal([]byte(j), &r) 35 | return r 36 | } 37 | 38 | 39 | func RandFloats(min, max float64, n int) float64 { 40 | rand.Seed(time.Now().UnixNano()) 41 | res := min + rand.Float64() * (max - min) 42 | res, _ = strconv.ParseFloat(fmt.Sprintf("%."+strconv.Itoa(n)+"f", res), 64) 43 | return res 44 | } -------------------------------------------------------------------------------- /logs/logs.go: -------------------------------------------------------------------------------- 1 | package logs 2 | 3 | import ( 4 | "log" 5 | "os" 6 | 7 | "github.com/gookit/color" 8 | ) 9 | 10 | var logger *log.Logger 11 | 12 | func AllowFileLogs() { 13 | logsFile, err := os.OpenFile("mtSecKill.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) 14 | if err == nil { 15 | logger = log.New(logsFile, "", log.Lshortfile|log.LstdFlags) 16 | } 17 | } 18 | 19 | //这个包用来统一的日志输出处理 20 | //目前只做简单两个方法 后续根据具体需要在这里增加日志操作 21 | func Println(v ...interface{}) { 22 | if logger != nil { 23 | logger.Println(v...) 24 | } 25 | log.Println(v...) 26 | } 27 | 28 | func print2(color2 color.Color, v ...interface{}) { 29 | if logger != nil { 30 | logger.Println(v...) 31 | } 32 | color2.Light().Println(v...) 33 | } 34 | 35 | func PrintlnSuccess(v ...interface{}) { 36 | print2(color.Green, v...) 37 | } 38 | 39 | func PrintlnInfo(v ...interface{}) { 40 | print2(color.LightCyan, v...) 41 | } 42 | 43 | func PrintlnWarning(v ...interface{}) { 44 | print2(color.Yellow, v...) 45 | } 46 | 47 | func PrintErr(v ...interface{}) { 48 | print2(color.FgLightRed, v...) 49 | } 50 | 51 | func Fatal(v ...interface{}) { 52 | log.Fatal(v...) 53 | } 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SecondKill 京东商品秒杀系统 2 | 3 | > ***这里来统一回复一下,对于不会编译的用户,可在 xxx 获取已经编译好的发布版 mac用户请在控制台中运行 其他错误可尝试升级浏览器 如果浏览器路径未找到请手动输入您的浏览器执行文件地址 最好是google浏览器*** 4 | > ***最后祝各位好运*** 5 | 6 | ## 特别声明: 7 | 8 | * 本仓库发布的`SecondKill`项目中涉及的任何脚本,仅用于测试和学习研究,禁止用于商业用途,不能保证其合法性,准确性,完整性和有效性,请根据情况自行判断。 9 | 10 | * 本项目内所有资源文件,禁止任何公众号、自媒体进行任何形式的转载、发布。 11 | 12 | * `WuhuGoldPilot` 对任何脚本问题概不负责,包括但不限于由任何脚本错误导致的任何损失或损害. 13 | 14 | * 间接使用脚本的任何用户,包括但不限于建立VPS或在某些行为违反国家/地区法律或相关法规的情况下进行传播, `WuhuGoldPilot` 对于由此引起的任何隐私泄漏或其他后果概不负责。 15 | 16 | * 请勿将`SecondKill`项目的任何内容用于商业或非法目的,否则后果自负。 17 | 18 | * 如果任何单位或个人认为该项目的脚本可能涉嫌侵犯其权利,则应及时通知并提供身份证明,所有权证明,我们将在收到认证文件后删除相关脚本。 19 | 20 | * 以任何方式查看此项目的人或直接或间接使用`SecondKill`项目的任何脚本的使用者都应仔细阅读此声明。`WuhuGoldPilot` 保留随时更改或补充此免责声明的权利。一旦使用并复制了任何相关脚本或`SecondKill`项目,则视为您已接受此免责声明。 21 | 22 | * 您必须在下载后的24小时内从计算机或手机中完全删除以上内容。 23 | 24 | * 本项目遵循`GPL-3.0 License`协议,如果本特别声明与`GPL-3.0 License`协议有冲突之处,以本特别声明为准。 25 | 26 | * 该版本为未测试版本 运行过程可能会出错 后续会测试优化 27 | 28 | > ***您使用或者复制了本仓库且本人制作的任何代码或项目,则视为`已接受`此声明,请仔细阅读*** 29 | > ***您在本声明未发出之时点使用或者复制了本仓库且本人制作的任何代码或项目且此时还在使用,则视为`已接受`此声明,请仔细阅读*** 30 | 31 | ## 主要功能 32 | - 抢购京东商品(包括但不限于飞天茅台) 33 | - 登陆京东商城([www.jd.com](http://www.jd.com/)) 34 | - 使用进程调起的浏览器登陆京东 【无需其他任何操作】 35 | - 秒杀预约后等待抢购 36 | - 定时开始自动抢购 37 | - 抢购过程中浏览器会打开对应works数的窗口,请不要大惊小怪 也不要关闭 38 | - (可选)抢购成功通过微信[一键免费推送消息](www.pushplus.plus)提醒用户 39 | 40 | ## 运行环境 41 | - [google浏览器] 42 | 43 | ## 运行参数 44 | - mtSecKill -sku=100012043978 -num=2 -works=6 -time=11:59:59 -payPwd=xxxx -token=xxx 运行参数 均有默认可以不加 45 | - 新增eid与fp参数 对于无法自动获取到的用户 请手动填入 默认空 会自动获取 46 | 47 | **注意**: 48 | 茅台商品id为100012043978,现在抢购时间为12点,payPwd为支付密码,抢购成功微信推送提醒令牌token参数可微信扫码登录https://www.pushplus.plus获取 49 | 50 | ## 打赏 51 | 要是客官抢到了茅台,心情好,请我喝一杯咖啡好不好:) 52 | ![微信](./assert/wepay.jpg)![支付宝](./assert/alipay.jpg) -------------------------------------------------------------------------------- /cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "flag" 6 | "os" 7 | "strings" 8 | "time" 9 | 10 | "github.com/WuhuGoldPilot/SecondKill/global" 11 | "github.com/WuhuGoldPilot/SecondKill/logs" 12 | "github.com/WuhuGoldPilot/SecondKill/secKill" 13 | ) 14 | 15 | var skuId = flag.String("sku", "100012043978", "茅台商品ID") 16 | var num = flag.Int("num", 2, "商品数量") 17 | var works = flag.Int("works", 5, "并发数") 18 | var start = flag.String("time", "11:59:59.500", "开始时间---不带日期") 19 | var brwoserPath = flag.String("execPath", "", "浏览器执行路径,路径不能有空格") 20 | var eid = flag.String("eid", "", "如果不传入,可自动获取,对于无法获取的用户可手动传入参数") 21 | var fp = flag.String("fp", "", "如果不传入,可自动获取,对于无法获取的用户可手动传入参数") 22 | var payPwd = flag.String("payPwd", "", "支付密码 可不填") 23 | var isFileLog = flag.Bool("isFileLog", false, "是否使用文件记录日志") 24 | 25 | func init() { 26 | flag.StringVar(&global.PushToken, "token", "", "pushplus一键推送微信提醒令牌 可不填") 27 | 28 | flag.Parse() 29 | } 30 | 31 | func main() { 32 | var err error 33 | 34 | if *isFileLog { 35 | logs.AllowFileLogs() 36 | } 37 | 38 | execPath := "" 39 | if *brwoserPath != "" { 40 | execPath = *brwoserPath 41 | } 42 | RE: 43 | jdSecKill := secKill.NewJdSecKill(execPath, *skuId, *num, *works) 44 | jdSecKill.StartTime, err = global.Hour2Unix(*start) 45 | if err != nil { 46 | logs.Fatal("开始时间初始化失败", err) 47 | } 48 | 49 | jdSecKill.PayPwd = *payPwd 50 | if *eid != "" { 51 | if *fp == "" { 52 | logs.Fatal("请传入fp参数") 53 | } 54 | jdSecKill.SetEid(*eid) 55 | } 56 | 57 | if *fp != "" { 58 | if *eid == "" { 59 | logs.Fatal("请传入eid参数") 60 | } 61 | jdSecKill.SetFp(*fp) 62 | } 63 | 64 | if jdSecKill.StartTime.Unix() < time.Now().Unix() { 65 | jdSecKill.StartTime = jdSecKill.StartTime.AddDate(0, 0, 1) 66 | } 67 | jdSecKill.SyncJdTime() 68 | logs.PrintlnInfo("开始执行时间为:", jdSecKill.StartTime.Format(global.DateTimeFormatStr)) 69 | 70 | err = jdSecKill.Run() 71 | if err != nil { 72 | if strings.Contains(err.Error(), "exec") { 73 | logs.PrintlnInfo("默认浏览器执行路径未找到," + execPath + " 请重新输入:") 74 | scanner := bufio.NewScanner(os.Stdin) 75 | for scanner.Scan() { 76 | execPath = scanner.Text() 77 | if execPath != "" { 78 | break 79 | } 80 | } 81 | goto RE 82 | } 83 | logs.Fatal(err) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394 h1:OYA+5W64v3OgClL+IrOD63t4i/RW7RqrAVl9LTZ9UqQ= 2 | github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394/go.mod h1:Q8n74mJTIgjX4RBBcHnJ05h//6/k6foqmgE45jTQtxg= 3 | github.com/chromedp/cdproto v0.0.0-20201009231348-1c6a710e77de/go.mod h1:zx0YH7hi8sqkYXAa0LZZxpQLDsU8/a2jzbYbK79dQO8= 4 | github.com/chromedp/cdproto v0.0.0-20201204063249-be40c824ad18 h1:Ci3nU2yz9gRTkITWowWFTtAKYXGwbFBM6cQNoNZxH8o= 5 | github.com/chromedp/cdproto v0.0.0-20201204063249-be40c824ad18/go.mod h1:55pim6Ht4LJKdVLlyFJV/g++HsEA1hQxPbB5JyNdZC0= 6 | github.com/chromedp/chromedp v0.5.4 h1:hQC6Wj+0mBu2B+HDF4eoAyTkDo0BnL16sz3gAAmZ8fc= 7 | github.com/chromedp/chromedp v0.5.4/go.mod h1:Jl2gvx3U9zl4lS/dMjDVGXX0jZ4qo/vsGreqYME/lqc= 8 | github.com/chromedp/sysutil v0.0.0-20201009230539-dc95e7e83e8a/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww= 9 | github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic= 10 | github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww= 11 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 12 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 13 | github.com/giant-stone/go v0.0.0-20201217075953-ae0ac5ecfb4b h1:nki2enK5n+WJDIyExfj89Tx+uMN6/ig0gJZXciw65gU= 14 | github.com/giant-stone/go v0.0.0-20201217075953-ae0ac5ecfb4b/go.mod h1:Qyvj5MG3Dc1v92kCVIHo499C8MObvNge3GW0rFWWCOg= 15 | github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= 16 | github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 17 | github.com/gobwas/httphead v0.0.0-20200921212729-da3d93bc3c58/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= 18 | github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= 19 | github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= 20 | github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= 21 | github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= 22 | github.com/gobwas/ws v1.0.4 h1:5eXU1CZhpQdq5kXbKb+sECH5Ia5KiO6CYzIzdlVx6Bs= 23 | github.com/gobwas/ws v1.0.4/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= 24 | github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= 25 | github.com/gookit/color v1.3.6 h1:Rgbazd4JO5AgSTVGS3o0nvaSdwdrS8bzvIXwtK6OiMk= 26 | github.com/gookit/color v1.3.6/go.mod h1:R3ogXq2B9rTbXoSHJ1HyUVAZ3poOJHpd9nQmyGZsfvQ= 27 | github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= 28 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 29 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 30 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 31 | github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= 32 | github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= 33 | github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 34 | github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= 35 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 36 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 37 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 38 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 39 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 40 | github.com/tidwall/gjson v1.6.7 h1:Mb1M9HZCRWEcXQ8ieJo7auYyyiSux6w9XN3AdTpxJrE= 41 | github.com/tidwall/gjson v1.6.7/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI= 42 | github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE= 43 | github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= 44 | github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU= 45 | github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= 46 | golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 47 | golang.org/x/sys v0.0.0-20201223074533-0d417f636930 h1:vRgIt+nup/B/BwIS0g2oC0haq0iqbV3ZA+u6+0TlNCo= 48 | golang.org/x/sys v0.0.0-20201223074533-0d417f636930/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 49 | -------------------------------------------------------------------------------- /chromedpEngine/allocator.go: -------------------------------------------------------------------------------- 1 | package chromedpEngine 2 | 3 | import ( 4 | "context" 5 | "math/rand" 6 | "net/http" 7 | "sync" 8 | "time" 9 | 10 | "github.com/WuhuGoldPilot/SecondKill/logs" 11 | "github.com/chromedp/cdproto/dom" 12 | "github.com/chromedp/cdproto/log" 13 | "github.com/chromedp/cdproto/network" 14 | "github.com/chromedp/cdproto/page" 15 | "github.com/chromedp/cdproto/target" 16 | "github.com/chromedp/chromedp" 17 | ) 18 | 19 | var DefaultOptions = []chromedp.ExecAllocatorOption{ 20 | chromedp.Flag("headless", false), 21 | chromedp.Flag("hide-scrollbars", false), 22 | chromedp.Flag("mute-audio", true), 23 | chromedp.Flag("disable-infobars", true), 24 | chromedp.Flag("enable-automation", false), 25 | chromedp.Flag("start-maximized", true), 26 | 27 | chromedp.Flag("disable-default-apps", false), 28 | chromedp.Flag("no-sandbox", false), 29 | // 隐身模式启动 30 | // chromedp.Flag("incognito", true), 31 | chromedp.Flag("disable-extensions", false), 32 | chromedp.Flag("disable-plugins", false), 33 | chromedp.NoDefaultBrowserCheck, 34 | chromedp.NoFirstRun, 35 | } 36 | 37 | var globalCtx *GlobalBackgroundCtx = nil 38 | var mu sync.Mutex 39 | 40 | type GlobalBackgroundCtx struct { 41 | background context.Context 42 | Cancel context.CancelFunc 43 | } 44 | 45 | func GetGlobalCtx() context.Context { 46 | if globalCtx == nil { 47 | NewGlobalCtx() 48 | } 49 | return globalCtx.background 50 | } 51 | 52 | func NewGlobalCtx() { 53 | 54 | mu.Lock() 55 | defer mu.Unlock() 56 | if globalCtx != nil { 57 | return 58 | } 59 | c, cc := context.WithCancel(context.Background()) 60 | globalCtx = &GlobalBackgroundCtx{ 61 | background: c, 62 | Cancel: cc, 63 | } 64 | } 65 | 66 | func CancelGlobalCtx() { 67 | mu.Lock() 68 | defer mu.Unlock() 69 | if globalCtx == nil { 70 | return 71 | } 72 | cc := globalCtx.Cancel 73 | cc() 74 | logs.PrintlnSuccess("cancel global ctx...") 75 | globalCtx = nil 76 | } 77 | 78 | var UserAgent = []string{ 79 | "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.75 Safari/537.36", 80 | "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50", 81 | "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0", 82 | "Mozilla/5.0 (X11; U; Linux x86_64; zh-CN; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10", 83 | "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 SE 2.X MetaSr 1.0", 84 | "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36 QIHU 360SE", 85 | "Mozilla/5.0 (Windows NT 6.1; Win64; x64; Trident/7.0; .NET CLR 2.0.50727; SLCC2; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET4.0C; .NET4.0E; rv:11.0) like Gecko", 86 | "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36 OPR/60.0.3255.84", 87 | "Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; en) Presto/2.8.131 Version/11.11", 88 | "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.57.2 (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2", 89 | "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 UBrowser/6.2.4098.3 Safari/537.36", 90 | "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.98 Safari/537.36 LBBROWSER", 91 | "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3676.400 QQBrowser/10.4.3473.400", 92 | "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36 Maxthon/5.2.7.2500", 93 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.131 Safari/537.36", 94 | "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 BIDUBrowser/8.7 Safari/537.36", 95 | "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36 QIHU 360EE", 96 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36", 97 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36", 98 | "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; WOW64; Trident/4.0; SLCC1)", 99 | "Mozilla/5.0 (Windows; U; Windows NT 6.0; en; rv:1.9.1.7) Gecko/20091221 Firefox/3.5.7", 100 | "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36", 101 | } 102 | 103 | func GetRandUserAgent() string { 104 | RE: 105 | al := len(UserAgent) 106 | if al > 1 { 107 | rand.Seed(time.Now().UnixNano()) 108 | return UserAgent[rand.Intn(al)] 109 | } 110 | goto RE 111 | } 112 | 113 | func AddDefaultOptions(option ...chromedp.ExecAllocatorOption) { 114 | DefaultOptions = append(DefaultOptions, option...) 115 | } 116 | 117 | func RequestByCookie(ctx context.Context, req *http.Request, isDisableRedirects bool) (*http.Response, error) { 118 | httpClient := &http.Client{} 119 | if isDisableRedirects { 120 | httpClient.CheckRedirect = func(req *http.Request, via []*http.Request) error { 121 | return http.ErrUseLastResponse 122 | } 123 | } 124 | cookies, err := network.GetCookies().WithUrls([]string{req.URL.String()}).Do(ctx) 125 | if err != nil { 126 | return nil, err 127 | } 128 | for _, c := range cookies { 129 | req.AddCookie(&http.Cookie{ 130 | Name: c.Name, 131 | Value: c.Value, 132 | }) 133 | } 134 | return httpClient.Do(req) 135 | } 136 | 137 | func CreateOptions(opts ...chromedp.ExecAllocatorOption) []chromedp.ExecAllocatorOption { 138 | options := append(chromedp.DefaultExecAllocatorOptions[:], DefaultOptions...) 139 | options = append(options, opts...) 140 | return options 141 | } 142 | 143 | func WaitDocumentUpdated(ctx context.Context) (<-chan struct{}, context.CancelFunc) { 144 | ctxNew, ccNew := context.WithCancel(ctx) 145 | _ = dom.Enable().Do(ctxNew) 146 | _ = page.Enable().Do(ctxNew) 147 | ch := make(chan struct{}, 1) 148 | chromedp.ListenTarget(ctxNew, func(ev interface{}) { 149 | isUpdated := false 150 | switch ev.(type) { 151 | case *dom.EventDocumentUpdated: 152 | isUpdated = true 153 | case *dom.EventCharacterDataModified: 154 | isUpdated = true 155 | case *target.EventTargetInfoChanged: 156 | isUpdated = true 157 | case *target.EventTargetCreated: 158 | isUpdated = true 159 | default: 160 | return 161 | } 162 | if isUpdated { 163 | select { 164 | case <-ctxNew.Done(): 165 | case ch <- struct{}{}: 166 | } 167 | close(ch) 168 | ccNew() 169 | return 170 | } 171 | }) 172 | return ch, ccNew 173 | } 174 | 175 | func NewExecCtx(opts ...chromedp.ExecAllocatorOption) (context.Context, context.CancelFunc) { 176 | topC, _ := context.WithCancel(GetGlobalCtx()) 177 | c, _ := chromedp.NewExecAllocator(topC, CreateOptions(opts...)...) 178 | ctx, cancel := chromedp.NewContext(c) 179 | return ctx, cancel 180 | } 181 | 182 | func NewExecRemoteCtx(remoteWs string, opts ...chromedp.ExecAllocatorOption) (context.Context, context.CancelFunc) { 183 | topC, _ := context.WithCancel(GetGlobalCtx()) 184 | c, _ := chromedp.NewExecAllocator(topC, CreateOptions(opts...)...) 185 | c, _ = chromedp.NewRemoteAllocator(c, remoteWs) 186 | ctx, cancel := chromedp.NewContext(c) 187 | return ctx, cancel 188 | } 189 | 190 | func NewExecAllocator(tasks chromedp.Tasks, opts ...chromedp.ExecAllocatorOption) error { 191 | //超时设置 192 | topC, topCC := context.WithCancel(GetGlobalCtx()) 193 | defer topCC() 194 | c, cc := chromedp.NewExecAllocator(topC, CreateOptions(opts...)...) 195 | defer cc() 196 | ctx, cancel := chromedp.NewContext(c) 197 | defer cancel() 198 | _ = log.Disable().Do(ctx) 199 | err := chromedp.Run(ctx, tasks) 200 | logs.PrintlnSuccess("chromedp.Run end...") 201 | if err != nil { 202 | return err 203 | } 204 | return nil 205 | } 206 | 207 | //阻塞浏览器方法 208 | func WaitAction(wait sync.WaitGroup) chromedp.ActionFunc { 209 | return func(ctx context.Context) error { 210 | wait.Add(1) 211 | wait.Wait() 212 | return nil 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /secKill/jdSecKill.go: -------------------------------------------------------------------------------- 1 | package secKill 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "io/ioutil" 8 | "math/rand" 9 | "net/http" 10 | "net/url" 11 | "os" 12 | "strconv" 13 | "strings" 14 | "sync" 15 | "time" 16 | 17 | "github.com/WuhuGoldPilot/SecondKill/chromedpEngine" 18 | "github.com/WuhuGoldPilot/SecondKill/global" 19 | "github.com/WuhuGoldPilot/SecondKill/logs" 20 | "github.com/axgle/mahonia" 21 | "github.com/chromedp/cdproto/cdp" 22 | "github.com/chromedp/cdproto/dom" 23 | "github.com/chromedp/cdproto/network" 24 | "github.com/chromedp/cdproto/page" 25 | "github.com/chromedp/cdproto/target" 26 | "github.com/chromedp/chromedp" 27 | "github.com/tidwall/gjson" 28 | ) 29 | 30 | var ErrEmptyData = errors.New("空数据") 31 | 32 | // jdSecKill 秒杀客户端结构 33 | type jdSecKill struct { 34 | // ctx 上下文 35 | ctx context.Context 36 | // cacal 取消函数 37 | cancel context.CancelFunc 38 | bCtx context.Context 39 | // isLogin 是否登陆 40 | isLogin bool 41 | isClose bool 42 | mu sync.Mutex 43 | userAgent string 44 | UserInfo gjson.Result 45 | // SkuId 商品id 46 | SkuId string 47 | SecKillUrl string 48 | SecKillNum int 49 | SecKillInfo gjson.Result 50 | eid string 51 | fp string 52 | Works int 53 | IsOkChan chan struct{} 54 | IsOk bool 55 | StartTime time.Time 56 | DiffTime int64 57 | PayPwd string 58 | } 59 | 60 | // NewJdSecKill 新建秒杀客户端 61 | func NewJdSecKill(execPath string, skuId string, num, works int) *jdSecKill { 62 | if works < 0 { 63 | works = 1 64 | } 65 | jsk := &jdSecKill{ 66 | ctx: nil, 67 | isLogin: false, 68 | isClose: false, 69 | userAgent: chromedpEngine.GetRandUserAgent(), 70 | SkuId: skuId, 71 | SecKillNum: num, 72 | Works: works, 73 | IsOk: false, 74 | IsOkChan: make(chan struct{}, 1), 75 | } 76 | jsk.ctx, jsk.cancel = chromedpEngine.NewExecCtx(chromedp.ExecPath(execPath), chromedp.UserAgent(jsk.userAgent)) 77 | return jsk 78 | } 79 | 80 | func (jsk *jdSecKill) SetEid(eid string) { 81 | jsk.eid = eid 82 | } 83 | 84 | func (jsk *jdSecKill) SetFp(fp string) { 85 | jsk.fp = fp 86 | } 87 | 88 | func (jsk *jdSecKill) Stop() { 89 | jsk.mu.Lock() 90 | defer jsk.mu.Unlock() 91 | if jsk.isClose { 92 | return 93 | } 94 | jsk.isClose = true 95 | c := jsk.cancel 96 | c() 97 | } 98 | 99 | func (jsk *jdSecKill) GetReq(reqUrl string, params map[string]string, referer string, ctx context.Context, isDisableRedirects bool) (gjson.Result, error) { 100 | if referer == "" { 101 | referer = "https://www.jd.com" 102 | } 103 | if ctx == nil { 104 | ctx = jsk.bCtx 105 | } 106 | req, _ := http.NewRequest("GET", reqUrl, nil) 107 | req.Header.Add("User-Agent", jsk.userAgent) 108 | req.Header.Add("Referer", referer) 109 | req.Header.Add("Host", req.URL.Host) 110 | if len(params) > 0 { 111 | q := req.URL.Query() 112 | for k, v := range params { 113 | q.Add(k, v) 114 | } 115 | req.URL.RawQuery = q.Encode() 116 | } 117 | resp, err := chromedpEngine.RequestByCookie(ctx, req, isDisableRedirects) 118 | if err != nil { 119 | return gjson.Result{}, err 120 | } 121 | if resp.StatusCode != 200 { 122 | logs.PrintlnWarning("httpCode: ", resp.StatusCode, "reqUrl: ", resp.Request.URL) 123 | } 124 | //设置cookie到浏览器 125 | for _, respCookie := range resp.Cookies() { 126 | ok, err := network.SetCookie(respCookie.Name, respCookie.Value).WithURL(resp.Request.URL.String()).Do(ctx) 127 | if !ok { 128 | logs.PrintErr(respCookie.Name, respCookie.Value, " cookie设置失败", err) 129 | } 130 | } 131 | defer resp.Body.Close() 132 | b, _ := ioutil.ReadAll(resp.Body) 133 | logs.PrintlnSuccess("Get请求接口:", req.URL) 134 | // logs.PrintlnSuccess(string(b)) 135 | logs.PrintlnInfo("=======================") 136 | r := FormatJdResponse(b, req.URL.String(), false) 137 | if r.Raw == "null" || r.Raw == "" { 138 | return gjson.Result{}, ErrEmptyData 139 | } 140 | return r, nil 141 | } 142 | 143 | func (jsk *jdSecKill) SyncJdTime() { 144 | resp, err := http.Get("https://a.jd.com//ajax/queryServerData.html") 145 | if err != nil { 146 | logs.PrintErr(err) 147 | os.Exit(0) 148 | return 149 | } 150 | defer resp.Body.Close() 151 | b, _ := ioutil.ReadAll(resp.Body) 152 | r := gjson.ParseBytes(b) 153 | jdTimeUnix := r.Get("serverTime").Int() 154 | jsk.DiffTime = global.UnixMilli() - jdTimeUnix 155 | logs.PrintlnInfo("服务器与本地时间差为: ", jsk.DiffTime, "ms") 156 | } 157 | 158 | func (jsk *jdSecKill) PostReq(reqUrl string, params url.Values, referer string, ctx context.Context, isDisableRedirects bool) (gjson.Result, error) { 159 | if ctx == nil { 160 | ctx = jsk.bCtx 161 | } 162 | req, _ := http.NewRequest("POST", reqUrl, strings.NewReader(params.Encode())) 163 | req.Header.Add("User-Agent", jsk.userAgent) 164 | if referer != "" { 165 | req.Header.Add("Referer", referer) 166 | } 167 | req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 168 | req.Header.Add("Host", req.URL.Host) 169 | resp, err := chromedpEngine.RequestByCookie(ctx, req, isDisableRedirects) 170 | if err != nil { 171 | return gjson.Result{}, err 172 | } 173 | 174 | if resp.StatusCode != 200 { 175 | logs.PrintlnWarning("httpCode: ", resp.StatusCode, "reqUrl: ", resp.Request.URL) 176 | } 177 | //设置cookie到浏览器 178 | for _, respCookie := range resp.Cookies() { 179 | _, _ = network.SetCookie(respCookie.Name, respCookie.Value).WithURL(resp.Request.URL.String()).Do(ctx) 180 | } 181 | defer resp.Body.Close() 182 | b, _ := ioutil.ReadAll(resp.Body) 183 | 184 | logs.PrintlnSuccess("Post请求连接", req.URL) 185 | logs.PrintlnInfo("=======================") 186 | r := FormatJdResponse(b, req.URL.String(), false) 187 | if r.Raw == "null" || r.Raw == "" { 188 | return gjson.Result{}, ErrEmptyData 189 | } 190 | return r, nil 191 | } 192 | 193 | func FormatJdResponse(b []byte, prefix string, isConvertStr bool) gjson.Result { 194 | r := string(b) 195 | if isConvertStr { 196 | r = mahonia.NewDecoder("gbk").ConvertString(r) 197 | } 198 | r = strings.TrimSpace(r) 199 | if prefix != "" { 200 | //这里针对http连接 自动提取jsonp的callback 201 | if strings.HasPrefix(prefix, "http") { 202 | pUrl, err := url.Parse(prefix) 203 | if err == nil { 204 | prefix = pUrl.Query().Get("callback") 205 | } 206 | } 207 | r = strings.TrimPrefix(r, prefix) 208 | } 209 | if strings.HasSuffix(r, ")") { 210 | r = strings.TrimLeft(r, `(`) 211 | r = strings.TrimRight(r, ")") 212 | } 213 | return gjson.Parse(r) 214 | } 215 | 216 | //初始化监听请求数据 217 | func (jsk *jdSecKill) InitActionFunc() chromedp.ActionFunc { 218 | return func(ctx context.Context) error { 219 | jsk.bCtx = ctx 220 | _ = network.Enable().Do(ctx) 221 | chromedp.ListenTarget(ctx, func(ev interface{}) { 222 | switch e := ev.(type) { 223 | case *network.EventResponseReceived: 224 | go func() { 225 | if strings.Contains(e.Response.URL, "passport.jd.com/user/petName/getUserInfoForMiniJd.action") { 226 | b, err := network.GetResponseBody(e.RequestID).Do(ctx) 227 | if err == nil { 228 | jsk.UserInfo = FormatJdResponse(b, e.Response.URL, false) 229 | } 230 | jsk.isLogin = true 231 | } 232 | }() 233 | 234 | } 235 | }) 236 | return nil 237 | } 238 | } 239 | 240 | func (jsk *jdSecKill) Run() error { 241 | return chromedp.Run(jsk.ctx, chromedp.Tasks{ 242 | jsk.InitActionFunc(), 243 | chromedp.Navigate("https://passport.jd.com/uc/login"), 244 | chromedp.ActionFunc(func(ctx context.Context) error { 245 | logs.PrintlnInfo("等待登陆......") 246 | for { 247 | select { 248 | case <-jsk.ctx.Done(): 249 | logs.PrintErr("浏览器被关闭,退出进程") 250 | return nil 251 | case <-jsk.bCtx.Done(): 252 | logs.PrintErr("浏览器被关闭,退出进程") 253 | return nil 254 | default: 255 | } 256 | if jsk.isLogin { 257 | logs.PrintlnSuccess(jsk.UserInfo.Get("realName").String() + ", 登陆成功........") 258 | break 259 | } 260 | } 261 | return nil 262 | }), 263 | jsk.GetEidAndFp(), 264 | chromedp.ActionFunc(func(ctx context.Context) error { 265 | u := "https://item.jd.com/" + jsk.SkuId + ".html" 266 | rand.Seed(time.Now().UnixNano()) 267 | _ = chromedp.Navigate(u).Do(ctx) 268 | for i := 0; i < jsk.Works; i++ { 269 | go func() { 270 | jsk.WaitStart() 271 | for { 272 | jsk.FetchSecKillUrl() 273 | logs.PrintlnInfo("正在访问抢购连接......") 274 | _, err := jsk.GetReq(jsk.SecKillUrl, nil, "https://item.jd.com/"+jsk.SkuId+".html", jsk.bCtx, true) 275 | //这里访问会响应302 禁止重定向后就会是空数据 所以这里空数据是正常的 276 | if err == nil || err.Error() == ErrEmptyData.Error() { 277 | break 278 | } 279 | } 280 | SecKillRE: 281 | //请求抢购连接,提交订单 282 | err := jsk.ReqSubmitSecKillOrder(jsk.bCtx) 283 | if err != nil { 284 | logs.PrintlnInfo(err, "等待重试") 285 | i := rand.Intn(200) 286 | time.Sleep(time.Duration(i) * time.Millisecond) 287 | goto SecKillRE 288 | } 289 | _ = chromedp.Navigate("https://order.jd.com/center/list.action").Do(jsk.bCtx) 290 | }() 291 | } 292 | select { 293 | case <-jsk.IsOkChan: 294 | logs.PrintlnInfo("抢购成功。。。10s后关闭进程...") 295 | _ = chromedp.Sleep(10 * time.Second).Do(ctx) 296 | case <-jsk.ctx.Done(): 297 | case <-jsk.bCtx.Done(): 298 | } 299 | return nil 300 | }), 301 | }) 302 | } 303 | 304 | func (jsk *jdSecKill) WaitStart() { 305 | st := jsk.StartTime.UnixNano() / 1e6 306 | logs.PrintlnInfo("等待时间到达" + jsk.StartTime.Format(global.DateTimeFormatStr) + "...... 请勿关闭浏览器") 307 | for { 308 | select { 309 | case <-jsk.ctx.Done(): 310 | logs.PrintErr("浏览器被关闭,退出进程") 311 | return 312 | case <-jsk.bCtx.Done(): 313 | logs.PrintErr("浏览器被关闭,退出进程") 314 | return 315 | default: 316 | } 317 | d := global.UnixMilli() - jsk.DiffTime 318 | if d >= st { 319 | logs.PrintlnInfo("时间到达。。。。开始执行", time.Now().Format(global.DateTimeFormatStr)) 320 | break 321 | } 322 | if st-d-4 > 0 { 323 | time.Sleep(time.Duration(st-d-4) * time.Millisecond) 324 | } 325 | } 326 | } 327 | 328 | func (jsk *jdSecKill) GetEidAndFp() chromedp.ActionFunc { 329 | return func(ctx context.Context) error { 330 | logs.PrintlnInfo(jsk.fp, jsk.eid) 331 | if jsk.eid != "" && jsk.fp != "" { 332 | logs.PrintlnInfo("已传入eid与fp,程序将不再自动获取 ") 333 | logs.PrintlnInfo("eid : ", jsk.eid, "fp : ", jsk.fp) 334 | return nil 335 | } 336 | RE: 337 | logs.PrintlnInfo("正在获取eid和fp参数....") 338 | _ = chromedp.Navigate("https://search.jd.com/Search?keyword=衣服").Do(ctx) 339 | logs.PrintlnInfo("等待页面更新完成....") 340 | _ = chromedp.WaitVisible(".gl-item").Do(ctx) 341 | var itemNodes []*cdp.Node 342 | err := chromedp.Nodes(".gl-item", &itemNodes, chromedp.ByQueryAll).Do(ctx) 343 | if err != nil { 344 | return err 345 | } 346 | n := itemNodes[rand.Intn(len(itemNodes))] 347 | _ = dom.ScrollIntoViewIfNeeded().WithNodeID(n.NodeID).Do(ctx) 348 | _, _, _, _ = page.Navigate("https://item.jd.com/" + n.AttributeValue("data-sku") + ".html").Do(ctx) 349 | 350 | logs.PrintlnInfo("等待商品详情页更新完成....") 351 | _ = chromedp.WaitVisible("#InitCartUrl").Do(ctx) 352 | _ = chromedp.Sleep(1 * time.Second).Do(ctx) 353 | _ = chromedp.Click("#InitCartUrl").Do(ctx) 354 | _ = chromedp.WaitVisible("#GotoShoppingCart").Do(ctx) 355 | _ = chromedp.Sleep(1 * time.Second).Do(ctx) 356 | _ = chromedp.Click("#GotoShoppingCart").Do(ctx) 357 | //_ = chromedp.Navigate("https://cart.jd.com/cart_index/").Do(ctx) 358 | ch, cc := chromedpEngine.WaitDocumentUpdated(ctx) 359 | logs.PrintlnInfo("等待购物车页面.....") 360 | <-ch 361 | cc() 362 | info, _ := target.GetTargetInfo().Do(ctx) 363 | if strings.Contains(info.URL, "cart.jd.com/cart_index") { 364 | logs.PrintlnInfo("Click, common-submit-btn") 365 | _ = chromedp.Sleep(1 * time.Second).Do(ctx) 366 | _ = chromedp.Click(".common-submit-btn").Do(ctx) 367 | } else { 368 | logs.PrintlnInfo("Click, submit-btn") 369 | _ = chromedp.WaitVisible("container", chromedp.ByID).Do(ctx) 370 | _ = chromedp.ScrollIntoView(".submit-btn").Do(ctx) 371 | _ = chromedp.Sleep(1 * time.Second).Do(ctx) 372 | _ = chromedp.Click(".submit-btn").Do(ctx) 373 | } 374 | 375 | //_ = chromedp.WaitVisible("#mainframe").Do(ctx) 376 | ch, cc = chromedpEngine.WaitDocumentUpdated(ctx) 377 | logs.PrintlnInfo("等待结算页加载完成..... 如遇到未选中商品错误,可手动选中后点击结算") 378 | <-ch 379 | cc() 380 | //执行js参数 将eid和fp显示到对应元素上 381 | _ = chromedp.Sleep(3 * time.Second).Do(ctx) 382 | res := make(map[string]interface{}) 383 | err = chromedp.Evaluate("_JdTdudfp", &res).Do(ctx) 384 | logs.PrintErr(err) 385 | logs.Println("_JdTdudfp: ", res) 386 | eid, ok := res["eid"] 387 | if !ok { 388 | logs.PrintlnInfo("获取eid失败,正在重试") 389 | goto RE 390 | } 391 | jsk.eid = eid.(string) 392 | jsk.fp = res["fp"].(string) 393 | 394 | if jsk.fp == "" || jsk.eid == "" || jsk.fp == "undefined" || jsk.eid == "undefined" { 395 | logs.PrintlnWarning("获取参数失败,等待重试。。。 重试过程过久可手动刷新浏览器") 396 | goto RE 397 | } 398 | logs.PrintlnInfo("参数获取成功:eid【" + jsk.eid + "】, fp【" + jsk.fp + "】") 399 | 400 | return nil 401 | } 402 | } 403 | 404 | func (jsk *jdSecKill) FetchSecKillUrl() { 405 | /*jsk.SecKillUrl = "https://marathon.jd.com/captcha.html?skuId="+jsk.SkuId+"&sn=c3f4ececd8461f0e4d7267e96a91e0e0&from=pc" 406 | return*/ 407 | logs.PrintlnInfo("开始获取抢购连接.....") 408 | for { 409 | if jsk.SecKillUrl != "" { 410 | break 411 | } 412 | jsk.SecKillUrl = jsk.GetSecKillUrl() 413 | logs.PrintlnWarning("抢购链接获取失败.....正在重试") 414 | } 415 | jsk.SecKillUrl = "https:" + strings.TrimPrefix(jsk.SecKillUrl, "https:") 416 | jsk.SecKillUrl = strings.ReplaceAll(jsk.SecKillUrl, "divide", "marathon") 417 | jsk.SecKillUrl = strings.ReplaceAll(jsk.SecKillUrl, "user_routing", "captcha.html") 418 | logs.PrintlnSuccess("抢购连接获取成功....", jsk.SecKillUrl) 419 | } 420 | 421 | func (jsk *jdSecKill) ReqSubmitSecKillOrder(ctx context.Context) error { 422 | if ctx == nil { 423 | ctx = jsk.bCtx 424 | } 425 | 426 | defer func() { 427 | if r := recover(); r != nil { 428 | logs.PrintErr(r) 429 | } 430 | }() 431 | //这里修改为直接使用http请求访问抢购结算页面 提高速度 432 | skUrl := fmt.Sprintf("https://marathon.jd.com/seckill/seckill.action?skuId=%s&num=%d&rid=%d", jsk.SkuId, jsk.SecKillNum, time.Now().Unix()) 433 | logs.PrintlnInfo("访问抢购订单结算页面......", skUrl) 434 | _, _ = jsk.GetReq(skUrl, nil, "https://item.jd.com/"+jsk.SkuId+".html", ctx, true) 435 | 436 | //这里直接使用浏览器跳转 主要目的是获取cookie 437 | /*jsk.GetReq(skUrl, nil, "https://item.jd.com/"+jsk.SkuId+".html", ctx) 438 | _, _, _, _ = page.Navigate(skUrl).WithReferrer("https://item.jd.com/"+jsk.SkuId+".html").Do(ctx)*/ 439 | 440 | logs.PrintlnInfo("获取抢购信息...............") 441 | err := jsk.GetSecKillInitInfo(ctx) 442 | if err != nil { 443 | logs.PrintErr("抢购失败:", err, "正在重试.......") 444 | return err 445 | } 446 | 447 | orderData := jsk.GetOrderReqData() 448 | 449 | if len(orderData) == 0 { 450 | return errors.New("订单参数生成失败") 451 | } 452 | logs.PrintlnInfo("订单参数:", orderData.Encode()) 453 | logs.PrintlnInfo("提交抢购订单.............") 454 | 455 | r, err := jsk.PostReq("https://marathon.jd.com/seckillnew/orderService/pc/submitOrder.action?skuId="+jsk.SkuId+"", orderData, skUrl, ctx, false) 456 | if err != nil { 457 | logs.PrintErr("订单提交失败,正在重新提交.....", " errMsg => ", err, " raw => ", r.Raw) 458 | return err 459 | } 460 | orderId := r.Get("orderId").String() 461 | if orderId != "" && orderId != "0" { 462 | jsk.IsOk = true 463 | jsk.IsOkChan <- struct{}{} 464 | logs.PrintlnInfo("抢购成功,订单编号:", r.Get("orderId").String()) 465 | global.NotifyUser("抢购成功,订单编号:", r.Get("orderId").String()) 466 | } else { 467 | if r.IsObject() || r.IsArray() { 468 | return errors.New("抢购失败:" + r.Raw) 469 | } 470 | return errors.New("抢购失败,再接再厉") 471 | } 472 | return nil 473 | } 474 | 475 | func (jsk *jdSecKill) GetOrderReqData() url.Values { 476 | logs.PrintlnInfo("生成订单所需参数...") 477 | defer func() { 478 | if f := recover(); f != nil { 479 | logs.PrintErr("订单参数错误:", f) 480 | } 481 | }() 482 | 483 | addressList := jsk.SecKillInfo.Get("addressList").Array() 484 | var defaultAddress gjson.Result 485 | for _, dAddress := range addressList { 486 | if dAddress.Get("defaultAddress").Bool() { 487 | logs.PrintlnInfo("获取到默认收货地址") 488 | defaultAddress = dAddress 489 | } 490 | } 491 | if defaultAddress.Raw == "" { 492 | logs.PrintlnInfo("没有获取到默认收货地址, 自动选择一个地址") 493 | defaultAddress = addressList[0] 494 | } 495 | invoiceInfo := jsk.SecKillInfo.Get("invoiceInfo") 496 | r := url.Values{ 497 | "skuId": []string{jsk.SkuId}, 498 | "num": []string{strconv.Itoa(jsk.SecKillNum)}, 499 | "addressId": []string{defaultAddress.Get("id").String()}, 500 | "yuShou": []string{"true"}, 501 | "isModifyAddress": []string{"false"}, 502 | "name": []string{defaultAddress.Get("name").String()}, 503 | "provinceId": []string{defaultAddress.Get("provinceId").String()}, 504 | "cityId": []string{defaultAddress.Get("cityId").String()}, 505 | "countyId": []string{defaultAddress.Get("countyId").String()}, 506 | "townId": []string{defaultAddress.Get("townId").String()}, 507 | "addressDetail": []string{defaultAddress.Get("addressDetail").String()}, 508 | "mobile": []string{defaultAddress.Get("mobile").String()}, 509 | "mobileKey": []string{defaultAddress.Get("mobileKey").String()}, 510 | "email": []string{defaultAddress.Get("email").String()}, 511 | "postCode": []string{""}, 512 | "invoiceTitle": []string{""}, 513 | "invoiceCompanyName": []string{""}, 514 | "invoiceContent": []string{}, 515 | "invoiceTaxpayerNO": []string{""}, 516 | "invoiceEmail": []string{""}, 517 | "invoicePhone": []string{invoiceInfo.Get("invoicePhone").String()}, 518 | "invoicePhoneKey": []string{invoiceInfo.Get("invoicePhoneKey").String()}, 519 | "invoice": []string{"true"}, 520 | "password": []string{jsk.PayPwd}, 521 | "codTimeType": []string{"3"}, 522 | "paymentType": []string{"4"}, 523 | "areaCode": []string{""}, 524 | "overseas": []string{"0"}, 525 | "phone": []string{""}, 526 | "eid": []string{jsk.eid}, 527 | "fp": []string{jsk.fp}, 528 | "token": []string{jsk.SecKillInfo.Get("token").String()}, 529 | "pru": []string{""}, 530 | } 531 | 532 | if invoiceInfo.Raw == "" { 533 | r["invoice"] = []string{"false"} 534 | } else { 535 | r["invoice"] = []string{"true"} 536 | } 537 | t := invoiceInfo.Get("invoiceTitle").String() 538 | if t != "" { 539 | r["invoiceTitle"] = []string{t} 540 | } else { 541 | r["invoiceTitle"] = []string{"-1"} 542 | } 543 | 544 | t = invoiceInfo.Get("invoiceContentType").String() 545 | if t != "" { 546 | r["invoiceContent"] = []string{t} 547 | } else { 548 | r["invoiceContent"] = []string{"1"} 549 | } 550 | 551 | return r 552 | } 553 | 554 | func (jsk *jdSecKill) GetSecKillInitInfo(ctx context.Context) error { 555 | r, err := jsk.PostReq("https://marathon.jd.com/seckillnew/orderService/pc/init.action", url.Values{ 556 | "sku": []string{jsk.SkuId}, 557 | "num": []string{strconv.Itoa(jsk.SecKillNum)}, 558 | "isModifyAddress": []string{"false"}, 559 | }, fmt.Sprintf("https://marathon.jd.com/seckill/seckill.action?skuId=%s&num=%d&rid=%d", jsk.SkuId, jsk.SecKillNum, time.Now().Unix()), ctx, false) 560 | if err != nil { 561 | return err 562 | } 563 | jsk.SecKillInfo = r 564 | logs.PrintlnInfo("秒杀信息获取成功:", jsk.SecKillInfo.Raw) 565 | return nil 566 | } 567 | 568 | func (jsk *jdSecKill) GetSecKillUrl() string { 569 | r, _ := jsk.GetReq("https://itemko.jd.com/itemShowBtn", map[string]string{ 570 | "callback": "jQuery" + strconv.FormatInt(global.GenerateRangeNum(1000000, 9999999), 10), 571 | "skuId": jsk.SkuId, 572 | "from": "pc", 573 | "_": strconv.FormatInt(time.Now().Unix()*1000, 10), 574 | }, "https://item.jd.com/"+jsk.SkuId+".html", nil, false) 575 | return r.Get("url").String() 576 | } 577 | --------------------------------------------------------------------------------