使用selenium找出外卖点餐次数最多的10个顾客

  大锅在做外卖,给我说能否统计出这半年点餐次数最多的10个顾客,我不知道APP本身是否有这个功能,想了下最近用selenium较多,就用selenium尝试下吧。

  1 定义一个类,这里先描述需要的属性和方法,后面再依次具体分析:

 1 class Order:
 2     def __init__(self, url, username, password):
 3         # URL以及用户名和密码
 4         self.url = url
 5         self.username = username
 6         self.password = password
 7         # webdriver对象
 8         self.driver = None
 9         # 日期列表
10         self.dateList = list()
11         # 存储订单的dict
12         self.orderDict = dict()
13
14     # 设置订单的时间范围
15     def setDate(self, startdate, enddate):
16         pass
17     # 登录
18     def login(self):
19         pass
20     # 退出
21     def logout(self):
22         pass
23     # 切换到历史订单页面
24     def switchToHistoryOrder(self):
25         pass
26     # 选择一个时间范围
27     def selectDate(self, startDate, endDate):
28         pass
29     # 订单信息存入self.orderDict
30     def saveOrderIntoDict(self, tel, name, address):
31         pass
32     # 处理当前页面的订单
33     def searchOrderListInCurrentPage(self):
34         pass
35     # 判断是否还有下一页
36     def hasNextPage(self):
37         pass
38     # 切换到下一页
39     def enterNextPage(self):
40         pass
41     # 抓取设定日期范围内的所有订单
42     def getAllOrders(self, startdate, enddate):
43         pass
44     # 筛选出点餐次数排名前N的顾客
45     def getTopN(self, n=10):
46         pass

  2 设置欲筛选的订单日期范围

  这里设置的日期格式必须是‘yyyy-mm-dd‘,由于该网站在查询订单的时候,时间范围必须是7天以内,比如直接查询2016-01-01到2016-02-28之间的订单是不行的,因此需要先将这段时间以7天为周期分割为多个时间段,然后再分段处理;

分割后的时间段存放在self.dateList中,list的元素为tuple,一个tuple表示一个时间段:

 1 # 设置订单的时间范围,日期格式必须是‘yyyy-mm-dd‘
 2     # 然后以7天为周期,将日期范围分割成list,list的每个元素为一个tuple,分别存放起止日期
 3     # 例如 [(‘2016-01-01‘, ‘2016-01-07‘), (‘2016-01-08‘, ‘2016-01-14‘)]
 4     def setDate(self, startdate, enddate):
 5         # 通过正则表达式检查日期格式
 6         pdate = re.compile(‘\d{4}-\d{2}-\d{2}‘)
 7         if pdate.search(startdate) and pdate.search(enddate):
 8             # 转换为datetime格式,便于日期计算
 9             startdate = datetime.datetime.strptime(startdate, ‘%Y-%m-%d‘)
10             enddate = datetime.datetime.strptime(enddate, ‘%Y-%m-%d‘)
11
12             # 将日期范围以7天为周期分割
13             days = (enddate - startdate).days + 1
14             cnt = days / 7
15             left = days - 7*cnt
16             for x in range(cnt):
17                 d1 = (startdate + datetime.timedelta(days=7*x))
18                 d2 = (d1 + datetime.timedelta(days=6))
19                 # datetime转换为str,再加入list中
20                 self.dateList.append((d1.strftime(‘%Y-%m-%d‘), d2.strftime(‘%Y-%m-%d‘)))
21             if left > 0:
22                 self.dateList.append(((startdate+datetime.timedelta(days=cnt*7)).strftime(‘%Y-%m-%d‘), enddate.strftime(‘%Y-%m-%d‘)))
23         else:
24             print u‘日期格式错误,必须为yyyy-mm-dd格式‘
25             exit(1)

测试一下:

1 order = Order(‘url‘, ‘username‘, ‘password‘)
2 order.setDate(‘2016-01-01‘, ‘2016-01-31‘)
3 print order.dateList
4 #输出为
5 [(‘2016-01-01‘, ‘2016-01-07‘), (‘2016-01-08‘, ‘2016-01-14‘), (‘2016-01-15‘, ‘2016-01-21‘), (‘2016-01-22‘, ‘2016-01-28‘), (‘2016-01-29‘, ‘2016-01-31‘)]

  3 登录与退出

  登录比较简单,直接过id定位到用户名和密码输入框,然后定位登录按钮点击登录即可,只是定位到输入框后需要先将框内的提示信息清除掉。

  退出就更简单了,直接关闭浏览器即可。

 1  # 登录
 2     def login(self):
 3         # 采用chrome浏览器
 4         self.driver = webdriver.Chrome()
 5         # 窗口最大化
 6         self.driver.maximize_window()
 7         # 设置超时时间
 8         self.driver.implicitly_wait(10)
 9         self.driver.get(self.url)
10
11         # 查找用户名输入框,先清除提示信息,再输入用户名
12         usr = self.driver.find_element_by_id(‘account-name‘)
13         usr.clear()
14         usr.send_keys(self.username)
15
16         # 查找密码输入框,先清除提示信息,再输入密码
17         passwd = self.driver.find_element_by_id(‘account-password‘)
18         passwd.clear()
19         passwd.send_keys(self.password)
20
21         # 点击登录
22         self.driver.find_element_by_id(‘account-login-btn‘).click()
23         return
24
25     # 退出
26     def logout(self):
27         self.driver.close()
28         return

  4 切换到历史订单页面

  登录后点击订单管理,然后点击历史订单,切换到历史订单页面,如下图所示:

  

  由于“订单管理”和“历史订单”这两个元素都是超链接,因此可以直接用超链接定位:

1     # 切换到历史订单页面
2     def switchToHistoryOrder(self):
3         self.driver.find_element_by_partial_link_text(u‘订单管理‘).click()
4         self.driver.find_element_by_partial_link_text(u‘历史订单‘).click()
5
6         # 切换frame,因为后续的所有处理都是在hashframe中,所有在这里切换
7         self.driver.switch_to.frame(‘hashframe‘)
8         return

  注意该方法最后有个切换frame的操作,下一步就知道为什么要添加这句了。

  5 选择订单日期范围,筛选出该日期范围内的订单

  注意这里的日期范围与第二步设置的日期范围不一样,第二步设置的日期范围是我们想要筛选的起止时间,这里的日期范围是第二步分割出来的其中一段。

与用户名密码框一样,这里的日期输入框也可采用id定位,同样定位后需要先将输入框预置的日期清除:

 1     # 在页面上设置订单的时间范围,并筛选出该时间范围内的所有订单
 2     # 调用该方法时,参数需从self.dateList中获取,dateList中的每一个tuple对应一组参数
 3     def selectDate(self, startDate, endDate):
 4         # 设置起始日期
 5         s = self.driver.find_element_by_id(‘J-start‘)
 6         s.clear()
 7         s.send_keys(startDate)
 8
 9         # 设置终止日期
10         e = self.driver.find_element_by_id(‘J-end‘)
11         e.clear()
12         e.send_keys(endDate)
13         return

  这里说一下第4步中的切换frame,我们先将切换frame这一句注释掉,然后来测试下选择日期:

1  order.login()
2  order.switchToHistoryOrder()
3  order.selectDate(‘2016-07-01‘, ‘2016-07-02‘)
4 # 输出
5 selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to locate element: {"method":"id","selector":"J-start"}

  可以看到出错了,提示没有需要定位的元素,为什么呢?通过HTML代码可以看到,这里采用了框架frame,所定位的元素在frame里面,如下所示:

  

  因此需要先切换到该frame里面,然后才能定位到frame里面的元素;由于后续所有的订单操作都在该frame里面,因此在上一步切换到历史订单页面后,就先切换到该frame,便于后续操作。

  6 将订单信息存入self.orderDict

  该方法是下一步需要调用的,因此这里先实现。每一个订单我们只需3个信息:姓名、电话、地址,然后将这三个元素表示的订单存入orderDict,但是由于我们要统计出点餐次数最多的10个顾客,

