做業要求:https://edu.cnblogs.com/campus/gzcc/GZCC-16SE1/homework/3159mysql
項目內容:web
本項目選擇 淘寶商品類目:零食正則表達式
數量:一共100頁,4400個零食商品sql
篩選條件:天貓、銷量從高到低、價格0元到200元之內chrome
項目目的:數據庫
項目步驟:json
項目環境:api
系統環境:win10 64位數組
工具:pycharm,chrome devTools,Anaconda服務器
由於淘寶網是有反爬蟲機制的,雖然我使用了多線程、修改headers參數,以及使用代理ip等,也考慮到我當前測試環境是使用校園網進行爬取淘寶商品信息的,學校只有一個公網ip,按照以往的經驗,使用校園網作測試環境的話是不容易被封的,但仍然不能保證每次100%爬取,因此我增長了循環爬取,每次循環爬取未爬取成功的頁面,直至全部的頁面所有爬取成功。
淘寶商品頁面上存儲的商品數據是以Json格式存儲的,在這裏我選擇用正則表達式進行解析:
代碼以下:
import re import time import random import requests import pandas as pd from retrying import retry from concurrent.futures import ThreadPoolExecutor start = time.clock() # 開始計時 # 請求頭池 user_agent = [ "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; AcooBrowser; .NET CLR 1.1.4322; .NET CLR 2.0.50727)", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; Acoo Browser; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; " ".NET CLR 3.0.04506)", "Mozilla/4.0 (compatible; MSIE 7.0; AOL 9.5; AOLBuild 4337.35; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR " "2.0.50727)", "Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)", "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR " "3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)", "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; " ".NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)", "Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.2; .NET CLR " "3.0.04506.30)", "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/523.15 (KHTML, like Gecko, Safari/419.3) Arora/0.3 (" "Change: 287 c9dfb30)", "Mozilla/5.0 (X11; U; Linux; en-US) AppleWebKit/527+ (KHTML, like Gecko, Safari/419.3) Arora/0.6", "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.2pre) Gecko/20070215 K-Ninja/2.1.1", "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9) Gecko/20080705 Firefox/3.0 Kapiko/3.0", "Mozilla/5.0 (X11; Linux i686; U;) Gecko/20070322 Kazehakase/0.4.5", "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.8) Gecko Fedora/1.9.0.8-1.fc10 Kazehakase/0.5.6", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.20 (KHTML, like Gecko) Chrome/19.0.1036.7 " "Safari/535.20", "Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; fr) Presto/2.9.168 Version/11.52", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.11 TaoBrowser/2.0 " "Safari/536.11", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.71 Safari/537.1 " "LBBROWSER", "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR " "3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; LBBROWSER)", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E; LBBROWSER)", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.84 Safari/535.11 " "LBBROWSER", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR " "3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)", "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR " "3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; QQBrowser/7.0.3698.400)", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E)", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; SV1; QQDownload 732; .NET4.0C; .NET4.0E; 360SE)", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E)", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR " "3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)", "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.89 Safari/537.1", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.89 Safari/537.1", "Mozilla/5.0 (iPad; U; CPU OS 4_2_1 like Mac OS X; zh-cn) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 " "Mobile/8C148 Safari/6533.18.5", "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0b13pre) Gecko/20110307 Firefox/4.0b13pre", "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:16.0) Gecko/20100101 Firefox/16.0", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11", "Mozilla/5.0 (X11; U; Linux x86_64; zh-CN; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 " "Safari/537.36", ] # 代理ip池 proxies = ['http://125.71.212.25:9000', 'http://202.109.157.47:9000', 'http://47.94.169.110:80', 'http://111.40.84.73:9999', 'http://114.245.221.21:8060', 'http://117.131.235.198:8060'] # plist 爲1-100頁的URL的編號num plist = [] for i in range(1, 101): j = 44 * (i - 1) plist.append(j) listno = plist datatmsp = pd.DataFrame(columns=[]) while True: @retry(stop_max_attempt_number=8) def network_programming(num): url = 'https://s.taobao.com/search?q=%E9%9B%B6%E9%A3%9F&imgfile=&js=1&stats_click=search_radio_tmall%3A1' \ '&initiative_id=staobaoz_20190508&tab=mall&ie=utf8&sort=sale-desc&filter=reserve_price%5B%2C200%5D' \ '&bcoffset=0&p4ppushleft=%2C44&s=' + str(num) random_user_agent = random.choice(user_agent) # 從user_agent池中隨機生成headers random_proxies = random.choice(proxies) # 從代理ip池中隨機生成proxies web = requests.get(url, headers={'user-agent': random_user_agent}, proxies={'http': random_proxies}) web.encoding = 'utf-8' return web # 多線程 def multithreading(): number = listno # 每次爬取未成功爬取的頁 event = [] with ThreadPoolExecutor(max_workers=10) as executor: for result in executor.map(network_programming, number, chunksize=10): event.append(result) return event headers = {"User-Agent": "Mozilla/5.0 (WindowsNT 10.0; WOW64);Chrome/55.0.2883.87 Safari/537.36"} listpg = [] event = multithreading() for i in event: json = re.findall('"auctions":(.*?),"recommendAuctions"', i.text) if len(json): table = pd.read_json(json[0]) datatmsp = pd.concat([datatmsp, table], axis=0, ignore_index=True) pg = re.findall('"pageNum":(.*?),"p4pbottom_up"', i.text)[0] # 記入每一次成功爬取的頁碼 listpg.append(pg) # 將爬取成功的頁碼轉爲url中的num值 lists = [] for a in listpg: b = 44 * (int(a) - 1) lists.append(b) listn = listno listno = [] for p in listn: if p not in lists: listno.append(p) # 當未爬取頁數未0時,終止循環 if len(listno) == 0: break datatmsp.to_excel('datatmsp.xls', index=False) end = time.clock() print("爬取完成 用時:", end - start, 's')
爬取到商品數據我是先以Excel文件的xls格式保存存儲到本地上,方便調試,如下圖1.1是已經爬取到的數據。
圖1.1 商品數據
從本地導入上一步爬取到商品數據:
import pandas as pd datatmsp = pd.read_excel('datatmsp.xls')
查看數據維度:
datatmsp.shape
圖2.1
經過數據維度能夠知道已成功爬取了100頁共4400個商品數據,而每一個商品數據共有21個字段。
接下來對數據缺失值進行分析,代碼以下:
# 數據缺失值分析 # 須要模塊 missingno庫 pip install missingno import missingno as msno msno.bar(datatmsp.sample(len(datatmsp)), figsize=(10, 4))
運行代碼後,發現pid字段和risk字段數據徹底缺失,如圖所示:
圖2.2 數據維度圖
將缺失值過半的列以及重複的行的商品數據將被刪除,代碼以下:
# 刪除缺失值過半的列 half_count = len(datatmsp)/2 datatmsp = datatmsp.dropna(thresh=half_count, axis=1) # 刪除重複行 datatmsp = datatmsp.drop_duplicates()
datatmsp.shape()
數據清洗後數據維度降到只有4389個商品數據,一共19個字段,刪除後的數據維度:
圖2.3 清洗後的數據維度圖
此時數據已經清洗好了,接下來把淘寶商品數據存進mysql數據庫中,由於我用的是私人云服務器上的mysql,在這裏就不寫鏈接參數,因此下面代碼中鏈接數據庫中的參數讀者參考本身的實際狀況進行修改哈。
import pymysql from sqlalchemy import create_engine # 與mysql服務器創建起鏈接 engine = create_engine('mysql+pymysql://{數據庫用戶名}:{密碼}@{ip地址或主機名}:{端口}/{表名}') con = engine.connect() # 將清洗好的商品數據存進mysql datatmsp.to_sql(name='snacks', con=con, if_exists='append', index=False)
數據成功導入mysql:
圖2.4 淘寶商品表
根據這次項目需求,本文只須要取item_loc,raw_title,view_price,view_sales這四列的數據,主要是對商品標題、區域、價格、銷量四個維度進行分析,代碼以下:
# 取出商品標題、區域、價格、銷量四個維度的數據 data = datatmsp[['item_loc', 'raw_title', 'view_price', 'view_sales']] data.head()
圖2.5
接下來,對商品數據進行分析前的預處理,代碼以下:
# 對商品所在地item_loc列中的省份和城市進行拆分,生成province列 data['province'] = data.item_loc.apply(lambda x: x.split()[0]) # 由於直轄市的省份和城市相同,在這裏根據字符長度進行判斷 data['city'] = data.item_loc.apply(lambda x: x.split()[0] if len(x) < 4 else x.split()[1]) # 提取商品銷售量view_sales列中的數組,獲得sales列 def dealSales(x): x = x.split('人')[0] if '萬' in x: if '.' in x: x = x.replace('.', '').replace('萬', '000') else: x = x.replace('萬', '0000') return x.replace('+', '') data['sales'] = data.view_sales.apply(lambda x: dealSales(x)) # 將sales列的數據類型改成int類型 data['sales'] = data.sales.astype('int') # 用province,city替換category,且轉換成與category相同的類型 list_col = ['province', 'city'] for i in list_col: data[i] = data[i].astype('category') # 刪除不用的列 data = data.drop(['item_loc', 'view_sales'], axis=1)
查看預處理後的前十行數據:
圖2.6 數據
使用jieba分詞器,對raw_title列每個商品標題進行分詞,經過停用表StopWords對標題進行去除停用詞。由於下面要統計每一個詞語的個數,因此 爲了準確性,在這裏對過濾後的數據 title_clean 中的每一個list的元素進行去重,即每一個標題被分割後的詞語惟一。代碼以下:
# 將全部商品標題轉換爲list title = data.raw_title.values.tolist() # 對每一個標題進行分詞,使用jieba分詞 import jieba title_s = [] for line in title: title_cut = jieba.lcut(line) title_s.append(title_cut) # 導入停用此表 stopwords = [line.strip() for line in open('StopWords.txt', 'r', encoding='utf-8').readlines()] # 剔除停用詞 title_clean = [] for line in title_s: line_clean = [] for word in line: if word not in stopwords: line_clean.append(word) title_clean.append(line_clean) # 進行去重 title_clean_dist = [] for line in title_clean: line_dist = [] for word in line: if word not in line_dist: line_dist.append(word) title_clean_dist.append(line_dist) # 將 title_clean_dist 轉化爲一個list allwords_clean_dist = [] for line in title_clean_dist: for word in line: allwords_clean_dist.append(word) # 把列表 allwords_clean_dist 轉爲數據框 df_allwords_clean_dist = pd.DataFrame({ 'allwords':allwords_clean_dist }) # 對過濾_去重的詞語 進行分類彙總 word_count = df_allwords_clean_dist.allwords.value_counts().reset_index() word_count.columns = ['word', 'count']
接下來須要對已分詞好的數據進行詞雲可視化,代碼以下:
from wordcloud import WordCloud import matplotlib.pyplot as plt from scipy.misc import imread plt.figure(figsize=(20,10)) # 讀取圖片 pic = imread("貓.png") w_c = WordCloud(font_path="simhei.ttf", background_color="white", mask=pic, max_font_size=100, margin=1) wc = w_c.fit_words({ x[0]:x[1] for x in word_count.head(100).values }) plt.imshow(wc, interpolation='bilinear') plt.axis("off") plt.show()
分析結論:
特產、零食、休閒、小吃等字眼的商品佔比較高;
假如所爬取到的商品標題中含有「糖果」一詞的銷量之和,也就是說求出具備「糖果」關鍵字的商品銷量之和。代碼以下:
import numpy as np # 從新更新索引,以前去重的時候沒有更新數據data的索引,致使部分行缺失值 data = data.reset_index(drop=True) # 不一樣關鍵詞word對應的sales之和的統計分析 w_s_sum = [] for w in word_count.word: i = 0 s_list = [] for t in title_clean_dist: if w in t: s_list.append(data.sales[i]); i+=1 w_s_sum.append(sum(s_list)) # list求和 df_w_s_sum = pd.DataFrame({'w_s_sum':w_s_sum}) # 把 word_count 與對應的 df_w_s_sum 合併爲一個表: df_word_sum = pd.concat([word_count, df_w_s_sum], axis=1, ignore_index=True) df_word_sum.columns = ['word', 'count', 'w_s_sum'] #添加列名
而後對df_word_sum中的word和w_s_sum兩列進行可視化,本文將取銷量排名前30的詞語進行繪圖:
df_word_sum.sort_values('w_s_sum', inplace=True, ascending=True) # 升序 df_w_s = df_word_sum.tail(30) # 取最大的30行數據 import matplotlib from matplotlib import pyplot as plt font = {'family' : 'SimHei'} # 設置字體 matplotlib.rc('font', **font) index = np.arange(df_w_s.word.size) plt.figure(figsize=(10,20)) plt.barh(index, df_w_s.w_s_sum, color='blue', align='center', alpha=0.8) plt.yticks(index, df_w_s.word, fontsize=15) #添加數據標籤 for y, x in zip(index, df_w_s.w_s_sum): plt.text(x, y, '%.0f' %x , ha='left', va='center', fontsize=15) plt.show()
由圖表可知:
1. 休閒零食小吃之類的銷量最高;
2. 組合、整裝商品佔比很高;
3. 從關鍵字能夠看出銷量榜上以網紅品牌爲主。
本文中限定所爬取的零食單品的銷售價格區間在0-200元,在這裏咱們結合自身產品狀況對商品的價格分佈狀況分析,代碼以下:
plt.figure(figsize=(7,5))
plt.hist(data['view_price'], bins=15, color='blue')
plt.xlabel('價格', fontsize=25)
plt.ylabel('商品數量', fontsize=25)
plt.title('不一樣價格對應的商品數量分佈', fontsize=17)
plt.show()
由圖表可知:
1. 商品數量集中在0-50元之間,整體呈現先增後減;
2. 低價位商品居多,價格在12-25元之間的商品最多,次之0-12元,商品最少的在價格160-180元之間;
爲了商品的可視化效果更直觀,在這裏咱們選擇銷量大於100的商品,代碼以下:
data_s = data[data['sales'] > 100] print('銷量100以上的商品佔比:%.3f'%(len(data_s) / len(data))) plt.figure(figsize=(12,8)) plt.hist(data_s['sales'],bins=20, color='blue') # 分二十組 plt.xlabel('銷量', fontsize=25) plt.ylabel('商品數量', fontsize=25) plt.title('不一樣銷量對應的商品數量分佈', fontsize=25) plt.show()
由圖表可知:
1. 銷量100以上的商品接近100%,其中銷量100-18000之間的商品最多;
2. 銷量在18000以上的,商品的數量降低的很厲害,低銷量商品居多。
3. 銷量在60000以上的商品不多。
在這裏咱們結合自身產品狀況對商品價格在0-200元之間對銷量的影響分析:
flg, ax = plt.subplots() ax.scatter(data['view_price'], data['sales'], color='blue') ax.set_xlabel('價格') ax.set_ylabel('銷量') ax.set_title('商品價格對銷量的影響') plt.show()
由圖表可知:
1. 整體趨勢:隨着商品價格增多,其銷量有所減小,商品價格對其銷量有影響的;
2. 價格在0-50之間的商品銷量比較集中,銷量在100-100000之間,價格150-200元之間的商品多數銷量偏少,少數相對較高。
代碼以下:
data['GMV'] = data['price'] * data['sales'] import seaborn as sns sns.regplot(x='price', y='GMV', data=data, color='purple')
由圖表可知:
1. 整體趨勢:由線性迴歸擬合線能夠看出,商品銷售額隨着價格增加呈現緩慢上升趨勢;
2. 多數商品的價格偏高,銷售額也偏低。
代碼以下:
plt.figure(figsize=(12,8)) data.province.value_counts().plot( kind='bar', color='purple') plt.xticks(rotation=0) plt.xlabel('省份') plt.ylabel('數量') plt.title('不一樣省份的商品數量分佈') plt.show()
由圖表可知:
1. 位於上海的商品最多,浙江次之,湖南第三,尤爲是上海的商品數量遠超過浙江、湖南、湖北等地,說明在零食這個子類目上,上海的店鋪居多。
2. 整體趨勢:商品店鋪大部分位於沿海地區以及長江中下游。