1. 程式人生 > 程式設計 >使用Python開發個京東上搶口罩的小例項(僅作技術研究學習使用)

使用Python開發個京東上搶口罩的小例項(僅作技術研究學習使用)

全國抗”疫”這麼久終於見到曙光,在家待了將近一個月,現在終於可以去上班了,可是卻發現出門必備的口罩卻一直買不到。最近看到京東上每天都會有口罩的秒殺活動,試了幾次卻怎麼也搶不到,到了搶購的時間,瀏覽器的頁面根本就重新整理不出來,等刷出來秒殺也結束了。現在每天只放出一萬個,卻有幾百萬人在搶,很想知道別人是怎麼搶到的,於是就在網上找了大神公開出來的搶購程式碼。看了下程式碼並不複雜,現在我們就報著學習的態度一起看看。

使用模組

requests:類似 urllib,主要用於向網站傳送 HTTP 請求。

beautifulsoup4:HTML 解析器,用於將 HTML 文件轉換成一個複雜的樹形結構。

pillow:Python 影象處理標準庫,用於識別驗證碼。

配置檔案

一般專案中我們都需要把一些可配置的內容放到配置檔案中,現在我們來看下這裡主要配置項:

# 郵寄地所屬地區ID

area = 123456

# 這是配置的商品的ID

skuid = 6828101

# 打碼伺服器的地址

captchaUrl = http://xxx/pic

# 通知郵箱

mail = [email protected]

# cookie的設定

cookies_String = shshshfpa21jsda8923892949204923123

OK,有了配置檔案,那我們就得有一段讀取配置檔案的程式碼,這段程式碼實現將配置內容載入到記憶體中。

import os
import configparser
# 載入配置檔案
class Config(object):
  def __init__(self,config_file='configDemo.ini'):
    self._path = os.path.join(os.getcwd(),config_file)
    if not os.path.exists(self._path):
      raise FileNotFoundError("No such file: config.ini")
    self._config = configparser.ConfigParser()
    self._config.read(self._path,encoding='utf-8-sig')
    self._configRaw = configparser.RawConfigParser()
    self._configRaw.read(self._path,encoding='utf-8-sig')
  def get(self,section,name):
    return self._config.get(section,name)
  def getRaw(self,name):
    return self._configRaw.get(section,name)

主程式模組

我看 GitHub 上也有實現了執行程式後通過京東 App 掃碼登陸,然後再通過登陸 Cookie 訪問網站的,不過這裡並沒有使用這種方式,畢竟我們開啟瀏覽器開發者工具也能很容易獲取到登陸的 Cookie,這裡就是將 Cookie 直接放到配置檔案裡的方式。

# 主程式入口
# 檢查是否存在要搶購的埠,然後進入迴圈掃描
if len(skuids) != 1:
  logger.info('請準備一件商品')
skuId = skuids[0]
flag = 1
# 迴圈掃描該商品是否有貨,有庫存即會自動下單,無庫存則休眠後繼續掃描
while (1):
  try:
    # 初始化校驗
    if flag == 1:
      logger.info('當前是V3版本')
      validate_cookies()  # 校驗登陸狀態
      getUsername()    # 獲取登陸使用者資訊
      select_all_cart_item()  # 全選購物車
      remove_item()      # 刪除購物車
      add_item_to_cart(skuId)  # 增加搶購的商品
    # 檢測配置檔案修改
    if int(time.time()) - configTime >= 60:
      check_Config()
    logger.info('第' + str(flag) + '次 ')
    # 計數器
    flag += 1
    # 檢查庫存模組
    inStockSkuid = check_stock(checksession,skuids,area)
    # 自動下單模組
    V3AutoBuy(inStockSkuid)
    # 休眠模組
    timesleep = random.randint(1,3) / 10
    time.sleep(timesleep)
    # 校驗是否還在登入模組
    if flag % 100 == 0:
      V3check(skuId)
  except Exception as e:
    print(traceback.format_exc())
    time.sleep(10)

以上就是該專案主程式,我已經將程式碼在原來基礎上增加了些註釋,可以讓我們更容易明白程式碼的含義。下面我們就選擇幾個比較關鍵的程式碼分析一下。

# 校驗登陸狀態
def validate_cookies():
  for flag in range(1,3):
    try:
      targetURL = 'https://order.jd.com/center/list.action'
      payload = {
        'rid': str(int(time.time() * 1000)),}
      resp = session.get(url=targetURL,params=payload,allow_redirects=False)
      if resp.status_code == requests.codes.OK:
        logger.info('登入成功')
        return True
      else:
        logger.info('第【%s】次請重新獲取cookie',flag)
        time.sleep(5)
        continue
    except Exception as e:
      logger.info('第【%s】次請重新獲取cookie',flag)
      time.sleep(5)
      continue
  message.sendAny('指令碼登入cookie失效了,請重新登入')
  sys.exit(1)

以上程式碼是每次呼叫時,迴圈兩次獲取通過 session 獲取當前登陸狀態,如果兩次後依然失敗則退出程式。

新增商品到購物車

接下來我們再看下如果新增商品到購物車的,程式碼如下:

