python+爬蟲+微信機器人 打造屬於你的網購價格監督利器

  • 2019 年 10 月 3 日
  • 筆記

寫在最前

  程式是為人類服務的,最近正好身邊小夥伴們在做球衣生意,當然是去nikenba專區購買了,可是有些熱門球衣發布幾分鐘就被搶完,有些折扣球衣也是很快就被搶售一空,那麼我們只能靠自己的眼睛一直盯著網站嗎?NoNoNo,作為電腦專業的學生,怎麼能為這種事情浪費時間呢?那肯定想法就是寫爬蟲自動比對價格啊,後來又在想,爬蟲數據也是在PC端啊,該怎麼實時提醒我們呢?再弄一個微信機器人發送數據不就可以了嗎?說干就干,程式碼開擼

先看下效果:

準備工作:

首先本文使用py3,需要安裝以下庫:

1)itchat

2)requests

3)apscheduler

分析網頁:

首先我們需要做什麼?毫無疑問,分析網頁,因為最重要的一步就是獲取數據,那麼如何獲取數據就是我們首先要克服的困難

附上 nike nba專區地址:https://www.nike.com/cn/w/nba-sleeveless-and-tank-tops-18iwiz9sbux

首先我們要明確一個地方,我們的目的是實時監控熱門打折球衣,所以我們的價格肯定首先降序排列,不過先不用著急,打開F12先看下調試器,對了我使用的是chrome瀏覽器

由於我們是先打開網頁再打開調試窗口,所以目前我們看不到數據,別急,我們刷新一下再看

哦吼,完蛋,怎麼這麼多東西貌似根本沒法看

別急 繼續分析,作為一個學(qiong)生(bi),我們肯定先關注價格了,當然要升序排列啊!

好的 點下瀏覽器調試窗口中的清除按鈕(就是下面這個藍色標記的按鈕)先清除下調試台中的數據 然後呢我們點下篩選方式價格由低到高(紅色標記的菜單鍵中選擇)

得到調試台如下,完蛋了還是一堆怎麼辦?

沒關係,至少現在網頁內容已經是按照價格升序排列了,我們再來看看得到的Network數據,挨個點一點看看,發現當點到名稱為graphql開頭的文件里去時候,有東西出現了

裡面的響應內容出現了幾個熟悉的隊名稱和球員名稱甚至還有價格,等等,這不就是我們要的數據嗎?

看來我們找對了地方,我們雙擊點開graphql開頭的網頁文件看看會有什麼呢? 。。。 看起來雜亂無章,但是貌似確實是我們要的數據,是json格式的

在網頁上看json簡直是折磨,好的,我們用python開始把這個網頁內容給弄下來仔細研究下

pycharm開搞

import requests  import json    #剛剛在調試台得到的地址  url='https://www.nike.com/w/graphql?queryid=filteredProductsWithContext&anonymousId=A54CD5202A87B54B4415AD4BC11E5692&uuids=1c7c3d67-5d46-432d-9910-b1128d1b6503,e09eabe9-5ff0-42af-b0a3-5f68af19d89a&language=zh-Hans&country=CN&sortBy=priceAsc'    #使json數據格式化輸出更好觀察  def better_jsprint(json_obj):      # 使用indent=4 這個參數對json進行數據格式化輸出      #因為json.dumps 序列化時對中文默認使用的ascii編碼.想輸出真正的中文需要指定ensure_ascii=False      return json.dumps(json.loads(json_obj),indent=4,ensure_ascii=False)    response=requests.get(url)    print(better_jsprint(response.text))

看看輸出什麼:

這樣看起來好多了,好的 似乎到這裡我們已經可以開始選取我們需要的數據進行記錄了,但是我們又會注意到一點,這個網頁的內容是瀑布流方式,也就是說滾輪往下滾動才會有更多的數據出現,可是我們目前只獲取了這個頁面最上端的數據,如果我們想獲取更多的數據怎麼辦?

我們還是使用調試台,其實他頁面只要變化,網站交互一定是有活動的,所以我們現在就觀察當滾輪往下滾動到瀑布流下端時調試台會出現什麼東西就可以了

往下滾動,發現調試台確實出現了很多新的文件,我們猜想這些文件中一定有瀑布流下端的數據,對了還記得我們剛才找到的文件名是什麼嗎?對的,是名稱為graphql開頭的文件,那麼會不會新的數據文件也是這個名字開頭的呢?我們使用調試台搜索下看看

來了來了,它真的出現了,現在出現了3個文件都是graphql名字開頭,毫無疑問第一個文件是我們上面找到的,那麼第二個第三個呢? 我們點開看看,會發現對應的商品名稱之類的真的是瀑布流下端的數據。