因此还要保存每个顾客的点餐次数。这里dict的元素格式为{‘tel‘: [‘name‘, ‘address‘, cnt]},由于姓名可能重复,因此采用了电话作为key值。如果某个顾客第一次点餐,保存时将点餐次数cnt初始化为1;

如果不是第一次点餐,则将该顾客对应的cnt值加1。

 1     # 将tel, name, address表示的订单信息存入self.orderDict
 2     # self.orderDict元素的形式为 {‘tel‘: [‘name‘, ‘address‘, cnt]}
 3     # 以电话号码为key,以姓名、地址、点餐次数组成的list为value
 4     # 当要添加的key不存在时,就将此订单加入orderDict,并且cnt初始化为1
 5     # 当要添加的key已经存在时,直接将该key对应的cnt加1
 6     def saveOrderIntoDict(self, tel, name, address):
 7         if self.orderDict.has_key(tel):
 8             self.orderDict[tel][2] += 1
 9         else:
10             self.orderDict[tel] = [name, address, 1]
11         return

  7 提取当前页面的所有订单,存入self.orderDict

  先来分析下订单部分的HTML代码,如下所示:

  

  简单说就是:用<ul>表示该页的所有订单,<ul>下面的每一个<li>表示一个具体订单,<li>下面的类型为user-info的<div>表示该订单的用户信息,<div>下面就是姓名电话等信息。

  因此定位过程如下:先用find_elements方法找到<ul>下面的所有<li>;然后遍历每一个<li>,定位用户信息;最后再从用户信息中定位姓名电话等。这里用到了层级定位

 1     # 抓取当前页面的所有订单,并调用saveOrderIntoDict将每个订单信息都存入self.orderDict
 2     def searchOrderListInCurrentPage(self):
 3         # 这里采用CSS定位,但是使用了find_elements,一次定位所有的订单
 4         orders = self.driver.find_elements_by_css_selector(‘ul.order-list.J-order-list > li‘)
 5
 6         # 遍历每一个订单,提取其中的用户信息,这里用到了层级定位
 7         for one in orders:
 8             # 先定位用户信息
 9             uesrinfo = one.find_element_by_css_selector(‘div.user-info‘)
10             # 再定位具体每项信息
11             name = uesrinfo.find_element_by_css_selector(‘span.b-title.user-name‘).text
12             # 有的用户没有写名字
13             if name == ‘‘:
14                 name = u‘无名氏‘
15             tel = uesrinfo.find_element_by_css_selector(‘span.b-title.user-phone.J-search-phone‘).text
16             address = uesrinfo.find_element_by_css_selector(‘div > span.fl.J-search-address‘).text
17             # 信息存入self.orderDict
18             self.saveOrderIntoDict(tel, name, address)
19         return

  8 判断是否还有下一页订单

  这里使用页面低端的翻页符号来定位,如下图所示。

  

  实现思路可参考 使用selenium实现简单网络爬虫抓取MM图片 ,检查是否到达页面底部的方法:

 1     # 判断是否还有下一页
 2     # 通过符号>>的上级标签<li>的class属性来判断,当还存在下一页(>>可点击)时,<li>的class属性值为空;当不存在下一页时,<li>的class属性值为disabled
 3     # 因此,当我们找到符号>>及其父元素<li class="disabled">时,即可认为不存在下一页,否则存在下一页
 4     def hasNextPage(self):
 5         try:
 6             # 注意这里的选择器一定要包含a[aria-label="Next"],不能只用li.disabled,因为上一页的符号也可能存在li.disabled,必须使用父子元素同时定位
 7             self.driver.find_element_by_css_selector(‘ul.J-pagination.pagination.pagination-md.pull-right > li.disabled > a[aria-label="Next"]‘)
 8             # 如果没抛出异常,说明找到了元素<li class="disabled">和子元素<a aria-label="Next">,没有下一页了
 9             return False
10         except NoSuchElementException as e:
11             # 抛出异常说明还存在下一页
12             return True

  9 切换到下一页订单

  切换很简单,直接定位翻页符号点击即可:

