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
USE_CUDNN :=1
USE_OPENCV :=0 OPENCV_VERSION :=3
CUDA_DIR :=/usr/local/cuda_env/cuda-8.0
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)換分為如下三個等級:
這里列舉了一部分,詳情可以查文檔(見文章底部)。
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()安裝:
下圖展示了業(yè)內(nèi)常用的兩種編譯為wasm的方式。
自己編譯wasm package的方法可參考官方手冊,大致步驟就是pull官方的編譯基礎(chǔ)鏡像,把待編譯包的setup.cfg文件放到模塊目錄里,再加上些hack的語句和配置(如果有的話),然后指定目標(biāo)進(jìn)行編譯。編譯成功后部署時,需要注意2點:
下面是一個自建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有如下幾個缺陷:
盡管有上述種種缺陷,得益于代碼移植的高效率和邏輯上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)載。
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已成為和lxml、html6lib一樣出色的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)庫 |
lxml HTML | BeautifulSoup(html, “l(fā)xml”) | 1.速度快 |
lxml XML | BeautifulSoup(html, [“l(fā)xml”, “xml”]) BeautifulSoup(html, “xml”) | 1.速度快 |
html5lib | BeautifulSoup(html, “html5lib”) | 1.做好的容錯性 |
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
將一段文檔傳入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
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中最重要的屬性:name和attributes
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>
解析過程:
soup=BeautifulSoup('<a class="celebrity">liyuhong</a>', features='lxml')
# 字典操作
soup.a.attrs['class']
#['celebrity']
# 或直接取點屬性
soup.a.attrs
#{'class': ['celebrity']}
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']
字符串常包含在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>
BeautifulSoup 對象表示的是一個文檔的全部內(nèi)容。大部分時候,可以把它當(dāng)作 Tag 對象。但是 BeautifulSoup 對象并不是真正的 HTM L或 XML 的 tag,它沒有attribute屬性,name 屬性是一個值為“[document]”的特殊屬性。
print(soup.name)
#[document]
print(type(soup))
#<class 'bs4.BeautifulSoup'>
特殊類型的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'>
# 獲取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)容
返回一個對象
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)簽,他返回的時一個對象
返回一個列表,列表里是所有符合要求的對象
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)
返回列表。類似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"]')
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"]))
*請認(rèn)真填寫需求信息,我們會在24小時內(nèi)與您取得聯(lián)系。