OK看起來我們現在確實得到了所有數據文件的url

我最初的想法是直接將3個url寫到一個列表中然後使用循環讀取如下圖(其實會發現第二個url與第三個看起來貌似一樣啊怎麼回事?下面有解釋別急)

後來呢我突然意識到,萬一商品更多了怎麼辦?會不會出現4個5個url?而總不能每次都靠人力去數有多少個url吧?然後就想,怎樣才能讓程式自動添加url呢?

我們再回頭看看第一次抓取下來的 url1 的json數據,首先嘗試下檢索page這個關鍵詞(畢竟一般程式設計師都會寫這個作為頁面標識吧?),哦霍,發現了了不得的東西,

這些數據看起來很眼熟啊,還有uuids?再比對下第一次抓的 url1 發現裡面的uuids還真的就是json裡面的數據,那麼又看到pages裡面有個next 納尼?這會不會是瀑布流下半部分url組成呢?快來比對 url2 地址

https://www.nike.com/w/graphql?queryid=products&anonymousId=A54CD5202A87B54B4415AD4BC11E5692&endpoint=%2Fproduct_feed%2Frollup_threads%2Fv2%3Ffilter%3Dmarketplace(CN)%26filter%3Dlanguage(zh-Hans)%26filter%3DemployeePrice(true)%26filter%3DattributeIds(1c7c3d67-5d46-432d-9910-b1128d1b6503%2Ce09eabe9-5ff0-42af-b0a3-5f68af19d89a)%26anchor%3D24%26count%3D24%26consumerChannelId%3Dd9a5bc42-4b9c-4976-858a-f159cf99c647%26sort%3DproductInfo.merchPrice.currentPriceAsc

嘗試檢索下next中的內容,發現真的存在與endpoint參數後面,哦霍 現在我們猜想,會不會每個json中都包含pages next這個數據

列印url2繼續檢索pages的next 

真的存在,並且還存在prev參數(前一頁),說明我們的猜想可能是正確的,這時候細心的小夥伴可能發現了 url2中的next內容與url1中一致啊,哦原來是這樣,這樣才導致了我們剛剛調試台中出現3個url文件但是第二個與第三個一樣的情況

但是我們猜想第三個url返回數據中應該沒有next否則就應該出現第四個文件了,我們來試一試

在url3返回數據中檢索next

真的為空了所以我們可以確定,只要瀑布流下方仍有數據,那麼一定存在next參數 因此我們可以確定瀑布流url寫法 我們網頁分析完成 接下來就要進行真正的程式碼編寫了

(其實我有一個疑問 url2與url3看起來確實是一模一樣的並且我嘗試做了差值運算,發現還是一樣的,但是返回數據確實不同,有大神可以發現這兩個url不同之處嗎 下面放上這兩個url)

(url已改變,根據官網實時更新數據一直在變)

url2='https://www.nike.com/w/graphql?queryid=products&anonymousId=A54CD5202A87B54B4415AD4BC11E5692&endpoint=%2Fproduct_feed%2Frollup_threads%2Fv2%3Ffilter%3Dmarketplace(CN)%26filter%3Dlanguage(zh-Hans)%26filter%3DemployeePrice(true)%26filter%3DattributeIds(1c7c3d67-5d46-432d-9910-b1128d1b6503%2Ce09eabe9-5ff0-42af-b0a3-5f68af19d89a)%26anchor%3D24%26count%3D24%26consumerChannelId%3Dd9a5bc42-4b9c-4976-858a-f159cf99c647%26sort%3DproductInfo.merchPrice.currentPriceAsc'  url3='https://www.nike.com/w/graphql?queryid=products&anonymousId=A54CD5202A87B54B4415AD4BC11E5692&endpoint=%2Fproduct_feed%2Frollup_threads%2Fv2%3Ffilter%3Dmarketplace(CN)%26filter%3Dlanguage(zh-Hans)%26filter%3DemployeePrice(true)%26filter%3DattributeIds(1c7c3d67-5d46-432d-9910-b1128d1b6503%2Ce09eabe9-5ff0-42af-b0a3-5f68af19d89a)%26anchor%3D48%26count%3D24%26consumerChannelId%3Dd9a5bc42-4b9c-4976-858a-f159cf99c647%26sort%3DproductInfo.merchPrice.currentPriceAsc'

urls構建與objects獲取

我們首先需要寫遞歸函數獲取所有urls

我們觀察json內容就會發現我們需要的商品數據都在一個名為objects的key中 因此需要將所有objects放在一起

