整合營銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          caffe的安裝筆記 Ubuntu16.04

          caffe的安裝筆記 Ubuntu16.04

          affe的安裝筆記 Ubuntu16.04

          跑實驗用過一次caffe,光安裝就用了一周,經(jīng)歷了各種錯誤,真的好難安裝,記錄下最后成功安裝的方法,希望給大家安裝時提供參考。

          1、下載安裝所需的包,例如anaconda,numpy ,protobuf,opencv,cython, scikit-image等

          opencv使用

          conda install opencv
          

          直接使用 conda install caffe-gpu安裝好所需的環(huán)境再使用源碼編譯,進(jìn)行下面的步驟

          再 使用conda uninstall caffe-gpu 去掉這個包,裝上需要的環(huán)境,但是這個包不能直接使用(我也不知道原因)。

          2、下載caffe

          git clone https://github.com/BVLC/caffe.git
          

          3、更改makefile.config

          • 使用GPU即使用cudnn,則去掉下一行的注釋
          USE_CUDNN :=1
          
          • 使用opencv編譯和使用,則去掉這行的注釋
          USE_OPENCV :=0
          OPENCV_VERSION :=3
          
          • 更改cuda的路徑
          CUDA_DIR :=/usr/local/cuda_env/cuda-8.0
          
          • 使用anaconda中的python,更改下面幾行,注意文件的名字及路徑是否正確,具體查看一下
          ANACONDA_HOME :=$(HOME)/anaconda3/envs/caffe
          PYTHON_INCLUDE :=$(ANACONDA_HOME)/include \
           $(ANACONDA_HOME)/include/python3.6m\
           $(ANACONDA_HOME)/lib/python3.6/site-packages/numpy/core/include
          PYTHON_LIB :=$(ANACONDA_HOME)/lib
          
          • 更改這兩行
          INCLUDE_DIRS :=$(PYTHON_INCLUDE) /usr/local/include /usr/include/hdf5/serial
          LIBRARY_DIRS :=$(PYTHON_LIB) /usr/local/lib /usr/lib /usr/lib/x86_64-linux-gnu /usr/lib/x86_64-linux-gnu/hdf5/serial/
          

          完整的makefile.config

          ## Refer to http://caffe.berkeleyvision.org/installation.html
          # Contributions simplifying and improving our build system are welcome!
          
          # cuDNN acceleration switch (uncomment to build with cuDNN).
          USE_CUDNN :=1
          
          # CPU-only switch (uncomment to build without GPU support).
          # CPU_ONLY :=1
          
          # uncomment to disable IO dependencies and corresponding data layers
          USE_OPENCV :=0
          # USE_LEVELDB :=0
          # USE_LMDB :=0
          
          # uncomment to allow MDB_NOLOCK when reading LMDB files (only if necessary)
          # You should not set this flag if you will be reading LMDBs with any
          # possibility of simultaneous read and write
          # ALLOW_LMDB_NOLOCK :=1
          
          # Uncomment if you're using OpenCV 3
          OPENCV_VERSION :=3
          
          # To customize your choice of compiler, uncomment and set the following.
          # N.B. the default for Linux is g++ and the default for OSX is clang++
          # CUSTOM_CXX :=g++
          
          # CUDA directory contains bin/ and lib/ directories that we need.
          CUDA_DIR :=/usr/local/cuda_env/cuda-8.0
          # On Ubuntu 14.04, if cuda tools are installed via
          # "sudo apt-get install nvidia-cuda-toolkit" then use this instead:
          # CUDA_DIR :=/usr
          
          # CUDA architecture setting: going with all of them.
          # For CUDA < 6.0, comment the *_50 through *_61 lines for compatibility.
          # For CUDA < 8.0, comment the *_60 and *_61 lines for compatibility.
          # For CUDA >=9.0, comment the *_20 and *_21 lines for compatibility.
          CUDA_ARCH :=-gencode arch=compute_20,code=sm_20 \
           -gencode arch=compute_20,code=sm_21 \
           -gencode arch=compute_30,code=sm_30 \
           -gencode arch=compute_35,code=sm_35 \
           -gencode arch=compute_50,code=sm_50 \
           -gencode arch=compute_52,code=sm_52 \
           -gencode arch=compute_60,code=sm_60 \
           -gencode arch=compute_61,code=sm_61 \
           -gencode arch=compute_61,code=compute_61
          
          # BLAS choice:
          # atlas for ATLAS (default)
          # mkl for MKL
          # open for OpenBlas
          BLAS :=atlas
          # Custom (MKL/ATLAS/OpenBLAS) include and lib directories.
          # Leave commented to accept the defaults for your choice of BLAS
          # (which should work)!
          # BLAS_INCLUDE :=/path/to/your/blas
          # BLAS_LIB :=/path/to/your/blas
          
          # Homebrew puts openblas in a directory that is not on the standard search path
          # BLAS_INCLUDE :=$(shell brew --prefix openblas)/include
          # BLAS_LIB :=$(shell brew --prefix openblas)/lib
          
          # This is required only if you will compile the matlab interface.
          # MATLAB directory should contain the mex binary in /bin.
          # MATLAB_DIR :=/usr/local
          # MATLAB_DIR :=/Applications/MATLAB_R2012b.app
          
          # NOTE: this is required only if you will compile the python interface.
          # We need to be able to find Python.h and numpy/arrayobject.h.
          #PYTHON_INCLUDE :=/usr/include/python2.7 \
          # /usr/lib/python2.7/dist-packages/numpy/core/include
          # Anaconda Python distribution is quite popular. Include path:
          # Verify anaconda location, sometimes it's in root.
          ANACONDA_HOME :=$(HOME)/anaconda3/envs/caffe
          PYTHON_INCLUDE :=$(ANACONDA_HOME)/include \
           $(ANACONDA_HOME)/include/python3.6m\
           $(ANACONDA_HOME)/lib/python3.6/site-packages/numpy/core/include
          
          # Uncomment to use Python 3 (default is Python 2)
          # PYTHON_LIBRARIES :=boost_python3 python3.5m
          # PYTHON_INCLUDE :=/usr/include/python3.5m \
          # /usr/lib/python3.5/dist-packages/numpy/core/include
          
          # We need to be able to find libpythonX.X.so or .dylib.
          # PYTHON_LIB :=/usr/lib
          PYTHON_LIB :=$(ANACONDA_HOME)/lib
          
          # Homebrew installs numpy in a non standard path (keg only)
          # PYTHON_INCLUDE +=$(dir $(shell python -c 'import numpy.core; print(numpy.core.__file__)'))/include
          # PYTHON_LIB +=$(shell brew --prefix numpy)/lib
          
          # Uncomment to support layers written in Python (will link against Python libs)
          # WITH_PYTHON_LAYER :=1
          
          # Whatever else you find you need goes here.
          INCLUDE_DIRS :=$(PYTHON_INCLUDE) /usr/local/include /usr/include/hdf5/serial
          LIBRARY_DIRS :=$(PYTHON_LIB) /usr/local/lib /usr/lib /usr/lib/x86_64-linux-gnu /usr/lib/x86_64-linux-gnu/hdf5/serial/
          
          # If Homebrew is installed at a non standard location (for example your home directory) and you use it for general dependencies
          # INCLUDE_DIRS +=$(shell brew --prefix)/include
          # LIBRARY_DIRS +=$(shell brew --prefix)/lib
          
          # NCCL acceleration switch (uncomment to build with NCCL)
          # https://github.com/NVIDIA/nccl (last tested version: v1.2.3-1+cuda8.0)
          # USE_NCCL :=1
          
          # Uncomment to use `pkg-config` to specify OpenCV library paths.
          # (Usually not necessary -- OpenCV libraries are normally installed in one of the above $LIBRARY_DIRS.)
          # USE_PKG_CONFIG :=1
          
          # N.B. both build and distribute dirs are cleared on `make clean`
          BUILD_DIR :=build
          DISTRIBUTE_DIR :=distribute
          
          # Uncomment for debugging. Does not work on OSX due to https://github.com/BVLC/caffe/issues/171
          # DEBUG :=1
          
          # The ID of the GPU that 'make runtest' will use to run unit tests.
          TEST_GPUID :=0
          
          # enable pretty build (comment to see full commands)
          Q ?=@
          

          4、更改makefile,更改這一行

          LIBRARIES +=opencv_core opencv_highgui opencv_imgproc opencv_imgcodecs
          

          5、編譯caffe

          cd caffe
          make all -j8
          make pycaffe
          

          6、更改.bashrc路徑

          export PYTHONPATH="/home/usrname/caffe/python:$PYTHONPATH"
          

          7、完整安裝caffe路徑(這個私人設(shè)置,不做參考)

          # add for use caffe copy from jhyang
          export LD_LIBRARY_PATH="/usr/local/cuda_env/cuda-8.0/lib64:/usr/local/cuda_env/cudnn-v5.1-for-cuda8.0/lib64:/usr/local/cuda_env/cudnn-v6.0-for-cuda8.0/lib64:/usr/local/cuda_env/cuda-9.0/lib64:/usr/local/cuda_env/cudnn-v7.0-for-cuda9.0/lib64"
          export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/usr/bin:/bin:/usr/sbin:/sbin"
          export LIBRARY_PATH="/usr/local/cuda_env/cuda-8.0/lib64:/usr/local/cuda_env/cudnn-v5.1-for-cuda8.0/lib64:/usr/local/cuda_env/cudnn-v6.0-for-cuda8.0/lib64:/usr/local/cuda_env/cuda-9.0/lib64:/usr/local/cuda_env/cudnn-v7.0-for-cuda9.0/lib64"
          export PYTHONPATH="/home/usr/caffe/python:$PYTHONPATH"
          export CPATH=$CPATH:/usr/local/cuda_env/cudnn-v6.0-for-cuda8.0/include:/usr/include/hdf5/serial/hdf5.h
          export CPATH=/usr/local/cuda_env/cudnn-v5.1-for-cuda8.0/include:/usr/local/cuda_env/cudnn-v6.0-for-cuda8.0/include:/usr/local/cuda_env/cudnn-v7.0-for-cuda9.0/include
          

          8、測試使用

          背景

          隨著機(jī)器學(xué)習(xí)的應(yīng)用面越來越廣,能在瀏覽器中跑模型推理的Javascript框架引擎也越來越多了。在項目中,前端同學(xué)可能會找到一些跑在服務(wù)端的python算法模型,很想將其直接集成到自己的代碼中,以Javascript語言在瀏覽器中運(yùn)行。

          對于一部分簡單的模型,推理的前處理、后處理比較容易,不涉及復(fù)雜的科學(xué)計算,碰到這種模型,最多做個模型格式轉(zhuǎn)化,然后用推理框架直接跑就可以了,這種移植成本很低。

          而很大一部分模型會涉及復(fù)雜的前處理、后處理,包括大量的矩陣運(yùn)算、圖像處理等Python代碼。這種情況一般的思路就是用Javascript語言將Python代碼手工翻譯一遍,這么做的問題是費(fèi)時費(fèi)力還容易出錯。

          Pyodide作為瀏覽器中的科學(xué)計算框架,很好的解決了這個問題:瀏覽器中運(yùn)行原生的Python代碼進(jìn)行前、后處理,大量numpy、scipy的矩陣、張量等計算無需翻譯為Javascript,為移植節(jié)省了很多工作。本文就基于pyodide框架,從理論和實戰(zhàn)兩個角度,幫助前端同學(xué)解決復(fù)雜模型的移植這一棘手問題。

          二 原理篇

          Pyodide是個可以在瀏覽器中跑的WebAssembly(wasm)應(yīng)用。它基于CPython的源代碼進(jìn)行了擴(kuò)展,使用emscripten編譯成為wasm,同時也把一大堆科學(xué)計算相關(guān)的pypi包也編譯成了wasm,這樣就能在瀏覽器中解釋執(zhí)行python語句進(jìn)行科學(xué)計算了。所以pyodide也必然遵循wasm的各種約束。Pyodide在瀏覽器中的位置如下圖所示:

          1 wasm內(nèi)存布局

          這是wasm線性內(nèi)存的布局:

          Data數(shù)據(jù)段是從0x400開始的, Function Table表也在其中,起始地址為memoryBase(Emscripten中默認(rèn)為1024,即0x400),STACKTOP為棧地址起始,堆地址起始為STACK_MAX。而我們實際更關(guān)心的是Javascript內(nèi)存與wasm內(nèi)存的互相訪問。

          2 Javascript與Python的互訪

          瀏覽器基于安全方面的考慮,防止wasm程序把瀏覽器搞崩潰,通過把wasm運(yùn)行在一個沙箱化的執(zhí)行環(huán)境中,禁止了wasm程序訪問Javascript內(nèi)存,而Javascript代碼卻可以訪問wasm內(nèi)存。因為wasm內(nèi)存本質(zhì)上是一個巨大的ArrayBuffer,接受Javascript的管理。我們稱之為“單向內(nèi)存訪問”。

          作為一個wasm格式的普通程序,pyodide被調(diào)用起來后,當(dāng)然只能直接訪問wasm內(nèi)存。

          為了實現(xiàn)互訪,pyodide引入了proxy,類似于指針:在Javascript側(cè),通過一個PyProxy對象來引用python內(nèi)存里的對象;在Python側(cè),通過一個JsProxy對象來引用Javascript內(nèi)存里的對象。

          在Javascript側(cè)生成一個PyProxy對象:

          const arr_pyproxy=pyodide.globals.get('arr')  // arr是python里的一個全局對象

          在Python側(cè)生成一個JsProxy對象:

          import js
          from js import foo   # foo是Javascript里的一個全局對象

          互訪時的類型轉(zhuǎn)換分為如下三個等級:

          • 【自動轉(zhuǎn)換】對于簡單類型,如數(shù)字、字符串、布爾等,會被自動拷貝內(nèi)存值,此時產(chǎn)生的就不是Proxy、而是最終的值了。
          • 【半自動轉(zhuǎn)換】非簡單的內(nèi)置類型,都需要通過to_js()、to_py()方式來顯式轉(zhuǎn)換:對于Python內(nèi)置的list、dict、numpy.ndarray等對象,不屬于簡單類型,不會自動轉(zhuǎn)換類型,必須通過pyodide.to_js()來轉(zhuǎn),相應(yīng)的會被轉(zhuǎn)成JS的list、map、TypedArray類型反過來也類似,通過to_py()方法,JS的TypedArray轉(zhuǎn)為memoryview,list、map轉(zhuǎn)為list、dict
          • 【手動轉(zhuǎn)換】各種class、function和用戶自定義類型,因為對方的語言沒有對應(yīng)的現(xiàn)成類型,所以只能以proxy的形式存在,需要通過運(yùn)算符來間接操縱,就像操縱提線木偶一樣。為了達(dá)到方便操縱的目的,pyodide對兩種語言進(jìn)行了語法模擬,用一種語言里的操作符模擬另一種語言的類似行為。例如:JS中的let a=new XXX(),在Python中就變?yōu)閍=XXX.new()。

          這里列舉了一部分,詳情可以查文檔(見文章底部)。

          Javascript的模塊也可以引入到Python中,這樣Python就能直接調(diào)用該模塊的接口和方法了。例如,pyodide沒有編譯opencv包,可以使用opencv.js:

          import pyodide
          import js.cv as cv2
          print(dir(cv2))

          這對于pyodide缺失的pypi包是個很好的補(bǔ)充。

          三 實踐篇

          我們從一個空白頁面開始。使用瀏覽器打開測試頁面(測試頁面見文章底部)。

          1 初始化python

          為了方便觀察運(yùn)行過程,使用動態(tài)的方式加載所需js和執(zhí)行python代碼。打開瀏覽器控制臺,依次運(yùn)行以下語句:

          function loadJS( url, callback ){
            var script=document.createElement('script'),
            fn=callback || function(){};
            script.type='text/javascript';
            script.onload=function(){
                fn();
            };
            script.src=url;
            document.getElementsByTagName('head')[0].appendChild(script);
          }
          // 加載opencv
          loadJS('https://test-bucket-duplicate.oss-cn-hangzhou.aliyuncs.com/public/opencv/opencv.js',  function(){
              console.log('js load ok');
          });
          
          // 加載推理引擎onnxruntime.js。當(dāng)然也可以使用其他推理引擎
          loadJS('https://test-bucket-duplicate.oss-cn-hangzhou.aliyuncs.com/public/onnxruntime/onnx.min.js',  function(){
              console.log('js load ok');
          });
          
          // 初始化python運(yùn)行環(huán)境
          loadJS('https://test-bucket-duplicate.oss-cn-hangzhou.aliyuncs.com/public/pyodide/0.18.0/pyodide.js',  function(){
              console.log('js load ok');
          });
          pyodide=await loadPyodide({ indexURL : "https://test-bucket-duplicate.oss-cn-hangzhou.aliyuncs.com/public/pyodide/0.18.0/"});
          await pyodide.loadPackage(['micropip']);

          至此,python和pip就安裝完畢了,都位于內(nèi)存文件系統(tǒng)中。我們可以查看一下python被安裝到了哪里:

          注意,這個文件系統(tǒng)是內(nèi)存里虛擬出來的,刷新頁面就丟失了。不過由于瀏覽器本身有緩存,所以刷新頁面后從服務(wù)端再次加載pyodide的引導(dǎo)js和主體wasm還是比較快的,只要不清理瀏覽器緩存。

          2 加載pypi包

          在pyodide初始化完成后,python系統(tǒng)自帶的標(biāo)準(zhǔn)模塊可以直接import。第三方模塊需要用micropip.install()安裝:

          • pypi.org上的純python包可以用micropip.install() 直接安裝
          • 含有C語言擴(kuò)展(編譯為動態(tài)鏈接庫)的wheel包,需要對照官方已編譯包的列表在列表中的直接用micropip.install()安裝不在這個列表里的,就需要自己手動編譯后發(fā)布到服務(wù)器后再用micropip.install()安裝。

          下圖展示了業(yè)內(nèi)常用的兩種編譯為wasm的方式。

          自己編譯wasm package的方法可參考官方手冊,大致步驟就是pull官方的編譯基礎(chǔ)鏡像,把待編譯包的setup.cfg文件放到模塊目錄里,再加上些hack的語句和配置(如果有的話),然后指定目標(biāo)進(jìn)行編譯。編譯成功后部署時,需要注意2點:

          • 設(shè)置允許跨域
          • 對于wasm格式的文件請求,響應(yīng)Header里應(yīng)當(dāng)帶上:"Content-type": "application/wasm"

          下面是一個自建wasm服務(wù)器的nginx/openresty示例配置:

                  location ~ ^/wasm/ {
                    add_header 'Access-Control-Allow-Origin' "*";
                    add_header 'Access-Control-Allow-Credentials' "true";
                    root /path/to/wasm_dir;
                    header_filter_by_lua '
                      uri=ngx.var.uri
                      if string.match(uri, ".js$")==nil then
                          ngx.header["Content-type"]="application/wasm"
                      end
                    ';
                  }

          回到我們的推理實例, 現(xiàn)在用pip安裝模型推理所需的numpy和Pillow包并將其import:

          await pyodide.runPythonAsync(`
          import micropip
          micropip.install(["numpy", "Pillow"])
          `);
          
          await pyodide.runPythonAsync(`
          import pyodide
          import js.cv as cv2
          import js.onnx as onnxruntime
          import numpy as np
          `);

          這樣python所需的opencv、onnxruntime包就已全部導(dǎo)入了。

          3 opencv的使用

          一般python里的圖片數(shù)組都是從JS里傳過來的,這里我們模擬構(gòu)造一張圖片,然后用opencv對其resize。上面提到過,pyodide官方的opencv還沒編譯出來。如果涉及到的opencv方法調(diào)用有其他pypi包的替代品,那是最好的:比如,cv.resize可以用Pillow庫的PIL.resize代替(注意Pillow的resize速度比opencv的resize要慢);cv.threshold可以用numpy.where代替。 否則只能調(diào)用opencv.js的能力了。為了演示pyodide語法,這里都從opencv.js庫里調(diào)用。

          await pyodide.runPythonAsync(`
          # 構(gòu)造一個1080p圖片
          h,w=1080,1920
          img=np.arange(h * w * 3, dtype=np.uint8).reshape(h, w, 3)
          
          # 使用cv2.resize將其縮小為1/10
          # 原python代碼:small_img=cv2.resize(img, (h_small, w_small))
          # 改成調(diào)用opencv.js:
          h_small,w_small=108, 192
          mat=cv2.matFromArray(h, w, cv2.CV_8UC3, pyodide.to_js(img.reshape(h * w * 3)))
          dst=cv2.Mat.new(h_small, w_small, cv2.CV_8UC3)  
          cv2.resize(mat, dst, cv2.Size.new(w_small, h_small), 0, 0, cv2.INTER_NEAREST)
          small_img=np.asarray(dst.data.to_py()).reshape(h_small, w_small, 3)
          `);

          傳參原則:除了簡單的數(shù)字、字符串類型可以直接傳,其他類型都需要通過pyodide.to_js()轉(zhuǎn)換后再傳入。 返回值的獲取也類似,除了簡單的數(shù)字、字符串類型可以直接獲取,其他類型都需要通過xx.to_py()轉(zhuǎn)換后獲取結(jié)果。

          接著對一個mask檢測其輪廓:

          await pyodide.runPythonAsync(`
          # 使用cv2.findContours來檢測輪廓。假設(shè)mask為二維numpy數(shù)組,只有0、1兩個值
          # 原python代碼:contours=cv2.findContours(mask, cv2.RETR_CCOMP,cv2.CHAIN_APPROX_NONE)
          # 改成調(diào)用opencv.js:
          contours_jsproxy=cv2.MatVector.new()   # cv2.Mat數(shù)組,對應(yīng)opencv.js中的 contours=new cv.MatVector()語句
          hierarchy_jsproxy=cv2.Mat.new()
          mat=cv2.matFromArray(mask.shape[0], mask.shape[1],  cv2.CV_8UC1, pyodide.to_js(mask.reshape(mask.size)))
          cv2.findContours(mat, pyodide.to_js(contours_jsproxy), pyodide.to_js(hierarchy_jsproxy), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
          # contours js格式轉(zhuǎn)python格式
          contours=[]
          for i in range(contours_jsproxy.size()):
            c_jsproxy=contours_jsproxy.get(i)
            c=np.asarray(c_jsproxy.data32S.to_py()).reshape(c_jsproxy.rows, c_jsproxy.cols, 2)
            contours.append(c)
          `);

          4 推理引擎的使用

          最后,用onnx.js加載模型并進(jìn)行推理,詳細(xì)語法可參考o(jì)nnx.js官方文檔。其他js版的推理引擎也都可以參考各自的文檔。

          await pyodide.runPythonAsync(`
          model_url="onnx模型的地址"
          session=onnxruntime.InferenceSession.new()
          session.loadModel(model_url)
          session.run(......)
          `);

          通過以上的操作,我們確保了一切都在python語法范圍內(nèi)進(jìn)行,這樣修改原始的Python文件就比較容易了:把不支持的函數(shù)替換成我們自定義的調(diào)用js的方法;原Python里的推理替換成調(diào)用js版的推理引擎;最后在Javascript主程序框架里加少許調(diào)用Python的膠水代碼就完成了。

          5 掛載持久存儲文件系統(tǒng)

          有時我們需要對一些數(shù)據(jù)持久保存,可以利用pyodide提供的持久化文件系統(tǒng)(其實是emscripten提供的),見手冊(文章底部)。

          // 創(chuàng)建掛載點
          pyodide.FS.mkdir('/mnt');
          // 掛載文件系統(tǒng)
          pyodide.FS.mount(pyodide.FS.filesystems.IDBFS, {}, '/mnt');
          // 寫入一個文件
          pyodide.FS.writeFile('/mnt/test.txt', 'hello world');
          // 真正的保存文件到持久文件系統(tǒng)
          pyodide.FS.syncfs(function (err) {
              console.log(err);
          });

          這樣文件就持久保存了。即使當(dāng)我們刷新頁面后,仍可以通過掛載該文件系統(tǒng)來讀出里面的內(nèi)容:

          // 創(chuàng)建掛載點
          pyodide.FS.mkdir('/mnt');
          // 掛載文件系統(tǒng)
          pyodide.FS.mount(pyodide.FS.filesystems.IDBFS, {}, '/mnt');
          // 寫入一個文件
          pyodide.FS.writeFile('/mnt/test.txt', 'hello world');
          // 真正的保存文件到持久文件系統(tǒng)
          pyodide.FS.syncfs(function (err) {
              console.log(err);
          });

          運(yùn)行結(jié)果如下:

          當(dāng)然,以上語句可以在python中以Proxy的語法方式運(yùn)行。

          持久文件系統(tǒng)有很多用處。例如,可以幫我們在多線程(webworker)之間共享大數(shù)據(jù);可以把模型文件持久存儲到文件系統(tǒng)里,無需每次都通過網(wǎng)絡(luò)加載。

          6 打wheel包

          單Python文件無需打包,直接當(dāng)成一個巨大的字符串,交給pyodide.runPythonAsync()運(yùn)行就好了。當(dāng)有多個Python文件時,我們可以把這些python文件打成普通wheel包,部署到webserver,然后可以用micropip直接安裝該wheel包:

          micropip.install("https://foo.com/bar-1.2.3-xxx.whl")
          from bar import ...

          注意,打wheel包需要有__init__.py文件,哪怕是個空文件。

          四 存在的缺陷

          目前pyodide有如下幾個缺陷:

          • Python運(yùn)行環(huán)境加載和初始化時間有點兒長,視網(wǎng)絡(luò)情況,幾秒到幾十秒都有可能。
          • pypi包支持的不完整。雖然pypi.org上的純python包都可以直接使用,但涉及到C擴(kuò)展寫的包,如果官方還沒編譯出來,那就需要自己動手編譯了。
          • 個別很常用的包,例如opencv,還沒成功編譯出來;模型推理框架一個都沒有。不過還好可以通過相應(yīng)的JS庫來彌補(bǔ)。
          • 如果python中調(diào)用了js庫的話:可能會產(chǎn)生一定的內(nèi)存拷貝開銷(從wasm內(nèi)存到JS內(nèi)存的來回拷貝)。尤其是大數(shù)組作為參數(shù)或返回值,在速度要求高的場合下,額外的內(nèi)存拷貝開銷就不能忽視了。python庫的方法接口可能跟其對應(yīng)的js庫的接口參數(shù)、返回值格式不一致,有一定的適配工作量。

          五 總結(jié)

          盡管有上述種種缺陷,得益于代碼移植的高效率和邏輯上1:1復(fù)刻的高可靠性保障,我們還是可以把這種方法運(yùn)用到多種業(yè)務(wù)場景里,為推動機(jī)器學(xué)習(xí)技術(shù)的應(yīng)用添磚加瓦。

          鏈接:

          1、測試頁面:

          https://test-bucket-duplicate.oss-cn-hangzhou.aliyuncs.com/public/pyodide/test.html

          2、文檔:

          https://pyodide.org/en/stable/usage/type-conversions.html

          3、官方已編譯包的列表:
          https://github.com/pyodide/pyodide/tree/main/packages

          4、手冊:

          https://emscripten.org/docs/api_reference/Filesystem-API.html

          作者 | 道仙

          原文鏈接:http://click.aliyun.com/m/1000299604/

          本文為阿里云原創(chuàng)內(nèi)容,未經(jīng)允許不得轉(zhuǎn)載。

          、bs4簡介

          BeautifulSoup,是python中的一個庫,是一個可以從HTML或XML文件中提取數(shù)據(jù)的Python庫;它能夠通過提供一些簡單的函數(shù)用來處理導(dǎo)航、搜索、修改分析樹等功能。它是一個工具箱,通過解析文檔為用戶提供需要抓取的數(shù)據(jù),因為簡單,所以不需要多少代碼就可以寫出一個完整的應(yīng)用程序。Beautiful Soup會幫你節(jié)省數(shù)小時甚至數(shù)天的工作時間。

          Beautiful Soup自動將輸入文檔轉(zhuǎn)換為Unicode編碼,輸出文檔轉(zhuǎn)換為utf-8編碼。你不需要考慮編碼方式,除非文檔沒有指定一個編碼方式,這時,Beautiful Soup就不能自動識別編碼方式了。然后,你僅僅需要說明一下原始編碼方式就可以了。

          Beautiful Soup已成為和lxmlhtml6lib一樣出色的python解釋器,為用戶靈活地提供不同的解析策略或強(qiáng)勁的速度。

          二、使用方法步驟

          1.安裝bs4

          pip install bs4
          mamba install bs4 -c conda-forge

          2.首先導(dǎo)入bs4庫

          from bs4 import BeautifulSoup

          3.利用requests打開網(wǎng)址

          當(dāng)然您也可以用其他方法打開網(wǎng)址。以https://www.sina.com.cn/為例

          import requests
          url="https://www.sina.com.cn/"
          r1=requests.get(url,'lxml')
          r1.encoding='utf-8'
          #print (r1.text)

          輸出的結(jié)果就是網(wǎng)頁的內(nèi)容。

          4.創(chuàng)造一個BeautifulSoup對象

          soup=BeautifulSoup(r1.text,'lxml')
          #print(soup)

          兩個參數(shù):第一個參數(shù)是要解析的html文本,第二個參數(shù)是使用那種解析器,對于HTML來講就是html.parser,這個是bs4自帶的解析器。

          如果一段HTML或XML文檔格式不正確的話,那么在不同的解析器中返回的結(jié)果可能是不一樣的。

          解析器

          使用方法

          優(yōu)勢

          python標(biāo)準(zhǔn)庫

          BeautifulSoup(html, “html.parser”)

          1、Python的內(nèi)置標(biāo)準(zhǔn)庫
          2、執(zhí)行速度適中
          3、文檔容錯能力強(qiáng)

          lxml HTML

          BeautifulSoup(html, “l(fā)xml”)

          1.速度快
          2.文檔容錯能力強(qiáng)

          lxml XML

          BeautifulSoup(html, [“l(fā)xml”, “xml”]) BeautifulSoup(html, “xml”)

          1.速度快
          2.唯一支持XML的解析器

          html5lib

          BeautifulSoup(html, “html5lib”)

          1.做好的容錯性
          2.以瀏覽器的方式解析文檔
          3.生成HTML5的文檔

          5.對獲取到的源碼進(jìn)行篩選和處理

          然后通過這個對象來實現(xiàn)對獲取到的源碼進(jìn)行篩選和處理

          print(soup.prettify()) #格式化輸出全部內(nèi)容
          print(soup.標(biāo)簽名)
          #標(biāo)簽名有html,head,title,meta,body,script,style等等https://pic.sogou.com/d?query=BS4%20%E5%9B%BE%E7%89%87&forbidqc=&entityid=&preQuery=&rawQuery=&queryList=&st=&did=43

          三、bs4對象的轉(zhuǎn)換

          將一段文檔傳入BeautifulSoup的構(gòu)造方法,就能得到一個文檔的對象

          from bs4 import BeautifulSoup
          # 獲得文檔對象 - soup
          soup=BeautifulSoup(open(文檔路徑, encoding=編碼格式),features=解析器)

          或傳入字符串或文件句柄

          # 傳入字符串
          soup=BeautifulSoup('recall', features='lxml')
          # 輸出<html><body><p>recall</p></body></html>
          
          # 傳入文件句柄
          soup=BeautifulSoup('<html>recall</html>')
          # 輸出<html><body><p>recall</p></body></html>

          解析過程:

          文檔被轉(zhuǎn)換成Unicode,并且HTML實例都被轉(zhuǎn)換成Unicode編碼

          BeautifulSoup選擇最合適的解析器來解析這段文檔,如果手動指定解析器,那么BeautifulSoup會選擇指定解析器來解析指定文檔

          注意:默認(rèn)解析器情況下,BeautifulSoup會將當(dāng)前文檔作為HTML格式解析,如果要解析XML文檔,需要指定"xml"解析器

          四、 對象的種類

          BeautifulSoup將復(fù)雜的HTML文檔解轉(zhuǎn)換成一個復(fù)雜的樹形結(jié)構(gòu),每個節(jié)點都是python對象,所有對象可以歸納為4種:Tag, NavigableString, BeautifulSoup, Comment

          1.Tag(標(biāo)簽)

          tag對象與XML或HTML原生文檔中的tag相同。

          soup=BeautifulSoup('<a>recall</a>', features='lxml')
          tag_soup=soup.a
          print(type(tag_soup))
          #<class 'bs4.element.Tag'>

          輸出:<class 'bs4.element.Tag'>

          tag的屬性

          tag中最重要的屬性:name和attributes

          (1)每個tag都有自己的名字,通過.name來獲取

          soup=BeautifulSoup('<a>recall</a>', features='lxml')
          print(soup.a)
          #<a>liyuhong</a>
          print(soup.a.name)
          #a

          tag.name值的改變(將改變HTML文檔):

          soup.a.name="p"
          print(soup.a)
          #None
          print(soup.p)
          #<p>recall</p>

          解析過程:

          (2)一個tag可能有多個屬性,操作與字典相同,通過.attrs來獲取。

          soup=BeautifulSoup('<a class="celebrity">liyuhong</a>', features='lxml')
          # 字典操作
          
          soup.a.attrs['class']
          #['celebrity']
          # 或直接取點屬性
          soup.a.attrs
          #{'class': ['celebrity']}

          (3)tag.attrs的改變(改、增、刪)

          soup=BeautifulSoup('<a class="celebrity">li</a>', features='lxml')
          # 直接打印源tag
          print(soup.a)
          #<a class="celebrity">li</a>
          # 1.改變屬性的值
          soup.a.attrs['class']='ljj'
          print(soup.a)
          #<a class="ljj">li</a>
          # 2.添加屬性的值
          soup.a.attrs['id']='li'
          print(soup.a)
          #<a class="celebrity" id="li">li</a>
          # 3.刪除屬性的值
          del soup.a.attrs['class']
          print(soup.a)
          #<a id="li">li</a>

          多值屬性:最常見的多值屬性是class,多值屬性的返回 list。

          soup=BeautifulSoup('<a class="celebrity ljj">li</a>', features='lxml')
          print(soup.a)
          print(soup.a.attrs['class']
          
          # 兩個值的class屬性
          #<a class="celebrity ljj">li</a>
          #['celebrity', 'ljj']

          2.NavigableString(標(biāo)簽的值)

          字符串常包含在tag內(nèi)。BeautifulSoup常用NavigableString類來包裝tag中的字符串。但是字符串中不能包含其他 tag。

          soup=BeautifulSoup('<a class="celebrity liyuhong">li</a>', features='lxml')
          
          print(soup.a.string)
          #li
          print(type(soup.a.string))
          #<class 'bs4.element.NavigableString'>

          tag中包含的字符串不能被編輯,但是可以被替換成其他字符串,用replace_with()方法

          soup=BeautifulSoup('<a class="celebrity ljj">li</a>', features='lxml')
          
          soup.a.string.replace_with('UFO')
          print(soup.a.string)
          #UFO
          print(soup.a)
          <a class="celebrity ljj">UFO</a>

          3.BeautifulSoup(文檔)

          BeautifulSoup 對象表示的是一個文檔的全部內(nèi)容。大部分時候,可以把它當(dāng)作 Tag 對象。但是 BeautifulSoup 對象并不是真正的 HTM L或 XML 的 tag,它沒有attribute屬性,name 屬性是一個值為“[document]”的特殊屬性。

          print(soup.name)
          #[document]
          print(type(soup))
          #<class 'bs4.BeautifulSoup'>

          4.Comment(注釋及特殊字符串)

          特殊類型的NavigableString,comment一般表示文檔的注釋部分。

          a1=BeautifulSoup("<b><!--This is a comment--></b>")
          
          comment=a1.b.string
          
          print(comment)          # This is a comment
          
          print(type(comment))    # <class 'bs4.element.Comment'>

          五、bs4語法

          (一)獲取屬性

          # 獲取p標(biāo)簽的屬性
          # 方法一
          soup.p.attrs(返回字典) or soup.p.attrs['class'](class返回列表,其余屬性返回字符串)
          
          # 方法二
          soup.p['class'](class返回列表,其余屬性返回字符串)
          
          # 方法三
          soup.p.get('class')(class返回列表,其余屬性返回字符串)

          (二)獲取文本

          # 獲取標(biāo)簽的值的三種方法
          
          soup.p.string
          
          soup.p.text
          
          soup.p.get.text()

          注意:當(dāng)標(biāo)簽的內(nèi)容還有標(biāo)簽時,string方法返回為None,而其他兩個方法獲取純文本內(nèi)容

          (三)find方法

          返回一個對象
          soup.find('a')
          soup.find('a', class_='xxx') # 注意class后的下劃線
          soup.find('a', title='xxx')
          soup.find('a', id='xxx')
          soup.find('a', id=compile(r'xxx'))

          注意:find只能找到符合要求的第一個標(biāo)簽,他返回的時一個對象

          (四)find_all方法

          返回一個列表,列表里是所有符合要求的對象

          soup.find_all('a')
          soup.find_all(['a','span']) #返回所有的a和span標(biāo)簽
          soup.find_all('a', class_='xxx')
          soup.find_all('a', id=compile(r'xxx'))
          # 提取出前兩個符合要求的
          soup.find_all('a', limit=3)

          (五)select方法

          返回列表。類似CSS寫法來篩選元素,標(biāo)簽名不加任何修飾,類名加點,id名加#

          1. 通過標(biāo)簽名查找

          soup.select('a')
          
          soup.select('a, span') # 注意引號的位置

          2.通過類名查找

          soup.select('.class')

          3.通過id名查找

          soup.select('#id')

          4. 通過子標(biāo)簽查找

          # 直接子元素(必須相鄰)
          soup.select('body > div >a')
          
          # 間接子元素(不需相鄰)
          soup.select('body a')

          5.組合查找

          標(biāo)簽名、類名、id名進(jìn)行組合,空格隔開

          soup.select('a #id')

          6.屬性查找

          soup.select('a[href="https://www.baidu.com"]')

          六、CSS選擇器

          BS4 支持大部分的 CSS 選擇器,比如常見的標(biāo)簽選擇器、類選擇器、id 選擇器,以及層級選擇器。Beautiful Soup 提供了一個 select() 方法,通過向該方法中添加選擇器,就可以在 HTML 文檔中搜索到與之對應(yīng)的內(nèi)容。

          css的規(guī)則

          標(biāo)簽名不加任何修飾,類名前加點,id名前加#,在這里用的方法大致是一樣的,用到的方法是soup.select(),它的返回值是一個列表list

          #通過標(biāo)簽名字
          print(soup.select('title'))
          
          #通過類名
          print(soup.select('.class'))
          
          #通過id
          print(soup.select('#a_1'))

          組合查找

          這里和在寫css的格式是一樣的

          print(soup.select('.class #id'))
          
          #意思是查找.class類下的id是id的元素

          子查詢

          print(soup.select('head > title'))
          

          屬性查找

          print(soup.select(p[class="new"]))

          主站蜘蛛池模板: 性色av一区二区三区夜夜嗨| 一区二区三区在线免费观看视频| 女人和拘做受全程看视频日本综合a一区二区视频 | 日韩在线视频一区| 亚洲AⅤ视频一区二区三区| 中文字幕一区精品| 无码人妻精品一区二区三区夜夜嗨| 成人午夜视频精品一区| 精品国产乱码一区二区三区| 精品成人乱色一区二区| AA区一区二区三无码精片| 欧洲精品码一区二区三区免费看 | 国产成人无码一区二区在线播放 | 国产精品高清一区二区三区不卡| 日韩电影一区二区| 中文字幕一区二区三区乱码| 精品福利一区二区三区| 精品视频一区二区三区四区五区 | 中文字幕av人妻少妇一区二区| 色狠狠色狠狠综合一区| 国产精品第一区揄拍| 成人在线观看一区| 精品一区二区三区3d动漫| 无码精品久久一区二区三区 | 精品视频在线观看你懂的一区| 中文字幕一区在线| 久久久综合亚洲色一区二区三区 | 亚洲一区中文字幕久久| 精品爆乳一区二区三区无码av| 一本久久精品一区二区| 国产精品亚洲专一区二区三区| 乱中年女人伦av一区二区| 真实国产乱子伦精品一区二区三区 | 日本内射精品一区二区视频| 精品午夜福利无人区乱码一区| 无码视频免费一区二三区| 一区二区三区四区在线视频 | 亚洲AV日韩AV天堂一区二区三区| 91精品福利一区二区三区野战| 国精产品一区一区三区有限在线| 日韩a无吗一区二区三区|