寫在最前
程序是為人類服務的,最近正好身邊小伙伴們在做球衣生意,當然是去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 +
'
\n\n
'
+ ((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 +
'
\n\n
'
+ ((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!
?
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061
微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元