def add_item_to_cart(sku_id):
  # 請求新增商品url
  url = 'https://cart.jd.com/gate.action'
  payload = {
    'pid': sku_id,'pcount': 1,'ptype': 1,}
  # 返回結果
  resp = session.get(url=url,params=payload)
  # 套裝商品加入購物車後直接跳轉到購物車頁面
  if 'https://cart.jd.com/cart.action' in resp.url:
    result = True
  else:
   # 普通商品成功加入購物車後會跳轉到提示 "商品已成功加入購物車!" 頁面
    soup = BeautifulSoup(resp.text,"html.parser")
    result = bool(soup.select('h3.ftx-02')) # [<h3 class="ftx-02">商品已成功加入購物車!</h3>]
  if result:
    logger.info('%s 已成功加入購物車',sku_id)
  else:
    logger.error('%s 新增到購物車失敗',sku_id)

在這裡,只是簡單幾行程式碼就能將埠新增到購物車了,而且這裡還區分了不同型別商品新增到購物車返回的頁面結果是不同的,所以要進行區別處理。

購買商品

將商品新增到購物車了,接下來我們就得提交結算頁了,也就是將商品提交到付款頁面,這段程式碼有點多,我簡化了下並加了些註釋:

def submit_order(session,risk_control,sku_id,submit_Time,encryptClientInfo,is_Submit_captcha,payment_pwd,submit_captcha_text,submit_captcha_rid):
  # 提交埠的url
  url = 'https://trade.jd.com/shopping/order/submitOrder.action'
  # 提交引數
  data = {
    'overseaPurchaseCookies': '','vendorRemarks': '[]','submitOrderParam.sopNotPutInvoice': 'false','submitOrderParam.trackID': 'TestTrackId','submitOrderParam.ignorePriceChange': '0','submitOrderParam.btSupport': '0','riskControl': risk_control,'submitOrderParam.isBestCoupon': 1,'submitOrderParam.jxj': 1,'submitOrderParam.trackId': '9643cbd55bbbe103eef18a213e069eb0',# Todo: need to get trackId
    'submitOrderParam.needCheck': 1,}
  # 如果用到京豆會需要輸入支付密碼
  def encrypt_payment_pwd(payment_pwd):
    return ''.join(['u3' + x for x in payment_pwd])
  # 校驗支付密碼
  if len(payment_pwd) > 0:
    data['submitOrderParam.payPassword'] = encrypt_payment_pwd(payment_pwd)
  # 請求報文頭
  headers = {
    "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","Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3","Referer": "http://trade.jd.com/shopping/order/getOrderInfo.action","Connection": "keep-alive",'Host': 'trade.jd.com',}
  # 訂單提交會嘗試兩次
  for count in range(1,3):
    logger.info('第[%s/%s]次嘗試提交訂單',count,3)
    try:
      # 可能會存在的校驗碼
      if is_Submit_captcha:
        captcha_result = page_detail_captcha(session,encryptClientInfo)
        # 驗證碼服務錯誤
        if not captcha_result:
          logger.error('驗證碼服務異常')
          continue
        data['submitOrderParam.checkcodeTxt'] = submit_captcha_text
        data['submitOrderParam.checkCodeRid'] = submit_captcha_rid
      # 提交訂單
      resp = session.post(url=url,data=data,headers=headers)
      resp_json = json.loads(resp.text)
      logger.info('本次提交訂單耗時[%s]毫秒',str(int(time.time() * 1000) - submit_Time))
      # 判斷是否提交成功
      if resp_json.get('success'):
        logger.info('訂單提交成功! 訂單號:%s',resp_json.get('orderId'))
        return True
      else:
        # 提交失敗返回的多種原因
        resultMessage,result_code = resp_json.get('message'),resp_json.get('resultCode')
        if result_code == 0:
          # self._save_invoice()
          if '驗證碼不正確' in resultMessage:
            resultMessage = resultMessage + '(驗證碼錯誤)'
            logger.info('提交訂單驗證碼[錯誤]')
            continue
          else:
            resultMessage = resultMessage + '(下單商品可能為第三方商品,將切換為普通發票進行嘗試)'
        elif result_code == 60077:
          resultMessage = resultMessage + '(可能是購物車為空 或 未勾選購物車中商品)'
        elif result_code == 60123:
          resultMessage = resultMessage + '(需要在payment_pwd引數配置支付密碼)'
        elif result_code == 60070:
          resultMessage = resultMessage + '(省份不支援銷售)'
          skuids.remove(sku_id)
          logger.info('[%s]型別口罩不支援銷售',sku_id)
        logger.info('訂單提交失敗,錯誤碼:%s,返回資訊:%s',result_code,resultMessage)
        logger.info(resp_json)
        return False
    except Exception as e:
      print(traceback.format_exc())
      continue

以上程式碼實現了商品自動提交到結算頁面,這段明顯比新增購物車要複雜,果然跟錢有關的都不簡單。好了,到了結算頁面剩下就是付款了,這個就不需要再搶了,畢竟也沒人會搶著給你付錢的。

好了本文主要講的使用Python開發個京東上搶口罩的小例項只作技術研究學習使用,請不要胡亂使用。更多關於Python模組requests,beautifulsoup4,pillow使用方法請檢視下面的相關連結