1     # 切换到下一页
2     # 这个比较简单,直接采用类似上面的定位器即可
3     def enterNextPage(self):
4         self.driver.find_element_by_css_selector(‘ul.J-pagination.pagination.pagination-md.pull-right a[aria-label="Next"] > span‘).click()
5         return

 10 抓取设置日期范围内的所有订单

  上面的方法都是些内部实现,无需直接调用,而该方法是供我们直接调用的,这里只是对上面那些方法的组合以及简单处理:

 1     # 抓取设定日期范围内的所有订单,抓取订单时直接调用该接口即可
 2     def getAllOrders(self, startdate, enddate):
 3         # 设置订单的时间范围
 4         self.setDate(startdate, enddate)
 5
 6         # 登录
 7         self.login()
 8
 9         # 切换到历史订单页面
10         self.switchToHistoryOrder()
11
12         # 遍历处理dateList中的所有日期对
13         for date in self.dateList:
14             # 选择date所标示的时间范围
15             self.selectDate(startDate=date[0], endDate=date[1])
16
17             # 依次处理筛选出来的每一页订单
18             while True:
19                 # 处理当前页面的订单
20                 self.searchOrderListInCurrentPage()
21                 # 是否还有下一页
22                 if self.hasNextPage():
23                     self.enterNextPage()
24                 else:
25                     break
26
27         # 退出
28         self.logout()
29         return

 11 筛选出点餐次数TOP N的顾客

  调用第10步的方法后,所有的订单信息都在self.orderDict中了,这里只需将dict转换为list,然后按照cnt降序排序,再输出前10的信息即可。

 1     # 筛选出点餐次数排名前N的顾客
 2     def getTopN(self, n=10):
 3         # 先将orderDict转换为list
 4         # {‘tel‘: [‘name‘, ‘address‘, cnt]} -> [(‘name‘, ‘tel‘, ‘address‘, cnt)]
 5         orderList = [(v[0], k, v[1], v[2]) for (k, v) in self.orderDict.iteritems()]
 6
 7         # 按照list中每个元素的cnt排序
 8         orderList.sort(key=lambda x: x[3], reverse=True)
 9
10         # 输出TOP N
11         num = len(orderList)
12         if num < n:
13             n = num
14         for i in range(n):
15             print orderList[i][0]
16             print orderList[i][1]
17             print orderList[i][2]
18             print orderList[i][3]

  现在测试一下整个代码:

1 if __name__ == ‘__main__‘:
2     order = Order(‘url‘, ‘username‘, ‘password‘)
3     # 抓取制定时间范围内的所有订单
4     order.getAllOrders(‘2016-05-23‘, ‘2016-05-25‘)
5     # 输出TOPN
6     order.getTopN(n=10)

  输出形式如下:

 1 姓名1
 2 13512345678
 3 地址1
 4 6
 5 姓名2
 6 13612345678
 7 地址2
 8 3
 9 姓名3
10 13712345678
11 地址3
12 1

时间: 2024-08-13 16:46:23

使用selenium找出外卖点餐次数最多的10个顾客的相关文章

找出1小时内占用cpu最多的10个进程的shell脚本

cpu时间是一项重要的资源,有时,我们需要跟踪某个时间内占用cpu周期最多的进程. 在普通的桌面系统或膝上系统中,cpu处于高负荷状态也许不会引发什么问题.但对于需要处理大量请求的服务器来讲,cpu是极其重要的资源. 通过监视某个时期内cpu的使用情况,我们可以找出长期占用cpu的进程并对其进行优化,或调试其它相关问题. 在linux系统中,ps命令用于收集系统中进程的详细信息.这些信息包括cpu使用情况.正在执行的命令.内存使用.进程状态等.记录在一个小时内占用过的cpu的进程,然后通过恰当地

一个站点的诞生03--抓取评论数最多的一万家餐厅

