Ver código fonte

添加二维码登录方式

HuangYiwei 5 anos atrás
pai
commit
fbccc95021
5 arquivos alterados com 240 adições e 60 exclusões
  1. 3 0
      .gitignore
  2. 10 0
      exception.py
  3. 184 34
      jd_spider_requests.py
  4. 1 0
      main.py
  5. 42 26
      util.py

+ 3 - 0
.gitignore

@@ -115,6 +115,9 @@ venv.bak/
 logs/
 .test
 test/
+cookies/
+*.cookies
+QRcode.png
 
 # jupyter
 .ipynb_checkpoints

+ 10 - 0
exception.py

@@ -0,0 +1,10 @@
+#!/usr/bin/env python
+# -*- encoding=utf8 -*-
+from jd_logger import logger
+
+
+class SKException(Exception):
+
+    def __init__(self, message):
+        super().__init__(message)
+        logger.error(message)

+ 184 - 34
jd_spider_requests.py

@@ -1,12 +1,19 @@
 import random
 import sys
 import time
+import requests
+import functools
+import json
+import os
+import pickle
+
+from lxml import etree
 from jd_logger import logger
 from timer import Timer
-import requests
-from util import parse_json, get_session, get_sku_title,send_wechat
+from util import parse_json, send_wechat, get_session, response_status, save_image, open_image
 from config import global_config
 from concurrent.futures import ProcessPoolExecutor
+from exception import SKException
 
 
 class JdSeckill(object):
@@ -20,6 +27,11 @@ class JdSeckill(object):
         self.seckill_order_data = dict()
         self.timers = Timer()
         self.default_user_agent = global_config.getRaw('config', 'DEFAULT_USER_AGENT')
+        self.headers = {'User-Agent': self.default_user_agent}
+        self.is_login = False
+        self.nick_name = None
+
+        self._load_cookies()
 
     def reserve(self):
         """
@@ -33,9 +45,6 @@ class JdSeckill(object):
         """
         self.__seckill()
 
-    def system_exit(self):
-        sys.exit(1)
-
     def wait_some_time(self):
         time.sleep(random.randint(100, 300) / 1000)
 
@@ -52,7 +61,7 @@ class JdSeckill(object):
         """
         预约
         """
-        self.login()
+        self._validate_cookies()
         while True:
             try:
                 self.make_reserve()
@@ -64,45 +73,178 @@ class JdSeckill(object):
         """
         抢购
         """
-        self.login()
+        self._validate_cookies()
         while True:
             try:
                 self.request_seckill_url()
                 while True:
                     self.request_seckill_checkout_page()
-                    if self.submit_seckill_order():
-                        self.system_exit()
+                    self.submit_seckill_order()
             except Exception as e:
                 logger.info('抢购发生异常,稍后继续执行!', e)
             self.wait_some_time()
 
