據(jù)可視化與銷(xiāo)量預(yù)測(cè)畢設(shè)項(xiàng)目全流程解析)
每年這個(gè)時(shí)候總有一批人被畢業(yè)設(shè)計(jì)折磨得睡不好覺(jué)。如果你點(diǎn)進(jìn)這個(gè)標(biāo)題大概率正在為到底做個(gè)什么題目既能過(guò)盲審、又能寫(xiě)滿(mǎn)論文、還不至于把自己逼瘋而發(fā)愁。Python電商數(shù)據(jù)可視化分析與銷(xiāo)量預(yù)測(cè)這個(gè)方向可以說(shuō)是我見(jiàn)過(guò)性?xún)r(jià)比極高的畢設(shè)選題之一它一個(gè)項(xiàng)目囊括了爬蟲(chóng)采集、數(shù)據(jù)清洗、數(shù)據(jù)庫(kù)設(shè)計(jì)、Web應(yīng)用開(kāi)發(fā)、可視化展示、機(jī)器學(xué)習(xí)建模和算法優(yōu)化一套流程下來(lái)既展示了完整的工程能力又有足夠的技術(shù)深度去應(yīng)付答辯。這篇文章我打算完全按照從零幫你把項(xiàng)目落地的思路來(lái)寫(xiě)把我自己在實(shí)際開(kāi)發(fā)這套系統(tǒng)時(shí)踩過(guò)的坑、試出來(lái)的經(jīng)驗(yàn)、以及最終定型的代方案都攤開(kāi)來(lái)講。無(wú)論你是打算直接照著一個(gè)完整項(xiàng)目抄作業(yè)還是想搞清楚里面每個(gè)環(huán)節(jié)為什么要這樣設(shè)計(jì)這篇內(nèi)容都能給你一個(gè)明確的參考。1. 項(xiàng)目整體設(shè)計(jì)與技術(shù)選型1.1 系統(tǒng)架構(gòu)的三層拆分先把這個(gè)系統(tǒng)拆開(kāi)看電商數(shù)據(jù)可視化分析與銷(xiāo)量預(yù)測(cè)到底是什么。它本質(zhì)上做的是三件事第一從公開(kāi)電商頁(yè)面上把商品數(shù)據(jù)拿下來(lái)第二把拿到的數(shù)據(jù)整理干凈并存儲(chǔ)第三基于這些數(shù)據(jù)做圖表展示和未來(lái)銷(xiāo)量預(yù)測(cè)。對(duì)應(yīng)到代碼層面就是一個(gè)標(biāo)準(zhǔn)的三層架構(gòu)。數(shù)據(jù)采集層負(fù)責(zé)爬取商品名稱(chēng)、價(jià)格、銷(xiāo)量、評(píng)論數(shù)、店鋪信息、上架時(shí)間等原始數(shù)據(jù)。這里我用的是Selenium框架它可以模擬真實(shí)瀏覽器操作能扛住大部分動(dòng)態(tài)加載頁(yè)面的反爬限制。數(shù)據(jù)處理與存儲(chǔ)層把采集到的臟數(shù)據(jù)進(jìn)行清洗、去重、格式化然后存入數(shù)據(jù)庫(kù)?;A(chǔ)數(shù)據(jù)量級(jí)用MySQL就足夠但如果你想給系統(tǒng)加分、或者數(shù)據(jù)量真的很大可以在這一層把Hadoop生態(tài)中的Hive或HDFS引入進(jìn)來(lái)做分布式存儲(chǔ)和離線(xiàn)統(tǒng)計(jì)。應(yīng)用展示與預(yù)測(cè)層用Flask作為Web框架搭建后臺(tái)管理系統(tǒng)和前端可視化頁(yè)面。同時(shí)把訓(xùn)練好的機(jī)器學(xué)習(xí)模型封裝成預(yù)測(cè)接口用戶(hù)在頁(yè)面上選擇商品或者輸入時(shí)間范圍就能得到未來(lái)的銷(xiāo)量走勢(shì)。這個(gè)三層設(shè)計(jì)不是拍腦袋定的。做畢業(yè)設(shè)計(jì)有一個(gè)很重要的原則每一層都要有獨(dú)立的技術(shù)難點(diǎn)但又不能復(fù)雜到一個(gè)人做不完。上面的拆分剛好滿(mǎn)足每個(gè)模塊都可以單獨(dú)拎出來(lái)寫(xiě)幾頁(yè)論文這個(gè)需求同時(shí)還避免了把所有代碼堆在一個(gè)腳本里、最后自己都看不下去的尷尬局面。1.2 為什么選了Flask而不是Django或Spring Boot這個(gè)問(wèn)題我在做技術(shù)選型時(shí)糾結(jié)了很久最后選Flask不是因?yàn)樗荘ython社區(qū)里最好的Web框架而是因?yàn)樗鼘?duì)畢設(shè)場(chǎng)景來(lái)說(shuō)是最合適的。Flask的核心優(yōu)勢(shì)在于輕。一個(gè)完整的可視化后臺(tái)用Flask只需要少量的路由代碼和模板渲染代碼就能跑起來(lái)。Django雖然自帶了Admin后臺(tái)、ORM權(quán)限體系這些現(xiàn)成的功能但是它的學(xué)習(xí)曲線(xiàn)比較陡并且很多內(nèi)置功能其實(shí)你根本用不上反而增加了項(xiàng)目的理解成本。Spring Boot就更不用說(shuō)了Java系的重型框架對(duì)一個(gè)以Python為主線(xiàn)的項(xiàng)目來(lái)說(shuō)屬于自己給自己挖坑。說(shuō)得直白一點(diǎn)畢業(yè)設(shè)計(jì)的核心目標(biāo)是用最少的時(shí)間成本把該有的功能全部落地并能在答辯時(shí)講清楚。Flask的路由裝飾器、Jinja2模板引擎和靈活的擴(kuò)展生態(tài)恰好滿(mǎn)足這個(gè)目標(biāo)。我自己的項(xiàng)目里Flask負(fù)責(zé)三塊內(nèi)容一是提供可視化頁(yè)面的路由和數(shù)據(jù)接口二是接收前端圖表組件傳過(guò)來(lái)的篩選參數(shù)并返回JSON格式的數(shù)據(jù)三是調(diào)用訓(xùn)練好的預(yù)測(cè)模型并返回預(yù)測(cè)結(jié)果。1.3 Hadoop在項(xiàng)目里的真實(shí)角色選做加分項(xiàng)標(biāo)題里提了hadoop這個(gè)關(guān)鍵詞我在設(shè)計(jì)項(xiàng)目時(shí)也認(rèn)真考慮過(guò)到底要不要真上Hadoop。最后我的結(jié)論是可以作為數(shù)據(jù)處理的可選增強(qiáng)模塊但不要讓它占據(jù)主鏈路。原因是Hadoop本身是一套分布式生態(tài)涉及到HDFS文件存儲(chǔ)、YARN資源調(diào)度、MapReduce計(jì)算框架。如果你的商品數(shù)據(jù)量級(jí)在幾十萬(wàn)條以?xún)?nèi)MySQL配合索引優(yōu)化處理起來(lái)根本沒(méi)有壓力強(qiáng)行上Hadoop反而會(huì)讓項(xiàng)目變得臃腫寫(xiě)代碼和調(diào)試的時(shí)間翻倍。但為什么標(biāo)題里還是要提它因?yàn)樗且粋€(gè)很好的加分點(diǎn)和答辯話(huà)題。你可以這樣設(shè)計(jì)在數(shù)據(jù)采集層定期把MySQL中的歷史數(shù)據(jù)同步到Hadoop的HDFS上然后用Hive寫(xiě)一些簡(jiǎn)單的SQL做離線(xiàn)統(tǒng)計(jì)比如計(jì)算每個(gè)品類(lèi)的月度銷(xiāo)量匯總、價(jià)格波動(dòng)區(qū)間。這些統(tǒng)計(jì)結(jié)果再回流到MySQL的匯總表中給可視化頁(yè)面提供數(shù)據(jù)支撐。這樣做的話(huà)你就有了一個(gè)完整的關(guān)系型數(shù)據(jù)庫(kù)分布式存儲(chǔ)的混合架構(gòu)既保證了實(shí)時(shí)查詢(xún)的性能又展示了大數(shù)據(jù)處理的能力。答辯時(shí)老師問(wèn)你為什么用Hadoop你就能說(shuō)出這套分層的理由這比空喊口號(hào)要扎實(shí)得多。2. 商品數(shù)據(jù)采集層Selenium的實(shí)踐與踩坑2.1 為什么選擇Selenium驅(qū)動(dòng)瀏覽器采集電商平臺(tái)的數(shù)據(jù)加載方式和普通靜態(tài)網(wǎng)頁(yè)完全不同。你直接在代碼里用requests請(qǐng)求頁(yè)面URL拿到的往往是空殼的HTML——真正的內(nèi)容是頁(yè)面加載后用JavaScript異步請(qǐng)求接口才渲染出來(lái)的。想拿到這些數(shù)據(jù)有三種常見(jiàn)的思路第一種是直接分析頁(yè)面背后的API接口自己構(gòu)造參數(shù)去請(qǐng)求JSON數(shù)據(jù)。這個(gè)方法效率最高、對(duì)服務(wù)器壓力最小但需要你花時(shí)間逆向接口的加密參數(shù)和簽名邏輯某些平臺(tái)的加密算法還挺復(fù)雜。第二種是使用Selenium這類(lèi)瀏覽器自動(dòng)化工具啟動(dòng)一個(gè)真實(shí)的瀏覽器去訪問(wèn)頁(yè)面等待頁(yè)面渲染完再抓取DOM節(jié)點(diǎn)中的數(shù)據(jù)。這種方法實(shí)現(xiàn)簡(jiǎn)單抗接口變動(dòng)能力強(qiáng)缺點(diǎn)是速度慢、資源占用大。第三種是無(wú)頭瀏覽器加代理工具的組合方案屬于進(jìn)階玩法兼顧了部分效率和偽裝效果但配置復(fù)雜度也上來(lái)了。對(duì)于畢業(yè)設(shè)計(jì)階段我最推薦Selenium。原因很現(xiàn)實(shí)它的代碼邏輯直白就是打開(kāi)頁(yè)面、等待加載、定位元素、提取文本這一套流程同時(shí)它繞開(kāi)了大部分需要逆向JS加密的難題能讓你把精力放在后面的數(shù)據(jù)處理和預(yù)測(cè)模型上而不是栽在爬蟲(chóng)這一關(guān)。2.2 Selenium實(shí)操定位策略與數(shù)據(jù)提取模板在寫(xiě)采集腳本時(shí)最核心的一塊就是元素的定位和數(shù)據(jù)提取。以采集商品列表頁(yè)為例核心參考代碼如下from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC import time options webdriver.ChromeOptions() # 避免被識(shí)別為自動(dòng)化工具的基礎(chǔ)配置 options.add_experimental_option(excludeSwitches, [enable-automation]) options.add_experimental_option(useAutomationExtension, False) driver webdriver.Chrome(optionsoptions) def fetch_product_list(page_url): driver.get(page_url) # 顯式等待關(guān)鍵元素出現(xiàn)不要用固定sleep硬等 WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.CSS_SELECTOR, div.product-item)) ) # 讓頁(yè)面充分渲染尤其是有滾動(dòng)加載的頁(yè)面 for _ in range(3): driver.execute_script(window.scrollTo(0, document.body.scrollHeight);) time.sleep(1) items driver.find_elements(By.CSS_SELECTOR, div.product-item) results [] for item in items: try: title item.find_element(By.CSS_SELECTOR, h3.product-title).text.strip() price item.find_element(By.CSS_SELECTOR, span.price).text.strip() sales item.find_element(By.CSS_SELECTOR, span.sales).text.strip() results.append({title: title, price: price, sales: sales}) except Exception: # 個(gè)別商品信息不全跳過(guò) continue return results # 翻頁(yè)采集示例只采集一定頁(yè)數(shù)避免請(qǐng)求過(guò)快 for page in range(1, 6): url fhttps://example.com/search?keyword無(wú)線(xiàn)耳機(jī)page{page} data fetch_product_list(url) print(f第{page}頁(yè)采集到{len(data)}條數(shù)據(jù)) time.sleep(3)這里有幾個(gè)關(guān)鍵細(xì)節(jié)值得展開(kāi)說(shuō)一下。首先是顯式等待WebDriverWait而不是隱式等待或固定sleep。固定sleep的問(wèn)題在于網(wǎng)絡(luò)快的時(shí)候浪費(fèi)大量時(shí)間空等網(wǎng)絡(luò)慢的時(shí)候頁(yè)面還沒(méi)加載完就開(kāi)始找元素直接拋異常。合理設(shè)置為最多等待10秒頁(yè)面元素一旦出現(xiàn)就立刻執(zhí)行下一步。其次是滾動(dòng)頁(yè)面。很多電商列表頁(yè)不是一次性把所有商品渲染出來(lái)的而是滾動(dòng)到頁(yè)面底部才觸發(fā)加載更多。我在代碼里用execute_script強(qiáng)行下拉了三次每次間隔一秒這樣能確保動(dòng)態(tài)加載的商品都出現(xiàn)在DOM里。最后是字段缺失的容錯(cuò)處理。實(shí)際采集時(shí)經(jīng)常遇到某個(gè)商品缺價(jià)格或缺銷(xiāo)量如果不對(duì)每個(gè)字段單獨(dú)做try異常捕獲整個(gè)腳本會(huì)因?yàn)橐粋€(gè)臟數(shù)據(jù)而崩潰。上面的代碼里把每個(gè)字段的提取都包在內(nèi)層try里失敗就跳過(guò)這條保證采集任務(wù)的連續(xù)性。2.3 Agent代理池與請(qǐng)求頻率控制標(biāo)題里的Agent這個(gè)詞我理解成兩層含義一是采集請(qǐng)求需要通過(guò)代理IP來(lái)輪換出口地址避免某個(gè)IP因請(qǐng)求頻率過(guò)高被平臺(tái)限制二是從工程架構(gòu)來(lái)講可以把采集任務(wù)設(shè)計(jì)成多節(jié)點(diǎn)分布式采集每個(gè)采集節(jié)點(diǎn)就是一個(gè)Agent。畢設(shè)階段實(shí)現(xiàn)分布式采集有點(diǎn)大動(dòng)干戈了但代理池的思路完全可以落地。Python的requests庫(kù)可以通過(guò)proxies參數(shù)傳入代理地址Selenium也可以在啟動(dòng)瀏覽器時(shí)通過(guò)selenium-wire或Chrome的proxy擴(kuò)展來(lái)設(shè)置代理。具體做法是準(zhǔn)備一批代理IP列表每次請(qǐng)求隨機(jī)取一個(gè)使用。但要注意一點(diǎn)代理質(zhì)量參差不齊有些免費(fèi)代理連接超時(shí)嚴(yán)重反而拖慢了采集效率。我的建議是不要過(guò)度追求代理池的規(guī)模而是準(zhǔn)備一個(gè)小型的、經(jīng)過(guò)連通性測(cè)試的高質(zhì)量代理池就夠用了。同時(shí)絕對(duì)要控制請(qǐng)求頻率每采集一頁(yè)至少間隔2到3秒高峰期再拉長(zhǎng)。提示在對(duì)合規(guī)性要求嚴(yán)格的場(chǎng)景中采集前一定要仔細(xì)閱讀目標(biāo)平臺(tái)的條款只采集公開(kāi)可訪問(wèn)的信息并控制請(qǐng)求頻率避免對(duì)目標(biāo)站點(diǎn)造成壓力。項(xiàng)目中建議只采集少量數(shù)據(jù)用于研究演示不要大規(guī)模、高頻率抓取。祝你的項(xiàng)目順順利利再說(shuō)一句實(shí)用經(jīng)驗(yàn)。Selenium啟動(dòng)的瀏覽器窗口是會(huì)被網(wǎng)站的前端JavaScript檢測(cè)到的常見(jiàn)的檢測(cè)手段是檢查navigator.webdriver屬性是否為true。上面的代碼里通過(guò)excludeSwitches參數(shù)做了一層基礎(chǔ)偽裝但如果你在實(shí)操中發(fā)現(xiàn)還是被識(shí)別可以再?lài)L試給瀏覽器注入一段JavaScript來(lái)覆蓋webdriver屬性。這一塊屬于道高一尺魔高一丈的對(duì)抗環(huán)節(jié)做到基礎(chǔ)偽裝加上頻率控制已經(jīng)夠用了不建議在畢設(shè)階段陷入太深。3. 數(shù)據(jù)清洗與特征工程3.1 真實(shí)數(shù)據(jù)有多臟清洗就有多重要我經(jīng)常在指導(dǎo)別人的時(shí)候強(qiáng)調(diào)一句話(huà)你后面預(yù)測(cè)模型的效果好不好從數(shù)據(jù)清洗這一步就已經(jīng)注定了。很多人采集完數(shù)據(jù)就急著寫(xiě)模型結(jié)果訓(xùn)練出來(lái)的模型準(zhǔn)確率慘不忍睹然后開(kāi)始懷疑算法不行——其實(shí)是數(shù)據(jù)根本沒(méi)過(guò)關(guān)。電商數(shù)據(jù)常見(jiàn)的臟數(shù)據(jù)問(wèn)題我列幾個(gè)典型的你就明白了銷(xiāo)量字段是文本格式比如500人付款或月銷(xiāo)1.2萬(wàn)需要正則表達(dá)式把數(shù)字提取出來(lái)并統(tǒng)一單位價(jià)格字段里混入了促銷(xiāo)活動(dòng)標(biāo)識(shí)比如原價(jià)后面跟了個(gè)劃線(xiàn)價(jià)同一件商品出現(xiàn)在多個(gè)分類(lèi)頁(yè)里造成重復(fù)記錄某些熱門(mén)商品數(shù)據(jù)被采集了多次但每次采集時(shí)價(jià)格和銷(xiāo)量都不同需要決定取最新還是取平均商品的標(biāo)題很長(zhǎng)里面塞滿(mǎn)了各種營(yíng)銷(xiāo)詞例如正品保障限時(shí)特惠對(duì)文本分析來(lái)說(shuō)都是噪聲。針對(duì)這些情況我會(huì)在項(xiàng)目里專(zhuān)門(mén)寫(xiě)一個(gè)數(shù)據(jù)清洗模塊處理流程如下import re import pandas as pd def clean_sales(value): # 示例把月銷(xiāo)1.2萬(wàn)轉(zhuǎn)為12000把500人付款轉(zhuǎn)為500 if not isinstance(value, str): return value value value.replace(人付款, ).replace(月銷(xiāo), ) if 萬(wàn) in value: return int(float(value.replace(萬(wàn), )) * 10000) return int(value) def clean_price(value): # 只保留數(shù)字和小數(shù)點(diǎn)去掉貨幣符號(hào) match re.search(r\d\.?\d*, str(value)) return float(match.group()) if match else None df pd.read_csv(raw_products.csv) df[sales_num] df[sales_text].apply(clean_sales) df[price_num] df[price_text].apply(clean_price) df df.drop_duplicates(subset[title, shop_name], keeplast) df df.dropna(subset[price_num, sales_num]) df df[df[price_num] 0]這里面最關(guān)鍵的一步是去重邏輯。我自己的采集中發(fā)現(xiàn)同一個(gè)商品如果被多個(gè)關(guān)鍵詞的搜索結(jié)果共享會(huì)在數(shù)據(jù)里出現(xiàn)很多次。通過(guò)標(biāo)題加店鋪名的組合作為去重鍵保留最后采集到的那條記錄這是最穩(wěn)妥的做法因?yàn)樽詈笠淮尾杉臄?shù)據(jù)在時(shí)間上最新更能反映商品的當(dāng)前狀態(tài)。3.2 銷(xiāo)量預(yù)測(cè)的特征工程怎么做清洗完之后我們需要思考一個(gè)問(wèn)題預(yù)測(cè)一件商品未來(lái)某天的銷(xiāo)量應(yīng)該用哪些信息作為輸入這就是特征工程要解決的事。我從實(shí)際項(xiàng)目中總結(jié)的經(jīng)驗(yàn)是這些特征通常分為四類(lèi)第一類(lèi)是商品基礎(chǔ)特征包括價(jià)格、評(píng)論數(shù)、評(píng)分、所屬品類(lèi)、是否參加促銷(xiāo)。這類(lèi)特征刻畫(huà)了商品自身的屬性是模型學(xué)習(xí)什么樣的商品好賣(mài)的主要依據(jù)。第二類(lèi)是時(shí)間特征包括距離上架的天數(shù)、星期幾、是否節(jié)假日、是否月初月末。電商銷(xiāo)量有明顯的周期性周末通常高于工作日節(jié)假日前后促銷(xiāo)活動(dòng)密集銷(xiāo)量會(huì)出現(xiàn)脈沖式上漲。如果不把時(shí)間特征告訴模型模型很難自己從數(shù)據(jù)里悟出這層規(guī)律。第三類(lèi)是歷史銷(xiāo)量統(tǒng)計(jì)特征包括過(guò)去7天平均銷(xiāo)量、過(guò)去30天銷(xiāo)量標(biāo)準(zhǔn)差、最近一天的銷(xiāo)量變化率。這類(lèi)特征本質(zhì)上是把時(shí)間序列的信息轉(zhuǎn)化為表格型特征也是預(yù)測(cè)效果最明顯的幾個(gè)特征之一。第四類(lèi)是競(jìng)爭(zhēng)環(huán)境特征包括同品類(lèi)商品的平均價(jià)格、同品類(lèi)商品的銷(xiāo)量排名百分位。商品之間的競(jìng)爭(zhēng)關(guān)系會(huì)影響個(gè)體的銷(xiāo)量表現(xiàn)但這個(gè)特征在畢設(shè)階段可以做成簡(jiǎn)化版本。這四類(lèi)特征匯總之后每一行就是一個(gè)商品在某天的特征向量每一行的目標(biāo)值就是商品在該天的實(shí)際銷(xiāo)量。整理成這種格式之后后面的機(jī)器學(xué)習(xí)模型就能直接吃進(jìn)去了。3.3 MySQL與Hadoop的存儲(chǔ)劃分關(guān)于存儲(chǔ)我前面提到過(guò)MySQL為主、Hadoop為可選增強(qiáng)。這里具體說(shuō)說(shuō)數(shù)據(jù)落在MySQL時(shí)的表結(jié)構(gòu)設(shè)計(jì)。我會(huì)把數(shù)據(jù)分成三張表商品基本信息表product_info、歷史銷(xiāo)量明細(xì)表sales_history、數(shù)據(jù)采集日志表crawl_log。商品信息表存儲(chǔ)固定的屬性字段一張記錄對(duì)應(yīng)一個(gè)商品歷史銷(xiāo)量明細(xì)表是長(zhǎng)表結(jié)構(gòu)一條記錄對(duì)應(yīng)一個(gè)商品在某個(gè)日期的銷(xiāo)量和價(jià)格采集日志表用來(lái)記錄每次采集的時(shí)間、數(shù)量、耗時(shí)、異常情況方便排查問(wèn)題。在設(shè)計(jì)銷(xiāo)量明細(xì)表時(shí)一定要在商品ID和日期上建立聯(lián)合索引。因?yàn)楹罄m(xù)所有的特征工程查詢(xún)都是根據(jù)商品ID查它的歷史銷(xiāo)量序列沒(méi)有索引的話(huà)數(shù)據(jù)量上來(lái)之后一個(gè)簡(jiǎn)單的查詢(xún)可能就要好幾秒鐘。至于Hadoop的引入方式我推薦一個(gè)輕量級(jí)做法每天采集結(jié)束后寫(xiě)一個(gè)同步腳本把銷(xiāo)售明細(xì)表中的數(shù)據(jù)以追加的方式寫(xiě)入HDFS路徑然后使用Hive創(chuàng)建一張外部表指向這個(gè)路徑。這樣你在論文里就可以寫(xiě)道系統(tǒng)通過(guò)Hadoop生態(tài)實(shí)現(xiàn)了歷史數(shù)據(jù)的分布式存儲(chǔ)為后續(xù)擴(kuò)大數(shù)據(jù)規(guī)模提供了橫向擴(kuò)展能力。不需要在畢設(shè)階段真的用MapReduce寫(xiě)復(fù)雜的分析任務(wù)但架構(gòu)的完整性和前瞻性已經(jīng)體現(xiàn)出來(lái)了。4. 可視化分析與銷(xiāo)量預(yù)測(cè)模型4.1 可視化框架選擇從ECharts到自繪圖表可視化是整個(gè)系統(tǒng)最直觀的部分也是答辯時(shí)最容易讓老師眼前一亮的模塊。我在項(xiàng)目里用的是ECharts搭配Python的Pyecharts封裝庫(kù)。如果你對(duì)前端技術(shù)不太熟悉用Pyecharts是明智的選擇你可以在Python代碼里直接鏈?zhǔn)秸{(diào)用圖表組件生成HTML文件或者嵌入到Flask的模板中。它支持折線(xiàn)圖、柱狀圖、餅圖、熱力圖、地圖等一系列常用圖表調(diào)用邏輯也很清晰。下面是一段生成銷(xiāo)量趨勢(shì)圖的示例from pyecharts.charts import Line from pyecharts import options as opts line ( Line() .add_xaxis(date_list) .add_yaxis(實(shí)際銷(xiāo)量, actual_sales, is_smoothTrue) .add_yaxis(預(yù)測(cè)銷(xiāo)量, predicted_sales, is_smoothTrue) .set_global_opts( title_optsopts.TitleOpts(title商品銷(xiāo)量趨勢(shì)與實(shí)際對(duì)比), tooltip_optsopts.TooltipOpts(triggeraxis), legend_optsopts.LegendOpts(pos_toptop), ) ) line.render(templates/charts/sales_trend.html)生成的HTML文件可以直接被Flask的模板引擎引用也可以利用render_embed()函數(shù)把圖表內(nèi)容直接嵌入頁(yè)面。實(shí)際項(xiàng)目里我會(huì)在頁(yè)面左側(cè)放商品篩選器右側(cè)放圖表區(qū)域用戶(hù)每切換一個(gè)商品前端就向后端發(fā)起異步請(qǐng)求后端返回該商品的銷(xiāo)量序列和預(yù)測(cè)結(jié)果前端動(dòng)態(tài)更新圖表。除了折線(xiàn)圖還有幾個(gè)圖表是可視化的標(biāo)配價(jià)格區(qū)間分布用柱狀圖品類(lèi)銷(xiāo)量占比用餅圖商品銷(xiāo)量排名用橫向條形圖。把這些圖表組合在一個(gè)數(shù)據(jù)看板頁(yè)面里給人的直觀感受就是這個(gè)項(xiàng)目做了完整的數(shù)據(jù)分析閉環(huán)這對(duì)論文評(píng)閱和答辯印象分都有不小的幫助。4.2 銷(xiāo)量預(yù)測(cè)模型怎么選從線(xiàn)性回歸到XGBoost銷(xiāo)量預(yù)測(cè)是本項(xiàng)目的算法核心也是論文里需要花大量篇幅去介紹的部分。我會(huì)建議你在項(xiàng)目里做多個(gè)模型的橫向?qū)Ρ榷皇侵慌芤粋€(gè)模型就交差。我先說(shuō)最簡(jiǎn)單也最穩(wěn)妥的基線(xiàn)模型——線(xiàn)性回歸。它的邏輯非常直白就是假設(shè)銷(xiāo)量和各個(gè)特征之間存在線(xiàn)性關(guān)系。線(xiàn)性回歸的效果通常不會(huì)特別驚艷但它有以下好處一是訓(xùn)練速度快幾乎不用調(diào)參二是結(jié)果可解釋性強(qiáng)每個(gè)特征前面的系數(shù)能直接說(shuō)明該特征對(duì)銷(xiāo)量的影響方向和幅度三是可以作為一個(gè)基準(zhǔn)后續(xù)的復(fù)雜模型效果必須比它好這才有說(shuō)服力。然后是樹(shù)模型這個(gè)應(yīng)該是你真正的主力模型。在實(shí)際項(xiàng)目中隨機(jī)森林和XGBoost或者LightGBM之間我更推薦直接上XGBoost因?yàn)樗鼘?duì)特征工程的要求相對(duì)寬容能夠自動(dòng)捕捉特征之間的非線(xiàn)性關(guān)系還帶正則化項(xiàng)防止過(guò)擬合。用一批特征數(shù)據(jù)訓(xùn)練XGBoost回歸模型核心代碼大概是這樣import xgboost as xgb from sklearn.model_selection import train_test_split from sklearn.metrics import mean_absolute_error, r2_score X feature_df.drop(sales, axis1) y feature_df[sales] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) model xgb.XGBRegressor( n_estimators300, max_depth6, learning_rate0.05, subsample0.8, colsample_bytree0.8 ) model.fit(X_train, y_train) y_pred model.predict(X_test) print(MAE:, mean_absolute_error(y_test, y_pred)) print(R2:, r2_score(y_test, y_pred))這里有幾個(gè)必須講清楚的參數(shù)n_estimators代表樹(shù)的數(shù)量太小了學(xué)習(xí)不充分太大了容易過(guò)擬合而且訓(xùn)練速度慢。我一般用300作為一個(gè)起點(diǎn)再結(jié)合早停機(jī)制確定最優(yōu)值。max_depth是樹(shù)的深度控制模型的復(fù)雜度太深會(huì)過(guò)擬合畢設(shè)數(shù)據(jù)量不大的話(huà)6左右是安全范圍。learning_rate是學(xué)習(xí)率調(diào)小一點(diǎn)能提升模型精度但相應(yīng)地需要更多樹(shù)來(lái)擬合0.05到0.1之間是常見(jiàn)的取值區(qū)間。subsample和colsample_bytree分別是行采樣和列采樣比例它們的存在能讓每棵樹(shù)看到的數(shù)據(jù)不完全相同增加模型的多樣性降低過(guò)擬合風(fēng)險(xiǎn)。以上這些都是可以寫(xiě)進(jìn)論文的參數(shù)分析內(nèi)容比單純貼一段訓(xùn)練代碼要有深度得多。4.3 Deep Learning要不要硬上LSTM很多學(xué)生會(huì)糾結(jié)一個(gè)點(diǎn)時(shí)間序列預(yù)測(cè)是不是得上LSTM才算高級(jí)我的回答是看你的數(shù)據(jù)量。LSTM長(zhǎng)短期記憶網(wǎng)絡(luò)是循環(huán)神經(jīng)網(wǎng)絡(luò)的一種變體非常擅長(zhǎng)捕捉序列數(shù)據(jù)中的長(zhǎng)期依賴(lài)關(guān)系。如果項(xiàng)目的銷(xiāo)量數(shù)據(jù)跨度超過(guò)一年、且是按天記錄的LSTM也許能比XGBoost多學(xué)到一些時(shí)間模式。但畢設(shè)項(xiàng)目讓你能采集到的數(shù)據(jù)往往只有近幾個(gè)月樣本量可能就幾百個(gè)時(shí)間點(diǎn)這種情況下LSTM不僅訓(xùn)練慢、調(diào)參復(fù)雜效果也未必比XGBoost好。我的建議是如果你想把項(xiàng)目拔高一個(gè)層次可以設(shè)計(jì)一個(gè)對(duì)比實(shí)驗(yàn)——在同樣的數(shù)據(jù)上分別訓(xùn)練線(xiàn)性回歸、XGBoost和LSTM三種模型用評(píng)估指標(biāo)展示各自的優(yōu)劣并分析造成差異的原因。最后得出結(jié)論在當(dāng)前小規(guī)模數(shù)據(jù)集上XGBoost在精度和穩(wěn)定性上表現(xiàn)最好LSTM雖然作為深度學(xué)習(xí)方法具備更強(qiáng)的序列建模能力但受限于數(shù)據(jù)規(guī)模和訓(xùn)練成本暫不采用。這個(gè)結(jié)論寫(xiě)在論文里非常合理也體現(xiàn)了你對(duì)不同模型的認(rèn)知深度。4.4 算法優(yōu)化從網(wǎng)格搜索到特征篩選標(biāo)題里的算法優(yōu)化四個(gè)字落到具體操作上我總結(jié)為三個(gè)層面。第一個(gè)層面是超參數(shù)優(yōu)化。用網(wǎng)格搜索或者隨機(jī)搜索來(lái)尋找更優(yōu)的模型參數(shù)組合核心代碼是Sklearn的GridSearchCV。典型案例就是在max_depth、n_estimators、learning_rate等參數(shù)上定義候選值范圍然后用交叉驗(yàn)證去評(píng)估參數(shù)組合的表現(xiàn)。交叉驗(yàn)證的作用是防止參數(shù)恰好在一個(gè)測(cè)試集上表現(xiàn)好、換個(gè)數(shù)據(jù)就崩掉它把訓(xùn)練數(shù)據(jù)切成多份輪流驗(yàn)證結(jié)果更有說(shuō)服力。第二個(gè)層面是特征選擇。不是所有特征都對(duì)預(yù)測(cè)有幫助有些特征甚至?xí)a(chǎn)生噪聲。常見(jiàn)做法有兩個(gè)一是用樹(shù)模型自帶的feature_importances_屬性查看特征重要性把排名靠后的特征去掉再訓(xùn)練一次二是用遞歸特征消除RFE或者基于正則化的方法篩選特征。實(shí)操中我發(fā)現(xiàn)歷史7天平均銷(xiāo)量和上架天數(shù)幾乎總是排在特征重要性的最前面而評(píng)論內(nèi)容長(zhǎng)度這類(lèi)文本型特征往往不重要。第三個(gè)層面是模型融合。如果時(shí)間充??梢宰龊?jiǎn)單的加權(quán)融合把XGBoost和線(xiàn)性回歸的預(yù)測(cè)結(jié)果按比例加權(quán)相加。由于兩個(gè)模型的預(yù)測(cè)誤差模式不完全一致融合后的結(jié)果通常比單個(gè)模型更穩(wěn)定。這是一種很實(shí)用的工程技巧答辯時(shí)也能拿出實(shí)操數(shù)據(jù)來(lái)講解優(yōu)化前后的效果對(duì)比。5. 常見(jiàn)問(wèn)題排查與實(shí)操技巧5.1 高頻問(wèn)題速查表我把這個(gè)項(xiàng)目從零到一搭建過(guò)程中最容易踩的坑整理成了一張表都是我自己以及找我交流過(guò)方案的同學(xué)實(shí)測(cè)碰到的真問(wèn)題。問(wèn)題現(xiàn)象根本原因解決思路Selenium啟動(dòng)后瀏覽器頁(yè)面空白Chrome驅(qū)動(dòng)版本不匹配查看本地Chrome版本到驅(qū)動(dòng)下載源下載對(duì)應(yīng)版本并配置環(huán)境變量頁(yè)面元素定位不到頁(yè)面未加載完或者元素在frame中換用WebDriverWait顯式等待檢查是否在iframe內(nèi)需要先切換frame采集的中文亂碼控制臺(tái)編碼問(wèn)題或頁(yè)面編碼識(shí)別錯(cuò)誤設(shè)置Python輸出編碼為utf-8響應(yīng)內(nèi)容指定正確字符集銷(xiāo)量字段五花八門(mén)電商頁(yè)面展示形態(tài)不統(tǒng)一寫(xiě)多個(gè)正則規(guī)則覆蓋人付款月銷(xiāo)已售等常用格式MySQL插入數(shù)據(jù)報(bào)主鍵沖突商品重復(fù)采集使用INSERT ... ON DUPLICATE KEY UPDATE做增量更新訓(xùn)練模型時(shí)特征全是NaN數(shù)據(jù)清洗環(huán)節(jié)沒(méi)有處理缺失值對(duì)數(shù)值特征用中位數(shù)填充對(duì)類(lèi)別特征用眾數(shù)填充預(yù)測(cè)結(jié)果全是同一個(gè)值模型欠擬合或特征與目標(biāo)值無(wú)關(guān)檢查特征與銷(xiāo)量的相關(guān)系數(shù)增加歷史銷(xiāo)量統(tǒng)計(jì)特征圖表在Flask頁(yè)面不顯示靜態(tài)資源路徑配置錯(cuò)誤確認(rèn)Flask的static目錄指向包含ECharts的JS文件夾5.2 獨(dú)家避坑經(jīng)驗(yàn)第一永遠(yuǎn)不要企圖把采集、清洗、建模、展示一股腦寫(xiě)完再調(diào)試。正確做法是分模塊開(kāi)發(fā)每個(gè)模塊單獨(dú)能跑通了再串聯(lián)。你可能會(huì)覺(jué)得這樣慢實(shí)際上這是最快的路。一個(gè)模塊出問(wèn)題時(shí)你能一眼定位到問(wèn)題在哪而不是在幾百行雜亂的代碼里大海撈針。第二關(guān)于數(shù)據(jù)庫(kù)的設(shè)計(jì)哪怕你不確定后面會(huì)不會(huì)用到某些字段也建議先預(yù)留。我在項(xiàng)目中后期遇到過(guò)這種情況寫(xiě)特征工程時(shí)需要用到商品上架日期但是原始表設(shè)計(jì)時(shí)沒(méi)加這個(gè)字段最后只能回頭重新采集數(shù)據(jù)。數(shù)據(jù)量小時(shí)還能忍受數(shù)據(jù)量大一點(diǎn)再補(bǔ)采時(shí)間和流量成本都很難受。第三整個(gè)系統(tǒng)里最不能出錯(cuò)的環(huán)節(jié)不是模型而是數(shù)據(jù)字段的類(lèi)型統(tǒng)一。特征矩陣?yán)锶绻麚诫s了字符串、空值和不同的數(shù)值精度模型訓(xùn)練時(shí)會(huì)報(bào)各種匪夷所思的錯(cuò)。強(qiáng)烈建議在每個(gè)環(huán)節(jié)的末尾都打印一行DataFrame的信息摘要包含每列的數(shù)據(jù)類(lèi)型和缺失值數(shù)量用不了多少時(shí)間卻能省掉無(wú)數(shù)排查過(guò)程的痛苦。第四答辯演示時(shí)一定要準(zhǔn)備一份穩(wěn)定的演示數(shù)據(jù)不要在演示現(xiàn)場(chǎng)臨時(shí)跑爬蟲(chóng)。采集過(guò)程受網(wǎng)絡(luò)、目標(biāo)站點(diǎn)頁(yè)面結(jié)構(gòu)變化的影響太大一旦現(xiàn)場(chǎng)翻車(chē)會(huì)非常被動(dòng)。正確做法是把采集好的數(shù)據(jù)固化到數(shù)據(jù)庫(kù)里演示時(shí)直接走讀數(shù)據(jù)庫(kù)→可視化→調(diào)模型預(yù)測(cè)的流程整個(gè)過(guò)程穩(wěn)定可控。6. 寫(xiě)在最后的經(jīng)驗(yàn)總結(jié)這個(gè)項(xiàng)目做完之后我最大的體會(huì)是畢設(shè)項(xiàng)目不一定要追求大而全但一定要形成邏輯閉環(huán)。從數(shù)據(jù)采集到數(shù)據(jù)清洗從數(shù)據(jù)庫(kù)設(shè)計(jì)到圖表展示再到最后的銷(xiāo)量預(yù)測(cè)每一步都要能回答我為什么這樣做這個(gè)問(wèn)題。能做到這一點(diǎn)論文的寫(xiě)作反而就水到渠成了因?yàn)槊總€(gè)章節(jié)都有真實(shí)的工作內(nèi)容和決策過(guò)程可以寫(xiě)。最后再分享一個(gè)小建議答辯之前自己把整個(gè)系統(tǒng)從數(shù)據(jù)采集開(kāi)始完整跑一遍感受一下哪些環(huán)節(jié)最慢、哪些環(huán)節(jié)最容易出問(wèn)題。然后針對(duì)性地寫(xiě)一份簡(jiǎn)短的系統(tǒng)說(shuō)明文檔把技術(shù)架構(gòu)圖、數(shù)據(jù)流向、模型評(píng)估指標(biāo)這些核心信息放在第一頁(yè)。這份文檔不僅能幫你自己整理思路往往還能在答辯現(xiàn)場(chǎng)變成救命稻草——當(dāng)老師問(wèn)到某個(gè)細(xì)節(jié)你一時(shí)想不起來(lái)時(shí)掃一眼文檔就能快速恢復(fù)記憶。做畢設(shè)是一場(chǎng)漫長(zhǎng)的拉鋸戰(zhàn)但它同時(shí)也是你第一次獨(dú)立完成數(shù)據(jù)采集→數(shù)據(jù)處理→算法建?!到y(tǒng)落地完整鏈條的寶貴機(jī)會(huì)。希望這篇文章能給你一個(gè)清晰的路標(biāo)讓你少走一些我當(dāng)年走過(guò)的彎路。