在大众点评网上,有非常多种方式对餐厅进行排序,比方http://www.dianping.com/search/category/1/10/o10,是上海全市依照评论总数最多对餐厅进行排序,以下有50个分页,也就是上海历年累计评论综述最多的750家餐厅.但仅仅有750家,少了点.上海有18个区,逐区点击的话,每区都会显示前750家餐厅,比方这个http://www.dianping.com/search/category/1/10/r802o10,是浦东新区八佰伴地段的前750家.上海如今有十万

位运算 找出给定的数中其他数都是两个,有两个是一个的数

题目大意: 给定你n个数, 其中有n-2个数都是两两成对的,有两个是单独出现的,如n = 8, 2 3 2 5 3 6 4 6, 这时候4和5是单独的两个,所以答案就是4,5,其中n的范围是1e6. 思路: 之前做过找一个单独的数的题,那个题是用一个比较巧妙的方法来做的,不过这个也是一类经典问题,用到了强大的位运算,有了那个题的基础再来做这个题就简单了.(附:找一个的题目链接). 刚开始我是用了O(nlogn)的时间复杂度来做的,先排序,然后用类似找一个的方法找出第二个.我觉得对于1e6的数据量

ytu 1061: 从三个数中找出最大的数(水题,模板函数练习 + 宏定义练习)

1061: 从三个数中找出最大的数Time Limit: 1 Sec  Memory Limit: 128 MBSubmit: 154  Solved: 124[Submit][Status][Web Board] Description 定义一个带参的宏(或者模板函数),从三个数中找出最大的数. Input 3个短整型数,空格隔开 3个实数,空格隔开 3个长整数,空格隔开 Output 最大的数,对于实数保留2位小数. Sample Input 1 2 3 1.5 4.7 3.2 123456

一个网站的诞生03--抓取评论数最多的一万家餐厅

在大众点评网上,有很多种方式对餐厅进行排序,比如http://www.dianping.com/search/category/1/10/o10,是上海全市按照评论总数最多对餐厅进行排序,下面有50个分页,也就是上海历年累计评论综述最多的750家餐厅.但只有750家,少了点.上海有18个区,逐区点击的话,每区都会显示前750家餐厅,比如这个http://www.dianping.com/search/category/1/10/r802o10,是浦东新区八佰伴地段的前750家.上海现在有十万家餐

一篇英文文档中找出频数最多的10个单词

"""一篇英文文档中找出频数最多的10个单词collections: Counter 提供计数器工具以支持方便和快速的计数 most_common(n) 返回n个最常见元素及其计数的列表,从最常见到最少. 如果省略nNone,则 most_common()返回计数器中的所有元素."""import refrom collections import Counter# print(dir(Counter))with open('english.tx

字节跳动二面 找出最小间断数

题目:给出一个数组,找出最小间断数,例如:[9,1,2,3,5,7,8]  最小间断数为3 最简单一种思路:将数组进行排序,排序后从最左端开始前一个元素与后一个元素相比,找出差不是1的即可,需要排序,时间复杂度就与排序算法相关, 第二种思路:找出数组中最大的一个元素,建立一个长度为该最大值的数组,然后遍历原始的数组,将元素组的值放到新数组中,并且放的下标位置就为元素的值,找到原数组最小的元素值,从该角标开始遍历新数组,若某一个位置的值为0则前一个位置就为所求,这种方式的话时间复杂度为O(n),但

Python 查找Twitter中最流行(转载最多)的10个Tweet

CODE: #!/usr/bin/python # -*- coding: utf-8 -*- ''' Created on 2014-7-4 @author: guaguastd @name: find_popular_retweets.py ''' # Finding the most popular retweets def popular_retweets(statuses): retweets = [ # Store out a tuple of these three values.

c++描述将一个2进制数转化成10进制数(用到初始化栈,进栈,入栈)

1 /* 2 c++描述将2进制数转化成10进制数 3 问题,1.初始化栈后,用new,不知道delete是否要再写一个函数释放内存, 4 还是在哪里可以加上delete 5 2.如果栈满了,我要分配多点空间,我想的办法是先用delete删除之前申请的 6 空间,再用new重新申请,但是c语言有一个函数 7 s->base =(ElemType*) realloc(s->base,(s->stackSize + STACKINCREMENT) * sizeof(ElemType));//