使用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
View Code

  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)
View Code

測試一下:

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')]
View Code

  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
View Code

  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
View Code

   注意該方法最后有個切換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
View Code

  這里說一下第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"}
View Code

  可以看到出錯了,提示沒有需要定位的元素,為什么呢?通過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
View Code

  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
View Code

  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
View Code

  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
View Code

  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
View Code

  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]
View Code

  現在測試一下整個代碼:

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)
View Code

  輸出形式如下:

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM