51Testing软件测试论坛

 找回密码
 (注-册)加入51Testing

QQ登录

只需一步,快速开始

微信登录,快人一步

查看: 464|回复: 1
打印 上一主题 下一主题

大麦网演唱会抢票程序之selenium

[复制链接]
  • TA的每日心情
    无聊
    昨天 09:14
  • 签到天数: 938 天

    连续签到: 5 天

    [LV.10]测试总司令

    跳转到指定楼层
    1#
    发表于 2023-4-25 14:48:33 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
    大麦网上,像林俊杰这类歌星的演唱会的门票,往往时间一到就瞬间售完,网速慢了或者手慢了都只能对着屏幕叹息了。所以有时候你愿意花这钱也不一定买得到看的机会。但是其实这个问题并不难解决,手速慢?那可以让代码模拟操作。网速慢?那可以把程序放到阿里服务器上运行。而这其中的关键就是编写一个抢票程序!
      要说抢票,网上关于12306的抢票程序多如繁星,但是关于大麦网的还真不多,但是核心要义都是一样的:模拟。其实这种程序一定程度上类似于爬虫,所以马上可以想到Python的urllib、BeautifulSoup、selenium、splinter等模块。最初为了方便,偷懒使用了splinter,封装得相当简单,但是功能不多,文档不全,网上资料少,写到后来,感觉主用这个模块解决不了自动登录问题,所以放弃转成了selenium。接下来,我将从账号登录、选择演唱会、购票、确认订单四个方面依次讲解思路。
      1.账号登录
      既然是抢票,我希望给定演唱会信息后一运行程序就能够马上进行,不需要其他的人工干预。这就要求我们的账号能够再抢票前自动登录绑定。其实要实现自动登录,由两种方式:①Cookie ②模拟登录。使用Cookie(原理:如果服务器端的$_COOKIE函数中记录了你的Cookie,那就可以直接调用登录,如果没有就需要人工登录了,登录了之后,二次访问界面就能把自己的Cookie保存到$_COOKIE函数)这种方式其实很方便,保存在本地,要用了调用一下,相当于带了一块出入皇宫的令牌,不用每次进入都验身啥的。但是Cookie涉及过期问题,同时是存在较大安全隐患。所以我决定玩玩另一个方式——模拟登录。
      大麦网的登录界面如下图:

      我们需要做的就是填充账号密码,填完了之后点击登录,其实人工登录到这里就完成了,但是用代码填充完内容后点击登录,惊喜就出现了,一个滑块突然出现在按钮上方,并红字提示需要将滑块滑到最右边才能点击登录。所以接下来我们除了要完成内容填充、按钮点击外,还要完成滑块的滑动。(我试过模拟人工填写信息来躲避这个滑块,但是奈何骗不过去,只好直面问题了)
      说实话,因为经验不足,内容填充花了我好几个小时,原因就是这个登录框其实是一个网页,封装在iframe标签中,作为外部网页的子界面,不管我用哪个工具包,用id、class、xpath等哪个方式,都无法定位到账号框和密码框。当我注意到iframe这个坑,才搞明白要用下面这句话来定位到子页面。
      self.driver.switch_to_frame('alibaba-login-box')#里面这个是iframe的id

      接下来就简单了,定位到两个框和一个按钮,然后点击一下按钮,保证滑块乖乖地出现。
      self.driver.find_element_by_id('fm-login-id').send_keys(self.uid)
      self.driver.find_element_by_id('fm-login-password').send_keys(self.upw)
      self.driver.find_element_by_tag_name("button").click()


      然后,想想自己是怎么滑动滑块的,在滑块处按下左键不动,移动鼠标向右,到最右边,然后松手。要模拟这个过程就需要用到ActionChains了。
      ActionChains(self.driver).click_and_hold(self.driver.find_element_by_id('nc_1_n1z')).perform()#按住滑块不动
      ActionChains(self.driver).move_by_offset(xoffset=250, yoffset=0).perform()#直接到终点,可能速度太快,会被系统判错误操作(这也是我不用drag_and_drop这个函数的原因),快到终点时停下
      for i in range(2):
         ActionChains(self.driver).move_by_offset(xoffset=10, yoffset=0).perform()#再慢慢滑两步
         sleep(0.1)
      sleep(0.5)#滑完了之后稍等下,让系统判断完毕
      ActionChains(self.driver).release().perform()#松开点击
      self.driver.find_element_by_tag_name("button").click()#点击登录


      结束了之后,记得加上下面这句话,从iframe切换出去。
      self.driver.switch_to_default_content()

      但是,过了两天,我发现这个代码在我的电脑上失效了(别人那里好像是可以的)。。。不知道什么原因,selenium打开浏览器之后,无论是代码滑动滑块还是我手动滑动滑块,都是被判无效的,但是我自己打开浏览器操作是可以的。我觉得我可能是被针对了(以前也碰到过两次),感觉没有办法解决之后,我开始着手用其他方式解决自动登录——Cookie。其实吧,这个是套路,直接贴代码就行了,有一个地方需要注意注意注意,重要的事情说三遍,因为网上的教程在这个地方很多都是错的,从而导致我当时代码调得差点自闭了。
          def get_cookie(self):
             self.driver.get(damai_url)
             print("###请点击登录###")
             while self.driver.title.find('大麦网-全球演出赛事官方购票平台')!=-1:
                 sleep(1)
             print("###请扫码登录###")
             while self.driver.title=='中文登录':
                 sleep(1)
             print("###扫码成功###")
             pickle.dump(self.driver.get_cookies(), open("cookies.pkl", "wb"))
             print("###Cookie保存成功###")

          def set_cookie(self):
              try:
                  cookies = pickle.load(open("cookies.pkl", "rb"))#载入cookie
                  for cookie in cookies:
                      cookie_dict = {
                          'domain':'.damai.cn',#必须有,不然就是假登录
                          'name': cookie.get('name'),
                          'value': cookie.get('value'),
                          "expires": "",
                          'path': '/',
                          'httpOnly': False,
                          'HostOnly': False,
                          'Secure': False}
                      self.driver.add_cookie(cookie_dict)
                  print('###载入Cookie###')
              except Exception as e:
                  print(e)


      之后只要如此调用就可以了。
        if not os.path.exists('cookies.pkl'):#如果不存在cookie.pkl,就获取一下
           self.get_cookie()
        else:
           self.driver.get(damai_url)
           self.set_cookie()


      2.选择演唱会
      接下来就很直接了。

      self.driver.find_elements_by_xpath('/html/body/div[1]/div/div[4]/input')[0].send_keys(self.name)#找到搜索栏,填入演唱会歌星的名字
      self.driver.find_elements_by_xpath('/html/body/div[1]/div/div[4]/div[1]')[0].click()#点击旁边的搜索按钮



      kinds=self.driver.find_element_by_id('category_filter_id').find_elements_by_tag_name('li')#选择演唱会类别
      for k in kinds:
         if k.text=='演唱会':
            k.click()
            break
      lists=self.driver.find_elements_by_id('content_list')[0].find_elements_by_tag_name('li')#获取所有可能演唱会
      titles=[]
      links=[]
      self.choose_result=0
      for li in lists:
         word_link=li.find_element_by_tag_name('h3')
         titles.append(word_link.text)
         temp_s=word_link.get_attribute('innerHTML').find('href')+6
         temp_e=word_link.get_attribute('innerHTML').find('target')-2
         links.append(word_link.get_attribute('innerHTML')[temp_s:temp_e])
         if li.find_element_by_tag_name('h3').text.find(self.place)!=-1:#选择地点正确的演唱会
            self.choose_result=len(titles)
            break
      self.url="https:"+links[self.choose_result-1]
      self.driver.get(self.url)#载入至购买界面


      3.购票

      datelist=self.driver.find_element_by_id("performList").find_elements_by_tag_name('li')#根据优先级选择一个可行日期
      for i in self.date:
      j=datelist[i-1].get_attribute('class')
      if j=='itm':
      datelist[i-1].click()
      break
      elif j=='itm itm-sel':
      break
      elif j=='itm itm-oos':
      continue
      sleep(1)
      pricelist=self.driver.find_element_by_id("priceList").find_elements_by_tag_name('li')#根据优先级选择一个可行票价
      for i in self.price:
      j=pricelist[i-1].get_attribute('class')
      if j=='itm':
      pricelist[i-1].click()
      break
      elif j=='itm itm-sel':
      break
      elif j=='itm itm-oos':
      continue
      sleep(1.5)
      cart=self.driver.find_element_by_id('cartList')
      try:#各种按钮的点击
      try:
      cart.find_element_by_class_name('ops').find_element_by_link_text("立即预定").click()
      self.status=3
      except:
      cart.find_element_by_class_name('ops').find_element_by_link_text("立即购买").click()
      self.status=4
      except:
      cart.find_element_by_class_name('ops').find_element_by_link_text("选座购买").click()
      self.status=5
      self.num+=1
      sleep(0.5)


      接下来就是要点击确定了,但是这个确定按钮有三类:立即预定、立即购买、选座购买,我们需要分别处理。如果出错,就刷新界面
      cart=self.driver.find_element_by_id('cartList')
      try:#各种按钮的点击
      try:
      cart.find_element_by_class_name('ops').find_element_by_link_text("立即预定").click()
      self.status=3
      except:
      cart.find_element_by_class_name('ops').find_element_by_link_text("立即购买").click()
      self.status=4
      except:
      cart.find_element_by_class_name('ops').find_element_by_link_text("选座购买").click()
      self.status=5


      如果没有成功跳转,我没抢票的经验,不知道接下来会出现什么,所以也不知道要怎么具体应对,所以就刷新页面,重复上述操作,只能暂时这么粗暴地来了。
      4.确认订单
      点击选座购买,如果跳转成功,接下来就是选座了,这个有点小麻烦,暂时没写代码应对,开始交给人工处理。
      点击立即预定和立即购买后如果正常跳转,之后就是确认订单信息与结算了,这时会有好多类型的界面,但是其中两类界面占了大多数,所以我就针对这两类进行编程。
      因为这个时候的界面涉及很多隐私信息,我就不放出来了,虽然可以打码,但是我就是不想放。大致的操作其实和前面是类似的:找到目标,点击。
      print('###开始确认订单###')
      print('###默认购票人信息###')  
      rn_button=self.driver.find_elements_by_xpath('/html/body/div[3]/div[3]/div[2]/div[2]/div/a')
      if len(rn_button)==1:#如果要求实名制
      print('###选择实名制信息###')
      rn_button[0].click()
      #选择实名信息
      tb=self.driver.find_element_by_xpath('/html/body/div[3]/div[3]/div[12]/div')
      lb=tb.find_elements_by_tag_name('label')[self.real_name]#选择第self.real_name个实名者
      lb.find_elements_by_tag_name('td')[0].click()
      tb.find_element_by_class_name('one-btn').click()
      print('###默认选择付款方式###')
      print('###确认商品清单###')
      rn_button=self.driver.find_elements_by_xpath('/html/body/div[3]/div[3]/div[3]/div[2]/div[2]/div/div/h2/a[1]')
      if len(rn_button)==1:#如果要求实名制
      print('###选择购票人信息###')
      rn_button[0].click()
      #选择实名信息
      tb=self.driver.find_element_by_xpath('/html/body/div[3]/div[3]/div[13]/div')
      lb=tb.find_elements_by_tag_name('label')[self.real_name]#选择第self.real_name个实名者
      lb.find_elements_by_tag_name('td')[0].click()
      tb.find_element_by_class_name('one-btn').click()
      print('###不选择订单优惠###')
      print('###请在付款完成后下载大麦[url=]APP[/url]进入订单详情页申请开具###')
      self.driver.find_element_by_id('orderConfirmSubmit').click()#同意以上协议并提交订单
      sleep(8)
      if self.driver.title.find('支付')!=-1:
      self.status=6
      print('###成功提交订单,请手动支付###')
      else:
      print('###提交订单失败,请查看问题###')


      我试了几个,基本都是可以抢票成功的,但是这是没有一窝蜂地去抢的情况下。

    分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
    收藏收藏1
    回复

    使用道具 举报

  • TA的每日心情
    开心
    前天 13:32
  • 签到天数: 1195 天

    连续签到: 3 天

    [LV.10]测试总司令

    2#
    发表于 2023-5-6 09:20:54 | 只看该作者
    早看到,就能抢到周杰伦演唱会的票了
    回复 支持 反对

    使用道具 举报

    本版积分规则

    关闭

    站长推荐上一条 /1 下一条

    小黑屋|手机版|Archiver|51Testing软件测试网 ( 沪ICP备05003035号 关于我们

    GMT+8, 2024-4-27 08:40 , Processed in 0.066707 second(s), 22 queries .

    Powered by Discuz! X3.2

    © 2001-2024 Comsenz Inc.

    快速回复 返回顶部 返回列表