遞歸函數(核心函數)如下

    #剛剛在調試台得到的初始地址      url1='https://www.nike.com/w/graphql?queryid=filteredProductsWithContext&anonymousId=A54CD5202A87B54B4415AD4BC11E5692&uuids=1c7c3d67-5d46-432d-9910-b1128d1b6503,e09eabe9-5ff0-42af-b0a3-5f68af19d89a&language=zh-Hans&country=CN&sortBy=priceAsc'        #觀察其他urls發現前面參數是一樣的如下 我們先寫前半部分      urlother='https://www.nike.com/w/graphql?queryid=products&anonymousId=A54CD5202A87B54B4415AD4BC11E5692&endpoint='      urls=[url1]        #空list存放物品資訊 觀察發現json中的objects數據類型為list      pricedictlist=[]        #遞歸函數得到urls列表以及每個url中物品數據      def get_url_objcts(url=url1):          #首先得到初始url的json數據          response=requests.get(url)          #只取有用的數據內容 仔細觀察json數據 得到下一個頁面的next參數          #urllib.parse.quote(text)          # 按照標準, URL 只允許一部分 ASCII 字元(數字字母和部分符號),其他的字元(如漢字)是不符合 URL 標準的。          # 所以 URL 中使用其他字元就需要進行 URL 編碼。          try:              nextpage_json=quote(response.json()['data']['filteredProductsWithContext']['pages']['next'])              #添加objects內容到列表              pricedictlist.extend(response.json()['data']['filteredProductsWithContext']['objects'])          except KeyError:              nextpage_json = quote(response.json()['data']['products']['pages']['next'])              # 添加objects內容到列表              pricedictlist.extend(response.json()['data']['products']['objects'])          except TypeError:              nextpage_json=''          #遞歸獲取url與objects          if nextpage_json!='':              urlnext=urlother+nextpage_json              urls.append(urlnext)              nextpage_json=''              get_url_objcts(urlnext)          #else只在不存在下一頁時執行,相當於此時已經完成了objects的獲取 下面構建發送資訊          else:              i = 0              STR = str('https://www.nike.com/cn/w/nba-sleeveless-and-tank-tops-18iwiz9sbux?sort=priceAsc')              compStr1 = ''              for each in pricedictlist:                  title = each['publishedContent']['properties']['seo']                  if title == None:                      continue                  currentPrice = each['productInfo'][0]['merchPrice']['currentPrice']                  fullPrice = each['productInfo'][0]['merchPrice']['fullPrice']                  #只選取有用的數據 我們不要童裝 同時只要打折商品                  if (not re.search('', str(title['slug']))) and (fullPrice != currentPrice):                      i = i + 1                      STR = STR + 'nn' + ((str(title['slug']) + "n" + "  原價" + str(fullPrice) + "  現價" + str(                          currentPrice)) + '  ' + str(currentPrice * 100 / fullPrice) + '%')                      #發現每個商品名稱後面都有獨特的商品碼為6個字母標識,所以切片記錄下來用於對比                      compStr1 = compStr1 + str(title['slug'][-6:])                STR = STR + 'n' + ("本次數據一共:" + str(i) + "")

這之上 我們已經完成了數據的獲取,接下來就是微信機器人發送了

itchat微信機器人

itchat是個人賬戶的開放源碼wechat api項目,它使您可以通過命令行訪問您的個人微信帳戶。

如何向群發送消息?

import itchat  #登錄微信網頁版 參數enableCmdQR=0會出現圖片二維碼登錄 為1則命令行窗口輸出字元二維碼 有的linux因為字元間距問題需要設置為2  itchat.auto_login(hotReload=0,enableCmdQR=0)    # 自己創建微信群,名稱自定,並且要保存到通訊錄  chatroomName = 'Money'  # 群名  itchat.get_chatrooms(update=True)  chatrooms = itchat.search_chatrooms(name=chatroomName)  # print(compStr0)  if len(chatrooms) == 0:      # print('沒有找到群聊:' + chatroomName)      exit(0)  else:      itchat.send_msg('hello world', toUserName=chatrooms[0]['UserName'])  # 發送消息

這就是簡單的發送消息了,將我們上面的程式接合就可以實現微信發送了 還差一步,沒錯就是定時任務的問題

Python定時任務框架apscheduler

聽名字就知道是幹什麼的 沒錯就是任務調度,我們可以使用這個庫簡潔的實現任務調度問題

簡單常式如下:

from apscheduler.schedulers.blocking import BlockingScheduler  import time  scheduler = BlockingScheduler()  def job1():   print ("%s: 執行任務" % time.asctime())  scheduler.add_job(job1, 'interval', seconds=3)  scheduler.start()

輸出:

Mon Aug 19 18:35:52 2019: 執行任務  Mon Aug 19 18:35:55 2019: 執行任務  Mon Aug 19 18:35:58 2019: 執行任務  Mon Aug 19 18:36:01 2019: 執行任務  Mon Aug 19 18:36:04 2019: 執行任務  Mon Aug 19 18:36:07 2019: 執行任務  Mon Aug 19 18:36:10 2019: 執行任務