-    def login(self):
-        for flag in range(1, 3):
-            try:
-                targetURL = 'https://order.jd.com/center/list.action'
-                payload = {
-                    'rid': str(int(time.time() * 1000)),
-                }
-                resp = self.session.get(
-                    url=targetURL, params=payload, allow_redirects=False)
-                if resp.status_code == requests.codes.OK:
-                    logger.info('校验是否登录[成功]')
-                    logger.info('用户:{}'.format(self.get_username()))
-                    return True
-                else:
-                    logger.info('校验是否登录[失败]')
-                    logger.info('请重新输入cookie')
-                    time.sleep(1)
-                    continue
-            except Exception as e:
-                logger.info('第【%s】次失败请重新获取cookie', flag)
-                time.sleep(1)
-                continue
-        self.system_exit()
+    def _load_cookies(self):
+        cookies_file = ''
+        for name in os.listdir('./cookies'):
+            if name.endswith('.cookies'):
+                cookies_file = './cookies/{0}'.format(name)
+                break
+        if cookies_file == '':
+            return
+        with open(cookies_file, 'rb') as f:
+            local_cookies = pickle.load(f)
+        self.session.cookies.update(local_cookies)
+        self.is_login = self._validate_cookies()
+
+    def _save_cookies(self):
+        cookies_file = './cookies/{0}.cookies'.format(self.nick_name)
+        directory = os.path.dirname(cookies_file)
+        if not os.path.exists(directory):
+            os.makedirs(directory)
+        with open(cookies_file, 'wb') as f:
+            pickle.dump(self.session.cookies, f)
+
+    def _validate_cookies(self):
+        """验证cookies是否有效(是否登陆)
+                通过访问用户订单列表页进行判断:若未登录,将会重定向到登陆页面。
+                :return: cookies是否有效 True/False
+                """
+        url = 'https://order.jd.com/center/list.action'
+        payload = {
+            'rid': str(int(time.time() * 1000)),
+        }
+        try:
+            resp = self.session.get(url=url, params=payload, allow_redirects=False)
+            if resp.status_code == requests.codes.OK:
+                return True
+        except Exception as e:
+            logger.error(e)
+
+        self.session = get_session()
+        return False
+
+    def _get_login_page(self):
+        url = "https://passport.jd.com/new/login.aspx"
+        page = self.session.get(url, headers=self.headers)
+        return page
+
+    def _get_QRcode(self):
+        url = 'https://qr.m.jd.com/show'
+        payload = {
+            'appid': 133,
+            'size': 147,
+            't': str(int(time.time() * 1000)),
+        }
+        headers = {
+            'User-Agent': self.default_user_agent,
+            'Referer': 'https://passport.jd.com/new/login.aspx',
+        }
+        resp = self.session.get(url=url, headers=headers, params=payload)
+
+        if not response_status(resp):
+            logger.info('获取二维码失败')
+            return False
+
+        QRCode_file = 'QRcode.png'
+        save_image(resp, QRCode_file)
+        logger.info('二维码获取成功,请打开京东APP扫描')
+        open_image(QRCode_file)
+        return True
+
+    def check_login(func):
+        """用户登陆态校验装饰器。若用户未登陆,则调用扫码登陆"""
+
+        @functools.wraps(func)
+        def new_func(self, *args, **kwargs):
+            if not self.is_login:
+                logger.info("{0} 需登陆后调用,开始扫码登陆".format(func.__name__))
+                self.login_by_QRcode()
+            return func(self, *args, **kwargs)
+
+        return new_func
+
+    def _get_QRcode_ticket(self):
+        url = 'https://qr.m.jd.com/check'
+        payload = {
+            'appid': '133',
+            'callback': 'jQuery{}'.format(random.randint(1000000, 9999999)),
+            'token': self.session.cookies.get('wlfstk_smdl'),
+            '_': str(int(time.time() * 1000)),
+        }
+        headers = {
+            'User-Agent': self.default_user_agent,
+            'Referer': 'https://passport.jd.com/new/login.aspx',
+        }
+        resp = self.session.get(url=url, headers=headers, params=payload)
+
+        if not response_status(resp):
+            logger.error('获取二维码扫描结果异常')
+            return False
+
+        resp_json = parse_json(resp.text)
+        if resp_json['code'] != 200:
+            logger.info('Code: %s, Message: %s', resp_json['code'], resp_json['msg'])
+            return None
+        else:
+            logger.info('已完成手机客户端确认')
+            return resp_json['ticket']
+
+    def _validate_QRcode_ticket(self, ticket):
+        url = 'https://passport.jd.com/uc/qrCodeTicketValidation'
+        headers = {
+            'User-Agent': self.default_user_agent,
+            'Referer': 'https://passport.jd.com/uc/login?ltype=logout',
+        }
+        resp = self.session.get(url=url, headers=headers, params={'t': ticket})
+
+        if not response_status(resp):
+            return False
+
+        resp_json = json.loads(resp.text)
+        if resp_json['returnCode'] == 0:
+            return True
+        else:
+            logger.info(resp_json)
+            return False
+
+    def login_by_QRcode(self):
+        """二维码登陆
+        :return:
+        """
+        if self.is_login:
+            logger.info('登录成功')
+            return
+
+        self._get_login_page()
+
+        # download QR code
+        if not self._get_QRcode():
+            raise SKException('二维码下载失败')
+
+        # get QR code ticket
+        ticket = None
+        retry_times = 85
+        for _ in range(retry_times):
+            ticket = self._get_QRcode_ticket()
+            if ticket:
+                break
+            time.sleep(2)
+        else:
+            raise SKException('二维码过期,请重新获取扫描')
+
+        # validate QR code ticket
+        if not self._validate_QRcode_ticket(ticket):
+            raise SKException('二维码信息校验失败')
+
+        logger.info('二维码登录成功')
+        self.is_login = True
+        self.nick_name = self.get_username()
+        self._save_cookies()
 
     def make_reserve(self):
         """商品预约"""
-        logger.info('商品名称:{}'.format(get_sku_title()))
+        logger.info('商品名称:{}'.format(self.get_sku_title()))
         url = 'https://yushou.jd.com/youshouinfo.action?'
         payload = {
             'callback': 'fetchJSON',
@@ -154,6 +296,14 @@ class JdSeckill(object):
         # 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}})
         return parse_json(resp.text).get('nickName')
 
+    def get_sku_title(self):
+        """获取商品名称"""
+        url = 'https://item.jd.com/{}.html'.format(global_config.getRaw('config', 'sku_id'))
+        resp = self.session.get(url).content
+        x_data = etree.HTML(resp)
+        sku_title = x_data.xpath('/html/head/title/text()')
+        return sku_title[0]
+
     def get_seckill_url(self):
         """获取商品的抢购链接
         点击"抢购"按钮后,会有两次302跳转,最后到达订单结算页面
@@ -191,7 +341,7 @@ class JdSeckill(object):
     def request_seckill_url(self):
         """访问商品的抢购链接(用于设置cookie等"""
         logger.info('用户:{}'.format(self.get_username()))
-        logger.info('商品名称:{}'.format(get_sku_title()))
+        logger.info('商品名称:{}'.format(self.get_sku_title()))
         self.timers.start()
         self.seckill_url[self.sku_id] = self.get_seckill_url()
         logger.info('访问商品的抢购连接...')

+ 1 - 0
main.py

@@ -21,6 +21,7 @@ if __name__ == '__main__':
     print(a)
 
     jd_seckill = JdSeckill()
+    jd_seckill.login_by_QRcode()
     choice_function = input('请选择:')
     if choice_function == '1':
         jd_seckill.reserve()

+ 42 - 26
util.py

@@ -1,8 +1,9 @@
 import json
 import random
 import requests
+import os
+
 from config import global_config
-from lxml import etree
 
 USER_AGENTS = [
     "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36",
@@ -68,40 +69,31 @@ def get_random_useragent():
     """
     return random.choice(USER_AGENTS)
 
-def get_cookies():
-    """解析cookies内容并添加到cookiesJar"""
-    manual_cookies = {}
-    for item in global_config.getRaw('config','cookies_String').split(';'):
-        name, value = item.strip().split('=', 1)
-        # 用=号分割,分割1次
-        manual_cookies[name] = value
-        # 为字典cookies添加内容
-    cookiesJar = requests.utils.cookiejar_from_dict(manual_cookies, cookiejar=None, overwrite=True)
-    return cookiesJar
-
+# def get_cookies():
+#     """解析cookies内容并添加到cookiesJar"""
+#     manual_cookies = {}
+#     for item in global_config.getRaw('config','cookies_String').split(';'):
+#         name, value = item.strip().split('=', 1)
+#         # 用=号分割,分割1次
+#         manual_cookies[name] = value
+#         # 为字典cookies添加内容
+#     cookiesJar = requests.utils.cookiejar_from_dict(manual_cookies, cookiejar=None, overwrite=True)
+#     return cookiesJar
+#
 def get_session():
     # 初始化session
     session = requests.session()
     session.headers = {"User-Agent": global_config.getRaw('config', 'DEFAULT_USER_AGENT'),
                        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
                        "Connection": "keep-alive"}
-    checksession = requests.session()
-    checksession.headers = {"User-Agent": global_config.getRaw('config', 'DEFAULT_USER_AGENT'),
-                            "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
-                            "Connection": "keep-alive"}
+    # checksession = requests.session()
+    # checksession.headers = {"User-Agent": global_config.getRaw('config', 'DEFAULT_USER_AGENT'),
+    #                         "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
+    #                         "Connection": "keep-alive"}
     # 获取cookies保存到session
-    session.cookies = get_cookies()
+    # session.cookies = get_cookies()
     return session
 
-def get_sku_title():
-    """获取商品名称"""
-    url = 'https://item.jd.com/{}.html'.format(global_config.getRaw('config','sku_id'))
-    session = get_session()
-    resp = session.get(url).content
-    x_data = etree.HTML(resp)
-    sku_title = x_data.xpath('/html/head/title/text()')
-    return sku_title[0]
-
 def send_wechat(message):
     """推送信息到微信"""
     url = 'http://sc.ftqq.com/{}.send'.format(global_config.getRaw('messenger', 'sckey'))
@@ -113,3 +105,27 @@ def send_wechat(message):
         'User-Agent':global_config.getRaw('config', 'DEFAULT_USER_AGENT')
     }
     requests.get(url, params=payload, headers=headers)
+
+def response_status(resp):
+    if resp.status_code != requests.codes.OK:
+        print('Status: %u, Url: %s' % (resp.status_code, resp.url))
+        return False
+    return True
+
+def open_image(image_file):
+    if os.name == "nt":
+        os.system('start ' + image_file)  # for Windows
+    else:
+        if os.uname()[0] == "Linux":
+            if "deepin" in os.uname()[2]:
+                os.system("deepin-image-viewer " + image_file)  # for deepin
+            else:
+                os.system("eog " + image_file)  # for Linux
+        else:
+            os.system("open " + image_file)  # for Mac
+
+
+def save_image(resp, image_file):
+    with open(image_file, 'wb') as f:
+        for chunk in resp.iter_content(chunk_size=1024):
+            f.write(chunk)