├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── chatpush ├── __init__.py ├── routes.py ├── settings.py ├── static │ └── img │ │ └── qr_code.png ├── templates │ └── index.html └── wx.py ├── config ├── __init__.py ├── mail_config.py ├── shop.json ├── tmall.json └── wechat_config.py ├── data.txt ├── img ├── pic.jpg └── qrcode.jpg ├── jdBuyMask ├── __init__.py ├── config.py ├── configDemo.ini ├── jdEmail.py ├── jdProgram.py ├── jdlogger.py ├── message.py ├── requirements.txt ├── util.py ├── wechat_ftqq.py └── 感谢作者 ├── log ├── __init__.py └── logger.py ├── src ├── chromedriver ├── chromedriver.exe ├── jdurl.txt ├── mail.py └── tmurl.txt ├── tmall_order.py ├── util ├── __init__.py ├── jd.txt ├── tm.txt └── util.py ├── watcher.py └── wechat_push.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .idea/* 3 | *.pyc 4 | imgs/*.png 5 | log/logs/*.txt 6 | src/imgs/*.png 7 | .DS_Store 8 | src/ghostdriver.log 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Daniel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # N95-watcher 2 | 🏷️监控京东、天猫、苏宁等商城口罩、消毒液、护目镜等物资有货信息,并推送微信提醒。抗击疫情!中国加油🇨🇳! 3 | 请不要用于非法用途!请不要倒卖口罩!此项目是为了学习技术,不对代码用途负责! 4 | 5 | ## 已有功能 6 | - 推送微信消息,根据商品列表监控商品 7 | - 推送商品页面截图,方便甄别有效信息 8 | - 根据已有接口查询商品并检查,然后推送 9 | - 发现需要抢购的商品,输入url自动下单抢购(支持天猫淘宝)tmall_order.py 10 | - 京东监控扫码一键登陆功能 jd_auto_order.py 11 | 12 | 13 | ### 如果对您有帮助 欢迎点亮 🌟star🌟 14 | ## 现已停止维护,但依旧可以加入微信群相互交流信息 15 | 16 | 17 | ## 自行微信推送使用方式 18 | - 请自行安装python 3,如果已安装版本是python 2,推荐使用版本管理工具。 19 | ```buildoutcfg 20 | python3:https://www.python.org/downloads/ 21 | ``` 22 | - 安装所需依赖 23 | ```buildoutcfg 24 | pip install wxpy 25 | pip install selenium 26 | pip install pillow 27 | 如果还有其他缺失 请根据提示 pip install 安装 28 | ``` 29 | - 运行watcher.py 30 | - 运行wechat_push.py 31 | - 修改程序内的要推送用户名 32 | - 扫码登陆 33 | - 开始推送 34 | 35 | ## 天猫、淘宝、天猫商城抢购功能 36 | ### 使用方法 37 | 1. 设置url 38 | 2. 设置天猫还是淘宝 39 | 3. 设置开抢时间 40 | 4. 运行程序 41 | 5. 扫码登录 42 | 6. 选中要购买商品以及相应种类等(必须选中!!!) 43 | 7. 等待自动下单 44 | 45 | 如果是windows系统,需要修改代码中 “chromedriver” 为 “chromedriver.exe” 46 | 47 | 48 | 49 | ## 京东监控扫码一键登陆功能 50 | ### 使用方法 51 | 1. 设置skuidsString:需要抢购的商品id ,逗号 ',' 分割 (已有默认口罩id) 52 | 2. 设置modelType:抢购模式 ,默认快速模式 53 | 3. 设置支付密码 payment_pwd 54 | 4. 启动程序,扫码登录京东,程序即可自动运行 55 | #### !!!注意,需要设置默认收货地址,订单地址即为默认收货地址,查询有货也是以此地址监测。 56 | -------------------------------------------------------------------------------- /chatpush/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from flask import Flask 4 | 5 | basedir = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) 6 | 7 | 8 | def create_app(config=None): 9 | app = Flask(__name__) 10 | app.config.from_object('chatpush.settings') 11 | app.config.root_path = os.getcwd() 12 | if 'FLASK_CONF' in os.environ: 13 | app.config.from_envvar('FLASK_CONF') 14 | if config is not None: 15 | if isinstance(config, dict): 16 | app.config.update(config) 17 | elif config.endswith('.cfg'): 18 | app.config.from_pyfile(config) 19 | 20 | from .routes import route_bp 21 | app.register_blueprint(route_bp) 22 | return app 23 | 24 | 25 | app = create_app() 26 | 27 | 28 | @app.after_request 29 | def after_request(response): 30 | response.headers.add('Access-Control-Allow-Origin', '*') 31 | response.headers.add( 32 | 'Access-Control-Allow-Headers', 'Content-Type,Authorization') 33 | response.headers.add('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE') 34 | return response 35 | -------------------------------------------------------------------------------- /chatpush/routes.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint 2 | from flask import render_template 3 | 4 | route_bp = Blueprint('route', __name__) 5 | 6 | 7 | @route_bp.route('/') 8 | def index(): 9 | # 显示登陆状态 10 | # return 'Hello, World!' 11 | return render_template('index.html') 12 | 13 | # 发送消息 14 | @route_bp.route('/send', methods=['POST']) 15 | def send_msg(): 16 | pass 17 | -------------------------------------------------------------------------------- /chatpush/settings.py: -------------------------------------------------------------------------------- 1 | # 默认配置 2 | 3 | DEBUG = True 4 | SECRET_KEY = 'default key' 5 | -------------------------------------------------------------------------------- /chatpush/static/img/qr_code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/westnestling/N95-watcher/6efefe9497ed95a75c7712323a85daec7eb7f3aa/chatpush/static/img/qr_code.png -------------------------------------------------------------------------------- /chatpush/templates/index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/westnestling/N95-watcher/6efefe9497ed95a75c7712323a85daec7eb7f3aa/chatpush/templates/index.html -------------------------------------------------------------------------------- /chatpush/wx.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from wxpy import * 4 | 5 | basedir = os.path.abspath(os.path.dirname(__file__)) 6 | 7 | 8 | def login(): 9 | bot = Bot(cache_path=True) 10 | group = bot.groups().search('口罩消毒液防货监控群')[0] 11 | print(group) 12 | 13 | 14 | def get_bot(): 15 | bot = Bot('bot.pkl', qr_path=os.path.join( 16 | basedir, 'QR.png'), console_qr=None) 17 | # bot.enable_puid() 18 | # bot.messages.max_history = 0 19 | return bot 20 | 21 | 22 | bot = get_bot() 23 | -------------------------------------------------------------------------------- /config/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/westnestling/N95-watcher/6efefe9497ed95a75c7712323a85daec7eb7f3aa/config/__init__.py -------------------------------------------------------------------------------- /config/mail_config.py: -------------------------------------------------------------------------------- 1 | mail_config = { 2 | "host":"smtp.qq.com", 3 | "user":"xxx@qq.com", 4 | "passwd":"xxx", 5 | "to":"xx@outlook.com" 6 | } -------------------------------------------------------------------------------- /config/shop.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "tmall", 4 | "key_word": "已下架,卖光,无货,已下柜,已下架,不支持销售,售完,缺货", 5 | "shop": [ 6 | "https://chaoshi.detail.tmall.com/item.htm?id=38354194509", 7 | "https://chaoshi.detail.tmall.com/item.htm?id=611911893371", 8 | "https://chaoshi.detail.tmall.com/item.htm?id=612169223963 ", 9 | "https://detail.tmall.com/item.htm?id=561316427429", 10 | "https://detail.tmall.com/item.htm?id=594842356608", 11 | "https://detail.tmall.com/item.htm?id=537783105938", 12 | "https://detail.tmall.com/item.htm?id=579147322829", 13 | "https://detail.tmall.com/item.htm?id=526256013584", 14 | "https://detail.tmall.com/item.htm?id=41930581793", 15 | "https://detail.tmall.com/item.htm?id=539798535423", 16 | "https://detail.tmall.com/item.htm?id=553350127780", 17 | "https://detail.tmall.com/item.htm?id=557041813996", 18 | "https://detail.tmall.com/item.htm?id=538688343613", 19 | "https://detail.tmall.com/item.htm?id=537539556119", 20 | "https://detail.tmall.com/item.htm?id=555176437380", 21 | "https://detail.tmall.com/item.htm?id=550189462849", 22 | "https://detail.tmall.com/item.htm?id=597463034616", 23 | "https://detail.tmall.com/item.htm?id=596869895887", 24 | "https://detail.tmall.com/item.htm?id=43836604206", 25 | "https://detail.tmall.com/item.htm?id=537539556119", 26 | "https://detail.tmall.com/item.htm?id=559868897280", 27 | "https://detail.tmall.com/item.htm?id=566694899095", 28 | "https://detail.tmall.com/item.htm?id=612169223963", 29 | "https://detail.tmall.com/item.htm?id=566700203403", 30 | "https://detail.tmall.com/item.htm?id=582154342729", 31 | "https://detail.tmall.com/item.htm?id=581704000297", 32 | "https://detail.tmall.com/item.htm?id=40844663705", 33 | "https://detail.tmall.com/item.htm?id=600932058333", 34 | "https://detail.tmall.com/item.htm?id=560454169970", 35 | "https://detail.tmall.com/item.htm?id=611218796479", 36 | "https://detail.tmall.com/item.htm?id=16914648132", 37 | "https://detail.tmall.com/item.htm?id=41930581793", 38 | "https://detail.tmall.com/item.htm?id=43557127826", 39 | "https://detail.tmall.com/item.htm?id=43836604206", 40 | "https://detail.tmall.com/item.htm?id=526256013584", 41 | "https://detail.tmall.com/item.htm?id=537783105938", 42 | "https://detail.tmall.com/item.htm?id=538688343613", 43 | "https://detail.tmall.com/item.htm?id=539329058910", 44 | "https://detail.tmall.com/item.htm?id=539798535423", 45 | "https://detail.tmall.com/item.htm?id=549951148887", 46 | "https://detail.tmall.com/item.htm?id=550181092780", 47 | "https://detail.tmall.com/item.htm?id=550189462849", 48 | "https://detail.tmall.com/item.htm?id=550225741968", 49 | "https://detail.tmall.com/item.htm?id=550281158824", 50 | "https://detail.tmall.com/item.htm?id=552790837013", 51 | "https://detail.tmall.com/item.htm?id=553350127780", 52 | "https://detail.tmall.com/item.htm?id=555176437380", 53 | "https://detail.tmall.com/item.htm?id=557041813996", 54 | "https://detail.tmall.com/item.htm?id=558596461970", 55 | "https://detail.tmall.com/item.htm?id=559101912744", 56 | "https://detail.tmall.com/item.htm?id=559303233985", 57 | "https://detail.tmall.com/item.htm?id=559360262475", 58 | "https://detail.tmall.com/item.htm?id=560359707378", 59 | "https://detail.tmall.com/item.htm?id=561316427429", 60 | "https://detail.tmall.com/item.htm?id=561897804878", 61 | "https://detail.tmall.com/item.htm?id=562699529401", 62 | "https://detail.tmall.com/item.htm?id=562800423283", 63 | "https://detail.tmall.com/item.htm?id=563451626963", 64 | "https://detail.tmall.com/item.htm?id=565876396202", 65 | "https://detail.tmall.com/item.htm?id=566000169760", 66 | "https://detail.tmall.com/item.htm?id=566805629407", 67 | "https://detail.tmall.com/item.htm?id=568302562015", 68 | "https://detail.tmall.com/item.htm?id=577253947669", 69 | "https://detail.tmall.com/item.htm?id=578323822970", 70 | "https://detail.tmall.com/item.htm?id=578409369729", 71 | "https://detail.tmall.com/item.htm?id=582357951467", 72 | "https://detail.tmall.com/item.htm?id=596733380975", 73 | "https://detail.tmall.com/item.htm?id=596869895887", 74 | "https://detail.tmall.com/item.htm?id=596970557748", 75 | "https://detail.tmall.com/item.htm?id=597463034616", 76 | "https://detail.tmall.com/item.htm?id=601608085564", 77 | "https://detail.tmall.com/item.htm?id=601612058331", 78 | "https://detail.tmall.com/item.htm?id=603383811589", 79 | "https://detail.tmall.com/item.htm?id=604035325011", 80 | "https://detail.tmall.com/item.htm?id=604099889111", 81 | "https://detail.tmall.com/item.htm?id=605661675553", 82 | "https://detail.tmall.com/item.htm?id=605668032834", 83 | "https://detail.tmall.com/item.htm?id=605988521690", 84 | "https://detail.tmall.com/item.htm?id=606134328835", 85 | "https://detail.tmall.com/item.htm?id=608557201009", 86 | "https://detail.tmall.com/item.htm?id=611223265045", 87 | "https://detail.tmall.com/item.htm?id=611587537638", 88 | "https://detail.tmall.com/item.htm?id=611610597456", 89 | "https://detail.tmall.com/item.htm?id=611712359485", 90 | "https://detail.tmall.com/item.htm?id=611891455980", 91 | "https://detail.tmall.com/item.htm?id=611945723187", 92 | "https://detail.tmall.com/item.htm?id=611951486219", 93 | "https://detail.tmall.com/item.htm?id=612142243066", 94 | "https://detail.tmall.com/item.htm?id=599648775985", 95 | "https://detail.tmall.com/item.htm?id=601992377478", 96 | "https://detail.tmall.com/item.htm?id=611586261649", 97 | "https://detail.tmall.com/item.htm?id=611272808632", 98 | "https://detail.tmall.com/item.htm?id=611524145911", 99 | "https://detail.tmall.com/item.htm?id=595114805524", 100 | "https://detail.tmall.com/item.htm?id=537464694779", 101 | "https://detail.tmall.com/item.htm?id=596809211292", 102 | "https://detail.tmall.com/item.htm?id=537539396284", 103 | "https://detail.tmall.com/item.htm?id=612082019478", 104 | "https://detail.tmall.com/item.htm?id=579580489169", 105 | "https://detail.tmall.com/item.htm?id=602693568062", 106 | "https://detail.tmall.com/item.htm?id=579147322829", 107 | "https://detail.tmall.com/item.htm?id=584460207534", 108 | "https://detail.tmall.com/item.htm?id=524798659351", 109 | "https://detail.tmall.com/item.htm?id=53778310593", 110 | "https://detail.tmall.com/item.htm?id=100006066047", 111 | "https://detail.tmall.com/item.htm?id=559828657513", 112 | "https://detail.tmall.com/item.htm?id=611949999645", 113 | "https://detail.tmall.com/item.htm?id=40844663705", 114 | "https://detail.tmall.com/item.htm?id=569492882306", 115 | "https://detail.tmall.com/item.htm?id=596143442666", 116 | "https://detail.tmall.com/item.htm?id=40844663705", 117 | "https://detail.tmall.com/item.htm?id=569492882306", 118 | "https://detail.tmall.com/item.htm?id=563451626963", 119 | "https://detail.tmall.com/item.htm?id=528937646086", 120 | "https://detail.tmall.com/item.htm?id=550189462849", 121 | "https://detail.tmall.com/item.htm?id=612169223963", 122 | "https://detail.tmall.com/item.htm?id=563451626963", 123 | "https://detail.tmall.com/item.htm?id=528937646086", 124 | "https://detail.tmall.com/item.htm?id=40844663705", 125 | "https://detail.tmall.com/item.htm?id=611586261649", 126 | "https://detail.tmall.com/item.htm?id=569492882306" 127 | ] 128 | } 129 | ] 130 | 131 | -------------------------------------------------------------------------------- /config/tmall.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "tmall", 4 | "key_word": "已下架,卖光,无货,已下柜,已下架,不支持销售,售完,缺货", 5 | "shop": [ 6 | "https://detail.tmall.com/item.htm?id=612010333086", 7 | "https://chaoshi.detail.tmall.com/item.htm?id=611911893371", 8 | "https://chaoshi.detail.tmall.com/item.htm?id=612169223963", 9 | "https://detail.tmall.com/item.htm?id=550189462849" 10 | ] 11 | } 12 | ] 13 | 14 | -------------------------------------------------------------------------------- /config/wechat_config.py: -------------------------------------------------------------------------------- 1 | 2 | # see http://wxpusher.zjiecode.com/docs/#/ 3 | 4 | APP_TOKEN = "xxx" 5 | 6 | UID = "1,2" -------------------------------------------------------------------------------- /data.txt: -------------------------------------------------------------------------------- 1 | 2020-02-14 13:22:57 理想生活上天猫 url:https://chaoshi.detail.tmall.com/item.htm?id=38354194509 2 | 2020-02-14 13:23:23 滴露消毒液1.2L*2瓶家用杀菌衣物室内除菌消毒水洗衣消毒官方旗舰-tmall.com天猫 url:https://detail.tmall.com/item.htm?id=594842356608 3 | 2020-02-14 13:30:05 3M口罩 专业防雾霾防尘透气口罩带呼吸阀 KN95口罩 耳带式-tmall.com天猫 url:https://detail.tmall.com/item.htm?id=604035325011 4 | 2020-02-14 13:30:46 一次性防尘口罩透气易呼吸防工业粉尘灰雾霾pm2.5甲醛口鼻罩覃-tmall.com天猫 url:https://detail.tmall.com/item.htm?id=608557201009 5 | -------------------------------------------------------------------------------- /img/pic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/westnestling/N95-watcher/6efefe9497ed95a75c7712323a85daec7eb7f3aa/img/pic.jpg -------------------------------------------------------------------------------- /img/qrcode.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/westnestling/N95-watcher/6efefe9497ed95a75c7712323a85daec7eb7f3aa/img/qrcode.jpg -------------------------------------------------------------------------------- /jdBuyMask/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/westnestling/N95-watcher/6efefe9497ed95a75c7712323a85daec7eb7f3aa/jdBuyMask/__init__.py -------------------------------------------------------------------------------- /jdBuyMask/config.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os 3 | import configparser 4 | 5 | 6 | class Config(object): 7 | def __init__(self, config_file='configDemo.ini'): 8 | self._path = os.path.join(os.getcwd(),'jdBuyMask', config_file) 9 | if not os.path.exists(self._path): 10 | raise FileNotFoundError("No such file: config.ini") 11 | self._config = configparser.ConfigParser() 12 | self._config.read(self._path, encoding='utf-8-sig') 13 | self._configRaw = configparser.RawConfigParser() 14 | self._configRaw.read(self._path, encoding='utf-8-sig') 15 | 16 | def get(self, section, name): 17 | return self._config.get(section, name) 18 | 19 | def getRaw(self, section, name): 20 | return self._configRaw.get(section, name) 21 | 22 | 23 | -------------------------------------------------------------------------------- /jdBuyMask/configDemo.ini: -------------------------------------------------------------------------------- 1 | [config] 2 | 3 | # 推送方式 1为邮箱,2为微信 4 | messageType =1 5 | # mail 6 | mail = xxx@qq.com 7 | # sc_key 方糖微信推送的key 不知道的请看http://sc.ftqq.com/3.version 8 | sc_key = test 9 | 10 | [V2] 11 | 12 | 13 | [Temporary] 14 | #一般不需要修改 15 | eid = 16 | fp = 17 | # 打码服务器 18 | captchaUrl = xxx -------------------------------------------------------------------------------- /jdBuyMask/jdEmail.py: -------------------------------------------------------------------------------- 1 | # -*- coding=utf-8 -*- 2 | ''' 3 | 发送邮件模块 4 | ''' 5 | import traceback 6 | 7 | 8 | def sendMail(mail, msgtext): 9 | try: 10 | import smtplib 11 | from email.mime.text import MIMEText 12 | # email 用于构建邮件内容 13 | from email.header import Header 14 | 15 | # 用于构建邮件头 16 | # 发信方的信息:发信邮箱,QQ 邮箱授权码 17 | from_addr = 'jdbuymask@163.com' 18 | password = 'alpsneahcyz123' 19 | 20 | # 收信方邮箱 21 | to_addr = mail 22 | # 发信服务器 23 | smtp_server = 'smtp.163.com' 24 | # 邮箱正文内容,第一个参数为内容,第二个参数为格式(plain 为纯文本),第三个参数为编码 25 | msg = MIMEText(msgtext, 'plain', 'utf-8') 26 | # 邮件头信息 27 | # msg['From'] = Header(from_addr) 28 | msg['From'] = Header(u'from Mark<{}>'.format(from_addr), 'utf-8') 29 | msg['To'] = Header(to_addr) 30 | msg['Subject'] = Header('京东口罩监控','utf-8') 31 | # 开启发信服务,这里使用的是加密传输 32 | server = smtplib.SMTP_SSL(host=smtp_server) 33 | server.connect(smtp_server, 465) 34 | # 登录发信邮箱 35 | server.login(from_addr, password) 36 | # 发送邮件 37 | # server.sendmail(from_addr, to_addr, msg.as_string()) 38 | # 关闭服务器 39 | server.quit() 40 | except Exception as e: 41 | print(traceback.format_exc()) 42 | -------------------------------------------------------------------------------- /jdBuyMask/jdProgram.py: -------------------------------------------------------------------------------- 1 | # -*- coding=utf-8 -*- 2 | ''' 3 | 查询库存 4 | ''' 5 | import json 6 | import random 7 | import time 8 | from io import BytesIO 9 | 10 | from PIL import Image 11 | 12 | from jdlogger import logger 13 | from jdBuyMask.util import parse_json, response_status 14 | import traceback 15 | 16 | 17 | def check_stock(checksession, skuids, area): 18 | start = int(time.time() * 1000) 19 | skuidString = ','.join(skuids) 20 | callback = 'jQuery' + str(random.randint(1000000, 9999999)) 21 | headers = { 22 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/531.36", 23 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3", 24 | "Referer": "https://cart.jd.com/cart.action", 25 | "Connection": "keep-alive", 26 | "Host":"c0.3.cn" 27 | } 28 | # 29 | url = 'https://c0.3.cn/stocks' 30 | payload = { 31 | 'type': 'getstocks', 32 | 'skuIds': skuidString, 33 | 'area': area, 34 | 'callback': callback, 35 | '_': int(time.time() * 1000), 36 | } 37 | resp = checksession.get(url=url, params=payload, headers=headers) 38 | inStockSkuid = [] 39 | nohasSkuid = [] 40 | unUseSkuid = [] 41 | for sku_id, info in parse_json(resp.text).items(): 42 | sku_state = info.get('skuState') # 商品是否上架 43 | stock_state = info.get('StockState') # 商品库存状态 44 | if sku_state == 1 and stock_state in (33, 40): 45 | inStockSkuid.append(sku_id) 46 | if sku_state == 0: 47 | unUseSkuid.append(sku_id) 48 | if stock_state == 34: 49 | nohasSkuid.append(sku_id) 50 | logger.info('检测[%s]个口罩有货,[%s]个口罩无货,[%s]个口罩下柜,耗时[%s]ms', len(inStockSkuid), len(nohasSkuid), len(unUseSkuid), 51 | int(time.time() * 1000) - start) 52 | 53 | if len(unUseSkuid) > 0: 54 | logger.info('[%s]口罩已经下柜', ','.join(unUseSkuid)) 55 | return inStockSkuid 56 | 57 | 58 | ''' 59 | 提交订单 60 | ''' 61 | 62 | 63 | def submit_order(session, risk_control, sku_id, skuids, submit_Time, encryptClientInfo, is_Submit_captcha, payment_pwd, 64 | submit_captcha_text, submit_captcha_rid): 65 | """ 66 | 67 | 重要: 68 | 1.该方法只适用于普通商品的提交订单(即可以加入购物车,然后结算提交订单的商品) 69 | 2.提交订单时,会对购物车中勾选✓的商品进行结算(如果勾选了多个商品,将会提交成一个订单) 70 | 71 | :return: True/False 订单提交结果 72 | """ 73 | url = 'https://trade.jd.com/shopping/order/submitOrder.action' 74 | # js function of submit order is included in https://trade.jd.com/shopping/misc/js/order.js?r=2018070403091 75 | 76 | data = { 77 | 'overseaPurchaseCookies': '', 78 | 'vendorRemarks': '[]', 79 | 'submitOrderParam.sopNotPutInvoice': 'false', 80 | 'submitOrderParam.trackID': 'TestTrackId', 81 | 'submitOrderParam.ignorePriceChange': '0', 82 | 'submitOrderParam.btSupport': '0', 83 | 'riskControl': risk_control, 84 | 'submitOrderParam.isBestCoupon': 1, 85 | 'submitOrderParam.jxj': 1, 86 | 'submitOrderParam.trackId': '9643cbd55bbbe103eef18a213e069eb0', # Todo: need to get trackId 87 | # 'submitOrderParam.eid': eid, 88 | # 'submitOrderParam.fp': fp, 89 | 'submitOrderParam.needCheck': 1, 90 | } 91 | 92 | def encrypt_payment_pwd(payment_pwd): 93 | return ''.join(['u3' + x for x in payment_pwd]) 94 | 95 | if len(payment_pwd) > 0: 96 | data['submitOrderParam.payPassword'] = encrypt_payment_pwd(payment_pwd) 97 | 98 | headers = { 99 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/531.36", 100 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3", 101 | "Referer": "http://trade.jd.com/shopping/order/getOrderInfo.action", 102 | "Connection": "keep-alive", 103 | 'Host': 'trade.jd.com', 104 | } 105 | for count in range(1, 3): 106 | logger.info('第[%s/%s]次尝试提交订单', count, 3) 107 | try: 108 | if is_Submit_captcha: 109 | captcha_result = page_detail_captcha(session, encryptClientInfo) 110 | # 验证码服务错误 111 | if not captcha_result: 112 | logger.error('验证码服务异常') 113 | continue 114 | data['submitOrderParam.checkcodeTxt'] = submit_captcha_text 115 | data['submitOrderParam.checkCodeRid'] = submit_captcha_rid 116 | resp = session.post(url=url, data=data, headers=headers) 117 | resp_json = json.loads(resp.text) 118 | logger.info('本次提交订单耗时[%s]毫秒', str(int(time.time() * 1000) - submit_Time)) 119 | # 返回信息示例: 120 | # 下单失败 121 | # {'overSea': False, 'orderXml': None, 'cartXml': None, 'noStockSkuIds': '', 'reqInfo': None, 'hasJxj': False, 'addedServiceList': None, 'sign': None, 'pin': 'xxx', 'needCheckCode': False, 'success': False, 'resultCode': 60123, 'orderId': 0, 'submitSkuNum': 0, 'deductMoneyFlag': 0, 'goJumpOrderCenter': False, 'payInfo': None, 'scaleSkuInfoListVO': None, 'purchaseSkuInfoListVO': None, 'noSupportHomeServiceSkuList': None, 'msgMobile': None, 'addressVO': None, 'msgUuid': None, 'message': '请输入支付密码!'} 122 | # {'overSea': False, 'cartXml': None, 'noStockSkuIds': '', 'reqInfo': None, 'hasJxj': False, 'addedServiceList': None, 'orderXml': None, 'sign': None, 'pin': 'xxx', 'needCheckCode': False, 'success': False, 'resultCode': 60017, 'orderId': 0, 'submitSkuNum': 0, 'deductMoneyFlag': 0, 'goJumpOrderCenter': False, 'payInfo': None, 'scaleSkuInfoListVO': None, 'purchaseSkuInfoListVO': None, 'noSupportHomeServiceSkuList': None, 'msgMobile': None, 'addressVO': None, 'msgUuid': None, 'message': '您多次提交过快,请稍后再试'} 123 | # {'overSea': False, 'orderXml': None, 'cartXml': None, 'noStockSkuIds': '', 'reqInfo': None, 'hasJxj': False, 'addedServiceList': None, 'sign': None, 'pin': 'xxx', 'needCheckCode': False, 'success': False, 'resultCode': 60077, 'orderId': 0, 'submitSkuNum': 0, 'deductMoneyFlag': 0, 'goJumpOrderCenter': False, 'payInfo': None, 'scaleSkuInfoListVO': None, 'purchaseSkuInfoListVO': None, 'noSupportHomeServiceSkuList': None, 'msgMobile': None, 'addressVO': None, 'msgUuid': None, 'message': '获取用户订单信息失败'} 124 | # {"cartXml":null,"noStockSkuIds":"xxx","reqInfo":null,"hasJxj":false,"addedServiceList":null,"overSea":false,"orderXml":null,"sign":null,"pin":"xxx","needCheckCode":false,"success":false,"resultCode":600157,"orderId":0,"submitSkuNum":0,"deductMoneyFlag":0,"goJumpOrderCenter":false,"payInfo":null,"scaleSkuInfoListVO":null,"purchaseSkuInfoListVO":null,"noSupportHomeServiceSkuList":null,"msgMobile":null,"addressVO":{"pin":"xxx","areaName":"","provinceId":xx,"cityId":xx,"countyId":xx,"townId":xx,"paymentId":0,"selected":false,"addressDetail":"xx","mobile":"xx","idCard":"","phone":null,"email":null,"selfPickMobile":null,"selfPickPhone":null,"provinceName":null,"cityName":null,"countyName":null,"townName":null,"giftSenderConsigneeName":null,"giftSenderConsigneeMobile":null,"gcLat":0.0,"gcLng":0.0,"coord_type":0,"longitude":0.0,"latitude":0.0,"selfPickOptimize":0,"consigneeId":0,"selectedAddressType":0,"siteType":0,"helpMessage":null,"tipInfo":null,"cabinetAvailable":true,"limitKeyword":0,"specialRemark":null,"siteProvinceId":0,"siteCityId":0,"siteCountyId":0,"siteTownId":0,"skuSupported":false,"addressSupported":0,"isCod":0,"consigneeName":null,"pickVOname":null,"shipmentType":0,"retTag":0,"tagSource":0,"userDefinedTag":null,"newProvinceId":0,"newCityId":0,"newCountyId":0,"newTownId":0,"newProvinceName":null,"newCityName":null,"newCountyName":null,"newTownName":null,"checkLevel":0,"optimizePickID":0,"pickType":0,"dataSign":0,"overseas":0,"areaCode":null,"nameCode":null,"appSelfPickAddress":0,"associatePickId":0,"associateAddressId":0,"appId":null,"encryptText":null,"certNum":null,"used":false,"oldAddress":false,"mapping":false,"addressType":0,"fullAddress":"xxxx","postCode":null,"addressDefault":false,"addressName":null,"selfPickAddressShuntFlag":0,"pickId":0,"pickName":null,"pickVOselected":false,"mapUrl":null,"branchId":0,"canSelected":false,"address":null,"name":"xxx","message":null,"id":0},"msgUuid":null,"message":"xxxxxx商品无货"} 125 | # {'orderXml': None, 'overSea': False, 'noStockSkuIds': 'xxx', 'reqInfo': None, 'hasJxj': False, 'addedServiceList': None, 'cartXml': None, 'sign': None, 'pin': 'xxx', 'needCheckCode': False, 'success': False, 'resultCode': 600158, 'orderId': 0, 'submitSkuNum': 0, 'deductMoneyFlag': 0, 'goJumpOrderCenter': False, 'payInfo': None, 'scaleSkuInfoListVO': None, 'purchaseSkuInfoListVO': None, 'noSupportHomeServiceSkuList': None, 'msgMobile': None, 'addressVO': {'oldAddress': False, 'mapping': False, 'pin': 'xxx', 'areaName': '', 'provinceId': xx, 'cityId': xx, 'countyId': xx, 'townId': xx, 'paymentId': 0, 'selected': False, 'addressDetail': 'xxxx', 'mobile': 'xxxx', 'idCard': '', 'phone': None, 'email': None, 'selfPickMobile': None, 'selfPickPhone': None, 'provinceName': None, 'cityName': None, 'countyName': None, 'townName': None, 'giftSenderConsigneeName': None, 'giftSenderConsigneeMobile': None, 'gcLat': 0.0, 'gcLng': 0.0, 'coord_type': 0, 'longitude': 0.0, 'latitude': 0.0, 'selfPickOptimize': 0, 'consigneeId': 0, 'selectedAddressType': 0, 'newCityName': None, 'newCountyName': None, 'newTownName': None, 'checkLevel': 0, 'optimizePickID': 0, 'pickType': 0, 'dataSign': 0, 'overseas': 0, 'areaCode': None, 'nameCode': None, 'appSelfPickAddress': 0, 'associatePickId': 0, 'associateAddressId': 0, 'appId': None, 'encryptText': None, 'certNum': None, 'addressType': 0, 'fullAddress': 'xxxx', 'postCode': None, 'addressDefault': False, 'addressName': None, 'selfPickAddressShuntFlag': 0, 'pickId': 0, 'pickName': None, 'pickVOselected': False, 'mapUrl': None, 'branchId': 0, 'canSelected': False, 'siteType': 0, 'helpMessage': None, 'tipInfo': None, 'cabinetAvailable': True, 'limitKeyword': 0, 'specialRemark': None, 'siteProvinceId': 0, 'siteCityId': 0, 'siteCountyId': 0, 'siteTownId': 0, 'skuSupported': False, 'addressSupported': 0, 'isCod': 0, 'consigneeName': None, 'pickVOname': None, 'shipmentType': 0, 'retTag': 0, 'tagSource': 0, 'userDefinedTag': None, 'newProvinceId': 0, 'newCityId': 0, 'newCountyId': 0, 'newTownId': 0, 'newProvinceName': None, 'used': False, 'address': None, 'name': 'xx', 'message': None, 'id': 0}, 'msgUuid': None, 'message': 'xxxxxx商品无货'} 126 | # {"orderXml":null,"cartXml":null,"noStockSkuIds":"","reqInfo":null,"hasJxj":false,"overSea":false,"addedServiceList":null,"sign":null,"pin":null,"needCheckCode":true,"success":false,"resultCode":0,"orderId":0,"submitSkuNum":0,"deductMoneyFlag":0,"goJumpOrderCenter":false,"payInfo":null,"scaleSkuInfoListVO":null,"purchaseSkuInfoListVO":null,"noSupportHomeServiceSkuList":null,"msgMobile":null,"addressVO":null,"msgUuid":null,"message":"验证码不正确,请重新填写"} 127 | # {'overSea': False, 'orderXml': None, 'cartXml': None, 'noStockSkuIds': '', 'reqInfo': None, 'hasJxj': False, 'addedServiceList': None, 'sign': None, 'pin': 'jd_7c3992aa27d1a', 'needCheckCode': False, 'success': False, 'resultCode': 60070, 'orderId': 0, 'submitSkuNum': 0, 'deductMoneyFlag': 0, 'goJumpOrderCenter': False, 'payInfo': None, 'scaleSkuInfoListVO': None, 'purchaseSkuInfoListVO': None, 'noSupportHomeServiceSkuList': None, 'msgMobile': None, 'addressVO': None, 'msgUuid': None, 'message': '抱歉,您当前选择的省份无法购买商品星工 KN95口罩防雾霾防尘防花粉PM2.5 硅胶鼻垫带阀耳戴透气 工业粉尘防护口罩 白色25只独立包装'} 128 | # 下单成功 129 | # {'overSea': False, 'orderXml': None, 'cartXml': None, 'noStockSkuIds': '', 'reqInfo': None, 'hasJxj': False, 'addedServiceList': None, 'sign': None, 'pin': 'xxx', 'needCheckCode': False, 'success': True, 'resultCode': 0, 'orderId': 8740xxxxx, 'submitSkuNum': 1, 'deductMoneyFlag': 0, 'goJumpOrderCenter': False, 'payInfo': None, 'scaleSkuInfoListVO': None, 'purchaseSkuInfoListVO': None, 'noSupportHomeServiceSkuList': None, 'msgMobile': None, 'addressVO': None, 'msgUuid': None, 'message': None} 130 | if resp_json.get('success'): 131 | logger.info('订单提交成功! 订单号:%s', resp_json.get('orderId')) 132 | return True 133 | else: 134 | resultMessage, result_code = resp_json.get('message'), resp_json.get('resultCode') 135 | if result_code == 0: 136 | # self._save_invoice() 137 | if '验证码不正确' in resultMessage: 138 | resultMessage = resultMessage + '(验证码错误)' 139 | logger.info('提交订单验证码[错误]') 140 | continue 141 | else: 142 | resultMessage = resultMessage + '(下单商品可能为第三方商品,将切换为普通发票进行尝试)' 143 | elif result_code == 60077: 144 | resultMessage = resultMessage + '(可能是购物车为空 或 未勾选购物车中商品)' 145 | elif result_code == 60123: 146 | resultMessage = resultMessage + '(需要在payment_pwd参数配置支付密码)' 147 | elif result_code == 60070: 148 | resultMessage = resultMessage + '(省份不支持销售)' 149 | skuids.remove(sku_id) 150 | logger.info('[%s]类型口罩不支持销售踢出', sku_id) 151 | logger.info('订单提交失败, 错误码:%s, 返回信息:%s', result_code, resultMessage) 152 | logger.info(resp_json) 153 | return False 154 | except Exception as e: 155 | print(traceback.format_exc()) 156 | continue 157 | 158 | 159 | ''' 160 | 订单页面验证码 161 | ''' 162 | 163 | 164 | def page_detail_captcha(session, isId): 165 | url = 'https://captcha.jd.com/verify/image' 166 | acid = '{}_{}'.format(random.random(), random.random()) 167 | payload = { 168 | 'acid': acid, 169 | 'srcid': 'trackWeb', 170 | 'is': isId, 171 | } 172 | headers = { 173 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/531.36", 174 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3", 175 | "Referer": "https://trade.jd.com/shopping/order/getOrderInfo.action", 176 | "Connection": "keep-alive", 177 | 'Host': 'captcha.jd.com', 178 | } 179 | try: 180 | resp = session.get(url=url, params=payload, headers=headers) 181 | if not response_status(resp): 182 | logger.error('获取订单验证码失败') 183 | return '' 184 | logger.info('解析验证码开始') 185 | image = Image.open(BytesIO(resp.content)) 186 | image.save('captcha.jpg') 187 | result = analysis_captcha(resp.content) 188 | if not result: 189 | logger.error('解析订单验证码失败') 190 | return '' 191 | global submit_captcha_text, submit_captcha_rid 192 | submit_captcha_text = result 193 | submit_captcha_rid = acid 194 | return result 195 | except Exception as e: 196 | logger.error('订单验证码获取异常:%s', e) 197 | return '' 198 | 199 | 200 | def analysis_captcha(session, captchaUrl, pic): 201 | for i in range(1, 10): 202 | try: 203 | url = captchaUrl 204 | resp = session.post(url, pic) 205 | if not response_status(resp): 206 | logger.error('解析验证码失败') 207 | continue 208 | logger.info('解析验证码[%s]', resp.text) 209 | return resp.text 210 | except Exception as e: 211 | print(traceback.format_exc()) 212 | continue 213 | return '' 214 | -------------------------------------------------------------------------------- /jdBuyMask/jdlogger.py: -------------------------------------------------------------------------------- 1 | # -*- coding=utf-8 -*- 2 | import logging 3 | import logging.handlers 4 | ''' 5 | 日志模块 6 | ''' 7 | LOG_FILENAME = 'jd_auto_order.log' 8 | 9 | logger = logging.getLogger() 10 | 11 | 12 | def set_logger(): 13 | logger.setLevel(logging.INFO) 14 | formatter = logging.Formatter('%(asctime)s %(levelname)s: %(message)s') 15 | 16 | console_handler = logging.StreamHandler() 17 | console_handler.setFormatter(formatter) 18 | logger.addHandler(console_handler) 19 | 20 | file_handler = logging.handlers.RotatingFileHandler( 21 | LOG_FILENAME, maxBytes=10485760, backupCount=5, encoding="utf-8") 22 | file_handler.setFormatter(formatter) 23 | logger.addHandler(file_handler) 24 | 25 | 26 | set_logger() 27 | -------------------------------------------------------------------------------- /jdBuyMask/message.py: -------------------------------------------------------------------------------- 1 | # -*- encoding=utf8 -*- 2 | from jdEmail import sendMail 3 | from wechat_ftqq import sendWechat 4 | 5 | 6 | class message(object): 7 | """消息推送类""" 8 | 9 | def __init__(self, messageType, sc_key, mail): 10 | if messageType == '2': 11 | if not sc_key: 12 | raise Exception('sc_key can not be empty') 13 | self.sc_key = sc_key 14 | elif messageType == '1': 15 | if not mail: 16 | raise Exception('mail can not be empty') 17 | self.mail = mail 18 | self.messageType = messageType 19 | 20 | def send(self, desp='', isOrder=False): 21 | desp = str(desp) 22 | if isOrder: 23 | msg = desp + ' 类型口罩,已经下单了。24小时内付款' 24 | else: 25 | msg = desp + ' 类型口罩,下单失败了' 26 | if self.messageType == '1': 27 | sendMail(self.mail, msg) 28 | if self.messageType == '2': 29 | sendWechat(sc_key=self.sc_key, desp=msg) 30 | 31 | def sendAny(self, desp=''): 32 | desp = str(desp) 33 | msg = desp 34 | if self.messageType == '1': 35 | sendMail(self.mail, msg) 36 | if self.messageType == '2': 37 | sendWechat(sc_key=self.sc_key, desp=msg) 38 | -------------------------------------------------------------------------------- /jdBuyMask/requirements.txt: -------------------------------------------------------------------------------- 1 | beautifulsoup4==4.8.2 2 | requests==2.22.0 3 | pillow==7.0.0 4 | -------------------------------------------------------------------------------- /jdBuyMask/util.py: -------------------------------------------------------------------------------- 1 | # -*- coding=utf-8 -*- 2 | import hashlib 3 | import json 4 | import socket 5 | import requests 6 | 7 | _dnscache = {} 8 | 9 | def parse_json(s): 10 | begin = s.find('{') 11 | end = s.rfind('}') + 1 12 | return json.loads(s[begin:end]) 13 | 14 | 15 | def getconfigMd5(): 16 | with open('jdBuyMask/configDemo.ini', 'r', encoding='utf-8') as f: 17 | configText = f.read() 18 | return hashlib.md5(configText.encode('utf-8')).hexdigest() 19 | 20 | def response_status(resp): 21 | if resp.status_code != requests.codes.OK: 22 | print('Status: %u, Url: %s' % (resp.status_code, resp.url)) 23 | return False 24 | return True 25 | 26 | 27 | def _setDNSCache(): 28 | """ 29 | Makes a cached version of socket._getaddrinfo to avoid subsequent DNS requests. 30 | """ 31 | 32 | def _getaddrinfo(*args, **kwargs): 33 | global _dnscache 34 | if args in _dnscache: 35 | # print(str(args) + " in cache") 36 | return _dnscache[args] 37 | 38 | else: 39 | # print(str(args) + " not in cache") 40 | _dnscache[args] = socket._getaddrinfo(*args, **kwargs) 41 | return _dnscache[args] 42 | 43 | if not hasattr(socket, '_getaddrinfo'): 44 | socket._getaddrinfo = socket.getaddrinfo 45 | socket.getaddrinfo = _getaddrinfo 46 | -------------------------------------------------------------------------------- /jdBuyMask/wechat_ftqq.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding=utf8 -*- 3 | import datetime 4 | import json 5 | 6 | import requests 7 | 8 | from jdlogger import logger 9 | 10 | 11 | def sendWechat(sc_key, text='京东商品监控', desp=''): 12 | if not text.strip(): 13 | logger.error('Text of message is empty!') 14 | return 15 | 16 | now_time = str(datetime.datetime.now()) 17 | desp = '[{0}]'.format(now_time) if not desp else '{0} [{1}]'.format(desp, now_time) 18 | 19 | try: 20 | resp = requests.get( 21 | 'https://sc.ftqq.com/{}.send?text={}&desp={}'.format(sc_key, text, desp) 22 | ) 23 | resp_json = json.loads(resp.text) 24 | if resp_json.get('errno') == 0: 25 | logger.info('Message sent successfully [text: %s, desp: %s]', text, desp) 26 | else: 27 | logger.error('Fail to send message, reason: %s', resp.text) 28 | except requests.exceptions.RequestException as req_error: 29 | logger.error('Request error: %s', req_error) 30 | except Exception as e: 31 | logger.error('Fail to send message [text: %s, desp: %s]: %s', text, desp, e) 32 | -------------------------------------------------------------------------------- /jdBuyMask/感谢作者: -------------------------------------------------------------------------------- 1 | jdBuyMask包下的类来自于 cycz 作者的 jdBuyMask 项目:https://github.com/cycz/jdBuyMask 2 | 感谢作者为开源社区做出的贡献 3 | 京东秒杀功能是我在其基础上做出的优化,极大简化了配置流程 4 | 5 | --author:daleychao -------------------------------------------------------------------------------- /log/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/westnestling/N95-watcher/6efefe9497ed95a75c7712323a85daec7eb7f3aa/log/__init__.py -------------------------------------------------------------------------------- /log/logger.py: -------------------------------------------------------------------------------- 1 | import logging, os 2 | import logging.handlers 3 | import datetime,time 4 | 5 | logger = logging.getLogger("basic_logger") 6 | logger.setLevel(logging.DEBUG) 7 | 8 | file_handler = logging.FileHandler(os.path.join(os.path.abspath(os.path.dirname(__file__)), "logs", "log_{}.txt".format(time.strftime("%d_%m_%Y")))) 9 | file_handler.setFormatter(logging.Formatter('[%(asctime)s] - [%(filename)s file line:%(lineno)d] - %(levelname)s: %(message)s')) 10 | file_handler.setLevel(logging.DEBUG) 11 | 12 | console_handler = logging.StreamHandler() 13 | console_handler.setFormatter(logging.Formatter('%(levelname)s: %(message)s')) 14 | console_handler.setLevel(logging.DEBUG) 15 | 16 | logger.addHandler(file_handler) 17 | logger.addHandler(console_handler) 18 | -------------------------------------------------------------------------------- /src/chromedriver: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/westnestling/N95-watcher/6efefe9497ed95a75c7712323a85daec7eb7f3aa/src/chromedriver -------------------------------------------------------------------------------- /src/chromedriver.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/westnestling/N95-watcher/6efefe9497ed95a75c7712323a85daec7eb7f3aa/src/chromedriver.exe -------------------------------------------------------------------------------- /src/jdurl.txt: -------------------------------------------------------------------------------- 1 | "https://u.jd.com/bsxNtB", 2 | -------------------------------------------------------------------------------- /src/mail.py: -------------------------------------------------------------------------------- 1 | import smtplib, traceback 2 | from email.mime.text import MIMEText 3 | from email.header import Header 4 | 5 | from config.mail_config import mail_config 6 | from log.logger import logger as log 7 | 8 | def send_mail(subject, content, receiver): 9 | message = MIMEText(content, 'plain', 'utf-8') 10 | message['From'] = Header(mail_config["user"], 'utf-8') 11 | message['To'] = Header(mail_config["to"], 'utf-8') 12 | message['Subject'] = Header(subject, 'utf-8') 13 | 14 | try: 15 | smtpObj = smtplib.SMTP() 16 | smtpObj.connect(mail_config["host"], 25) # 25 为 SMTP 端口号 17 | smtpObj.login(mail_config["user"], mail_config["passwd"]) 18 | smtpObj.sendmail(mail_config["user"], receiver, message.as_string()) 19 | log.info("mail sent success.") 20 | except smtplib.SMTPException as e: 21 | log.error("mail sent failed") 22 | log.error(str(e)) 23 | log.error(traceback.format_exc()) -------------------------------------------------------------------------------- /src/tmurl.txt: -------------------------------------------------------------------------------- 1 | "https://detail.tmall.com/item.htm?id=40844663705", 2 | "https://detail.tmall.com/item.htm?id=569492882306", 3 | "https://detail.tmall.com/item.htm?id=596143442666", 4 | "https://detail.tmall.com/item.htm?id=40844663705", 5 | "https://detail.tmall.com/item.htm?id=569492882306", 6 | "https://detail.tmall.com/item.htm?id=563451626963", 7 | "https://detail.tmall.com/item.htm?id=528937646086", 8 | "https://detail.tmall.com/item.htm?id=550189462849", 9 | "https://detail.tmall.com/item.htm?id=612169223963", 10 | "https://detail.tmall.com/item.htm?id=563451626963", 11 | "https://detail.tmall.com/item.htm?id=528937646086", 12 | "https://detail.tmall.com/item.htm?id=40844663705", 13 | "https://detail.tmall.com/item.htm?id=611586261649", 14 | "https://detail.tmall.com/item.htm?id=569492882306", 15 | "https://detail.tmall.com/item.htm?id=596143442666", 16 | "https://detail.tmall.com/item.htm?id=582357951467", 17 | "https://detail.tmall.com/item.htm?id=566831497212", 18 | "https://detail.tmall.com/item.htm?id=604035325011", 19 | "https://detail.tmall.com/item.htm?id=537464694779", 20 | -------------------------------------------------------------------------------- /tmall_order.py: -------------------------------------------------------------------------------- 1 | # python3.6.5 2 | # coding:utf-8 3 | # 天猫淘宝自动下单 用于定时抢购 4 | import os 5 | import sys 6 | from selenium import webdriver 7 | import requests 8 | import time 9 | from selenium.webdriver.chrome.options import Options 10 | 11 | # 创建浏览器对象 12 | chrome_options = Options() 13 | # 关闭使用 ChromeDriver 打开浏览器时上部提示语 "Chrome正在受到自动软件的控制" 14 | chrome_options.add_argument("disable-infobars") 15 | # 允许浏览器重定向,Framebusting requires same-origin or a user gesture 16 | chrome_options.add_argument("disable-web-security") 17 | 18 | if sys.platform == "win32": 19 | driver = webdriver.Chrome(os.path.join(os.path.abspath(os.path.dirname(__file__)) + "\\src", "chromedriver.exe"), 20 | chrome_options=chrome_options) 21 | else: 22 | driver = webdriver.Chrome(os.path.join(os.path.dirname(__file__) + "/src", "chromedriver"), 23 | chrome_options=chrome_options) 24 | # 窗口最大化显示 25 | driver.maximize_window() 26 | 27 | 28 | 29 | def login(url, mall): 30 | ''' 31 | 登陆函数 32 | 33 | url:商品的链接 34 | mall:商城类别 35 | ''' 36 | driver.get(url) 37 | driver.implicitly_wait(10) 38 | time.sleep(2) 39 | # 淘宝和天猫的登陆链接文字不同 40 | if mall == '1': 41 | # 找到并点击淘宝的登陆按钮 42 | driver.find_element_by_link_text("亲,请登录").click() 43 | elif mall == '2': 44 | # 找到并点击天猫的登陆按钮 45 | driver.find_element_by_link_text("请登录").click() 46 | print("请在30秒内完成登录") 47 | # 用户扫码登陆 48 | time.sleep(30) 49 | 50 | 51 | def buy(buy_time, mall, time_dif): 52 | ''' 53 | 购买函数 54 | 55 | buy_time:购买时间 56 | mall:商城类别 57 | ''' 58 | print("开始购买") 59 | if mall == '1': 60 | # "立即购买"的css_selector 61 | btn_buy = '#J_juValid > div.tb-btn-buy > a' 62 | # "立即下单"的css_selector 63 | btn_order = '#submitOrder_1 > div.wrapper > a' 64 | elif mall == '3': 65 | btn_buy = '#J_Go' 66 | btn_order = '#submitOrderPC_1 > div > a' 67 | else: 68 | btn_buy = '#J_LinkBuy' 69 | btn_order = '#submitOrderPC_1 > div > a' 70 | 71 | timeArray = time.strptime(buy_time, "%Y-%m-%d %H:%M:%S") 72 | # 转为时间戳 73 | timeStamp = int(time.mktime(timeArray)) 74 | print("开始准备购买") 75 | 76 | while True: 77 | # 现在时间大于预设时间则开售抢购 78 | tmp_time = time.time() 79 | if tmp_time >= (timeStamp - time_dif): 80 | try: 81 | print("开始购买" + str(time.time())) 82 | # 找到“立即购买”,点击 83 | if selector: 84 | print("点击" + str(time.time())) 85 | selector.click() 86 | break 87 | except: 88 | pass 89 | while True: 90 | try: 91 | # 找到“立即下单”,点击, 92 | # print("尝试提交订单") 93 | order_selector = driver.find_elements_by_css_selector(btn_order) 94 | if order_selector: 95 | print("购买" + str(time.time())) 96 | order_selector[-1].click() 97 | # 下单成功,跳转至支付页面 98 | print("购买成功" + str(time.time() - tmp_time)) 99 | break 100 | driver.refresh() 101 | except: 102 | driver.refresh() 103 | time.sleep(0.01) 104 | 105 | 106 | def get_server_time(): 107 | time_start = time.time() 108 | r1 = requests.get(url='http://api.m.taobao.com/rest/api3.do?api=mtop.common.getTimestamp', 109 | headers={ 110 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 UBrowser/6.2.4098.3 Safari/537.36'}) 111 | x = eval(r1.text) 112 | tmp = time.time() - time_start 113 | timeNum = int(x['data']['t']) 114 | 115 | timeStamp = float(timeNum / 1000) 116 | print(tmp) 117 | # timeArray = time.localtime(timeStamp) 118 | # otherStyleTime = time.strftime("%Y-%m-%d %H:%M:%S", timeArray) 119 | return timeStamp, tmp 120 | 121 | 122 | # 使用方法 123 | # 1 设置url 124 | # 2 设置天猫还是淘宝 125 | # 3 设置开抢时间 126 | # 4 运行程序 127 | # 5 扫码登录 128 | # 6 选中要购买商品以及相应种类等(必须选中!!!) 129 | # 7 自动下单 130 | # 131 | 132 | if __name__ == "__main__": 133 | # 输入要购买物品 url 134 | # 如果是天猫超市的抢购 请先加入购物车 此处为购物车链接 135 | url = "https://cart.taobao.com/cart.htm" 136 | # 请选择商城(淘宝 1 天猫 2 3 通过购物车 输入数字: 137 | mall = '3' 138 | # 输入开售时间 139 | bt = "2020-03-01 15:00:00" 140 | server_time, tmp = get_server_time() 141 | time_dif = time.time() - server_time + tmp + tmp 142 | login(url, mall) 143 | buy(bt, mall, 2 * time_dif + 0.5) 144 | # driver.quit() 145 | -------------------------------------------------------------------------------- /util/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/westnestling/N95-watcher/6efefe9497ed95a75c7712323a85daec7eb7f3aa/util/__init__.py -------------------------------------------------------------------------------- /util/jd.txt: -------------------------------------------------------------------------------- 1 | 65437208345 2 | 7498169 3 | 7498165 4 | 7263128 5 | 7498167 6 | 17449572304 7 | 37934196731 8 | 100001086804 9 | 56657322838 10 | 56657322841 11 | 100005294853 12 | 1938795 13 | 15595191653 14 | 15595191654 15 | 45923412989 16 | -------------------------------------------------------------------------------- /util/tm.txt: -------------------------------------------------------------------------------- 1 | 537539556119 2 | 559868897280 3 | 566694899095 4 | 612169223963 5 | 566700203403 6 | 582154342729 7 | 581704000297 8 | 40844663705 9 | 600932058333 10 | 560454169970 11 | 611218796479 12 | 16914648132 13 | 41930581793 14 | 43557127826 15 | 43836604206 16 | 526256013584 17 | 537783105938 18 | 538688343613 19 | 539329058910 20 | 539798535423 21 | 549951148887 22 | 550181092780 23 | 550189462849 24 | 550225741968 25 | 550281158824 26 | 552790837013 27 | 553350127780 28 | 555176437380 29 | 557041813996 30 | 558596461970 31 | 559101912744 32 | 559303233985 33 | 559360262475 34 | 560359707378 35 | 561316427429 36 | 561897804878 37 | 562699529401 38 | 562800423283 39 | 563451626963 40 | 565876396202 41 | 566000169760 42 | 566805629407 43 | 568302562015 44 | 577253947669 45 | 578323822970 46 | 578409369729 47 | 582357951467 48 | 596733380975 49 | 596869895887 50 | 596970557748 51 | 597463034616 52 | 601608085564 53 | 601612058331 54 | 603383811589 55 | 604035325011 56 | 604099889111 57 | 605661675553 58 | 605668032834 59 | 605988521690 60 | 606134328835 61 | 608557201009 62 | 611223265045 63 | 611587537638 64 | 611610597456 65 | 611712359485 66 | 611891455980 67 | 611945723187 68 | 611951486219 69 | 612142243066 70 | 599648775985 71 | 601992377478 72 | 611586261649 73 | 611272808632 74 | 611524145911 75 | 595114805524 76 | 537464694779 77 | 596809211292 78 | 537539396284 79 | 612082019478 80 | 579580489169 81 | 602693568062 82 | 579147322829 83 | 584460207534 84 | 524798659351 85 | 53778310593 86 | 100006066047 87 | 559828657513 88 | 611949999645 -------------------------------------------------------------------------------- /util/util.py: -------------------------------------------------------------------------------- 1 | def readjd(): 2 | file = open("jd.txt") 3 | for line in file: 4 | print("\"https://item.jd.com/" + line.strip() + ".html\",") 5 | file.close() 6 | 7 | 8 | def readtm(): 9 | file = open("tm.txt") 10 | for line in file: 11 | print("\"https://detail.tmall.com/item.htm?id=" + line.strip() + "\",") 12 | file.close() 13 | 14 | 15 | # 将商品id批量 处理成 url 16 | if __name__ == '__main__': 17 | readjd() 18 | -------------------------------------------------------------------------------- /watcher.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os, time, json 3 | 4 | lib_path = os.path.join(os.path.dirname(__file__))[:-3] 5 | sys.path.append(lib_path) 6 | 7 | from selenium import webdriver 8 | from log.logger import logger as log 9 | from PIL import Image 10 | 11 | browser = None 12 | 13 | 14 | def check_shop(url, keywords): 15 | global browser 16 | browser.get(url) 17 | time.sleep(5) 18 | find_flag = False 19 | for keyword in keywords: 20 | if keyword in browser.page_source: 21 | find_flag = keyword 22 | break 23 | if not find_flag and '出错啦' not in browser.title: 24 | log.warning("FIND!!!") 25 | log.warning(url) 26 | log.warning(keywords) 27 | # "发现口罩有货!!", 28 | fo = open("../data.txt", "r") 29 | lines = fo.readlines() 30 | fo.close() 31 | fo = open("../data.txt", "w") 32 | str_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) 33 | lines.append(str_time+" "+browser.title+" url:"+url+"\n") 34 | fo.writelines(lines) 35 | fo.close() 36 | print("发现口罩有货!!"+url) 37 | browser.save_screenshot("imgs/" + str_time + ".png") 38 | time.sleep(5) 39 | 40 | 41 | 42 | 43 | def check_all_shops(): 44 | with open(os.path.join(os.path.dirname(__file__),"config","shop.json"), "r", encoding='UTF-8') as f: 45 | infos = json.loads(f.read()) 46 | for info in infos: 47 | for shop in info["shop"]: 48 | log.info("checking {} / {}".format(shop, info.get("keyword"))) 49 | keywords = info.get("key_word").split(",") 50 | check_shop(shop, keywords) 51 | 52 | 53 | 54 | # 加载 config/shop.json 中的商品,并检查有货状态,如果有货保存在 data.txt 中 55 | if __name__ == "__main__": 56 | browser = webdriver.Chrome(os.path.join(os.path.dirname(__file__),"src", "chromedriver")) 57 | while True: 58 | check_all_shops() 59 | # browser.quit() -------------------------------------------------------------------------------- /wechat_push.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | import tinify 5 | import json 6 | from urllib.request import Request, urlopen 7 | import time 8 | import requests 9 | import sys 10 | import random 11 | from PIL import Image 12 | from selenium import webdriver 13 | 14 | # 导入模块 15 | basedir = os.path.abspath(os.path.dirname(__file__)) 16 | from wxpy import * 17 | import itchat 18 | 19 | 20 | def login(): 21 | # bot = Bot(cache_path=True) 22 | bot = Bot() 23 | groups = bot.groups() 24 | group1 = bot.groups().search(keywords='口罩消毒液放货监控群') 25 | group = bot.groups().search(keywords='口罩消毒液放货监控群')[0] 26 | print(group) 27 | while True: 28 | group.send('有货啦') 29 | return bot 30 | 31 | 32 | def get_bot(): 33 | bot = Bot('bot.pkl', qr_path=os.path.join( 34 | basedir, 'QR.png'), console_qr=None) 35 | # bot.enable_puid() 36 | # bot.messages.max_history = 0 37 | return bot 38 | 39 | 40 | # bot = get_bot() 41 | 42 | def save_jd_url(url): 43 | try: 44 | fo = open("src/jdurl.txt", "r") 45 | lines = fo.readlines() 46 | fo.close() 47 | fo = open("src/jdurl.txt", "w") 48 | lines.append("\"" + url + "\",\n") 49 | fo.writelines(lines) 50 | fo.close() 51 | except Exception: 52 | print("读取data失败") 53 | 54 | 55 | def save_tm_url(url): 56 | try: 57 | fo = open("src/tmurl.txt", "r") 58 | lines = fo.readlines() 59 | fo.close() 60 | fo = open("src/tmurl.txt", "w") 61 | lines.append("\"" + url + "\",\n") 62 | fo.writelines(lines) 63 | fo.close() 64 | except Exception: 65 | print("读取data失败") 66 | 67 | 68 | 69 | 70 | 71 | 72 | def get_outfile(infile, outfile): 73 | if outfile: 74 | return outfile 75 | dir, suffix = os.path.splitext(infile) 76 | outfile = '{}-out{}'.format(dir, suffix) 77 | return outfile 78 | 79 | 80 | 81 | 82 | 83 | def resize_image(infile, outfile='', x_s=1124): 84 | """修改图片尺寸 85 | :param infile: 图片源文件 86 | :param outfile: 重设尺寸文件保存地址 87 | :param x_s: 设置的宽度 88 | :return: 89 | """ 90 | im = Image.open(infile) 91 | x, y = im.size 92 | y_s = int(y * x_s / x) 93 | out = im.resize((x_s, y_s), Image.ANTIALIAS) 94 | outfile = get_outfile(infile, outfile) 95 | out.save(outfile) 96 | 97 | 98 | 99 | # 推送data.txt中的数据 并且通过接口查询是否有货 100 | if __name__ == '__main__': 101 | print(os.path.join(os.path.dirname(__file__))) 102 | browser = webdriver.Chrome(os.path.join(os.path.dirname(__file__) + "/src", "chromedriver")) 103 | bot = Bot() 104 | # 修改群名 105 | group = bot.groups().search(keywords='口罩消毒液放货监控群')[0] 106 | group2 = bot.groups().search(keywords='口罩消毒液监控群')[0] 107 | url = "http://invoice.pinhai.com.cn/index/maskloglist" 108 | # 包装头部 109 | firefox_headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:23.0) Gecko/20100101 Firefox/23.0'} 110 | # 构建请求 111 | pre_time = "" 112 | i = 0 113 | data_map = {} 114 | while (1): 115 | if (i + 1) % 5 == 0: 116 | print("已为您成功您监控" + str(i + 1) + "次") 117 | try: 118 | # 读取data.txt中的数据并推送 119 | file = open("data.txt") 120 | try: 121 | for line in file: 122 | if line[21:50] not in data_map: 123 | group2.send("发现有货!!!\n" + line) 124 | # 压缩图片到500k以下 125 | file_name = "src/imgs/"+line[:19] 126 | try: 127 | resize_image(file_name + ".png", file_name + "_copy.png") 128 | group.send("发现有货!!!\n" + line) 129 | group2.send_image(file_name+"_copy.png", media_id=None) 130 | group.send_image(file_name+"_copy.png", media_id=None) 131 | except Exception: 132 | data_map[line[21:50]] = line[21:50] 133 | continue 134 | data_map[line[21:50]] = line[21:50] 135 | print("读取data.txt结束") 136 | except Exception: 137 | print("读取data失败") 138 | finally: 139 | file.close() 140 | # 注释开始 141 | request = Request(url, headers=firefox_headers) 142 | html = urlopen(request) 143 | data = html.read() 144 | strs = str(data, 'UTF-8') 145 | list = json.loads(strs)['data']['maskloglists'] 146 | data_json = list[-1] 147 | title = data_json["title"] 148 | 149 | if pre_time != data_json["ctime"] and data_json["title"] not in data_map: 150 | print(data_json) 151 | data_map[data_json["title"]] = data_json['url'] 152 | data = "商品名:【" + data_json["title"] + "】\n" + data_json["card_text"] + "\n购买链接:" + data_json[ 153 | 'url'] 154 | print(data) 155 | # 再次检查 156 | browser.get(data_json['url']) 157 | time.sleep(3) 158 | find_flag = False 159 | for keyword in ['无货', '下柜', '已下架', '不支持销售', '售完', '卖光','京东(JD.COM)-正品低价']: 160 | if keyword in browser.page_source: 161 | find_flag = keyword 162 | break 163 | if find_flag is not False: 164 | continue 165 | # 发送微信信息 166 | img_name = "imgs/" + time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) 167 | browser.save_screenshot(img_name+ "B.png") 168 | group2.send(data) 169 | resize_image(img_name + "B.png", img_name + "B_copy.png") 170 | group2.send_image(img_name + "B_copy.png", media_id=None) 171 | # time.sleep(1) 172 | group.send(data) 173 | url_save = data_json['url'] 174 | if "jd" in url_save: 175 | save_jd_url(url_save) 176 | elif "tmall" in url_save: 177 | save_tm_url(url_save) 178 | print("推送成功") 179 | except Exception: 180 | print("第" + str(i + 1) + "次推送挂掉了") 181 | i = i - 1 182 | sleep_time = 10 + int(random.random() * 10) 183 | time.sleep(sleep_time) 184 | i = i + 1 185 | --------------------------------------------------------------------------------