據(jù)畢設實戰(zhàn):Hadoop+PySpark+Scrapy酒店推薦系統(tǒng)全解析)
又到了一年一度的畢業(yè)設計季節(jié)每年這個時候我都能在后臺收到大量私信大數(shù)據(jù)方向的畢設到底做什么怎么做才能既有技術深度又能順利通過答辯說實話我接觸過很多做所謂“推薦系統(tǒng)”的同學一大半都是下載一個公開數(shù)據(jù)集跑一個現(xiàn)成的協(xié)同過濾代碼再套個前端頁面就交差了。數(shù)據(jù)本身不是自己獲取的整個處理鏈路也走不通答辯的時候一問就露餡。這篇文章我就以自己實際帶過的一個項目為例完整拆解一套真正“拿得出手”的大數(shù)據(jù)畢設——基于Hadoop、PySpark、Scrapy的酒店推薦系統(tǒng)同時包含酒店知識圖譜構建、數(shù)據(jù)分析可視化和完整的Web交互。這套項目從零開始覆蓋了數(shù)據(jù)采集、存儲、計算、算法、可視化全鏈路也是我在實際環(huán)境中反復調(diào)試跑通的方案每一部分我都會講清楚設計思路和踩坑記錄希望能給正在選題或已經(jīng)入坑的同學一個直接能參考的模板。從選題思路到系統(tǒng)落地這套酒店推薦系統(tǒng)到底做了什么1.1 一套完整的“數(shù)據(jù)閉環(huán)”是什么樣的很多同學對大數(shù)據(jù)的理解停留在“用了Hadoop、Spark就算大數(shù)據(jù)”這是最大的誤區(qū)。真正的難點在于把數(shù)據(jù)鏈路的每一個環(huán)節(jié)串起來形成一個能夠自洽運轉的閉環(huán)。這套酒店推薦系統(tǒng)的核心鏈路是Scrapy爬蟲抓取真實的酒店數(shù)據(jù) - 清洗后存入HDFS - PySpark做離線分析和特征計算 - 協(xié)同過濾算法生成個性化推薦 - 同時抽取實體關系構建知識圖譜 - 最后在Web端完成推薦展示、圖譜交互和可視化大屏。這樣的設計有幾個非常實際的好處。首先是數(shù)據(jù)來源真實可靠評審老師問“你的數(shù)據(jù)哪來的”你理直氣壯回答“自己爬的”這就已經(jīng)比一票用公開數(shù)據(jù)集的同學站得住腳。其次是技術棧覆蓋全面從爬蟲、大數(shù)據(jù)存儲計算到機器學習算法、前端可視化每一層都有可講的東西完全能撐起一篇畢業(yè)設計的深度。1.2 為什么選這四件套Hadoop、PySpark、Scrapy、知識圖譜選型這件事不只是“聽說這幾個技術很火”而是要根據(jù)項目需求來。我做這套設計的時候選型邏輯是這樣考慮的Hadoop擔任的是底層存儲和基礎計算的角色。HDFS天然適合存儲海量非結構化或半結構化的酒店數(shù)據(jù)比如評論文本、用戶日志、爬取抓包返回的JSON而MapReduce雖然現(xiàn)在用得少了但作為離線批處理的底層思想寫進論文里能體現(xiàn)你對分布式計算原理的理解。PySpark更像是把這些數(shù)據(jù)“盤活”的關鍵。用Spark做數(shù)據(jù)清洗和特征計算比單純寫MapReduce效率高太多而且Python接口對做畢設的同學極其友好。你在PySpark里寫DataFrame操作感覺和pandas很像但底層是分布式執(zhí)行這個“既熟悉又高級”的體驗非常適合畢設場景。Scrapy是這套系統(tǒng)的數(shù)據(jù)入口。作為Python生態(tài)里最成熟的爬蟲框架它的并發(fā)調(diào)度、中間件機制和Pipeline數(shù)據(jù)流設計可以非常優(yōu)雅地支撐多頁面、多字段的酒店數(shù)據(jù)采集。知識圖譜是用來“拔高”的部分。單純做推薦系統(tǒng)在畢業(yè)設計里已經(jīng)不算新鮮了但如果你把酒店、城市、品牌、設施、用戶偏好這些信息抽成實體和關系用圖數(shù)據(jù)庫Neo4j存儲再在前端畫出一張可交互的知識圖譜整個項目的技術亮點立刻不一樣了。這也是答辯時最容易引起老師興趣的模塊。1.3 畢設交付物包含哪些東西一個完整的畢設項目代碼只是其中之一。這套項目正常交付時包含了全套源碼、畢業(yè)論文LW文檔、答辯PPT和一份詳細的視頻講解。論文里我重點寫了三章推薦算法設計與實現(xiàn)、知識圖譜構建、大數(shù)據(jù)平臺部署PPT則把系統(tǒng)架構和核心截圖作為亮點展示詳細講解則是錄制的系統(tǒng)演示覆蓋了從爬蟲啟動到知識圖譜交互的每個操作步驟。交付物完整的好處是無論導師從哪個維度驗收你都有內(nèi)容可講。數(shù)據(jù)從哪來Scrapy爬蟲系統(tǒng)設計細節(jié)2.1 目標站點分析與爬蟲策略酒店數(shù)據(jù)去哪爬我當時選型的標準有三條一是數(shù)據(jù)維度豐富至少包含酒店名稱、地址、價格、評分、評論數(shù)和經(jīng)緯度二是頁面結構相對規(guī)整便于批量解析三是數(shù)據(jù)量需求在幾千條以上才有分析價值。國內(nèi)主流的在線旅游平臺基本都符合條件但不同平臺的頁面結構差異很大有的需要處理動態(tài)加載有的則藏在iframe里還有的需要登錄才能看完整信息。這里要特別提醒一點爬蟲不是寫一次就完了更不是運行起來就一勞永逸。目標網(wǎng)站的前端結構經(jīng)常改版包括標簽屬性變化、接口參數(shù)加密、反爬策略升級等這些東西都會直接導致爬蟲失效。因此設計階段就要把“易維護性”考慮進去字段解析和頁面下載邏輯盡量解耦這樣某一部分掛了修復成本會小很多。2.2 Scrapy的核心架構與代碼實現(xiàn)Scrapy的工作流程可以簡單理解為Spider發(fā)請求拿到響應通過Selector或Selenium解析出結構化字段打包成Item交給Pipeline做清洗和存儲而下載中間件和爬蟲中間件就像關卡一樣夾在中間分別負責請求的預處理和后處理。我舉一個Spider的核心代碼片段這是抓取酒店列表頁的最小實現(xiàn)import scrapy from hotel_spider.items import HotelItem class HotelListSpider(scrapy.Spider): name hotel_list start_urls [https://example.travel.com/hotels/city/beijing] def parse(self, response): # 定位酒店條目區(qū)域每個酒店一個card節(jié)點 hotel_cards response.css(div.hotel-card) for card in hotel_cards: item HotelItem() item[name] card.css(h3.hotel-name::text).get() item[price] card.css(span.price-num::text).get() item[score] card.css(span.score::text).get() item[comment_num] card.css(span.comment-num::text).get() yield item # 下一頁鏈接用yield繼續(xù)交給調(diào)度器 next_page response.css(a.next::attr(href)).get() if next_page: yield response.follow(next_page, callbackself.parse)這里有個容易被忽略的細節(jié)Item在不同版本中的用法略有差異。早期的Scrapy版本直接在Item里定義Field新版中你還可以用attrs庫定義dataclass風格的Item類。兩種寫法都能運行但為了論文寫起來更清楚我推薦使用dataclass風格因為字段類型一目了然from dataclasses import dataclass dataclass class HotelItem: name: str city: str address: str price: float score: float comment_num: int lat: float lng: float2.3 動態(tài)內(nèi)容與iframe頁面怎么處理很多旅游平臺為了增加爬取難度詳情數(shù)據(jù)都放到了動態(tài)請求里而列表頁本身只是個殼。有些甚至把關鍵信息放在iframe里直接在Spider里用response.css根本取不到東西。這時候常規(guī)方案是引入Playwright或Selenium來渲染頁面再把渲染完成的HTML交給Scrapy解析。scrapy-playwright是目前最順手的方案它把Playwright的能力以中間件的形式整合進Scrapy生態(tài)使用起來非常自然。在settings.py里做以下配置DOWNLOAD_HANDLERS { http: scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler, https: scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler, } PLAYWRIGHT_LAUNCH_OPTIONS { headless: True, }然后在Spider中這樣請求yield scrapy.Request( urldetail_url, callbackself.parse_detail, meta{playwright: True} )這樣Scrapy就會用無頭瀏覽器加載頁面。遇到iframe嵌套內(nèi)容時ProcessRequest可以在meta中增加playwright_page操作先用page.frame_locator定位到對應的frame再取內(nèi)容。這個方案比裸用Selenium穩(wěn)定得多內(nèi)存控制也好很多。2.4 反爬應對與請求調(diào)度的平衡術說句實在話爬蟲最大的坑不是解析頁面而是反爬。目標站點常見的反爬措施包括IP頻率限制、User-Agent檢測、Cookie校驗、驗證碼、JS加密參數(shù)等。畢設場景下不建議做那種對抗性極強的破解一方面法律和技術倫理上有風險另一方面也沒有必要——我們需要的只是幾千條能說明問題的數(shù)據(jù)控制頻率、偽裝請求頭就足夠。我在這個項目中的策略是用下載中間件維護一個UA池并疊加一個簡單的IP代理池class RandomUserAgentMiddleware: def process_request(self, request, spider): ua random.choice(spider.settings.get(USER_AGENT_LIST)) request.headers[User-Agent] ua return None class ProxyMiddleware: def process_request(self, request, spider): proxy random.choice(spider.settings.get(PROXY_POOL)) request.meta[proxy] proxy return None除此之外最重要的是控制并發(fā)。Scrapy默認的并發(fā)是16但爬酒店這類站點我建議調(diào)到4到6下載延遲設置在1到2秒。這個節(jié)奏跑起來既不會觸發(fā)封禁數(shù)據(jù)采集速度也不算慢。實際跑下來采集2000家酒店的詳情加評論大概是兩三個小時的事完全夠用。2.5 清洗規(guī)則與字段落地數(shù)據(jù)爬到之后不能直接進HDFS一定要做清洗。我在Pipeline里做了三件核心的事一是字段校正。價格、評分這種字段從HTML里取出來是帶、分這類符號的字符串必須統(tǒng)一格式化成float字符串里混有的空格、換行要strip掉經(jīng)緯度如果缺失要根據(jù)酒店地址用離線地理編碼補一次。二是去重。很多平臺列表頁會重復公示同一家酒店我在Pipeline里維護了一個seen集合以“酒店名城市地址”作為唯一鍵去重。這種方式比單個字段去重可靠得多。三是空值策略。核心字段如名稱、城市如果為空直接丟棄該條記錄而評論數(shù)為空時我可以填0因為后面做推薦時會用評分算權重評論數(shù)為0的代表冷門酒店本身也有分析價值。清洗完之后我按日期分區(qū)寫JSON格式落盤到HDFS目錄 /data/hotel/raw/20250301/ 下面方便后續(xù)Spark直接讀取和溯源。離線計算的基座Hadoop平臺搭建與PySpark數(shù)據(jù)處理3.1 Hadoop集群怎么搭最省心很多同學一提到搭建Hadoop就頭皮發(fā)麻其實畢設場景根本沒必要搭真實的分布式集群一臺機器跑偽分布式模式就完全夠了。所謂偽分布式就是在單機上同時運行NameNode、DataNode、ResourceManager和NodeManager等進程邏輯上和分布式一樣只是所有進程都在一臺機器里而已。畢設的側重點是讓你把原理講清楚流程跑通偽分布式完全能達到這個目標而且省去了多臺機器聯(lián)調(diào)的痛苦。我實際使用的環(huán)境是一臺8核16G內(nèi)存的服務器CentOS 7系統(tǒng)裝了Hadoop 3.3.x版本、Spark 3.3.x版本和對應的PySpark。啟動前需要配置core-site.xml、hdfs-site.xml和yarn-site.xml這三個文件。core-site.xml里指定NameNode地址hdfs-site.xml里設置副本數(shù)為1yarn-site.xml配置ResourceManager地址。這里有一個新手必踩的坑很多教程會告訴你啟動前先格式化NameNode步驟是bin/hdfs namenode -format但如果你操作不當、多次執(zhí)行格式化會導致NameNode的clusterID和DataNode不一致結果數(shù)據(jù)節(jié)點死活起不來。我當年在這個問題上卡了一個下午。解決辦法也不難先停掉所有進程把tmp目錄下的dfs數(shù)據(jù)徹底刪掉再重新格式化和啟動。順序一定是清空數(shù)據(jù)目錄 - 格式化 - start-dfs.sh - start-yarn.sh。3.2 PySpark從HDFS讀取數(shù)據(jù)做特征工程Hadoop啟動之后HDFS就是整個系統(tǒng)的數(shù)據(jù)中樞。爬蟲清洗好的數(shù)據(jù)寫入HDFSPySpark再通過hdfs://協(xié)議的路徑讀取。這樣做的好處是存儲和計算分離符合大數(shù)據(jù)的經(jīng)典范式論文里寫出來也更專業(yè)。PySpark讀JSON并做基礎清洗的代碼大致是from pyspark.sql import SparkSession from pyspark.sql.functions import col, when, isnan spark SparkSession.builder \ .appName(hotel_etl) \ .getOrCreate() df spark.read.json(/data/hotel/raw/20250301/*.json) df df.dropDuplicates([name, city, address]) df df.filter(col(name).isNotNull() (col(price) 0)) df df.withColumn( price_band, when(col(price) 300, 經(jīng)濟型) .when(col(price) 600, 舒適型) .when(col(price) 1000, 高檔型) .otherwise(豪華型) )這里需要多說一嘴Spark的惰性求值機制。你在代碼里寫withColumn、filter這些操作時Spark并沒有真正執(zhí)行只有當遇到action操作比如.count()、.write時才會真正觸發(fā)計算。理解這個機制很重要因為很多同學在寫大數(shù)據(jù)代碼的時候用pandas的思維逐行執(zhí)行結果日志看不懂、性能也上不去。之后我把處理好的數(shù)據(jù)寫回HDFS的清洗目錄同時把用戶評論數(shù)據(jù)單獨抽出來為下一步的推薦算法和知識圖譜構建做準備。3.3 用戶評分矩陣怎么造推薦算法最經(jīng)典的數(shù)據(jù)格式是“用戶-物品-評分”三元組。但我爬到的數(shù)據(jù)并沒有真實用戶的下單評分因為那屬于平臺的核心數(shù)據(jù)我根本拿不到。這時候就要回到畢設的本質造數(shù)。我采用了“模擬用戶行為”的方式為推薦算法生成輸入數(shù)據(jù)先爬取幾千條真實酒店數(shù)據(jù)包括評分、價格、評論數(shù)、地理位置、設施標簽等然后根據(jù)這些內(nèi)容物構造500個虛擬用戶再按照用戶的偏好類型給不同的酒店打分。比如模擬“預算敏感型”用戶對經(jīng)濟型酒店打高分模擬“舒適優(yōu)先型”用戶對高檔型酒店打高分評分范圍1到5分。這樣生成的評分矩陣雖然不是用戶真實行為但具有明顯的群體偏好差異跑推薦算法能出非常清晰的效果而且我在論文里會把數(shù)據(jù)構造方法完整說明不構成學術造假。3.4 PySpark在Windows和Linux下的版本兼容坑這幾年很多同學的開發(fā)機是Windows這也是個大坑。PySpark在Windows下最常遇到的報錯是Failed to locate the winutils binary in the Hadoop binaries這是因為Spark在Windows環(huán)境下需要winutils.exe和hadoop.dll來模擬Linux的Hadoop環(huán)境。你必須下載對應Hadoop版本的winutils放到一個目錄然后配置HADOOP_HOME環(huán)境變量指向它。另外注意PySpark、Spark和Java版本之間必須匹配比如Spark 3.3.x要求Java 8或11Python 3.8以上版本不匹配會在啟動時就拋出各類奇怪異常。我比較推薦的做法是本地Windows只做代碼編寫和小規(guī)模邏輯驗證真正跑全量數(shù)據(jù)時把代碼放到Linux服務器上執(zhí)行既免去了winutils的折騰運行速度也快得多。推薦系統(tǒng)核心協(xié)同過濾算法實現(xiàn)與調(diào)優(yōu)4.1 為什么選協(xié)同過濾而不是深度學習現(xiàn)在一提起推薦系統(tǒng)很多人的第一反應是深度學習。但畢設場景需要理性評估深度推薦模型如DeepFM、DIN需要大量樣本和特征工程訓練時間長、解釋性差答辯時老師問起來你很難在十分鐘內(nèi)講明白。而協(xié)同過濾作為推薦系統(tǒng)最經(jīng)典的算法原理清晰、實現(xiàn)成本低、效果可解釋非常適合作為畢業(yè)設計的主算法。協(xié)同過濾的核心假設是喜歡過相似物品的人未來也容易喜歡相似的東西。我在這套系統(tǒng)里同時實現(xiàn)了基于用戶的協(xié)同過濾UserCF和基于物品的協(xié)同過濾ItemCF然后按權重融合取兩者之長。4.2 基于用戶的協(xié)同過濾實現(xiàn)UserCF的思路分三步第一步計算用戶之間的相似度第二步找到和目標用戶最相似的K個用戶第三步根據(jù)這K個用戶對某些酒店的評分加權預測目標用戶對未評分酒店的評分。相似度我用的是余弦相似度計算方式是把兩個用戶的評分向量看作高維空間的兩個向量用夾角余弦衡量方向的相似性。從PySpark處理的用戶-酒店評分矩陣中我直接加載并轉為Python字典然后用內(nèi)存計算實現(xiàn)算法主體def user_cf_predict(user_id, k10): # 加載用戶的評分字典 {user_id: {hotel_id: rating}} ratings load_rating_matrix() target_user_ratings ratings[user_id] # 計算目標用戶與其他所有用戶的余弦相似度 sims [] for other_id, other_ratings in ratings.items(): if other_id user_id: continue common set(target_user_ratings.keys()) set(other_ratings.keys()) if len(common) 0: continue # 點積 / 模長乘積 dot sum(target_user_ratings[h] * other_ratings[h] for h in common) norm1 sum(r ** 2 for r in target_user_ratings.values()) ** 0.5 norm2 sum(r ** 2 for r in other_ratings.values()) ** 0.5 sim dot / (norm1 * norm2 1e-9) sims.append((other_id, sim)) # 取相似度最高的k個用戶 sims.sort(keylambda x: x[1], reverseTrue) top_k sims[:k] # 加權預測目標用戶對候選酒店的評分 candidate_hotels set() for other_id, _ in top_k: candidate_hotels.update(ratings[other_id].keys()) candidate_hotels - set(target_user_ratings.keys()) pred_scores {} for hotel in candidate_hotels: score 0 sim_sum 0 for other_id, sim in top_k: if hotel in ratings[other_id]: score sim * ratings[other_id][hotel] sim_sum abs(sim) if sim_sum 0: pred_scores[hotel] score / sim_sum return sorted(pred_scores.items(), keylambda x: x[1], reverseTrue)[:10]基于物品的協(xié)同過濾主體思路類似只是把“用戶相似”換成“物品相似”核心邏輯是先建立物品間相似度矩陣再根據(jù)用戶歷史評分過的物品去找相似的物品。4.3 混合推薦與熱度兜底UserCF和ItemCF各有利弊。UserCF在用戶數(shù)量少但物品數(shù)量多的時候效果更靈敏它更偏向社會化和熱點發(fā)現(xiàn)而ItemCF更穩(wěn)定適合用戶興趣比較固定的場景。我實際測試后發(fā)現(xiàn)單用任何一種都會出現(xiàn)部分用戶推薦列表為空或冷門物品被遺忘的情況因此我做了加權混合最終得分 0.5 * UserCF得分 0.4 * ItemCF得分 0.1 * 酒店熱度分。熱度分是我額外設計的用于緩解冷啟動問題。計算方式為熱度分 0.4 * 歸一化評論數(shù) 0.3 * 歸一化評分 0.3 * 歸一化點擊瀏覽數(shù)。這個混合策略保證了系統(tǒng)對新注冊用戶無評分歷史也能推薦當前熱門酒店不至于頁面空白。4.4 評估指標怎么寫進論文畢設論文里光有算法還不夠還得有實驗分析。推薦系統(tǒng)最常見的離線評估指標有三個精確率、召回率和覆蓋率。我的做法是隨機留出20%的評分作為測試集用剩余80%訓練算法然后對每個測試用戶生成Top10推薦列表計算預測命中比例。實測下來這套混合推薦在500用戶、3000酒店的數(shù)據(jù)規(guī)模下精確率大概在18%到22%之間召回率在12%左右覆蓋率能到30%以上。這個數(shù)據(jù)不算驚艷但作為本科畢設已經(jīng)很有說服力而且我把不同K值下的效果變化做了折線圖表直接放進論文的實驗章節(jié)。酒店知識圖譜實體建模、Neo4j存儲與前端交互5.1 從數(shù)據(jù)到知識圖譜里有哪些實體和關系知識圖譜本質上是把散落的、孤立的數(shù)據(jù)變成相互關聯(lián)的知識網(wǎng)絡非常契合酒店領域的業(yè)務形態(tài)。酒店數(shù)據(jù)天然適合用圖結構表達因為酒店和城市、品牌、設施、用戶之間天然存在大量關聯(lián)關系。我把實體分為五類酒店Hotel核心實體屬性包括名稱、價格、評分、地址、經(jīng)緯度城市City酒店所在城市品牌Brand酒店所屬品牌如某國際連鎖品牌或本土品牌設施Facility如健身房、游泳池、免費停車、行政酒廊等星級Star從經(jīng)濟型到豪華型的等級對應的關系有酒店-位于-城市、酒店-屬于-品牌、酒店-提供-設施、酒店-屬于-星級。有了這四類關系整張圖就能表達出“上海的某連鎖酒店提供健身房且屬于高檔型”這樣的復雜信息。5.2 Neo4j寫入與Cypher查詢知識圖譜的存儲我用的是Neo4j它是目前使用率最高的圖數(shù)據(jù)庫。安裝Neo4j之后我用PySpark處理好的結構化數(shù)據(jù)通過py2neo庫批量寫入。寫一個最關鍵的Cypher例子用于創(chuàng)建酒店和城市的關系MERGE (c:City {name: 上海}) MERGE (h:Hotel {name: 上海某酒店, price: 780, score: 4.5}) MERGE (h)-[:LOCATED_IN]-(c)MERGE語句是Neo4j中非常重要的概念如果節(jié)點已存在則不重復創(chuàng)建這是防止批量導入大量重復數(shù)據(jù)的關鍵。我還為酒店和品牌、酒店和設施創(chuàng)建了類似的關系。等數(shù)據(jù)全部導入后我可以執(zhí)行這樣一條查詢找出上海所有評分大于4.5且?guī)Ы∩矸康木频闙ATCH (h:Hotel)-[:LOCATED_IN]-(c:City {name: 上海}), (h)-[:HAS_FACILITY]-(f:Facility {name: 健身房}) WHERE h.score 4.5 RETURN h.name, h.price, h.score這種多跳關聯(lián)查詢在傳統(tǒng)關系型數(shù)據(jù)庫里要寫多重JOIN而在圖數(shù)據(jù)庫里就是一行模式匹配的事。把這個查詢示例寫進論文能非常直觀地體現(xiàn)知識圖譜的查詢優(yōu)勢。5.3 圖譜可視化從Neo4j到Vue前端Neo4j自帶的Browser就可以可視化圖結構但既然是個Web畢設項目就必須在自家系統(tǒng)里嵌入一個交互式的知識圖譜頁面。技術選型上我用的是Vue3加ECharts的關系圖。ECharts雖然常用圖表類型是折線圖和柱狀圖但它的graph系列也能很好地展示關系網(wǎng)絡。在Vue3中我通過Axios調(diào)用后端接口把Neo4j查到的節(jié)點和關系轉成ECharts需要的nodes和links格式const chartData { nodes: res.data.nodes.map(n ({ id: n.id, name: n.name, category: n.category, symbolSize: n.category Hotel ? 60 : 40, itemStyle: { color: categoryColor[n.category] }, })), links: res.data.links.map(l ({ source: l.source, target: l.target, label: { show: true, formatter: l.relation } })) };ECharts關系圖支持節(jié)點拖拽、縮放、點擊高亮等交互。這個頁面做完之后用戶可以點擊某個城市節(jié)點圖譜會自動高亮該城市下的所有酒店及其關聯(lián)設施交互體驗非常直觀。在答辯演示現(xiàn)場這個頁面往往是老師停留時間最長的一塊。數(shù)據(jù)分析可視化與Web系統(tǒng)整體集成6.1 從統(tǒng)計報表到多維度可視化數(shù)據(jù)分析可視化是體現(xiàn)數(shù)據(jù)處理能力的重要窗口也是畢設中比較容易出效果的部分。我基于HDFS中存儲并經(jīng)過Spark聚合的結果數(shù)據(jù)用圖表展示了多個維度的分析內(nèi)容城市酒店數(shù)量分布用柱狀圖展示熱門旅游城市的酒店供給量排名價格區(qū)間分布用餅圖展示經(jīng)濟型、舒適型、高檔型、豪華型的占比評分與評論數(shù)散點圖用散點圖觀察酒店評分和評論熱度之間的相關性設施詞頻統(tǒng)計用詞云展示出現(xiàn)頻率最高的酒店設施關鍵詞這些圖表全部由ECharts渲染數(shù)據(jù)由后端的SpringBoot或Flask接口提供。Spark預聚合的數(shù)據(jù)寫入MySQL或HBase接口再從其中查詢返回給前端整體鏈路清晰且每一層都有事可講。別小看這幾個圖表答辯時老師很喜歡問“你分析了哪些維度”“發(fā)現(xiàn)了什么規(guī)律”提前準備一兩個分析結論很有必要。比如我當時總結出熱門旅游城市的中檔酒店數(shù)量最多但評分和評論數(shù)的相關性并不顯著說明口碑好的酒店不一定是熱門酒店。6.2 系統(tǒng)后端架構與前端頁面后端我采用了SpringBoot原因是Java生態(tài)成熟、和Hadoop棧關系緊密而且很多同學的課程項目用的就是Java。為了讓前端調(diào)用方便我把推薦接口、知識圖譜接口和統(tǒng)計分析接口放在一起統(tǒng)一管理。整個Web應用包含四個核心頁面推薦頁用戶輸入ID或選擇偏好系統(tǒng)返回Top10推薦酒店列表知識圖譜頁展示酒店關聯(lián)關系網(wǎng)絡支持節(jié)點點擊和縮放拖拽數(shù)據(jù)大屏頁用圖表聚合展示全國酒店數(shù)據(jù)分析結果爬蟲監(jiān)控頁展示爬蟲運行狀態(tài)、最近采集條數(shù)等前端用Vue3加Element Plus搭界面樣式追求干凈直接、偏“數(shù)據(jù)產(chǎn)品”風格。這個系統(tǒng)做完以后無論是截圖放論文還是現(xiàn)場演示效果都遠超過那種只有一個命令行輸出結果的畢設。6.3 系統(tǒng)集成時最容易出的問題集成環(huán)節(jié)最常遇到的就是環(huán)境變量和端口沖突。Hadoop的50070端口老版本是50070新版本是9870、Yarn的8088端口、Spark的4040端口、SpringBoot的8080端口、Neo4j的7474端口、Vue DevServer的5173端口這些默認端口之間并不沖突但如果你本機跑著其他服務很容易被占用。我當時就遇到過8080被占用導致SpringBoot啟動失敗的情況最后把所有服務的端口都列成一個表格逐個檢查才解決。另外一個常見問題是跨域。Vue開發(fā)服務器默認跑在5173端口后端跑在8080端口前端直接請求后端接口會報跨域錯誤。解決辦法是在后端配置CORS過濾器允許所有來源的跨域請求。這個屬于小坑但頻率極高提前配置好能省去大量聯(lián)調(diào)時間。實戰(zhàn)排雷從Hadoop到Spark再到爬蟲的典型問題匯總7.1 Hadoop相關格式化失敗與DataNode連不上Hadoop啟動格式化是高頻問題。網(wǎng)上教程說啟動前先格式化但沒說不能反復格式化。如果你格式化一次啟動了又停了改完配置又格式化就會導致NameNode的clusterID與DataNode不一致。現(xiàn)象是jps命令能看到DataNode進程但Web界面里DataNode列表是空的。解決辦法是把Hadoop的tmp目錄和dfs的name/data目錄全部刪除重新格式化。另外我建了個習慣每次做重大配置修改之前先備份原配置文件避免改壞了找不到原始狀態(tài)。7.2 PySpark相關內(nèi)存溢出與Shuffle異常PySpark跑全量數(shù)據(jù)的時候我遇到過OutOfMemory錯誤。原因是在做用戶相似度矩陣時我試圖把全量用戶的相似度廣播到所有Executor數(shù)據(jù)量一大內(nèi)存直接爆掉。解決辦法第一是調(diào)大Executor內(nèi)存在提交任務時設置spark.executor.memory4g第二是優(yōu)化代碼把需要廣播的數(shù)據(jù)控制在最小范圍只廣播TopN相似用戶而不是全量矩陣。這個教訓很適合寫在論文的“系統(tǒng)優(yōu)化”部分能顯示出你對分布式計算的深入理解。7.3 爬蟲相關驗證碼、字段缺失與封禁我實際爬取過程中印象最深的是驗證碼問題。剛開始爬某個平臺正常瀏覽頁面完全沒有驗證碼但只要爬蟲速度一快立刻彈驗證碼。后來我把下載延遲調(diào)到2秒并啟用IP輪換基本就能穩(wěn)定繞過。字段缺失也很常見部分民宿類酒店沒有評分這部分數(shù)據(jù)我做了特殊標記在推薦算法里用默認評分兜底處理。整個項目從爬蟲到可視化大概花了三周左右的時間其中環(huán)境搭建和版本兼容問題占了將近一半。這也給正在做畢設的同學一個建議不要把時間排得太滿給環(huán)境問題留足緩沖期。技術上遇到的小問題絕大多數(shù)都能在官方文檔或者Stack Overflow找到答案關鍵是學會看日志Java和Python的報錯堆棧信息其實已經(jīng)把問題定位得很清楚了靜下心來自查往往比盲目搜索更快。