
1. 項目概述為什么用Selenium抓取抖音視頻最近在做一個數據分析項目需要一批特定主題的短視頻作為素材。市面上現成的數據集要么不夠新要么標簽不符合我的需求。于是我自然而然地把目光投向了抖音——這個巨大的內容寶庫。手動下載效率太低而且容易出錯。直接調用官方API對于個人開發者和小型研究項目來說門檻和風險都太高。幾番權衡之后我決定采用一個相對“溫和”且可控的方案使用Selenium模擬真人操作從抖音網頁版抓取視頻。你可能會問為什么是Selenium在爬蟲領域Scrapy、requests這些庫不是更直接嗎這里的關鍵在于抖音這類現代單頁應用SPA的動態特性。它的內容尤其是視頻流是通過JavaScript異步加載的傳統的靜態HTML解析方法很難直接獲取到視頻的真實地址。而Selenium的核心價值在于它能驅動一個真實的瀏覽器如Chrome完整地執行頁面上的所有JavaScript代碼讓頁面呈現出最終用戶看到的樣子。這樣我們就能像真人一樣通過瀏覽器的開發者工具DevTools去“觀察”到網絡請求從而定位到那個最關鍵的.mp4文件地址。這本質上是一種“所見即所得”的抓取思路雖然效率上不如直接分析接口那么高但勝在穩定、直觀且能繞過一些簡單的反爬機制如需要執行JS才能生成的令牌。這個項目適合誰呢如果你是一名數據分析師、機器學習愛好者需要構建自己的視頻數據集或者是一名開發者想學習如何處理動態網頁和媒體內容亦或是僅僅對自動化技術感興趣想了解如何讓程序模擬人的瀏覽行為那么接下來的內容會對你很有幫助。我會從環境搭建、核心思路、代碼實現到避坑技巧完整地走一遍這個流程。需要提前說明的是本文所有技術討論均基于學習與研究目的旨在探討技術實現原理實際操作中請務必嚴格遵守相關平臺的服務條款尊重內容創作者的版權。2. 核心思路與方案設計2.1 技術選型Selenium ChromeDriver的組合邏輯選擇Selenium作為主力工具背后有幾個核心考量。首先瀏覽器環境真實性。抖音的反爬策略會檢測請求頭、Cookie、甚至瀏覽器指紋。使用Selenium驅動的Chrome瀏覽器其發出的網絡請求和產生的瀏覽器指紋與真人操作幾乎無異這為我們提供了天然的掩護。其次強大的交互模擬能力。抓取過程需要滾動頁面、點擊按鈕如“展開更多評論”、處理彈窗如登錄框這些交互Selenium都能通過定位元素并執行點擊、輸入等命令來完美模擬。最后對動態內容的完全渲染。這是解決核心問題的鑰匙只有讓頁面JS執行完畢視頻元素和對應的源文件地址才會暴露在DOM樹或網絡請求中。為什么不直接用requests庫模擬接口因為抖音的接口參數往往經過復雜的加密且頻繁變動逆向工程成本極高。而Selenium的策略是“繞過”接口分析直接獲取最終結果視頻文件對于快速實現、驗證想法的場景來說路徑更短。當然這套方案的代價是資源消耗大每個實例都是一個完整的瀏覽器進程和速度相對較慢需要等待頁面加載和渲染。因此它更適合對實時性要求不高、但需要高成功率的中小規模抓取任務。配套工具方面ChromeDriver是連接Selenium代碼與Chrome瀏覽器的橋梁版本必須與本地安裝的Chrome瀏覽器嚴格對應否則會無法啟動。瀏覽器開發者工具DevTools是我們的“眼睛”后續定位視頻地址全靠它。代碼層面我將使用Python因為它擁有最成熟的Selenium綁定庫和豐富的數據處理生態。2.2 抓取流程的整體架構設計整個抓取流程可以抽象為一個狀態機其核心目標是安全、穩定地獲取到視頻的MP4直鏈。我設計的流程如下初始化與啟動配置Selenium WebDriver設置無頭模式Headless后臺運行、反檢測參數并啟動瀏覽器。導航與等待訪問目標抖音用戶主頁或特定話題Challenge頁面使用“顯式等待”策略智能等待關鍵元素如視頻列表容器加載完成這是保證腳本健壯性的關鍵。頁面滾動與內容加載抖音采用無限滾動加載。我們需要模擬滾動操作并判斷何時停止例如達到目標數量或頁面不再加載新內容。視頻鏈接提取這是技術核心。滾動加載出視頻后我們需要從頁面中解析出每一個視頻帖子的獨立鏈接即每個視頻的詳情頁URL。深入詳情頁獲取媒體源逐個訪問上一步獲取的詳情頁鏈接。在此頁面內通過Selenium執行腳本或監聽網絡請求從視頻播放器標簽video的src屬性中或者從DevTools Network面板中過濾出的media類型請求里提取出最高清版本的.mp4文件直鏈。下載與存儲使用requests庫配合從瀏覽器中繼承的請求頭特別是Referer和User-Agent下載MP4文件并按照規則如用戶ID/視頻ID命名存儲。循環與終止循環處理所有目標視頻鏈接完成后優雅關閉瀏覽器驅動。這個流程中第4步和第5步是難點和重點。直接在當前列表頁可能無法獲取到高清源跳轉到詳情頁是更可靠的方案。同時整個流程必須植入足夠的隨機延遲和人類行為模擬如隨機滾動幅度以避免觸發頻率限制。3. 環境準備與關鍵配置3.1 基礎環境搭建步驟首先確保你的Python環境建議3.8及以上已就緒。然后通過pip安裝核心庫pip install selenium webdriver-manager這里特別推薦webdriver-manager這個庫。它的一大功勞是能自動下載和管理與你的Chrome瀏覽器版本匹配的ChromeDriver省去了手動查找和配置的麻煩。接下來是初始化WebDriver的代碼這里包含了幾個至關重要的配置項from selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager from selenium.webdriver.chrome.options import Options def create_driver(): chrome_options Options() # 1. 啟用無頭模式后臺運行不顯示GUI chrome_options.add_argument(--headlessnew) # 新版Chrome推薦使用‘new’ # 2. 禁用自動化控制標志降低被檢測風險 chrome_options.add_experimental_option(excludeSwitches, [enable-automation]) chrome_options.add_experimental_option(useAutomationExtension, False) # 3. 修改navigator.webdriver屬性進一步偽裝 chrome_options.add_argument(--disable-blink-featuresAutomationControlled) # 4. 設置用戶代理User-Agent模擬真實瀏覽器 chrome_options.add_argument(user-agentMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36) # 5. 其他優化參數 chrome_options.add_argument(--disable-gpu) # 某些系統上需要 chrome_options.add_argument(--no-sandbox) # Linux環境下常需 chrome_options.add_argument(--disable-dev-shm-usage) # 解決共享內存問題 # 使用webdriver-manager自動管理驅動 service Service(ChromeDriverManager().install()) driver webdriver.Chrome(serviceservice, optionschrome_options) # 執行CDP命令覆蓋WebDriver屬性關鍵反檢測步驟 driver.execute_cdp_cmd(Page.addScriptToEvaluateOnNewDocument, { source: Object.defineProperty(navigator, webdriver, { get: () undefined }); }) return driver注意無頭模式雖然節省資源但在調試初期建議先注釋掉--headless參數讓瀏覽器窗口顯示出來。這樣你能直觀地看到腳本的操作過程方便定位問題。3.2 反爬策略的針對性配置抖音這類平臺會對自動化腳本進行檢測。上述代碼中的幾個點就是為此準備的excludeSwitches和useAutomationExtension移除Chrome瀏覽器中“正受到自動測試軟件控制”的提示。--disable-blink-featuresAutomationControlled和execute_cdp_cmd這兩者是組合拳。前者禁用某些可能暴露自動化特征的Blink功能后者直接在頁面加載前注入JavaScript將navigator.webdriver屬性重寫為undefined。很多網站通過檢測這個屬性是否為true來判斷是否為自動化瀏覽器。User-Agent設置一個常見的、更新的桌面版Chrome UA避免使用默認的測試UA。這些措施能應對基礎的反爬但要知道高級的反爬系統如行為指紋識別可能結合鼠標移動軌跡、點擊頻率、頁面停留時間等多維度判斷。因此在核心操作邏輯中引入隨機性至關重要。4. 核心抓取流程實現4.1 導航目標頁面與智能等待假設我們的目標是抓取某個特定用戶主頁下的所有視頻。首先導航到該頁面例如https://www.douyin.com/user/MS4wLjABAAAA...。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 driver create_driver() user_url https://www.douyin.com/user/目標用戶_SECRET_UID driver.get(user_url) # 關鍵使用顯式等待等待視頻列表容器出現 try: # 抖音網頁版視頻列表通常在一個類名為‘Eie04v01’或類似結構的div中需實時探查 video_list_container WebDriverWait(driver, 15).until( EC.presence_of_element_located((By.CSS_SELECTOR, div[data-e2euser-post-list])) # 示例選擇器需更新 ) print(頁面核心內容加載成功) except Exception as e: print(f等待頁面超時或元素未找到: {e}) driver.quit()這里使用了WebDriverWait配合expected_conditions進行顯式等待。它與簡單的time.sleep(10)強制等待有本質區別。顯式等待會每隔一段時間默認0.5秒檢查一次條件是否滿足一旦滿足就立即繼續執行最大程度節省時間并提高穩定性。presence_of_element_located表示元素出現在DOM中即可不一定需要可見或可點擊。實操心得抖音的頁面結構可能頻繁變動>def scroll_and_collect_links(driver, max_scrolls20): collected_links set() # 使用集合避免重復 last_height driver.execute_script(return document.documentElement.scrollHeight) scroll_attempt 0 while scroll_attempt max_scrolls: # 1. 模擬滾動到底部 driver.execute_script(window.scrollTo(0, document.documentElement.scrollHeight);) time.sleep(random.uniform(2.0, 4.0)) # 隨機等待模擬人類閱讀時間 # 2. 等待新內容加載 new_height driver.execute_script(return document.documentElement.scrollHeight) if new_height last_height: # 高度未變可能已加載完畢或遇到“暫時沒有更多了” print(頁面滾動到底部可能已無新內容。) # 可以再嘗試等待一下或點擊“加載更多”按鈕如果存在 time.sleep(3) new_height driver.execute_script(return document.documentElement.scrollHeight) if new_height last_height: break # 確認加載完畢退出循環 last_height new_height # 3. 在當前視圖中提取視頻詳情頁鏈接 # 抖音視頻鏈接通常包含 /video/ 路徑 video_elements driver.find_elements(By.CSS_SELECTOR, a[href*/video/]) for elem in video_elements: href elem.get_attribute(href) if href and douyin.com/video/ in href: # 清理鏈接去除可能的查詢參數獲取標準格式 clean_link href.split(?)[0] collected_links.add(clean_link) print(f發現視頻鏈接: {clean_link}) scroll_attempt 1 print(f已完成第 {scroll_attempt} 次滾動已收集 {len(collected_links)} 個唯一鏈接。) # 4. 隨機休息降低請求頻率 time.sleep(random.uniform(1.0, 2.5)) return list(collected_links) # 執行滾動收集 video_detail_links scroll_and_collect_links(driver, max_scrolls15) print(f總共收集到 {len(video_detail_links)} 個視頻詳情頁鏈接。)這段代碼的邏輯是滾動 → 等待加載 → 解析當前DOM中的鏈接 → 去重存儲。max_scrolls參數用于控制最大滾動次數防止無限循環。random.uniform引入的隨機等待時間是模擬人類行為、規避反爬的重要一環。4.3 從詳情頁提取視頻直鏈這是最具技術挑戰性的一步。視頻文件通常不會直接以video src...的形式靜態寫在HTML里而是由前端播放器動態加載。我們有幾種策略策略一從video標簽直接提取如果幸運的話在部分頁面結構下視頻源地址可能直接存在于video標簽的src屬性中。我們可以嘗試查找def get_video_url_from_video_tag(driver, page_url): driver.get(page_url) time.sleep(random.uniform(3, 5)) # 等待頁面和視頻播放器初始化 try: # 嘗試尋找video標簽 video_tag WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.TAG_NAME, video)) ) video_src video_tag.get_attribute(src) if video_src and video_src.endswith(.mp4): return video_src except Exception as e: print(f從video標簽獲取失敗: {e}) return None策略二監聽網絡請求更可靠的方法更通用的方法是在頁面加載過程中從瀏覽器開發者工具的網絡請求記錄中篩選出視頻media類型的請求。Selenium本身不直接提供網絡請求監聽功能但我們可以通過啟用Chrome的性能日志Performance Log或使用DevTools Protocol (CDP) 來實現。這里介紹使用CDP命令Network.enable來監聽請求的方法def get_video_url_via_cdp(driver, page_url): # 啟用網絡跟蹤 driver.execute_cdp_cmd(Network.enable, {}) mp4_urls [] # 定義請求事件監聽器Python中通過CDP命令回調較復雜此處簡化為腳本注入后獲取 # 更實用的方法是先導航到一個空白頁設置監聽再導航到目標頁最后讀取日志。 # 但Selenium Python對CDP事件回調支持不直接一個變通方案是 # 1. 導航到目標頁 driver.get(page_url) time.sleep(4) # 確保視頻請求發生 # 2. 嘗試從當前頁面的全局變量或播放器實例中獲取抖音可能將地址存儲在JS變量中 # 這是一個探索性過程需要分析抖音前端代碼 script // 嘗試尋找視頻播放器組件 let videoPlayers document.querySelectorAll(video); for (let player of videoPlayers) { if (player.src player.src.includes(.mp4)) { return player.src; } // 有些播放器使用source標簽 let source player.querySelector(source[typevideo/mp4]); if (source source.src) { return source.src; } } // 嘗試從常見的全局變量或React/Vue組件狀態中尋找需要具體分析 // 例如 window._feInstance?.state?.videoInfo?.url return null; video_url driver.execute_script(script) if video_url: return video_url # 3. 如果上述方法失敗一個“笨”但有效的方法是分析頁面HTML中可能隱藏的JSON數據 # 抖音經常將視頻信息放在script typeapplication/json或某個大JSON字符串中 page_source driver.page_source # 這里可以編寫正則表達式來搜索包含.mp4鏈接的JSON塊例如 import re # 這是一個非常簡化的示例實際模式復雜得多 pattern r\(https:[^\\s]*?\.mp4[^\\s]*?)\ found_urls re.findall(pattern, page_source) for url in found_urls: if douyin in url or tiktok in url: # 簡單過濾 return url return None核心技巧在實際操作中策略二中的“分析頁面JSON數據”是最穩定、最高效的方法。你需要使用開發者工具在Network面板中搜索.mp4找到一個視頻請求后在Headers或Preview中查看其完整URL。然后在Elements面板中搜索這個URL的一部分或者搜索videoInfo、playApi等關鍵詞往往能找到一段包含所有視頻信息的巨大JSON對象。將這個JSON對象的路徑例如某個script idRENDER_DATA標簽的內容通過driver.execute_script(return document.getElementById(RENDER_DATA).textContent;)獲取下來再用json.loads可能需要urllib.parse.unquote解碼解析就能結構化地提取出視頻標題、描述、點贊數以及不同清晰度的視頻播放地址。這是專業爬蟲的常用手段。4.4 視頻下載與存儲管理獲取到MP4直鏈后下載就相對簡單了。但需要注意下載時需要攜帶合適的請求頭特別是Referer通常需要設置為抖音的域名否則服務器可能會拒絕服務。import requests import os from urllib.parse import urlparse def download_video(video_url, referer_url, save_dir./videos): if not video_url: return False os.makedirs(save_dir, exist_okTrue) # 從URL中提取一個合理的文件名如視頻ID parsed_url urlparse(video_url) # 假設URL格式為 .../video_id.mp4?... path parsed_url.path video_id os.path.splitext(os.path.basename(path))[0] or unknown_video filename f{video_id}.mp4 filepath os.path.join(save_dir, filename) headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Referer: referer_url, # 關鍵告訴服務器請求來自抖音頁面 Accept: */*, Accept-Language: zh-CN,zh;q0.9, Connection: keep-alive, } try: print(f正在下載: {filename}) response requests.get(video_url, headersheaders, streamTrue, timeout30) response.raise_for_status() # 檢查HTTP錯誤 with open(filepath, wb) as f: for chunk in response.iter_content(chunk_size8192): f.write(chunk) print(f下載成功: {filepath}) return True except Exception as e: print(f下載失敗 {video_url}: {e}) return False # 在主循環中調用 for idx, detail_link in enumerate(video_detail_links): print(f\n處理第 {idx1}/{len(video_detail_links)} 個視頻: {detail_link}) mp4_url get_video_url_via_cdp(driver, detail_link) # 或者用其他方法 if mp4_url: success download_video(mp4_url, detail_link) if not success: # 可以加入重試邏輯 pass # 處理間隔隨機化 time.sleep(random.uniform(2, 5))5. 高級技巧與穩定性優化5.1 處理登錄狀態與Cookie要抓取個人主頁或“朋友”標簽頁的內容需要登錄狀態。Selenium可以模擬登錄但更推薦手動登錄后導出Cookie供腳本使用這樣更穩定且能繞過復雜的登錄驗證如滑塊驗證碼。用配置好的Selenium驅動打開抖音并手動登錄。登錄成功后使用driver.get_cookies()獲取所有Cookie。將Cookie列表以JSON格式保存到本地文件。在后續的抓取腳本啟動后先訪問一次抖音首頁然后通過driver.add_cookie(cookie_dict)循環添加所有Cookie最后刷新頁面或訪問目標頁即可保持登錄狀態。def load_cookies_from_file(driver, cookie_filecookies.json): import json driver.get(https://www.douyin.com) # 先導航到域名 time.sleep(2) with open(cookie_file, r, encodingutf-8) as f: cookies json.load(f) for cookie in cookies: # 確保domain字段有效抖音可能是 .douyin.com if domain in cookie and cookie[domain] not in [.douyin.com, www.douyin.com]: cookie[domain] .douyin.com try: driver.add_cookie(cookie) except Exception as e: print(f添加Cookie失敗: {cookie.get(name)}, 錯誤: {e}) driver.refresh() # 刷新使Cookie生效 time.sleep(3) # 檢查是否登錄成功例如查找用戶頭像元素 try: driver.find_element(By.CSS_SELECTOR, div[data-e2euser-avatar]) print(Cookie登錄成功) return True except: print(Cookie登錄失敗可能需要重新獲取。) return False5.2 應對頁面結構變化與元素定位抖音前端迭代很快選擇器很容易失效。提高腳本魯棒性的方法使用多種定位策略組合不要只依賴一個CSS選擇器??梢越M合>def find_element_with_retry(driver, selectors, byBy.CSS_SELECTOR, timeout10): 嘗試多個選擇器直到找到一個元素。 selectors: 選擇器字符串列表 for selector in selectors: try: element WebDriverWait(driver, timeout).until( EC.presence_of_element_located((by, selector)) ) return element except: continue raise NoSuchElementException(f所有選擇器都未找到元素: {selectors}) # 使用示例 video_container find_element_with_retry(driver, [ div[data-e2euser-post-list], .Eie04v01, //div[contains(class, video-list)] # XPath示例 ])5.3 引入更逼真的人類行為模擬簡單的隨機等待還不夠。我們可以模擬更復雜的行為模式隨機滾動幅度不完全滾動到底部有時滾動一半有時小幅回滾。模擬鼠標移動使用Selenium的ActionChains在頁面上隨機移動鼠標。隨機點擊非功能區域偶爾在空白處點擊一下。變化操作間隔使用正態分布或隨機區間來生成等待時間使其更接近真人操作。from selenium.webdriver.common.action_chains import ActionChains import random import numpy as np def human_like_scroll(driver): current_height driver.execute_script(return document.documentElement.scrollHeight) viewport_height driver.execute_script(return window.innerHeight) # 隨機決定滾動目標位置例如滾動到頁面高度的70%-100%之間 scroll_to_ratio random.uniform(0.7, 1.0) target_scroll int(current_height * scroll_to_ratio) # 分步滾動模擬人類的不連貫性 steps random.randint(3, 6) step_size target_scroll // steps for i in range(steps): driver.execute_script(fwindow.scrollBy(0, {step_size});) time.sleep(random.uniform(0.2, 0.8)) # 每步之間短暫停頓 # 偶爾小幅回滾 if random.random() 0.7: driver.execute_script(window.scrollBy(0, -100);) time.sleep(random.uniform(0.5, 1.2)) # 模擬鼠標隨機移動 actions ActionChains(driver) x_offset random.randint(-50, 50) y_offset random.randint(-50, 50) try: # 移動到某個隨機元素上如果找不到則移動到文檔某處 some_elements driver.find_elements(By.TAG_NAME, div)[:10] if some_elements: actions.move_to_element(random.choice(some_elements)).move_by_offset(x_offset, y_offset).perform() except: pass time.sleep(random.uniform(1.5, 3.5)) # 滾動后“閱讀”時間6. 常見問題排查與解決方案實錄在實際操作中你幾乎一定會遇到下面這些問題。這里是我踩過坑后總結的排查清單。6.1 元素找不到NoSuchElementException這是最常見的問題。可能原因1頁面未加載完。解決方案始終使用WebDriverWait進行顯式等待而不是time.sleep。確保等待條件正確如元素可見(visibility_of_element_located)或可點擊(element_to_be_clickable)??赡茉?選擇器已過時。解決方案打開瀏覽器開發者工具使用選擇器檢查工具重新定位元素。優先使用>