系網(wǎng)絡(luò)分析:Python爬蟲與圖數(shù)據(jù)挖掘?qū)崙?zhàn))
做社交網(wǎng)絡(luò)分析這幾年我最常被問到的問題就是那些大V到底是怎么影響普通用戶的一個話題是怎么從某個小圈子火起來的團隊內(nèi)部誰是真正的信息樞紐說實話這些問題用傳統(tǒng)的關(guān)系型數(shù)據(jù)庫和統(tǒng)計報表很難回答但如果你把目光轉(zhuǎn)向圖數(shù)據(jù)挖掘把用戶當成節(jié)點、把關(guān)注和互動當成邊很多模糊的直覺就能變成可量化、可驗證的結(jié)論。這篇文章是我在完成一個模擬項目X一個基于Python抓取公開社交頁面、構(gòu)建用戶關(guān)系網(wǎng)絡(luò)并做圖挖掘分析的個人項目之后的完整復(fù)盤。項目標題叫“社交媒體用戶關(guān)系網(wǎng)絡(luò)分析基于Python的先進爬蟲技術(shù)與圖數(shù)據(jù)挖掘?qū)崙?zhàn)”聽起來有點長但拆開看其實就兩件事一是用爬蟲拿到數(shù)據(jù)二是用圖挖掘從數(shù)據(jù)里撈到有價值的結(jié)構(gòu)信息。我會從整體設(shè)計思路、爬蟲架構(gòu)、圖數(shù)據(jù)庫選型、核心算法到踩坑記錄把整個流程完整地過一遍。如果你正準備做類似的數(shù)據(jù)分析項目或者已經(jīng)在做但卡在了某個環(huán)節(jié)這篇內(nèi)容應(yīng)該能幫你少走不少彎路。1. 整體設(shè)計與技術(shù)選型思路1.1 從標題拆解核心需求拿到這個標題的時候我第一反應(yīng)是把它拆成三個核心問題第一數(shù)據(jù)從哪來社交媒體用戶關(guān)系網(wǎng)絡(luò)說到底是一張巨大的有向圖但圖的構(gòu)建必須依賴真實的數(shù)據(jù)。公開頁面上的關(guān)注列表、粉絲列表、轉(zhuǎn)發(fā)記錄、評論關(guān)系這些都是原始素材??蓡栴}在于這些數(shù)據(jù)分散且量大手動一頁頁復(fù)制顯然不現(xiàn)實所以必須用爬蟲來批量采集。第二數(shù)據(jù)怎么組織用戶和用戶之間的關(guān)系天然適合用圖模型表達誰關(guān)注了誰就是一條有向邊。但我們總不能把所有數(shù)據(jù)都塞進一個大JSON文件里然后到處復(fù)制。需要一個既能存儲圖結(jié)構(gòu)、又能高效查詢的數(shù)據(jù)庫方案同時還要能把圖數(shù)據(jù)導(dǎo)出給算法庫使用。第三價值怎么提煉光有圖和數(shù)據(jù)庫還不夠圖數(shù)據(jù)挖掘的目的是找出隱藏的模式哪些用戶是意見領(lǐng)袖哪些社區(qū)內(nèi)部聯(lián)系緊密信息更傾向于沿著什么路徑傳播。這需要中心性分析、社區(qū)發(fā)現(xiàn)、影響力傳播等算法來幫忙。這三個問題其實是層層遞進的關(guān)系采集是地基存儲是承重墻挖掘是最后的裝修。任何一個環(huán)節(jié)出問題后面都做不下去。1.2 Python技術(shù)棧的選型邏輯為什么選擇Python而不是其他語言或者為什么不直接用現(xiàn)成平臺我的實際體會是Python在圖分析和數(shù)據(jù)處理這個領(lǐng)域有最完整的生態(tài)不需要自己從零造輪子。爬蟲層面請求庫用的是requests解析HTML用的是BeautifulSoup和lxml如果遇到需要異步并發(fā)采集的場景還有httpx和aiohttp可以上。模擬項目X的數(shù)據(jù)量不算特別大大約幾萬個節(jié)點所以單機爬蟲完全夠用。如果你要抓的是千萬級以上的數(shù)據(jù)那可能要上Scrapy配合分布式部署但對大多數(shù)個人項目來說requests加限速策略就已經(jīng)很穩(wěn)了。圖分析層面Python有兩個方向可走一個是NetworkX適合分析中小規(guī)模圖靈活、API豐富、社區(qū)活躍另一個是Neo4j適合大規(guī)模圖數(shù)據(jù)的存儲和查詢配合Cypher查詢語言做復(fù)雜路徑分析很方便。我在項目里兩個都用到了分析算法走NetworkX數(shù)據(jù)持久化走Neo4j。選擇這些工具的唯一標準就是夠用、好調(diào)試、出問題能在網(wǎng)上找到答案。別迷信花哨的技術(shù)棧能解決問題才是第一位的。1.3 為什么用圖而不用傳統(tǒng)表格這是很多剛接觸項目的人會有的疑惑用戶之間的關(guān)注關(guān)系用兩張表用戶表、關(guān)系表也能存為什么要特意用圖數(shù)據(jù)庫和圖算法我打個比方如果用戶關(guān)系是一堆環(huán)節(jié)扣在一起的鎖鏈表格只是把這堆鎖鏈平鋪在地上你能看到每一環(huán)但看不出哪些環(huán)連成了什么樣的形狀。而圖模型直接保留了鎖鏈的連接方式你一眼就能看到哪里是核心節(jié)點、哪里只是末端鏈路。具體到分析場景很多問題天然就是圖問題。比如尋找“連接兩個社區(qū)的關(guān)鍵橋梁”在表格里你得寫多表聯(lián)查邏輯復(fù)雜且效率低但在圖里它就是一次“最短路徑”或者“介數(shù)中心性”計算幾行代碼就出結(jié)果。這也是項目標題里把“圖數(shù)據(jù)挖掘”作為重點的原因關(guān)系網(wǎng)絡(luò)的本質(zhì)就是圖繞開圖去分析反而是舍近求遠。2. 核心細節(jié)解析與實操要點2.1 明確可采集的數(shù)據(jù)邊界在寫任何爬蟲之前務(wù)必把“能抓什么”和“該抓什么”搞清楚。模擬項目X當時選擇采集的目標是一家主流社交媒體平臺上的公開用戶頁面數(shù)據(jù)包括用戶名、用戶簡介、關(guān)注列表、粉絲列表以及部分公開互動記錄。這里的“公開”兩個關(guān)鍵詞很重要只有用戶主動公開的信息才在采集范圍內(nèi)私密賬號、非公開內(nèi)容一概不碰。合規(guī)方面有幾個最基本的紅線第一嚴格遵守目標平臺的robots.txt文件設(shè)置第二控制請求頻率不給對方服務(wù)器造成壓力第三采集到的數(shù)據(jù)不涉及個人隱私信息比如手機號、真實姓名、家庭住址這些即使在公開頁面出現(xiàn)也不能入庫第四采集行為本身只用于學(xué)術(shù)分析或個人研究不用于商業(yè)化的用戶畫像或營銷。如果你準備發(fā)布相關(guān)項目也建議把所有采集代碼和數(shù)據(jù)都脫敏處理后再展示。另外有一個容易被忽略的細節(jié)平臺方可能會在頁面里設(shè)置“陷阱”字段用來識別爬蟲。識別這些字段不難難的是每天都去檢查頁面結(jié)構(gòu)是否變了。所以我建議在爬蟲代碼里把頁面解析邏輯和字段提取邏輯分開這樣一旦頁面改版只需要修改一小塊解析代碼不至于整套流程癱瘓。2.2 數(shù)據(jù)模型設(shè)計節(jié)點與邊的定義關(guān)系網(wǎng)絡(luò)圖的數(shù)據(jù)模型設(shè)計是整個項目最重要的地基。我在模擬項目X里定義了這樣一套模型節(jié)點Node代表用戶節(jié)點屬性包括user_id、screen_name、follower_count、friend_count、verified、created_at等。邊Edge代表用戶之間的互動或關(guān)注關(guān)系邊屬性包括relation_type關(guān)注、轉(zhuǎn)發(fā)、評論、提及、weight互動次數(shù)、timestamp最近一次互動時間。比較關(guān)鍵的設(shè)計決策是我只保留了“關(guān)系類型”和“權(quán)重”兩個核心屬性其他諸如轉(zhuǎn)發(fā)內(nèi)容本身、評論的完整文本就沒有放進圖里而是另外存了一份原始數(shù)據(jù)表。原因是圖模型擅長分析結(jié)構(gòu)不擅長做全文檢索如果想把文本和結(jié)構(gòu)混在一起查詢效率會大打折扣。關(guān)于邊的方向用戶關(guān)注的邊是天然有向的——A關(guān)注B和B關(guān)注A是兩回事。所以我將圖定義為有向圖。但后續(xù)做社區(qū)發(fā)現(xiàn)和節(jié)點影響力分析時有些算法默認不支持有向圖比如某些版本的Louvain聚類這時可以臨時把有向圖轉(zhuǎn)成無向圖處理。這個“先有向、后無向”的切換邏輯屬于實操中默認的處理方式我在后面算法部分會詳細說明。2.3 爬蟲架構(gòu)設(shè)計與限速策略模擬項目X的爬蟲用了非常經(jīng)典的模塊化設(shè)計URL管理器負責維護待采集和已采集的URL隊列下載器負責發(fā)送HTTP請求并接收響應(yīng)解析器負責從HTML或JSON中提取用戶數(shù)據(jù)和關(guān)系數(shù)據(jù)存儲模塊負責把數(shù)據(jù)寫入數(shù)據(jù)庫和本地文件。這四個模塊互相解耦哪怕只改其中一個也不會影響其他模塊。限速策略是爬蟲能長期穩(wěn)定運行的命脈。我當時的策略很簡單也很有效每個請求之間至少要間隔1到2秒高峰期自動拉大間隔到5秒以上單日請求總量不設(shè)頂?shù)坏┠扯螘r間響應(yīng)碼連續(xù)出現(xiàn)非200情況立刻暫停采集等一段時間再恢復(fù)。這種“慢就是快”的節(jié)奏很多人一開始理解不了但實測下來反而是總體耗時最短的方案。另外強烈建議即使你只是短時間跑一下也一定要寫一個可中斷、可續(xù)跑的邏輯。因為社交平臺數(shù)據(jù)量巨大一次任務(wù)往往要跑好幾個小時中間可能斷網(wǎng)、報錯或者被限流。如果爬蟲不允許斷點續(xù)傳意味著所有已采集的數(shù)據(jù)都要重來那才是真的災(zāi)難。我通常使用本地JSON文件記錄已完成的任務(wù)列表每次重啟時先加載這個列表跳過已經(jīng)采集過的用戶。2.4 賬號管理與Headers模擬進階如果你只抓幾個公開頁面用默認的User-Agent就夠了。但當你抓到幾千上萬個用戶頁面的時候目標平臺一定會注意到你的存在。這時候最基本的對策就是維護一個請求頭池里面包含不同類型的User-Agent、Accept-Language和Referer每次請求隨機挑選一組。比請求頭更管用的是cookie的有效期管理。很多社交平臺對未登錄用戶展示的信息有限登錄后可見字段更多。但登錄態(tài)的cookie很容易過期而且同一個cookie短時間內(nèi)大量使用也會觸發(fā)風控。我的做法是準備多個賬號定期輪換cookie同時把每個賬號的請求頻率盡可能控制在接近真人行為。注意這里說的賬號輪換只適用于單機小規(guī)模采集如果數(shù)據(jù)量再大建議考慮更正規(guī)的數(shù)據(jù)獲取渠道。頁面內(nèi)容方面大部分現(xiàn)代社交平臺已經(jīng)全面轉(zhuǎn)向JSON接口而不是傳統(tǒng)的服務(wù)端渲染HTML。這意味著你在瀏覽器里看到的動態(tài)內(nèi)容往往是通過API接口異步加載的。實際采集時直接請求這些JSON接口比解析HTML要穩(wěn)定得多。判斷方法非常簡單打開瀏覽器開發(fā)者工具在Network面板里看XHR請求凡是返回JSON數(shù)據(jù)的就是你需要找的接口。3. 實操過程與核心環(huán)節(jié)實現(xiàn)3.1 環(huán)境準備與依賴工具清單模擬項目X最終跑在Python 3.10環(huán)境下主要依賴包如下requests發(fā)送HTTP請求是所有采集動作的基礎(chǔ)。BeautifulSoup4 lxml解析舊版HTML頁面在新版JSON接口項目中用得較少但留著備用。networkx構(gòu)建圖結(jié)構(gòu)、計算中心性指標、執(zhí)行社區(qū)發(fā)現(xiàn)算法。python-louvain執(zhí)行Louvain社區(qū)發(fā)現(xiàn)的核心庫。pyvis快速生成可在瀏覽器中交互展示的網(wǎng)絡(luò)圖。neo4j-driver連接Neo4j圖數(shù)據(jù)庫進行Cypher查詢和數(shù)據(jù)寫入。安裝指令可以直接用pip完成這里不再贅述。如果過程中遇到依賴沖突建議用venv或conda隔離出一個干凈的環(huán)境不要直接在全局環(huán)境硬裝尤其是networkx和scipy之間偶爾會有版本兼容問題單獨隔離環(huán)境能省去不少麻煩。3.2 爬蟲核心代碼的結(jié)構(gòu)演示下面的代碼展示了爬蟲中最核心的請求和響應(yīng)處理邏輯。我不打算貼完整幾百行的項目代碼只把關(guān)鍵模塊展示出來并逐一解釋為什么這樣寫。import requests import json import random import time from typing import Dict, List, Optional HEADERS_POOL [ { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Accept-Language: zh-CN,zh;q0.9, Referer: https://www.example.com/ }, { User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15, Accept-Language: en-US,en;q0.8, Referer: https://www.example.com/ } ] class SocialCrawler: def __init__(self, max_retry: int 3): self.session requests.Session() self.max_retry max_retry def _get_random_headers(self) - Dict[str, str]: return random.choice(HEADERS_POOL) def fetch_json(self, url: str, params: Optional[Dict] None) - Optional[Dict]: for attempt in range(1, self.max_retry 1): try: resp self.session.get( url, paramsparams, headersself._get_random_headers(), timeout15 ) if resp.status_code 200: return resp.json() elif resp.status_code in (403, 429): wait_time 60 * attempt print(f觸發(fā)限流等待 {wait_time} 秒后重試) time.sleep(wait_time) else: print(f請求失敗: {resp.status_code}) except Exception as e: print(f請求異常: {e}) time.sleep(5 * attempt) return None def close(self): self.session.close()這段代碼看起來簡單但有幾個點是決定成敗的第一使用同一個requests.Session實例可以在多次請求之間維持HTTP連接池減少TCP握手開銷第二對403和429狀態(tài)碼做專門處理這兩類響應(yīng)說明你已經(jīng)觸發(fā)了平臺的風控必須停下來等待而不是繼續(xù)硬撞第三重試的時間采用線性退避而不是固定時間這是基于我自己的經(jīng)驗如果對方限制比較嚴格固定時間的重試很快就會被識別。3.3 從用戶頁到關(guān)系邊的數(shù)據(jù)鏈路有了爬蟲基礎(chǔ)能力后接下來要解決的是“怎么從零構(gòu)造出一張關(guān)系網(wǎng)絡(luò)”。我的執(zhí)行順序是這樣的第一步選定種子用戶。種子用戶可以是某個領(lǐng)域的大V也可以是隨機抽取的中小博主。種子質(zhì)量直接影響后續(xù)網(wǎng)絡(luò)的覆蓋度建議選5到10個不同圈子的人避免整個圖扎堆在一個社區(qū)里。第二步通過種子用戶的關(guān)注列表向外擴展。每抓到一個新用戶就把他的關(guān)注列表記錄下來形成“種子用戶→關(guān)注對象”的有向邊。然后再從關(guān)注對象里挑一部分繼續(xù)抓他們的關(guān)注列表。這個過程在技術(shù)圈叫BFS廣度優(yōu)先搜索式爬取。第三步按需采集粉絲列表。粉絲列表的接口通常比關(guān)注列表更容易被限制而且單個大V的粉絲數(shù)量可能是幾十萬甚至上百萬全部采集是不現(xiàn)實的。我采取的策略是只采集種子用戶及其核心關(guān)注對象的粉絲列表并設(shè)定每個用戶粉絲列表最多取前1000條。這個閾值不是因為技術(shù)上做不到更多而是因為分析幾千個用戶的結(jié)構(gòu)特征和關(guān)系路徑已經(jīng)足夠回答項目核心問題了。第四步清洗與去重。不同接口返回的用戶名格式可能不一致有的帶昵稱、有的不帶有的user_id是字符串、有的是整數(shù)。我在入庫前做了統(tǒng)一格式化user_id全部轉(zhuǎn)成字符串昵稱去掉首尾空格同時對每條邊做唯一性校驗避免重復(fù)建邊。import networkx as nx def build_graph_from_relations(relations): G nx.DiGraph() for relation in relations: source relation[source_user_id] target relation[target_user_id] if source target: continue G.add_edge(source, target) return G這里有個很容易踩的小坑很多人在建圖時會把重復(fù)邊直接覆蓋但正確的做法是先把權(quán)重累加起來再決定是否保留多條并行邊。我實際項目中更多使用的是“聚合權(quán)重”的方式也就是如果A關(guān)注B并且A也轉(zhuǎn)發(fā)過B的內(nèi)容那么A到B的這條邊權(quán)重就是2表示多重關(guān)系。3.4 圖數(shù)據(jù)的Neo4j持久化操作圖分析完成后如果項目需要對外展示或持續(xù)查詢往往要把結(jié)果存入圖數(shù)據(jù)庫。Neo4j是我用的最多的選擇它對流式寫入和路徑查詢都很友好。寫入Neo4j的核心過程是Cypher語句下面這段是我在項目中反復(fù)使用的批量導(dǎo)入模式UNWIND $batch as row MERGE (u:User {user_id: row.source_id}) MERGE (v:User {user_id: row.target_id}) MERGE (u)-[r:FOLLOWS]-(v) ON CREATE SET r.weight row.weight ON MATCH SET r.weight r.weight row.weight這里使用MERGE而不是CREATE原因很簡單避免重復(fù)導(dǎo)入的時候產(chǎn)生重復(fù)節(jié)點和重復(fù)邊。UNWIND加參數(shù)化查詢可以把幾千條邊一次寫入比逐條CREATE快非常多。如果你有百萬級以上的邊要入庫建議使用Neo4j的neo4j-admin import工具那個走的是批量文件導(dǎo)入通道速度是普通Cypher寫入的幾十倍。3.5 圖挖掘核心算法的實測過程圖構(gòu)建好之后最讓人興奮的部分來了把數(shù)據(jù)喂給算法看它們能得出什么結(jié)論。我在模擬項目X中主要用了三個算法分別是PageRank、介數(shù)中心性和Louvain社區(qū)發(fā)現(xiàn)。PageRank的計算過程非常直觀。它假設(shè)一個用戶在關(guān)系網(wǎng)絡(luò)中“跟隨”另一個用戶就相當于一次投票PageRank值越高的用戶說明被越多高權(quán)重用戶關(guān)注和互動。光看粉絲數(shù)有時會失真因為僵尸粉可能占到一半以上但PageRank因為考慮的是傳遞權(quán)重反而能更接近真實影響力。介數(shù)中心性回答的是另一個問題哪些用戶雖然在粉絲數(shù)量上不算頂尖卻在信息流動中起到關(guān)鍵橋梁作用。算法原理是統(tǒng)計所有節(jié)點對之間的最短路徑看某節(jié)點出現(xiàn)在多少條最短路徑上。出現(xiàn)越多介數(shù)中心性越高。這個指標用來識別隱藏的關(guān)鍵節(jié)點特別有效很多看起來默默無聞的運營人員實際上掌控著大量信息中轉(zhuǎn)。Louvain社區(qū)發(fā)現(xiàn)則是把關(guān)系緊密的用戶自動劃分為一個個小組。它基于模塊度優(yōu)化通過不斷迭代把節(jié)點歸并到讓社區(qū)結(jié)構(gòu)更清晰的模塊中。項目中跑出來的社區(qū)效果非常好有些社區(qū)我一眼就認出來是某個興趣領(lǐng)域的人群有些社區(qū)則需要結(jié)合用戶標簽才能判斷主題。代碼調(diào)用方式很簡單import networkx as nx from networkx.algorithms import community pagerank_scores nx.pagerank(G, alpha0.85) betweenness_scores nx.betweenness_centrality(G, k200) communities community.louvain_communities(G, resolution1.0)這里有兩個參數(shù)值得解釋一下。alpha0.85是PageRank算法中常見的阻尼系數(shù)它的含義是“用戶在關(guān)系網(wǎng)絡(luò)中繼續(xù)沿著邊跳轉(zhuǎn)的概率”剩下的0.15概率會隨機跳到任意節(jié)點。這個值不是拍腦袋定的而是一組實驗后證明效果較穩(wěn)定的默認值。k200表示只對200個隨機節(jié)點對計算最短路徑來做介數(shù)中心性估計因為全量計算的時間復(fù)雜度太高對于幾萬節(jié)點的圖來說完全不現(xiàn)實。3.6 結(jié)果可視化的呈現(xiàn)技巧分析結(jié)果如果只是藏在代碼里很難讓人直觀感受到價值所以我用pyvis和Gephi做了兩層可視化。pyvis的優(yōu)勢是可以生成獨立的HTML文件在瀏覽器里交互查看適合快速分享給同事或朋友。做法上我先把NetworkX的圖直接傳給pyvis再按PageRank值調(diào)整節(jié)點大小按社區(qū)標簽調(diào)整節(jié)點顏色。這樣圖一打開一眼就能看到哪些節(jié)點是核心、哪些社區(qū)是主體。Gephi更適合做深度美化。它可以對布局做力導(dǎo)向調(diào)整也能把社區(qū)聚類的效果渲染得更有層次感。我的建議是如果只是自己看用pyvis足夠如果要寫報告或者做分享用Gephi導(dǎo)出的圖更專業(yè)??梢暬幸粋€容易被忽略的操作要點在展示大規(guī)模網(wǎng)絡(luò)時務(wù)必設(shè)置好節(jié)點大小和顏色的映射閾值否則所有小節(jié)點擠在一起整個圖只會變成一團墨看不清任何結(jié)構(gòu)。我當時在Gephi里把PageRank排名前100的節(jié)點單獨選中放大做了局部高亮效果比全圖展示好得多。4. 常見問題與排查技巧實錄4.1 爬蟲請求被頻繁攔截的解決方案網(wǎng)上很多人討論爬蟲被攔截的問題說來說去無外乎幾點請求頻率太高、請求頭不完整、cookie失效、行為異常。我在模擬項目X期間也遇到過幾次最有價值的一條經(jīng)驗是一旦開始被攔截首先不要急著加大訪問間隔而是先檢查你的請求頭里有沒有遺漏關(guān)鍵字段。有一次我連續(xù)幾小時采集都正常突然所有請求都返回了驗證碼頁面。排查后發(fā)現(xiàn)并不是因為我頻率提高而是因為某個新接口要求額外的自定義Header字段如果該字段存在請求就是合法的如果缺失則直接被當成爬蟲攔截。加上這個字段之后一切恢復(fù)正常。更高的排查技巧是查看返回的HTML里是否包含特定提示文字。多數(shù)平臺攔截后返回的頁面文案都是固定的類模板比如“當前網(wǎng)絡(luò)環(huán)境異常”或要求完成安全驗證之類的話。把這些關(guān)鍵詞加入爬蟲日志的告警條件一旦命中就立刻通知自己能有效避免長時間無效抓取。4.2 圖數(shù)據(jù)導(dǎo)入Neo4j的性能優(yōu)化在把幾十萬條邊導(dǎo)入Neo4j時我曾經(jīng)犯過一個低級的錯誤使用逐條CREATE加MERGE寫循環(huán)結(jié)果跑了整整一個晚上還沒跑完。后來改成UNWIND批量寫入幾分鐘就完成了。速度差距如此大主要原因是每執(zhí)行一條Cypher語句Neo4j都需要做一次事務(wù)提交和網(wǎng)絡(luò)往返批量寫入則把幾千條數(shù)據(jù)放到一個事務(wù)里開銷被平均攤薄了。還有一個優(yōu)化點是索引。在建完節(jié)點后立刻在user_id字段上建立唯一約束或索引。否則后續(xù)每次MERGE節(jié)點時都要做全庫掃描數(shù)據(jù)量一大就寸步難行。建立索引的Cypher語句非常簡單CREATE CONSTRAINT IF NOT EXISTS ON (u:User) ASSERT u.user_id IS UNIQUE;4.3 PageRank結(jié)果異常的排查實錄項目第一次跑PageRank的時候我拿到結(jié)果后傻眼了排名第一的竟然是一個粉絲數(shù)只有幾百的小號。當時第一反應(yīng)是算法出了問題但仔細排查之后發(fā)現(xiàn)問題出在圖的構(gòu)建階段。這個小號有個特殊行為它在短時間內(nèi)關(guān)注了大量用戶而這些用戶又都回關(guān)或者轉(zhuǎn)發(fā)過它的內(nèi)容導(dǎo)致它的入邊權(quán)重特別高。再加上PageRank計算時邊的權(quán)重對結(jié)果影響很大所以它就“意外”地成了高影響力節(jié)點。這個現(xiàn)象其實反映了PageRank的一個重要特性它衡量的是節(jié)點在給定網(wǎng)絡(luò)結(jié)構(gòu)中的相對重要性而不是絕對知名度。解決方式也很簡單只需要在構(gòu)建圖時過濾掉“過度關(guān)注型”節(jié)點或者對權(quán)重做一次歸一化處理問題就消失了。這個經(jīng)驗讓我意識到在用算法做判斷前一定要先對圖數(shù)據(jù)的特性有基本認知否則再好的算法也容易被異常數(shù)據(jù)帶偏。4.4 社區(qū)發(fā)現(xiàn)算法結(jié)果與預(yù)期不符還有一次讓我印象很深的異常使用Louvain算法跑社區(qū)劃分時某個明顯不同的兩個用戶群被分到了同一個社區(qū)里。我起初懷疑是算法參數(shù)沒調(diào)好于是嘗試調(diào)整resolution參數(shù)但效果不明顯。后來仔細查看了這兩個用戶群的原始邊數(shù)據(jù)才發(fā)現(xiàn)他們之間存在大量低權(quán)重的關(guān)注邊。也就是說這兩個用戶群體之間并不是沒有聯(lián)系而是有很多的“弱連接”。Louvain算法在識別社區(qū)時對這些弱連接比較敏感所以把它們歸到了一個更大的社區(qū)里。這提醒我社區(qū)發(fā)現(xiàn)的結(jié)果本質(zhì)上是一個“視角”問題。算法的目標是把圖切成模塊度最高的若干塊但不一定符合人類的認知標簽。拿到結(jié)果后需要結(jié)合業(yè)務(wù)視角做后處理必要時可以通過調(diào)整resolution來達到更細粒度的劃分。這項操作需要反復(fù)試驗我最終的參數(shù)調(diào)整范圍大約在0.7到1.2之間多次對比后選擇了一組最貼近業(yè)務(wù)場景的結(jié)果。4.5 實操心得與避坑清單前面說了很多問題和排查過程這里整理一份我的獨家避坑清單都是常規(guī)文檔里不會寫的第一爬蟲代碼必須帶斷點續(xù)跑功能。無論你的采集任務(wù)有多短都要假設(shè)它會中斷而且要保證中斷后能快速恢復(fù)。我當時就是靠這個功能避免了多次因網(wǎng)絡(luò)抖動導(dǎo)致的全量重爬。第二構(gòu)建圖之前先做一層“邊數(shù)過濾”。如果你采集的數(shù)據(jù)中存在大量用戶互相關(guān)注的情況圖中會形成非常密集的全連接小團體這會嚴重拖慢后續(xù)算法的運行速度也會干擾中心性指標的計算結(jié)果。我的做法是刪除邊數(shù)超過閾值比如300條的用戶節(jié)點因為這類節(jié)點往往是機構(gòu)號或營銷號對分析真實社交關(guān)系沒有太大貢獻。第三NetworkX的內(nèi)存問題不可忽視。幾萬個節(jié)點加幾十萬條邊NetworkX還能扛得住但如果到幾百萬條邊內(nèi)存就會明顯吃緊。屆時建議換用圖數(shù)據(jù)庫配合Cypher查詢做分析或者使用Spark GraphX這類分布式圖計算框架。第四算法參數(shù)不是一成不變的。PageRank的alpha、社區(qū)發(fā)現(xiàn)的resolution、介數(shù)中心性的k采樣比例都需要根據(jù)你的圖規(guī)模和業(yè)務(wù)場景反復(fù)調(diào)整。先跑一版默認參數(shù)看結(jié)果再根據(jù)結(jié)果微調(diào)是最高效的做法。第五做社交網(wǎng)絡(luò)分析必須有倫理意識。采集公開數(shù)據(jù)是合法的但是數(shù)據(jù)使用邊界一定要清楚。不要去挖掘用戶的隱私信息也不要把分析結(jié)果用于騷擾、歧視或操縱他人。能在技術(shù)文章里把這種意識寫明白的博主不多但我認為這是行業(yè)長期健康發(fā)展的前提。5. 從數(shù)據(jù)到洞察圖挖掘結(jié)果的業(yè)務(wù)應(yīng)用與擴展方向圖數(shù)據(jù)和算法只是過程真正有價值的是最后的洞察。我以模擬項目X為例把分析結(jié)果應(yīng)用到幾個具體場景中這塊內(nèi)容偏分享性質(zhì)供你后續(xù)參考。第一個應(yīng)用方向是“關(guān)鍵節(jié)點發(fā)現(xiàn)”。通過PageRank和介數(shù)中心性的排序我找出了在一個特定話題領(lǐng)域中真正有話語權(quán)的30個用戶。他們中有些是粉絲過百萬的知名博主但也有一些是粉絲數(shù)不大、卻頻繁被核心用戶轉(zhuǎn)發(fā)的中轉(zhuǎn)節(jié)點。如果要做內(nèi)容推廣這些人比單純看粉絲數(shù)選人要精準得多。第二個應(yīng)用方向是“傳播路徑還原”。我在圖上選取了一個種子節(jié)點用NetworkX的最短路徑算法回溯了一條熱門話題的傳播鏈路發(fā)現(xiàn)話題最初在兩個小社區(qū)里萌芽然后經(jīng)過幾個中轉(zhuǎn)節(jié)點才被大V轉(zhuǎn)發(fā)擴散到全網(wǎng)。這條鏈路光看數(shù)據(jù)表是看不出來的但畫在圖上一目了然。第三個應(yīng)用方向是“社區(qū)重疊分析”。很多用戶同時屬于多個社區(qū)他們就像一個橋墩把不同圈子的信息連接起來。通過提取節(jié)點所屬的多個社區(qū)標簽我做了一個簡單的多維分類將用戶劃分為“圈內(nèi)活躍型”“跨圈橋梁型”“邊緣觀望型”這在用戶畫像構(gòu)建和內(nèi)容推薦里都有實用價值。后續(xù)如果你想把這個項目擴展成更完整的產(chǎn)品有幾個方向值得考慮一是接入實時流數(shù)據(jù)讓圖動態(tài)更新分析熱點事件的演化過程二是結(jié)合自然語言處理技術(shù)把用戶的文本內(nèi)容向量化后作為節(jié)點屬性做“內(nèi)容結(jié)構(gòu)”的聯(lián)合分析三是引入時序圖模型分析關(guān)系強度的變化趨勢。這些方向每一塊都能撐起一個獨立項目但地基還是這篇文章里講的爬蟲、建圖和圖挖掘這套完整流程。根據(jù)我個人經(jīng)驗做這類項目的最大收益不只是產(chǎn)出了一份分析報告而是養(yǎng)成了“用結(jié)構(gòu)視角看問題”的習(xí)慣。關(guān)系網(wǎng)絡(luò)分析并不是什么高不可攀的前沿技術(shù)它本質(zhì)上就是一套方法論的組合用爬蟲拿數(shù)據(jù)、用圖建模、用算法找模式、用可視化講故事。你只要把這四步走通了任何帶有關(guān)系屬性的數(shù)據(jù)集都能拿過來做一次有意思的探索。如果你正準備上手我建議從一個小目標開始先抓1000個用戶的關(guān)系數(shù)據(jù)跑一遍中心性指標和社區(qū)發(fā)現(xiàn)再試著把結(jié)論用圖展示出來。等你完整跑通了這條鏈路再考慮擴大數(shù)據(jù)量和引入更復(fù)雜的算法也不遲。希望這篇復(fù)盤對你有點幫助。