頁:從服務鏈路到瀏覽器配置排查)
敲下jupyter notebook回車終端刷出一大屏日志最后一行清清楚楚寫著http://localhost:8888/?token…然后——瀏覽器一聲不吭什么都不動。這個場面我少說遇到過七八次不同的筆記本、不同的系統(tǒng)、不同的裝法癥狀幾乎一模一樣。Jupyter notebook 無法跳轉網(wǎng)頁這個問題本身不復雜但它惡心人的點在于服務其實已經(jīng)起來了內(nèi)核也能跑就是那一下自動幫你打開默認瀏覽器的動作沒做成于是新手完全不知道下一步該干什么只能反復關掉終端重敲命令越敲越懵。這篇文章針對的就是這一類情況命令行里 notebook 已經(jīng)啟動、但瀏覽器沒自動彈出或者手動打開地址后頁面空白、報連接被拒絕甚至是jupyter notebook 打不開、無法運行、單元格執(zhí)行代碼沒有任何反應這些連帶的毛病。不管你用的是jupyter notebook還是jupyter notebook網(wǎng)頁版那種遠端形態(tài)只要能摸到終端這里的方法大多能套用。我會從啟動鏈路講到配置修改再順手把ImportError: DLL load failed while importing rpds這種啟動前就崩掉的坑一起收拾盡量做到你照著抄就能用。1. 先把為什么沒跳轉這件事拆開看1.1 一條 jupyter notebook 命令背后到底跑了什么很多人把jupyter notebook當成一個打開瀏覽器的程序這個認知一開始就偏了。它的真實身份是一個本地 Web 服務啟動時先拉起一個基于 tornado 的 HTTP 服務進程讀取配置里綁定的 IP 和端口生成一個帶 token 的訪問路徑把服務注冊到運行目錄最后才開始嘗試調(diào)用 Python 標準庫里的webbrowser模塊去喚起系統(tǒng)默認瀏覽器。也就是說你看到的這一整屏日志其實分兩段含義。前半段是服務已就緒后半段才是我試著幫你打開頁面。這兩件事是解耦的——服務起來了瀏覽器那一步失敗進程照樣活著日志照樣刷只是你眼前什么都沒有。理解了這一點后面所有排查思路都順了先確認服務在不在再單獨處理瀏覽器那一步。我習慣把日志里那行帶 token 的 URL 當作服務健康證明。只要它能被打印出來就說明端口綁定成功、配置沒寫壞、Python 依賴沒缺問題 90% 以上出在客戶端側的網(wǎng)絡訪問或者瀏覽器喚起環(huán)節(jié)。反過來說如果你連這行 URL 都沒看到那壓根不是沒跳轉的問題得往依賴和配置方向查。1.2 三種典型現(xiàn)象對應三類根因同樣是打不開網(wǎng)頁細節(jié)差別很大先看現(xiàn)象再決定往哪個方向查能省掉大量瞎試的時間?,F(xiàn)象大概率原因優(yōu)先排查動作終端正常瀏覽器完全沒反應默認瀏覽器未注冊、webbrowser 調(diào)用失敗先忽略自動跳轉手動復制 URL 訪問瀏覽器彈出但提示無法訪問此網(wǎng)站端口綁定 IP 與訪問地址不匹配、IPv6 解析問題把 localhost 換成 127.0.0.1 重試頁面打開但要輸入密碼/tokentoken 參數(shù)丟了或被截斷用jupyter notebook list反查完整地址頁面打開卻一直轉圈、空白安全軟件攔截、瀏覽器插件干擾、緩存問題換隱私窗口 換端口重試服務根本沒起來就報錯退出依賴缺失、DLL 加載失敗、端口被占用看報錯最后一行別只看開頭這張表我自己排過很多次順序基本沒變過。完全沒反應這一檔最容易被誤判成嚴重故障實際上它往往是最容易解決的因為服務本身是好的。我見過有同事因為這個重裝了三次 Anaconda最后發(fā)現(xiàn)只是默認瀏覽器的注冊表項被某個卸載殘留搞壞了。1.3 一個必須建立的認知服務歸服務瀏覽器歸瀏覽器這是我最想強調(diào)的一點。Jupyter 的瀏覽器喚起依賴于 Python 的webbrowser模塊而這個模塊在不同平臺上的實現(xiàn)方式差別巨大Windows 上它去讀注冊表里http協(xié)議關聯(lián)的程序macOS 上它調(diào)用open命令Linux 上它按BROWSER環(huán)境變量、xdg-open、x-www-browser的順序一路找。任何一個環(huán)節(jié)有了偏差自動跳轉就會靜默失敗——注意是靜默它不會給你報錯日志里那行 URL 照打不誤。所以從今天開始我建議你把手動訪問當成常規(guī)操作而不是應急手段。就像服務器運維不會指望每次都自動登錄一樣本地開發(fā)也完全可以接受我自己粘貼一下地址。一旦建立這個習慣你會發(fā)現(xiàn) Jupyter 無法跳轉網(wǎng)頁根本不算故障頂多算個不便利。2. 手動接管不依賴自動跳轉的穩(wěn)妥打開方式2.1 從日志和 list 命令里讀出真實地址終端輸出的那行 URL 是最權威的。它長這樣http://127.0.0.1:8888/tree?token4f2a9c1e8b7d3a5f6e0b2c4d8a1f3e5b7c9d0a2f注意三塊信息協(xié)議和地址http://127.0.0.1:8888、路徑/tree、查詢參數(shù)?token…。token 是長長一串十六進制字符長度通常 48 位左右它在本次服務生命周期內(nèi)是有效的復制時必須完整。實際工作中最常見的低級失誤就是復制時少了幾位或者把末尾的標點符號一起粘進去了——瀏覽器不會告訴你你的 token 少了一位它只會讓你重新登錄。如果你不小心把終端滾屏沖掉了或者用的是圖形化啟動器根本看不到日志別急。新開一個終端窗口敲jupyter notebook list它會列出當前機器上所有正在運行的 notebook 服務格式大致是Currently running servers: http://127.0.0.1:8888/?token4f2a9c1e... :: /Users/me/workspace/notebooks右邊那段路徑就是服務的工作目錄左邊就是完整入口。用新版jupyter_server的話對應命令是jupyter server list。這兩個命令我?guī)缀趺看螕Q機器都會用比翻終端歷史快得多。注意如果list返回的是There are no running servers那說明服務確實沒起來或者已經(jīng)被關掉了這時候再談跳轉失敗就跑偏了得回到啟動環(huán)節(jié)排查。2.2 手動粘貼 URL 時容易踩的三個小坑第一個坑是地址被瀏覽器當成搜索詞。有些瀏覽器地址欄在你粘貼127.0.0.1:8888這種沒有 http 前綴的內(nèi)容時會自作聰明地跳去搜索引擎。養(yǎng)成帶完整http://前綴的習慣就沒事了。第二個坑是中文輸入法狀態(tài)。全角冒號、全角斜杠在地址欄里看起來幾乎一樣但服務器完全不認。我有一次排查了二十分鐘最后發(fā)現(xiàn)是8888里的冒號是全角的——這種錯誤極其隱蔽因為你盯著屏幕看不出差別。第三個坑是 token 里的字符被自動轉義。某些編輯器粘貼時會智能處理把?或者轉成全角。穩(wěn)妥做法是把整行 URL 一次性復制不要在中間插手動修改。2.3 換端口、指定綁定地址把不確定因素擠出去端口沖突是啟動失敗的常見原因尤其在同時開多個項目、或者上一次的進程沒退干凈的時候。默認 8888 被占用時Jupyter 會自動往后找 8889、8890但有時候它找到的端口恰好被別的東西占著表現(xiàn)就會很怪。干脆手動指定jupyter notebook --no-browser --port8899 --ip127.0.0.1--no-browser的意思是別費勁幫我開瀏覽器了加上它以后啟動過程會干凈很多等于你主動關掉了那個不可靠的環(huán)節(jié)。--ip127.0.0.1把服務綁定在回環(huán)地址上只有本機能訪問這是本地開發(fā)最安全也最不容易出問題的選擇。想查端口到底被誰占了Windows 下用netstat -ano | findstr :8888macOS 和 Linux 下用lsof -i :8888拿到 PID 之后自己判斷要不要結束那個進程。我個人的習慣是見到一次沖突就永久換一個自己喜歡的端口比如 8899、8866 這類不容易撞車又不算規(guī)范的號省得每次都折騰。2.4 設個訪問密碼從此不再跟 token 搏斗token 長、難記、復制容易出錯。更好的做法是給 notebook 設一個固定密碼jupyter notebook password它會提示你輸兩遍密碼然后把哈希值寫進~/.jupyter/jupyter_server_config.json。之后不管服務怎么重啟、端口怎么變你打開頁面都只需要輸這個密碼徹底告別 token 復制。這一步對經(jīng)常重啟服務的人價值極大我基本拿到新機器第一件事就是設它。提示只有當你把--ip設成0.0.0.0讓局域網(wǎng)其他設備也能訪問時密碼才從方便變成必需。綁定回環(huán)地址的情況下密碼純粹是給自己省事用的。3. 讓自動跳轉重新工作配置層面的修復3.1 生成配置文件先找到它再說改Jupyter 的配置文件不會默認存在得先生成jupyter notebook --generate-config它會告訴你文件生成在哪兒同時提醒你如果文件已存在則不會覆蓋。三個平臺的位置分別是系統(tǒng)配置文件路徑WindowsC:\Users\你的用戶名\.jupyter\jupyter_notebook_config.pymacOS/Users/你的用戶名/.jupyter/jupyter_notebook_config.pyLinux/home/你的用戶名/.jupyter/jupyter_notebook_config.py注意這是.jupyter目錄前面有個點Windows 上這個目錄不一定隱藏但需要手動輸入路徑才能進。文件本身是一個超長的 Python 腳本里面幾百行全是注釋真正要改的只有那么幾行。用編輯器打開它別用記事本——編碼問題會讓中文注釋變亂碼還可能在你保存時悄悄加上 BOM。3.2 關鍵幾行配置怎么改才有效最核心的是指定瀏覽器。Windows 下 Python 的webbrowser模塊讀注冊表找默認瀏覽器一旦注冊表項殘缺它就找不到目標。解決辦法是手工注冊一個import webbrowser webbrowser.register( chrome, None, webbrowser.GenericBrowser(rC:\Program Files\Google\Chrome\Application\chrome.exe) ) c.ServerApp.browser chrome這里有幾處細節(jié)值得說。路徑用原始字符串r...避免反斜杠被當轉義符webbrowser.register的第一個參數(shù)是你自己起的名字第二個參數(shù)傳None表示不指定內(nèi)部實現(xiàn)名第三個參數(shù)才是真正的可執(zhí)行文件路徑。注冊完再通過c.ServerApp.browser指名用它。另一個容易被忽略的選項是重定向文件c.ServerApp.use_redirect_file False這個選項默認為True時Jupyter 會先在系統(tǒng)臨時目錄寫一個 HTML 跳轉文件再讓瀏覽器打開這個本地文件通過它跳到真正的服務地址。某些環(huán)境下臨時目錄權限異常、瀏覽器對本地文件策略收緊這一步會卡住表現(xiàn)就是瀏覽器彈了一下又沒了或者干脆沒動靜。設成False之后它直接打開http://地址鏈路短了一截出問題的概率明顯下降。我在 Windows 上處理這類問題時這一行幾乎是必改項。3.3 版本差異對照NotebookApp 還是 ServerApp這是新手最容易改錯的地方。Jupyter 從 notebook 6 升級到 notebook 7 時底層從自己的服務實現(xiàn)換成了jupyter_server配置項前綴隨之從NotebookApp變成了ServerApp。你從網(wǎng)上搜到的老教程寫的都是c.NotebookApp.browser在 notebook 7 上是不生效的。版本配置前綴主配置文件notebook 6 及更早c.NotebookApp.*jupyter_notebook_config.pynotebook 7 / jupyter_serverc.ServerApp.*jupyter_server_config.py混合環(huán)境兩者都寫最省事兩個文件都可以放想確認自己的版本敲jupyter notebook --version我的做法比較粗暴兩個配置文件里都把對應前綴的配置項寫一遍。反正多寫幾行不影響能兼容我所有臨時切換的環(huán)境。這不算優(yōu)雅但確實省事尤其當你需要在多臺機器之間同步配置的時候。3.4 寫個啟動腳本把這一堆參數(shù)固化下來命令行參數(shù)太多記不住就把它寫進腳本。macOS / Linux 下建個start_nb.sh#!/usr/bin/env bash jupyter notebook \ --no-browser \ --port8899 \ --ip127.0.0.1 \ --notebook-dir$HOME/workspace/notebooksWindows 下對應寫個start_nb.batecho off jupyter notebook --no-browser --port8899 --ip127.0.0.1 --notebook-dirD:\workspace\notebooks pause注意 Windows 腳本里最后加pause否則雙擊運行會一閃而過什么都看不見。--notebook-dir指定工作目錄也很實用避免每次啟動都從用戶主目錄開始翻文件。我用了幾年下來這類小腳本省的時間比想象中多因為記參數(shù)本身就是一種隱性消耗。4. 瀏覽器側和系統(tǒng)側的坑那些不賴 Jupyter 的鍋4.1 localhost 與 127.0.0.1 之間的微妙差異現(xiàn)代瀏覽器解析localhost時會同時拿到 IPv6 的::1和 IPv4 的127.0.0.1并且傾向于優(yōu)先試 IPv6。而 Jupyter 默認綁定的是 IPv4 的127.0.0.1。當系統(tǒng)里 IPv6 回環(huán)配置有點問題或者某個安全軟件攔了::1上的連接就會出現(xiàn)服務明明在跑瀏覽器卻說無法訪問此網(wǎng)站。這類問題最省事的驗證方法是直接換地址試。把地址欄里的localhost全部替換成127.0.0.1其余部分不動回車。能打開就說明是解析層面的問題之后把127.0.0.1寫進書簽固定下來即可。反過來也可以把服務綁到::1上測試但我個人不推薦IPv4 的兼容性在各類工具鏈里更保險。4.2 安全軟件、緩存和瀏覽器插件的干擾防火墻和安全軟件對本地程序監(jiān)聽端口 瀏覽器發(fā)起本機回環(huán)請求這個組合有時會特別敏感。表現(xiàn)是第一次訪問被攔之后瀏覽器緩存了一個失敗的響應你刷新多少次都是那個錯誤頁。這時候別跟錯誤頁較勁換隱私窗口試一次如果隱私窗口能開那就是緩存或插件的問題。插件方面廣告攔截類、隱私保護類擴展有時會把帶長隨機字符串的 URL也就是帶 token 的地址判定成可疑跟蹤鏈接直接攔掉。這種攔截通常不彈提示你在頁面上什么異常都看不到就是白屏。排查辦法是一次性禁用所有擴展再訪問確認后再逐個打開定位。還有一類更隱蔽的某些瀏覽器會把127.0.0.1的請求走系統(tǒng)網(wǎng)絡配置比如公司電腦上的統(tǒng)一網(wǎng)絡設置導致本機請求被繞到別處。如果換端口、換地址都不行可以在瀏覽器設置里檢查一下有沒有配過全局的網(wǎng)絡轉發(fā)規(guī)則。4.3 默認瀏覽器注冊異常怎么辦Windows 上這個問題出現(xiàn)頻率不低。癥狀是你雙擊任意網(wǎng)頁鏈接系統(tǒng)能正常打開瀏覽器但 Jupyter 就是喚不起來。原因在于 Windows 有兩套默認程序的記錄系統(tǒng) Shell 用一套Python 的webbrowser模塊讀的是另一套注冊表里的http協(xié)議關聯(lián)兩者不同步時就會出現(xiàn)這種割裂。修復思路有三條按成本從低到高排第一打開系統(tǒng)設置里的默認應用把默認瀏覽器重新指定一次有時候這一下就能把協(xié)議關聯(lián)刷新好第二走前面說的webbrowser.register手工注冊跳過注冊表查詢第三直接用--no-browser加上手動訪問徹底繞開這個環(huán)節(jié)。我絕大多數(shù)時候選第三條把精力留給真正寫代碼的部分。5. 順手解決幾個高發(fā)伴生問題5.1 jupyter notebook 命令找不到或啟動了沒反應命令找不到通常有兩層原因。一是真的沒裝pip install notebook解決二是裝了但不在當前環(huán)境的 PATH 里這種情況在 conda 多環(huán)境共存時特別常見。先在終端敲which jupyter # macOS / Linux where jupyter # Windows看它指向哪個路徑是不是你預期的那個環(huán)境。如果不是最直接的活法是激活正確的環(huán)境conda activate myenv python -m notebook用python -m notebook而不是裸敲jupyter好處是它一定用的是當前解釋器對應的那個 notebook 包不會被 PATH 里其他環(huán)境搶走。這個技巧我強烈建議記住能避開一大堆明明裝了卻說找不到的怪事。5.2 ImportError: DLL load failed while importing rpds 怎么處理這個報錯挺有代表性值得單獨講。rpds-py是一個用 Rust 寫的 Python 擴展包新版nbformat用它在做 JSON 引用解析而nbformat又是 notebook 的必需依賴。所以在 Windows 上你敲下啟動命令后什么都還沒發(fā)生就看到了這一行ImportError: DLL load failed while importing rpds: 找不到指定的模塊。找不到指定的模塊通常意味著兩類問題一是缺 Microsoft Visual C 運行庫Rust 編譯出來的擴展依賴vcruntime140.dll這類文件二是裝上去的 wheel 和當前 Python 的版本、位數(shù)不匹配。排查順序我一般這么走先單獨驗證是不是這個包的問題python -c import rpds; print(rpds.__version__)如果同樣報錯那就鎖定它了。接著嘗試強制重裝清掉可能損壞的緩存pip install --force-reinstall --no-cache-dir rpds-py如果還是不行去裝一遍 Microsoft Visual C Redistributable 的 2015-2022 版本裝完重啟終端再試。還有一種情況是 conda 和 pip 裝的包混在一個環(huán)境里二進制接口對不上這種時候最干脆的做法是重建一個干凈環(huán)境conda create -n nb python3.11 conda activate nb pip install notebook我個人的經(jīng)驗是凡是 Windows 上出現(xiàn) DLL 相關的導入錯誤八成以上的問題都能被補運行庫 強制重裝這一套組合解決。剩下的兩成基本都是環(huán)境混裝造成的重建環(huán)境反而比修修補補快。5.3 頁面能打開但單元格執(zhí)行代碼沒反應這個癥狀和跳轉失敗屬于同一條問題鏈上的兩端。頁面能顯示說明前端服務沒問題但執(zhí)行沒反應說明前端和后端之間的實時通信通道斷了。Jupyter 前端和內(nèi)核之間用的是 WebSocket一旦這條通道被攔、被超時、或者端口映射不對你點運行就會一直顯示[*]永遠不變成數(shù)字。先看右下角的內(nèi)核狀態(tài)指示它顯示的是已連接還是未連接。如果是未連接試著在菜單里重啟內(nèi)核Kernel → Restart。經(jīng)常遇到的情況是頁面開得太久筆記本休眠過WebSocket 已經(jīng)斷了但前端沒察覺重啟一下內(nèi)核就好。如果重啟無效檢查一下是不是同一個 notebook 服務被多個瀏覽標簽頁同時打開著偶爾會互相干擾。關掉多余的標簽頁只留一個再試一次。還有一種可能是某個單元格里有死循環(huán)或者阻塞式等待比如input()內(nèi)核被卡住了這種情況只能中斷內(nèi)核Interrupt或者強制重啟。5.4 關于自動補齊和 Markdown 目錄的順帶說明熱詞里常出現(xiàn)jupyter notebook代碼自動補齊和jupyter notebook怎么生成markdown目錄語法既然繞不開簡單交代一下我的做法。自動補齊方面老版 notebook 依賴jupyter_contrib_nbextensions里的 Hinterland 插件配置起來偏麻煩notebook 7 已經(jīng)內(nèi)置了基礎的 Tab 補全。如果追求接近 IDE 的體驗可以上jupyter-lsp加對應語言服務器配置一次就能用很久。我自己的取舍是只在寫大型項目時才折騰 LSP日常隨手跑的腳本用內(nèi)置補全夠了配置成本不值得。Markdown 目錄方面有兩種路線。一是裝目錄擴展老版走 nbextensions 里的 Table of Contents新版在 JupyterLab 里有對應的 toc 插件它會根據(jù)標題自動生成可點擊的側邊導航。二是純 Markdown 手寫錨點鏈接用[跳轉文字](#標題)的語法標題里的空格換成短橫線、去掉特殊符號。手寫的方式看著笨但完全無依賴導出的.md文件拿到任何平臺都能用我給別人交付筆記時更傾向這種方式。6. 速查表和我踩過的那些坑6.1 一張能貼在手邊的排查表報錯或現(xiàn)象常見原因處理動作瀏覽器不彈但終端正常默認瀏覽器未注冊、temp 目錄不可寫復制 URL 手動訪問設use_redirect_file FalseAddress already in use端口被占用換--port或用 netstat / lsof 查占用進程頁面提示無法訪問此網(wǎng)站localhost 解析到 IPv6改用127.0.0.1一直提示輸入密碼或 tokentoken 截斷、未設密碼jupyter notebook list取全地址或jupyter notebook password啟動即報 DLL load failed缺運行庫或 wheel 不匹配裝 VC 運行庫強制重裝rpds-py命令找不到環(huán)境 PATH 未激活用python -m notebook單元格一直[*]內(nèi)核未連接重啟內(nèi)核關掉多余標簽頁頁面白屏擴展攔截或緩存換隱私窗口禁用插件6.2 我踩過的幾個坑都挺有代表性第一個曾經(jīng)為了讓同事能訪問我機器上的 notebook把--ip設成了0.0.0.0結果同事能連上我自己卻打不開了。原因是當時瀏覽器里存的還是localhost:8888而綁定到0.0.0.0之后服務監(jiān)聽的地址變了回環(huán)訪問的行為在某些系統(tǒng)上不一樣。改回用本機實際 IP 或者127.0.0.1就正常了。這件事的教訓是綁定地址和訪問地址必須成對考慮不能只改一頭。第二個有一次改配置文件時用記事本保存加上 BOM 頭結果 Jupyter 啟動時報了個語法錯誤指向文件第一行。我盯著那行看了很久也沒看出問題最后用十六進制工具打開才發(fā)現(xiàn)開頭多了三個字節(jié)。從那以后配置類文件我一律用 VS Code 或者 Vim 改并且在編輯器里把保存時移除 BOM設成默認。第三個在裝了多套 Python 的機器上jupyter notebook用的是 A 環(huán)境pip install裝到了 B 環(huán)境于是怎么裝都提示模塊找不到。這個問題在 conda 用戶里極其普遍。解決辦法就是前面說的python -m notebook加python -m pip install讓解釋器自己保證一致性。6.3 從源頭減少這類故障的環(huán)境習慣折騰到后來我總結出幾條習慣能顯著降低這類問題的出現(xiàn)頻率。一是每個項目一個獨立環(huán)境絕不往 base 環(huán)境里裝 notebook。conda 也好 venv 也好反正別混。混裝是 DLL 報錯、版本沖突、模塊找不到這三類問題的最大來源。二是把依賴寫進requirements.txt或者environment.yml換機器時一次性重建而不是一臺一臺手工 pip。我現(xiàn)在的做法是新機器直接conda env create -f environment.yml五分鐘搞定比手工裝兩小時靠譜。三是固定啟動方式。我為每個常用目錄建了啟動腳本腳本里寫死了端口、綁定地址、工作目錄和--no-browser。這樣一來無法跳轉網(wǎng)頁這件事在我這兒根本不會發(fā)生因為跳轉這個環(huán)節(jié)從一開始就被我關掉了。地址從哪來打開瀏覽器書簽里存好的127.0.0.1:8899輸一次密碼完事。四是把版本信息記在筆記本旁邊。jupyter notebook --version、python --version、pip list的結果截圖存一份出問題時能快速判斷是不是升級導致的。我有一次就是升級了 notebook 大版本配置前綴沒改白折騰了半天。說到底Jupyter notebook 無法跳轉網(wǎng)頁這件事真正的解法不是一個命令而是一套心態(tài)把自動打開瀏覽器當成一個可有可無的便利功能而不是啟動流程的必要環(huán)節(jié)。一旦這么想所有的排查都變得簡單了——服務在不在、地址對不對、能不能訪問三個問題問完答案基本就出來了。剩下的那些 DLL、內(nèi)核、補全的問題按前面表格里的順序一個個過也都能解決。我現(xiàn)在拿到一臺新機器裝完 notebook 的第一件事就是jupyter notebook password加上寫啟動腳本然后把這個腳本的快捷方式丟到 Dock 或者任務欄。從那天起那個終端刷屏但瀏覽器沒動靜的場景就再也沒出現(xiàn)過。