├── .DS_Store
├── .github
└── workflows
│ └── main.yml
├── README.md
├── __init__.py
├── __manifest__.py
├── __pycache__
└── __init__.cpython-37.pyc
├── controllers
├── Main.py
├── __init__.py
└── __pycache__
│ ├── Main.cpython-37.pyc
│ └── __init__.cpython-37.pyc
├── data
└── jd_cookie.xml
├── models
├── .DS_Store
├── __init__.py
├── __pycache__
│ ├── __init__.cpython-37.pyc
│ └── jd_access_token.cpython-37.pyc
├── jd_access_token.py
└── tools
│ ├── .DS_Store
│ ├── .idea
│ ├── .gitignore
│ ├── inspectionProfiles
│ │ └── profiles_settings.xml
│ ├── jd_maotai_seckill-master.iml
│ ├── misc.xml
│ └── modules.xml
│ ├── README.md
│ ├── __pycache__
│ ├── config.cpython-37.pyc
│ ├── exception.cpython-37.pyc
│ ├── jd_logger.cpython-37.pyc
│ ├── jd_spider_requests.cpython-37.pyc
│ ├── timer.cpython-37.pyc
│ └── util.cpython-37.pyc
│ ├── config.ini
│ ├── config.py
│ ├── exception.py
│ ├── jd_logger.py
│ ├── jd_spider_requests.py
│ ├── main.py
│ ├── qr_code.png
│ ├── requirements.txt
│ ├── timer.py
│ └── util.py
├── security
├── ir.model.access.csv
└── security.xml
├── static
├── .DS_Store
└── description
│ └── icon.png
└── views
├── jd_access_token.xml
└── menu_main.xml
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cyril0216/jd-master/8f72cad5a88457d74752b4d8771b1f606458ad8c/.DS_Store
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | # This is a basic workflow to help you get started with Actions
2 |
3 | name: CI
4 |
5 | # Controls when the workflow will run
6 | on:
7 | # Triggers the workflow on push or pull request events but only for the master branch
8 | push:
9 | branches: [ master ]
10 | pull_request:
11 | branches: [ master ]
12 |
13 | # Allows you to run this workflow manually from the Actions tab
14 | workflow_dispatch:
15 |
16 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel
17 | jobs:
18 | # This workflow contains a single job called "build"
19 | build:
20 | # The type of runner that the job will run on
21 | runs-on: ubuntu-latest
22 |
23 |
24 | # Steps represent a sequence of tasks that will be executed as part of the job
25 | steps:
26 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
27 | - uses: actions/checkout@v2
28 |
29 | # Runs a single command using the runners shell
30 | - name: Run a one-line script
31 | run: echo Hello, world!
32 |
33 | # Runs a set of commands using the runners shell
34 | - name: Run a multi-line script
35 | run: |
36 | echo Add other actions to build,
37 | echo test, and deploy your project.
38 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 当前模块重点
2 | ---------------
3 |
4 | 功能说明:基于odoo-13,自动登录京东获取cookie
5 |
6 | 1.新增账户配置模块,每天自动更新cookie
7 |
8 | 2.基本架构布置
9 |
10 |
11 |
12 | 功能点:自动抢购茅台
13 | ----------------------
14 | 定时任务设置
15 |
--------------------------------------------------------------------------------
/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 | # -*- coding: utf-8 -*-
3 | # @Author:Cyril
4 |
5 | from . import controllers
6 | from . import models
7 | from . import data
8 |
--------------------------------------------------------------------------------
/__manifest__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 | # -*- coding: utf-8 -*-
3 | # @Author:Cyril
4 |
5 | {
6 | 'name': "京东茅台助手",
7 | 'summary': 'design by Cyril',
8 | 'version': '1.0',
9 | 'category': "基础",
10 | 'sequence': 10,
11 | 'author': 'Cyril',
12 | 'website': 'https://github.com/cyril0216/jd-master',
13 | 'depends': ['base'],
14 | 'data': [
15 | 'security/security.xml',
16 | 'security/ir.model.access.csv',
17 | 'data/jd_cookie.xml',
18 | 'views/menu_main.xml',
19 | 'views/jd_access_token.xml',
20 | ],
21 | 'demo': [
22 | # 'demo/demo.xml',
23 | ],
24 | 'qweb': [
25 | # 'static/src/xml/test.xml',
26 | ],
27 | 'installable': True,
28 | 'application': True,
29 | 'auto_install': False,
30 | 'description': """
31 |
32 | """,
33 | }
34 |
--------------------------------------------------------------------------------
/__pycache__/__init__.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cyril0216/jd-master/8f72cad5a88457d74752b4d8771b1f606458ad8c/__pycache__/__init__.cpython-37.pyc
--------------------------------------------------------------------------------
/controllers/Main.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 | # -*- coding: utf-8 -*-
3 | # @Author:Cyril
4 |
5 | import os
6 | from jinja2 import Environment, FileSystemLoader
7 |
8 | global BASE_DIR
9 | BASE_DIR = os.path.dirname(os.path.dirname(__file__))
10 | global env
11 | templateLoader = FileSystemLoader(searchpath=BASE_DIR + "/templates")
12 | env = Environment(loader=templateLoader)
13 | import logging
14 |
15 | _logger = logging.getLogger(__name__)
16 |
17 | import odoo
18 | from odoo import http
19 | from odoo.http import request
20 | from odoo.addons.web.controllers.main import WebClient, Binary, Home
21 | import json
22 |
23 |
24 | class Website(Home):
25 | pass
26 |
--------------------------------------------------------------------------------
/controllers/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 | # -*- coding: utf-8 -*-
3 | # @Author:Cyril
4 |
5 |
6 | from . import Main
--------------------------------------------------------------------------------
/controllers/__pycache__/Main.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cyril0216/jd-master/8f72cad5a88457d74752b4d8771b1f606458ad8c/controllers/__pycache__/Main.cpython-37.pyc
--------------------------------------------------------------------------------
/controllers/__pycache__/__init__.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cyril0216/jd-master/8f72cad5a88457d74752b4d8771b1f606458ad8c/controllers/__pycache__/__init__.cpython-37.pyc
--------------------------------------------------------------------------------
/data/jd_cookie.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 京东更新cookie
7 | 100
8 | minutes
9 | -1
10 |
11 |
12 | model.get_token()
13 | code
14 |
15 |
16 |
--------------------------------------------------------------------------------
/models/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cyril0216/jd-master/8f72cad5a88457d74752b4d8771b1f606458ad8c/models/.DS_Store
--------------------------------------------------------------------------------
/models/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 | # -*- coding: utf-8 -*-
3 | # @Author:Cyril
4 |
5 | from . import jd_access_token
6 |
7 |
8 |
--------------------------------------------------------------------------------
/models/__pycache__/__init__.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cyril0216/jd-master/8f72cad5a88457d74752b4d8771b1f606458ad8c/models/__pycache__/__init__.cpython-37.pyc
--------------------------------------------------------------------------------
/models/__pycache__/jd_access_token.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cyril0216/jd-master/8f72cad5a88457d74752b4d8771b1f606458ad8c/models/__pycache__/jd_access_token.cpython-37.pyc
--------------------------------------------------------------------------------
/models/jd_access_token.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 | # -*- coding: utf-8 -*-
3 | # @Author:Cyril
4 |
5 | from datetime import datetime, timedelta
6 | from functools import partial
7 | from itertools import groupby
8 |
9 | from odoo import api, fields, models, SUPERUSER_ID, _
10 | from odoo.exceptions import AccessError, UserError, ValidationError
11 | from odoo.tools.misc import formatLang, get_lang
12 | from odoo.osv import expression
13 | from odoo.tools import float_is_zero, float_compare
14 | from werkzeug.urls import url_encode
15 | import requests, json
16 |
17 | class JD_access_token(models.Model):
18 | _name = "jd_access_token"
19 | _description = "京东账户相关"
20 | _log_access = True
21 | _check_worflow_auto = False
22 |
23 |
24 | account = fields.Char(string='账户')
25 | passwd = fields.Integer(string='密码')
26 | cookie = fields.Char(string='cookie')
27 |
28 |
29 | def get_token(self):
30 |
31 |
32 | pass
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/models/tools/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cyril0216/jd-master/8f72cad5a88457d74752b4d8771b1f606458ad8c/models/tools/.DS_Store
--------------------------------------------------------------------------------
/models/tools/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Datasource local storage ignored files
5 | /dataSources/
6 | /dataSources.local.xml
7 | # Editor-based HTTP Client requests
8 | /httpRequests/
9 |
--------------------------------------------------------------------------------
/models/tools/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/models/tools/.idea/jd_maotai_seckill-master.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/models/tools/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/models/tools/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/models/tools/README.md:
--------------------------------------------------------------------------------
1 | # Jd_Seckill
2 |
3 | ## 特别声明:
4 |
5 | * 本仓库发布的`jd_seckill`项目中涉及的任何脚本,仅用于测试和学习研究,禁止用于商业用途,不能保证其合法性,准确性,完整性和有效性,请根据情况自行判断。
6 |
7 | * 本项目内所有资源文件,禁止任何公众号、自媒体进行任何形式的转载、发布。
8 |
9 | * `huanghyw` 对任何脚本问题概不负责,包括但不限于由任何脚本错误导致的任何损失或损害.
10 |
11 | * 间接使用脚本的任何用户,包括但不限于建立VPS或在某些行为违反国家/地区法律或相关法规的情况下进行传播, `huanghyw` 对于由此引起的任何隐私泄漏或其他后果概不负责。
12 |
13 | * 请勿将`jd_seckill`项目的任何内容用于商业或非法目的,否则后果自负。
14 |
15 | * 如果任何单位或个人认为该项目的脚本可能涉嫌侵犯其权利,则应及时通知并提供身份证明,所有权证明,我们将在收到认证文件后删除相关脚本。
16 |
17 | * 以任何方式查看此项目的人或直接或间接使用`jd_seckill`项目的任何脚本的使用者都应仔细阅读此声明。`huanghyw` 保留随时更改或补充此免责声明的权利。一旦使用并复制了任何相关脚本或`jd_seckill`项目,则视为您已接受此免责声明。
18 |
19 | * 您必须在下载后的24小时内从计算机或手机中完全删除以上内容。
20 |
21 | * 本项目遵循`GPL-3.0 License`协议,如果本特别声明与`GPL-3.0 License`协议有冲突之处,以本特别声明为准。
22 |
23 | > ***您使用或者复制了本仓库且本人制作的任何代码或项目,则视为`已接受`此声明,请仔细阅读***
24 | > ***您在本声明未发出之时点使用或者复制了本仓库且本人制作的任何代码或项目且此时还在使用,则视为`已接受`此声明,请仔细阅读***
25 |
26 | ## 简介
27 | 通过我这段时间的使用(2020-12-12至2020-12-17),证实这个脚本确实能抢到茅台。我自己三个账号抢了四瓶,帮两个朋友抢了4瓶。
28 | 大家只要确认自己配置文件没有问题,Cookie没有失效,坚持下去总能成功的。
29 |
30 | 根据这段时间大家的反馈,除了茅台,其它不需要加购物车的商品也不能抢。具体原因还没有进行排查,应该是京东非茅台商品抢购流程发生了变化。
31 | 为了避免耽误大家的时间,先不要抢购非茅台商品。
32 | 等这个问题处理好了,会上线新版本。
33 |
34 |
35 | ## 暗中观察
36 |
37 | 根据12月14日以来抢茅台的日志分析,大胆推断再接再厉返回Json消息中`resultCode`与小白信用的关系。
38 | 这里主要分析出现频率最高的`90016`和`90008`。
39 |
40 | ### 样例JSON
41 | ```json
42 | {'errorMessage': '很遗憾没有抢到,再接再厉哦。', 'orderId': 0, 'resultCode': 90016, 'skuId': 0, 'success': False}
43 | {'errorMessage': '很遗憾没有抢到,再接再厉哦。', 'orderId': 0, 'resultCode': 90008, 'skuId': 0, 'success': False}
44 | ```
45 |
46 | ### 数据统计
47 |
48 | | 案例 | 小白信用 | 90016 | 90008 | 抢到耗时 |
49 | | ---- | ---- | ---- | ---- | ---- |
50 | | 张三 | 63.8 | 59.63% | 40.37% | 暂未抢到 |
51 | | 李四 | 92.9 | 72.05% | 27.94% | 4天 |
52 | | 王五 | 99.6 | 75.70% | 24.29% | 暂未抢到 |
53 | | 赵六 | 103.4 | 91.02% | 8.9% | 2天 |
54 |
55 | ### 猜测
56 | 推测返回90008是京东的风控机制,代表这次请求直接失败,不参与抢购。
57 | 小白信用越低越容易触发京东的风控。
58 |
59 | 从数据来看小白信用与风控的关系大概每十分为一个等级,所以赵六基本上没有被拦截,李四和王五的拦截几率相近,张三的拦截几率最高。
60 |
61 | 风控放行后才会进行抢购,这时候用的应该是水库计数模型,假设无法一次性拿到所有数据的情况下来尽量的做到抢购成功用户的均匀分布,这样就和概率相关了。
62 |
63 | > 综上,张三想成功有点困难,小白信用是100+的用户成功几率最大。
64 |
65 | ## 主要功能
66 |
67 | - 登陆京东商城([www.jd.com](http://www.jd.com/))
68 | - 用京东APP扫码给出的二维码
69 | - 预约茅台
70 | - 定时自动预约
71 | - 秒杀预约后等待抢购
72 | - 定时开始自动抢购
73 |
74 | ## 运行环境
75 |
76 | - [Python 3](https://www.python.org/)
77 |
78 | ## 第三方库
79 |
80 | - 需要使用到的库已经放在requirements.txt,使用pip安装的可以使用指令
81 | `pip install -r requirements.txt`
82 | - 如果国内安装第三方库比较慢,可以使用以下指令进行清华源加速
83 | `pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple/`
84 |
85 | ## 使用教程
86 | #### 1. 推荐Chrome浏览器
87 | #### 2. 网页扫码登录,或者账号密码登录
88 | #### 3. 填写config.ini配置信息
89 | (1)`eid`和`fp`找个普通商品随便下单,然后抓包就能看到,这两个值可以填固定的
90 | > 随便找一个商品下单,然后进入结算页面,打开浏览器的调试窗口,切换到控制台Tab页,在控制台中输入变量`_JdTdudfp`,即可从输出的Json中获取`eid`和`fp`。
91 | > 不会的话参考原作者的issue https://github.com/zhou-xiaojun/jd_mask/issues/22
92 |
93 | (2)`sku_id`,`DEFAULT_USER_AGENT`
94 | > `sku_id`已经按照茅台的填好。
95 | > `cookies_string` 现在已经不需要填写了
96 | > `DEFAULT_USER_AGENT` 可以用默认的。谷歌浏览器也可以浏览器地址栏中输入about:version 查看`USER_AGENT`替换
97 |
98 | (3)配置一下时间
99 | > 现在不强制要求同步最新时间了,程序会自动同步京东时间
100 | >> 但要是电脑时间快慢了好几个小时,最好还是同步一下吧
101 |
102 | 以上都是必须的.
103 | > tips:
104 | > 在程序开始运行后,会检测本地时间与京东服务器时间,输出的差值为本地时间-京东服务器时间,即-50为本地时间比京东服务器时间慢50ms。
105 | > 本代码的执行的抢购时间以本地电脑/服务器时间为准
106 |
107 | (4)修改抢购瓶数
108 | > 代码中默认抢购瓶数为2,且无法在配置文件中修改
109 | > 如果一个月内抢购过一瓶,最好修改抢购瓶数为1
110 | > 具体修改为:在`jd_spider_requests.py`文件中搜索`self.seckill_num = 2`,将`2`改为`1`
111 |
112 | #### 4.运行main.py
113 | 根据提示选择相应功能即可
114 |
115 | #### 5.抢购结果确认
116 | 抢购是否成功通常在程序开始的一分钟内可见分晓!
117 | 搜索日志,出现“抢购成功,订单号xxxxx",代表成功抢到了,务必半小时内支付订单!程序暂时不支持自动停止,需要手动STOP!
118 | 若两分钟还未抢购成功,基本上就是没抢到!程序暂时不支持自动停止,需要手动STOP!
119 |
120 | ## 打赏
121 | 不用再打赏了,抢到茅台的同学请保持这份喜悦,没抢到的继续加油 :)
122 |
123 | ## 感谢
124 | ##### 非常感谢原作者 https://github.com/zhou-xiaojun/jd_mask 提供的代码
125 | ##### 也非常感谢 https://github.com/wlwwu/jd_maotai 进行的优化
126 |
--------------------------------------------------------------------------------
/models/tools/__pycache__/config.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cyril0216/jd-master/8f72cad5a88457d74752b4d8771b1f606458ad8c/models/tools/__pycache__/config.cpython-37.pyc
--------------------------------------------------------------------------------
/models/tools/__pycache__/exception.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cyril0216/jd-master/8f72cad5a88457d74752b4d8771b1f606458ad8c/models/tools/__pycache__/exception.cpython-37.pyc
--------------------------------------------------------------------------------
/models/tools/__pycache__/jd_logger.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cyril0216/jd-master/8f72cad5a88457d74752b4d8771b1f606458ad8c/models/tools/__pycache__/jd_logger.cpython-37.pyc
--------------------------------------------------------------------------------
/models/tools/__pycache__/jd_spider_requests.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cyril0216/jd-master/8f72cad5a88457d74752b4d8771b1f606458ad8c/models/tools/__pycache__/jd_spider_requests.cpython-37.pyc
--------------------------------------------------------------------------------
/models/tools/__pycache__/timer.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cyril0216/jd-master/8f72cad5a88457d74752b4d8771b1f606458ad8c/models/tools/__pycache__/timer.cpython-37.pyc
--------------------------------------------------------------------------------
/models/tools/__pycache__/util.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cyril0216/jd-master/8f72cad5a88457d74752b4d8771b1f606458ad8c/models/tools/__pycache__/util.cpython-37.pyc
--------------------------------------------------------------------------------
/models/tools/config.ini:
--------------------------------------------------------------------------------
1 | [config]
2 | # eid, fp参数
3 | eid = "DYJCGUILOGVI55HBEAGKQFT5C3X7IYEEGDZILVUQ65GGPFIMDAB7JMNJ4EXEIQX6E7FJVEFLAHSK4G54VTANTMB5E4"
4 | fp = "dc677fa7156b4e6308d68d2fe5c68d22"
5 |
6 | # 商品id
7 | sku_id = 100012043978
8 | # 设定时间 # 2020-12-09 10:00:00.100000
9 | buy_time = 09:59:59.500
10 | # 默认UA
11 | DEFAULT_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
12 | random_useragent = false
13 |
14 | [account]
15 | payment_pwd = ""
16 |
17 | [messenger]
18 | enable = false
19 | sckey =
20 |
--------------------------------------------------------------------------------
/models/tools/config.py:
--------------------------------------------------------------------------------
1 | import os
2 | import configparser
3 |
4 |
5 | class Config(object):
6 | def __init__(self, config_file='config.ini'):
7 | self._path = os.path.join(os.getcwd(), config_file)
8 | if not os.path.exists(self._path):
9 | raise FileNotFoundError("No such file: config.ini")
10 | self._config = configparser.ConfigParser()
11 | self._config.read(self._path, encoding='utf-8-sig')
12 | self._configRaw = configparser.RawConfigParser()
13 | self._configRaw.read(self._path, encoding='utf-8-sig')
14 |
15 | def get(self, section, name):
16 | return self._config.get(section, name)
17 |
18 | def getRaw(self, section, name):
19 | return self._configRaw.get(section, name)
20 |
21 |
22 | global_config = Config()
23 |
--------------------------------------------------------------------------------
/models/tools/exception.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- encoding=utf8 -*-
3 |
4 |
5 | class SKException(Exception):
6 |
7 | def __init__(self, message):
8 | super().__init__(message)
9 |
--------------------------------------------------------------------------------
/models/tools/jd_logger.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import logging.handlers
3 | '''
4 | 日志模块
5 | '''
6 | LOG_FILENAME = 'jd_seckill.log'
7 | logger = logging.getLogger()
8 |
9 |
10 | def set_logger():
11 | logger.setLevel(logging.INFO)
12 | formatter = logging.Formatter('%(asctime)s - %(process)d-%(threadName)s - '
13 | '%(pathname)s[line:%(lineno)d] - %(levelname)s: %(message)s')
14 | console_handler = logging.StreamHandler()
15 | console_handler.setFormatter(formatter)
16 | logger.addHandler(console_handler)
17 | file_handler = logging.handlers.RotatingFileHandler(
18 | LOG_FILENAME, maxBytes=10485760, backupCount=5, encoding="utf-8")
19 | file_handler.setFormatter(formatter)
20 | logger.addHandler(file_handler)
21 |
22 | set_logger()
--------------------------------------------------------------------------------
/models/tools/jd_spider_requests.py:
--------------------------------------------------------------------------------
1 | import random
2 | import time
3 | import requests
4 | import functools
5 | import json
6 | import os
7 | import pickle
8 |
9 | from lxml import etree
10 | from jd_logger import logger
11 | from timer import Timer
12 | from config import global_config
13 | from concurrent.futures import ProcessPoolExecutor
14 | from exception import SKException
15 | from util import (
16 | parse_json,
17 | send_wechat,
18 | wait_some_time,
19 | response_status,
20 | save_image,
21 | open_image
22 | )
23 |
24 |
25 | class SpiderSession:
26 | """
27 | Session相关操作
28 | """
29 | def __init__(self):
30 | self.cookies_dir_path = "./cookies/"
31 | self.user_agent = global_config.getRaw('config', 'DEFAULT_USER_AGENT')
32 |
33 | self.session = self._init_session()
34 |
35 | def _init_session(self):
36 | session = requests.session()
37 | session.headers = self.get_headers()
38 | return session
39 |
40 | def get_headers(self):
41 | return {"User-Agent": self.user_agent,
42 | "Accept": "text/html,application/xhtml+xml,application/xml;"
43 | "q=0.9,image/webp,image/apng,*/*;"
44 | "q=0.8,application/signed-exchange;"
45 | "v=b3",
46 | "Connection": "keep-alive"}
47 |
48 | def get_user_agent(self):
49 | return self.user_agent
50 |
51 | def get_session(self):
52 | """
53 | 获取当前Session
54 | :return:
55 | """
56 | return self.session
57 |
58 | def get_cookies(self):
59 | """
60 | 获取当前Cookies
61 | :return:
62 | """
63 | return self.get_session().cookies
64 |
65 | def set_cookies(self, cookies):
66 | self.session.cookies.update(cookies)
67 |
68 | def load_cookies_from_local(self):
69 | """
70 | 从本地加载Cookie
71 | :return:
72 | """
73 | cookies_file = ''
74 | if not os.path.exists(self.cookies_dir_path):
75 | return False
76 | for name in os.listdir(self.cookies_dir_path):
77 | if name.endswith(".cookies"):
78 | cookies_file = '{}{}'.format(self.cookies_dir_path, name)
79 | break
80 | if cookies_file == '':
81 | return False
82 | with open(cookies_file, 'rb') as f:
83 | local_cookies = pickle.load(f)
84 | self.set_cookies(local_cookies)
85 |
86 | def save_cookies_to_local(self, cookie_file_name):
87 | """
88 | 保存Cookie到本地
89 | :param cookie_file_name: 存放Cookie的文件名称
90 | :return:
91 | """
92 | cookies_file = '{}{}.cookies'.format(self.cookies_dir_path, cookie_file_name)
93 | directory = os.path.dirname(cookies_file)
94 | if not os.path.exists(directory):
95 | os.makedirs(directory)
96 | with open(cookies_file, 'wb') as f:
97 | pickle.dump(self.get_cookies(), f)
98 |
99 |
100 | class QrLogin:
101 | """
102 | 扫码登录
103 | """
104 | def __init__(self, spider_session: SpiderSession):
105 | """
106 | 初始化扫码登录
107 | 大致流程:
108 | 1、访问登录二维码页面,获取Token
109 | 2、使用Token获取票据
110 | 3、校验票据
111 | :param spider_session:
112 | """
113 | self.qrcode_img_file = 'qr_code.png'
114 |
115 | self.spider_session = spider_session
116 | self.session = self.spider_session.get_session()
117 |
118 | self.is_login = False
119 | self.refresh_login_status()
120 |
121 | def refresh_login_status(self):
122 | """
123 | 刷新是否登录状态
124 | :return:
125 | """
126 | self.is_login = self._validate_cookies()
127 |
128 | def _validate_cookies(self):
129 | """
130 | 验证cookies是否有效(是否登陆)
131 | 通过访问用户订单列表页进行判断:若未登录,将会重定向到登陆页面。
132 | :return: cookies是否有效 True/False
133 | """
134 | url = 'https://order.jd.com/center/list.action'
135 | payload = {
136 | 'rid': str(int(time.time() * 1000)),
137 | }
138 | try:
139 | resp = self.session.get(url=url, params=payload, allow_redirects=False)
140 | if resp.status_code == requests.codes.OK:
141 | return True
142 | except Exception as e:
143 | logger.error("验证cookies是否有效发生异常", e)
144 | return False
145 |
146 | def _get_login_page(self):
147 | """
148 | 获取PC端登录页面
149 | :return:
150 | """
151 | url = "https://passport.jd.com/new/login.aspx"
152 | page = self.session.get(url, headers=self.spider_session.get_headers())
153 | return page
154 |
155 | def _get_qrcode(self):
156 | """
157 | 缓存并展示登录二维码
158 | :return:
159 | """
160 | url = 'https://qr.m.jd.com/show'
161 | payload = {
162 | 'appid': 133,
163 | 'size': 147,
164 | 't': str(int(time.time() * 1000)),
165 | }
166 | headers = {
167 | 'User-Agent': self.spider_session.get_user_agent(),
168 | 'Referer': 'https://passport.jd.com/new/login.aspx',
169 | }
170 | resp = self.session.get(url=url, headers=headers, params=payload)
171 |
172 | if not response_status(resp):
173 | logger.info('获取二维码失败')
174 | return False
175 |
176 | save_image(resp, self.qrcode_img_file)
177 | logger.info('二维码获取成功,请打开京东APP扫描')
178 | open_image(self.qrcode_img_file)
179 | return True
180 |
181 | def _get_qrcode_ticket(self):
182 | """
183 | 通过 token 获取票据
184 | :return:
185 | """
186 | url = 'https://qr.m.jd.com/check'
187 | payload = {
188 | 'appid': '133',
189 | 'callback': 'jQuery{}'.format(random.randint(1000000, 9999999)),
190 | 'token': self.session.cookies.get('wlfstk_smdl'),
191 | '_': str(int(time.time() * 1000)),
192 | }
193 | headers = {
194 | 'User-Agent': self.spider_session.get_user_agent(),
195 | 'Referer': 'https://passport.jd.com/new/login.aspx',
196 | }
197 | resp = self.session.get(url=url, headers=headers, params=payload)
198 |
199 | if not response_status(resp):
200 | logger.error('获取二维码扫描结果异常')
201 | return False
202 |
203 | resp_json = parse_json(resp.text)
204 | if resp_json['code'] != 200:
205 | logger.info('Code: %s, Message: %s', resp_json['code'], resp_json['msg'])
206 | return None
207 | else:
208 | logger.info('已完成手机客户端确认')
209 | return resp_json['ticket']
210 |
211 | def _validate_qrcode_ticket(self, ticket):
212 | """
213 | 通过已获取的票据进行校验
214 | :param ticket: 已获取的票据
215 | :return:
216 | """
217 | url = 'https://passport.jd.com/uc/qrCodeTicketValidation'
218 | headers = {
219 | 'User-Agent': self.spider_session.get_user_agent(),
220 | 'Referer': 'https://passport.jd.com/uc/login?ltype=logout',
221 | }
222 |
223 | resp = self.session.get(url=url, headers=headers, params={'t': ticket})
224 | if not response_status(resp):
225 | return False
226 |
227 | resp_json = json.loads(resp.text)
228 | if resp_json['returnCode'] == 0:
229 | return True
230 | else:
231 | logger.info(resp_json)
232 | return False
233 |
234 | def login_by_qrcode(self):
235 | """
236 | 二维码登陆
237 | :return:
238 | """
239 | self._get_login_page()
240 |
241 | # download QR code
242 | if not self._get_qrcode():
243 | raise SKException('二维码下载失败')
244 |
245 | # get QR code ticket
246 | ticket = None
247 | retry_times = 85
248 | for _ in range(retry_times):
249 | ticket = self._get_qrcode_ticket()
250 | if ticket:
251 | break
252 | time.sleep(2)
253 | else:
254 | raise SKException('二维码过期,请重新获取扫描')
255 |
256 | # validate QR code ticket
257 | if not self._validate_qrcode_ticket(ticket):
258 | raise SKException('二维码信息校验失败')
259 |
260 | self.refresh_login_status()
261 |
262 | logger.info('二维码登录成功')
263 |
264 |
265 | class JdSeckill(object):
266 |
267 | def __init__(self):
268 | self.spider_session = SpiderSession()
269 | self.spider_session.load_cookies_from_local()
270 | self.qrlogin = QrLogin(self.spider_session)
271 |
272 | # 初始化信息
273 | self.sku_id = global_config.getRaw('config', 'sku_id')
274 | self.seckill_num = 1
275 | self.seckill_init_info = dict()
276 | self.seckill_url = dict()
277 | self.seckill_order_data = dict()
278 | self.timers = Timer()
279 |
280 | self.session = self.spider_session.get_session()
281 | self.user_agent = self.spider_session.user_agent
282 | self.nick_name = None
283 |
284 | def login_by_qrcode(self):
285 | """
286 | :return:
287 | """
288 | if self.qrlogin.is_login:
289 | logger.info('登录成功')
290 | return
291 |
292 | self.qrlogin.login_by_qrcode()
293 |
294 | if self.qrlogin.is_login:
295 | self.nick_name = self.get_username()
296 | self.spider_session.save_cookies_to_local(self.nick_name)
297 | else:
298 | raise SKException("登录失败!")
299 |
300 | def check_login(func):
301 | """
302 | 用户登陆态校验装饰器。若用户未登陆,则调用扫码登陆
303 | """
304 | @functools.wraps(func)
305 | def new_func(self, *args, **kwargs):
306 | if not self.qrlogin.is_login:
307 | logger.info("{0} 需登陆后调用,开始扫码登陆".format(func.__name__))
308 | self.login_by_qrcode()
309 | return func(self, *args, **kwargs)
310 | return new_func
311 |
312 | @check_login
313 | def reserve(self):
314 | """
315 | 预约
316 | """
317 | self._reserve()
318 |
319 | @check_login
320 | def seckill(self):
321 | """
322 | 抢购
323 | """
324 | self._seckill()
325 |
326 | @check_login
327 | def seckill_by_proc_pool(self, work_count=5):
328 | """
329 | 多进程
330 | work_count:进程数量
331 | """
332 | with ProcessPoolExecutor(work_count) as pool:
333 | for i in range(work_count):
334 | pool.submit(self.seckill)
335 |
336 | def _reserve(self):
337 | """
338 | 预约
339 | """
340 | while True:
341 | try:
342 | self.make_reserve()
343 | break
344 | except Exception as e:
345 | logger.info('预约发生异常!', e)
346 | wait_some_time()
347 |
348 | def _seckill(self):
349 | """
350 | 抢购
351 | """
352 | while True:
353 | try:
354 | self.request_seckill_url()
355 | while True:
356 | self.request_seckill_checkout_page()
357 | self.submit_seckill_order()
358 | except Exception as e:
359 | logger.info('抢购发生异常,稍后继续执行!', e)
360 | wait_some_time()
361 |
362 | def make_reserve(self):
363 | """商品预约"""
364 | logger.info('商品名称:{}'.format(self.get_sku_title()))
365 | url = 'https://yushou.jd.com/youshouinfo.action?'
366 | payload = {
367 | 'callback': 'fetchJSON',
368 | 'sku': self.sku_id,
369 | '_': str(int(time.time() * 1000)),
370 | }
371 | headers = {
372 | 'User-Agent': self.user_agent,
373 | 'Referer': 'https://item.jd.com/{}.html'.format(self.sku_id),
374 | }
375 | resp = self.session.get(url=url, params=payload, headers=headers)
376 | resp_json = parse_json(resp.text)
377 | reserve_url = resp_json.get('url')
378 | self.timers.start()
379 | while True:
380 | try:
381 | self.session.get(url='https:' + reserve_url)
382 | logger.info('预约成功,已获得抢购资格 / 您已成功预约过了,无需重复预约')
383 | if global_config.getRaw('messenger', 'enable') == 'true':
384 | success_message = "预约成功,已获得抢购资格 / 您已成功预约过了,无需重复预约"
385 | send_wechat(success_message)
386 | break
387 | except Exception as e:
388 | logger.error('预约失败正在重试...')
389 |
390 | def get_username(self):
391 | """获取用户信息"""
392 | url = 'https://passport.jd.com/user/petName/getUserInfoForMiniJd.action'
393 | payload = {
394 | 'callback': 'jQuery{}'.format(random.randint(1000000, 9999999)),
395 | '_': str(int(time.time() * 1000)),
396 | }
397 | headers = {
398 | 'User-Agent': self.user_agent,
399 | 'Referer': 'https://order.jd.com/center/list.action',
400 | }
401 |
402 | resp = self.session.get(url=url, params=payload, headers=headers)
403 |
404 | try_count = 5
405 | while not resp.text.startswith("jQuery"):
406 | try_count = try_count - 1
407 | if try_count > 0:
408 | resp = self.session.get(url=url, params=payload, headers=headers)
409 | else:
410 | break
411 | wait_some_time()
412 | # 响应中包含了许多用户信息,现在在其中返回昵称
413 | # jQuery2381773({"imgUrl":"//storage.360buyimg.com/i.imageUpload/xxx.jpg","lastLoginTime":"","nickName":"xxx","plusStatus":"0","realName":"xxx","userLevel":x,"userScoreVO":{"accountScore":xx,"activityScore":xx,"consumptionScore":xxxxx,"default":false,"financeScore":xxx,"pin":"xxx","riskScore":x,"totalScore":xxxxx}})
414 | return parse_json(resp.text).get('nickName')
415 |
416 | def get_sku_title(self):
417 | """获取商品名称"""
418 | url = 'https://item.jd.com/{}.html'.format(global_config.getRaw('config', 'sku_id'))
419 | resp = self.session.get(url).content
420 | x_data = etree.HTML(resp)
421 | sku_title = x_data.xpath('/html/head/title/text()')
422 | return sku_title[0]
423 |
424 | def get_seckill_url(self):
425 | """获取商品的抢购链接
426 | 点击"抢购"按钮后,会有两次302跳转,最后到达订单结算页面
427 | 这里返回第一次跳转后的页面url,作为商品的抢购链接
428 | :return: 商品的抢购链接
429 | """
430 | url = 'https://itemko.jd.com/itemShowBtn'
431 | payload = {
432 | 'callback': 'jQuery{}'.format(random.randint(1000000, 9999999)),
433 | 'skuId': self.sku_id,
434 | 'from': 'pc',
435 | '_': str(int(time.time() * 1000)),
436 | }
437 | headers = {
438 | 'User-Agent': self.user_agent,
439 | 'Host': 'itemko.jd.com',
440 | 'Referer': 'https://item.jd.com/{}.html'.format(self.sku_id),
441 | }
442 | while True:
443 | resp = self.session.get(url=url, headers=headers, params=payload)
444 | resp_json = parse_json(resp.text)
445 | if resp_json.get('url'):
446 | # https://divide.jd.com/user_routing?skuId=8654289&sn=c3f4ececd8461f0e4d7267e96a91e0e0&from=pc
447 | router_url = 'https:' + resp_json.get('url')
448 | # https://marathon.jd.com/captcha.html?skuId=8654289&sn=c3f4ececd8461f0e4d7267e96a91e0e0&from=pc
449 | seckill_url = router_url.replace(
450 | 'divide', 'marathon').replace(
451 | 'user_routing', 'captcha.html')
452 | logger.info("抢购链接获取成功: %s", seckill_url)
453 | return seckill_url
454 | else:
455 | logger.info("抢购链接获取失败,稍后自动重试")
456 | wait_some_time()
457 |
458 | def request_seckill_url(self):
459 | """访问商品的抢购链接(用于设置cookie等"""
460 | logger.info('用户:{}'.format(self.get_username()))
461 | logger.info('商品名称:{}'.format(self.get_sku_title()))
462 | self.timers.start()
463 | self.seckill_url[self.sku_id] = self.get_seckill_url()
464 | logger.info('访问商品的抢购连接...')
465 | headers = {
466 | 'User-Agent': self.user_agent,
467 | 'Host': 'marathon.jd.com',
468 | 'Referer': 'https://item.jd.com/{}.html'.format(self.sku_id),
469 | }
470 | self.session.get(
471 | url=self.seckill_url.get(
472 | self.sku_id),
473 | headers=headers,
474 | allow_redirects=False)
475 |
476 | def request_seckill_checkout_page(self):
477 | """访问抢购订单结算页面"""
478 | logger.info('访问抢购订单结算页面...')
479 | url = 'https://marathon.jd.com/seckill/seckill.action'
480 | payload = {
481 | 'skuId': self.sku_id,
482 | 'num': self.seckill_num,
483 | 'rid': int(time.time())
484 | }
485 | headers = {
486 | 'User-Agent': self.user_agent,
487 | 'Host': 'marathon.jd.com',
488 | 'Referer': 'https://item.jd.com/{}.html'.format(self.sku_id),
489 | }
490 | self.session.get(url=url, params=payload, headers=headers, allow_redirects=False)
491 |
492 | def _get_seckill_init_info(self):
493 | """获取秒杀初始化信息(包括:地址,发票,token)
494 | :return: 初始化信息组成的dict
495 | """
496 | logger.info('获取秒杀初始化信息...')
497 | url = 'https://marathon.jd.com/seckillnew/orderService/pc/init.action'
498 | data = {
499 | 'sku': self.sku_id,
500 | 'num': self.seckill_num,
501 | 'isModifyAddress': 'false',
502 | }
503 | headers = {
504 | 'User-Agent': self.user_agent,
505 | 'Host': 'marathon.jd.com',
506 | }
507 | resp = self.session.post(url=url, data=data, headers=headers)
508 |
509 | resp_json = None
510 | try:
511 | resp_json = parse_json(resp.text)
512 | except Exception:
513 | raise SKException('抢购失败,返回信息:{}'.format(resp.text[0: 128]))
514 |
515 | return resp_json
516 |
517 | def _get_seckill_order_data(self):
518 | """生成提交抢购订单所需的请求体参数
519 | :return: 请求体参数组成的dict
520 | """
521 | logger.info('生成提交抢购订单所需参数...')
522 | # 获取用户秒杀初始化信息
523 | self.seckill_init_info[self.sku_id] = self._get_seckill_init_info()
524 | init_info = self.seckill_init_info.get(self.sku_id)
525 | default_address = init_info['addressList'][0] # 默认地址dict
526 | invoice_info = init_info.get('invoiceInfo', {}) # 默认发票信息dict, 有可能不返回
527 | token = init_info['token']
528 | data = {
529 | 'skuId': self.sku_id,
530 | 'num': self.seckill_num,
531 | 'addressId': default_address['id'],
532 | 'yuShou': 'true',
533 | 'isModifyAddress': 'false',
534 | 'name': default_address['name'],
535 | 'provinceId': default_address['provinceId'],
536 | 'cityId': default_address['cityId'],
537 | 'countyId': default_address['countyId'],
538 | 'townId': default_address['townId'],
539 | 'addressDetail': default_address['addressDetail'],
540 | 'mobile': default_address['mobile'],
541 | 'mobileKey': default_address['mobileKey'],
542 | 'email': default_address.get('email', ''),
543 | 'postCode': '',
544 | 'invoiceTitle': invoice_info.get('invoiceTitle', -1),
545 | 'invoiceCompanyName': '',
546 | 'invoiceContent': invoice_info.get('invoiceContentType', 1),
547 | 'invoiceTaxpayerNO': '',
548 | 'invoiceEmail': '',
549 | 'invoicePhone': invoice_info.get('invoicePhone', ''),
550 | 'invoicePhoneKey': invoice_info.get('invoicePhoneKey', ''),
551 | 'invoice': 'true' if invoice_info else 'false',
552 | 'password': global_config.get('account', 'payment_pwd'),
553 | 'codTimeType': 3,
554 | 'paymentType': 4,
555 | 'areaCode': '',
556 | 'overseas': 0,
557 | 'phone': '',
558 | 'eid': global_config.getRaw('config', 'eid'),
559 | 'fp': global_config.getRaw('config', 'fp'),
560 | 'token': token,
561 | 'pru': ''
562 | }
563 |
564 | return data
565 |
566 | def submit_seckill_order(self):
567 | """提交抢购(秒杀)订单
568 | :return: 抢购结果 True/False
569 | """
570 | url = 'https://marathon.jd.com/seckillnew/orderService/pc/submitOrder.action'
571 | payload = {
572 | 'skuId': self.sku_id,
573 | }
574 | try:
575 | self.seckill_order_data[self.sku_id] = self._get_seckill_order_data()
576 | except Exception as e:
577 | logger.info('抢购失败,无法获取生成订单的基本信息,接口返回:【{}】'.format(str(e)))
578 | return False
579 |
580 | logger.info('提交抢购订单...')
581 | headers = {
582 | 'User-Agent': self.user_agent,
583 | 'Host': 'marathon.jd.com',
584 | 'Referer': 'https://marathon.jd.com/seckill/seckill.action?skuId={0}&num={1}&rid={2}'.format(
585 | self.sku_id, self.seckill_num, int(time.time())),
586 | }
587 | resp = self.session.post(
588 | url=url,
589 | params=payload,
590 | data=self.seckill_order_data.get(
591 | self.sku_id),
592 | headers=headers)
593 | resp_json = None
594 | try:
595 | resp_json = parse_json(resp.text)
596 | except Exception as e:
597 | logger.info('抢购失败,返回信息:{}'.format(resp.text[0: 128]))
598 | return False
599 | # 返回信息
600 | # 抢购失败:
601 | # {'errorMessage': '很遗憾没有抢到,再接再厉哦。', 'orderId': 0, 'resultCode': 60074, 'skuId': 0, 'success': False}
602 | # {'errorMessage': '抱歉,您提交过快,请稍后再提交订单!', 'orderId': 0, 'resultCode': 60017, 'skuId': 0, 'success': False}
603 | # {'errorMessage': '系统正在开小差,请重试~~', 'orderId': 0, 'resultCode': 90013, 'skuId': 0, 'success': False}
604 | # 抢购成功:
605 | # {"appUrl":"xxxxx","orderId":820227xxxxx,"pcUrl":"xxxxx","resultCode":0,"skuId":0,"success":true,"totalMoney":"xxxxx"}
606 | if resp_json.get('success'):
607 | order_id = resp_json.get('orderId')
608 | total_money = resp_json.get('totalMoney')
609 | pay_url = 'https:' + resp_json.get('pcUrl')
610 | logger.info('抢购成功,订单号:{}, 总价:{}, 电脑端付款链接:{}'.format(order_id, total_money, pay_url))
611 | if global_config.getRaw('messenger', 'enable') == 'true':
612 | success_message = "抢购成功,订单号:{}, 总价:{}, 电脑端付款链接:{}".format(order_id, total_money, pay_url)
613 | send_wechat(success_message)
614 | return True
615 | else:
616 | logger.info('抢购失败,返回信息:{}'.format(resp_json))
617 | if global_config.getRaw('messenger', 'enable') == 'true':
618 | error_message = '抢购失败,返回信息:{}'.format(resp_json)
619 | send_wechat(error_message)
620 | return False
621 |
--------------------------------------------------------------------------------
/models/tools/main.py:
--------------------------------------------------------------------------------
1 | import sys
2 | from jd_spider_requests import JdSeckill
3 |
4 |
5 | if __name__ == '__main__':
6 | a = """
7 | 功能列表:
8 |
9 | 1.预约商品
10 | 2.秒杀抢购商品
11 | """
12 | print(a)
13 |
14 | jd_seckill = JdSeckill()
15 | choice_function = input('请选择:')
16 | if choice_function == '1':
17 | jd_seckill.reserve()
18 | elif choice_function == '2':
19 | jd_seckill.seckill_by_proc_pool()
20 | else:
21 | print('没有此功能')
22 | sys.exit(1)
23 |
24 |
--------------------------------------------------------------------------------
/models/tools/qr_code.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cyril0216/jd-master/8f72cad5a88457d74752b4d8771b1f606458ad8c/models/tools/qr_code.png
--------------------------------------------------------------------------------
/models/tools/requirements.txt:
--------------------------------------------------------------------------------
1 | certifi==2020.4.5.1
2 | chardet==3.0.4
3 | idna==2.9
4 | lxml==4.5.1
5 | requests==2.23.0
6 | urllib3==1.25.9
7 |
--------------------------------------------------------------------------------
/models/tools/timer.py:
--------------------------------------------------------------------------------
1 | # -*- coding:utf-8 -*-
2 | import time
3 | import requests
4 | import json
5 |
6 | from datetime import datetime
7 | from jd_logger import logger
8 | from config import global_config
9 |
10 |
11 | class Timer(object):
12 | def __init__(self, sleep_interval=0.5):
13 | # '2018-09-28 22:45:50.000'
14 | # buy_time = 2020-12-22 09:59:59.500
15 | buy_time_everyday = global_config.getRaw('config', 'buy_time').__str__()
16 | localtime = time.localtime(time.time())
17 | self.buy_time = datetime.strptime(
18 | localtime.tm_year.__str__() + '-' + localtime.tm_mon.__str__() + '-' + localtime.tm_mday.__str__()
19 | + ' ' + buy_time_everyday,
20 | "%Y-%m-%d %H:%M:%S.%f")
21 | self.buy_time_ms = int(time.mktime(self.buy_time.timetuple()) * 1000.0 + self.buy_time.microsecond / 1000)
22 | self.sleep_interval = sleep_interval
23 |
24 | self.diff_time = self.local_jd_time_diff()
25 |
26 | def jd_time(self):
27 | """
28 | 从京东服务器获取时间毫秒
29 | :return:
30 | """
31 | url = 'https://a.jd.com//ajax/queryServerData.html'
32 | ret = requests.get(url).text
33 | js = json.loads(ret)
34 | return int(js["serverTime"])
35 |
36 | def local_time(self):
37 | """
38 | 获取本地毫秒时间
39 | :return:
40 | """
41 | return int(round(time.time() * 1000))
42 |
43 | def local_jd_time_diff(self):
44 | """
45 | 计算本地与京东服务器时间差
46 | :return:
47 | """
48 | return self.local_time() - self.jd_time()
49 |
50 | def start(self):
51 | logger.info('正在等待到达设定时间:{},检测本地时间与京东服务器时间误差为【{}】毫秒'.format(self.buy_time, self.diff_time))
52 | while True:
53 | # 本地时间减去与京东的时间差,能够将时间误差提升到0.1秒附近
54 | # 具体精度依赖获取京东服务器时间的网络时间损耗
55 | if self.local_time() - self.diff_time >= self.buy_time_ms:
56 | logger.info('时间到达,开始执行……')
57 | break
58 | else:
59 | time.sleep(self.sleep_interval)
60 |
--------------------------------------------------------------------------------
/models/tools/util.py:
--------------------------------------------------------------------------------
1 | import json
2 | import random
3 | import requests
4 | import os
5 | import time
6 |
7 | from config import global_config
8 |
9 | USER_AGENTS = [
10 | "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36",
11 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.1 Safari/537.36",
12 | "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36",
13 | "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36",
14 | "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2226.0 Safari/537.36",
15 | "Mozilla/5.0 (Windows NT 6.4; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2225.0 Safari/537.36",
16 | "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2225.0 Safari/537.36",
17 | "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2224.3 Safari/537.36",
18 | "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.93 Safari/537.36",
19 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.124 Safari/537.36",
20 | "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2049.0 Safari/537.36",
21 | "Mozilla/5.0 (Windows NT 4.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2049.0 Safari/537.36",
22 | "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.67 Safari/537.36",
23 | "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.67 Safari/537.36",
24 | "Mozilla/5.0 (X11; OpenBSD i386) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36",
25 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1944.0 Safari/537.36",
26 | "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.3319.102 Safari/537.36",
27 | "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.2309.372 Safari/537.36",
28 | "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.2117.157 Safari/537.36",
29 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.47 Safari/537.36",
30 | "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1866.237 Safari/537.36",
31 | "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.137 Safari/4E423F",
32 | "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.116 Safari/537.36 Mozilla/5.0 (iPad; U; CPU OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B334b Safari/531.21.10",
33 | "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.517 Safari/537.36",
34 | "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1667.0 Safari/537.36",
35 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1664.3 Safari/537.36",
36 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1664.3 Safari/537.36",
37 | "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.16 Safari/537.36",
38 | "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1623.0 Safari/537.36",
39 | "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.17 Safari/537.36",
40 | "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.62 Safari/537.36",
41 | "Mozilla/5.0 (X11; CrOS i686 4319.74.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.57 Safari/537.36",
42 | "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.2 Safari/537.36",
43 | "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1468.0 Safari/537.36",
44 | "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1467.0 Safari/537.36",
45 | "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1464.0 Safari/537.36",
46 | "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1500.55 Safari/537.36",
47 | "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36",
48 | "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36",
49 | "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36",
50 | "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36",
51 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36",
52 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36",
53 | "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.90 Safari/537.36",
54 | "Mozilla/5.0 (X11; NetBSD) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.116 Safari/537.36",
55 | "Mozilla/5.0 (X11; CrOS i686 3912.101.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.116 Safari/537.36",
56 | "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.60 Safari/537.17",
57 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1309.0 Safari/537.17",
58 | "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.15 (KHTML, like Gecko) Chrome/24.0.1295.0 Safari/537.15",
59 | "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.14 (KHTML, like Gecko) Chrome/24.0.1292.0 Safari/537.14"
60 | ]
61 |
62 |
63 | def parse_json(s):
64 | begin = s.find('{')
65 | end = s.rfind('}') + 1
66 | return json.loads(s[begin:end])
67 |
68 |
69 | def get_random_useragent():
70 | """生成随机的UserAgent
71 | :return: UserAgent字符串
72 | """
73 | return random.choice(USER_AGENTS)
74 |
75 |
76 | def wait_some_time():
77 | time.sleep(random.randint(100, 300) / 1000)
78 |
79 |
80 | def send_wechat(message):
81 | """推送信息到微信"""
82 | url = 'http://sc.ftqq.com/{}.send'.format(global_config.getRaw('messenger', 'sckey'))
83 | payload = {
84 | "text":'抢购结果',
85 | "desp": message
86 | }
87 | headers = {
88 | 'User-Agent':global_config.getRaw('config', 'DEFAULT_USER_AGENT')
89 | }
90 | requests.get(url, params=payload, headers=headers)
91 |
92 |
93 | def response_status(resp):
94 | if resp.status_code != requests.codes.OK:
95 | print('Status: %u, Url: %s' % (resp.status_code, resp.url))
96 | return False
97 | return True
98 |
99 |
100 | def open_image(image_file):
101 | if os.name == "nt":
102 | os.system('start ' + image_file) # for Windows
103 | else:
104 | if os.uname()[0] == "Linux":
105 | if "deepin" in os.uname()[2]:
106 | os.system("deepin-image-viewer " + image_file) # for deepin
107 | else:
108 | os.system("eog " + image_file) # for Linux
109 | else:
110 | os.system("open " + image_file) # for Mac
111 |
112 |
113 | def save_image(resp, image_file):
114 | with open(image_file, 'wb') as f:
115 | for chunk in resp.iter_content(chunk_size=1024):
116 | f.write(chunk)
117 |
--------------------------------------------------------------------------------
/security/ir.model.access.csv:
--------------------------------------------------------------------------------
1 | "id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
2 | "access_jd_access_token_read","jd_access_token","model_jd_access_token","base.group_user",1,1,1,1
3 |
--------------------------------------------------------------------------------
/security/security.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 京东账户相关
6 |
7 |
8 | 订单配置
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/static/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cyril0216/jd-master/8f72cad5a88457d74752b4d8771b1f606458ad8c/static/.DS_Store
--------------------------------------------------------------------------------
/static/description/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cyril0216/jd-master/8f72cad5a88457d74752b4d8771b1f606458ad8c/static/description/icon.png
--------------------------------------------------------------------------------
/views/jd_access_token.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | jd_access_token.form
6 | jd_access_token
7 |
8 |
23 |
24 |
25 |
26 | jd_access_token.tree
27 | jd_access_token
28 | 1
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | 京东账户相关
39 | ir.actions.act_window
40 | jd_access_token
41 | tree,form
42 |
43 |
44 | 创建账户.
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/views/menu_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------