最後一步就是將上面講的所有來一個大集合

程式送上~:

將環境配置好,直接放在自己的伺服器就可以運行了,這一步就不再贅述

import re  import requests  from urllib.parse import quote  import itchat  from datetime import datetime  from apscheduler.schedulers.blocking import BlockingScheduler    #登錄微信網頁版 參數enableCmdQR=0會出現圖片二維碼登錄 為1則命令行窗口輸出字元二維碼 有的linux因為字元間距問題需要設置為2  itchat.auto_login(hotReload=0,enableCmdQR=2)    #比較字元串,用於判斷是否更新數據  compStr0='fuckkkkkkkkkkkkkkkk'    def main():      #剛剛在調試台得到的初始地址      url1='https://www.nike.com/w/graphql?queryid=filteredProductsWithContext&anonymousId=A54CD5202A87B54B4415AD4BC11E5692&uuids=1c7c3d67-5d46-432d-9910-b1128d1b6503,e09eabe9-5ff0-42af-b0a3-5f68af19d89a&language=zh-Hans&country=CN&sortBy=priceAsc'        #觀察其他urls發現前面參數是一樣的如下 我們先寫前半部分      urlother='https://www.nike.com/w/graphql?queryid=products&anonymousId=A54CD5202A87B54B4415AD4BC11E5692&endpoint='      urls=[url1]        #空list存放物品資訊 觀察發現json中的objects數據類型為list      pricedictlist=[]        #遞歸函數得到urls列表以及每個url中物品數據      def get_url_objcts(url=url1):          #首先得到初始url的json數據          response=requests.get(url)          #只取有用的數據內容 仔細觀察json數據 得到下一個頁面的next參數          #urllib.parse.quote(text)          # 按照標準, URL 只允許一部分 ASCII 字元(數字字母和部分符號),其他的字元(如漢字)是不符合 URL 標準的。          # 所以 URL 中使用其他字元就需要進行 URL 編碼。          try:              nextpage_json=quote(response.json()['data']['filteredProductsWithContext']['pages']['next'])              pricedictlist.extend(response.json()['data']['filteredProductsWithContext']['objects'])          except KeyError:              nextpage_json = quote(response.json()['data']['products']['pages']['next'])              pricedictlist.extend(response.json()['data']['products']['objects'])          except TypeError:              nextpage_json=''          #遞歸獲取url與objects          if nextpage_json!='':              urlnext=urlother+nextpage_json              urls.append(urlnext)              nextpage_json=''              get_url_objcts(urlnext)          #else只在不存在下一頁時執行,相當於此時已經完成了objects的獲取          else:              i = 0              STR = str('https://www.nike.com/cn/w/nba-sleeveless-and-tank-tops-18iwiz9sbux?sort=priceAsc')              compStr1 = ''              for each in pricedictlist:                  title = each['publishedContent']['properties']['seo']                  if title == None:                      continue                  currentPrice = each['productInfo'][0]['merchPrice']['currentPrice']                  fullPrice = each['productInfo'][0]['merchPrice']['fullPrice']                  #只選取有用的數據 我們不要童裝 同時只要打折商品                  if (not re.search('', str(title['slug']))) and (fullPrice != currentPrice):                      i = i + 1                      STR = STR + 'nn' + ((str(title['slug']) + "n" + "  原價" + str(fullPrice) + "  現價" + str(                          currentPrice)) + '  ' + str(currentPrice * 100 / fullPrice) + '%')                      #發現每個商品名稱後面都有獨特的商品碼為6個字母標識,所以切片記錄下來用於對比                      compStr1 = compStr1 + str(title['slug'][-6:])                STR = STR + 'n' + ("本次數據一共:" + str(i) + "")                #自己創建微信群,名稱自定              chatroomName = 'Money'  # 群名              itchat.get_chatrooms(update=True)              chatrooms = itchat.search_chatrooms(name=chatroomName)              global compStr0              # print(compStr0)              if len(chatrooms) == 0:                  # print('沒有找到群聊:' + chatroomName)                  exit(0)              else:                  #判斷數據是否變化                  if (compStr1 != compStr0):                      itchat.send_msg(STR, toUserName=chatrooms[0]['UserName'])  # 發送消息                      compStr0 = compStr1              # print(compStr0)      get_url_objcts()      sched = BlockingScheduler()  # 任務調度 每2分鐘觸發 時間自定  sched.add_job(main, 'interval', minutes=2, next_run_time=datetime.now())  sched.start()    itchat.run()

轉載請註明出處 thank you!