整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

caffe的安装笔记 Ubuntu16.04

caffe的安装笔记 Ubuntu16.04

affe的安装笔记 Ubuntu16.04

跑实验用过一次caffe,光安装就用了一周,经历了各种错误,真的好难安装,记录下最后成功安装的方法,希望给大家安装时提供参考。

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

opencv使用

conda install opencv

直接使用 conda install caffe-gpu安装好所需的环境再使用源码编译,进行下面的步骤

再 使用conda uninstall caffe-gpu 去掉这个包,装上需要的环境,但是这个包不能直接使用(我也不知道原因)。

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路径(这个私人设置,不做参考)

# 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、测试使用

背景

随着机器学习的应用面越来越广,能在浏览器中跑模型推理的Javascript框架引擎也越来越多了。在项目中,前端同学可能会找到一些跑在服务端的python算法模型,很想将其直接集成到自己的代码中,以Javascript语言在浏览器中运行。

对于一部分简单的模型,推理的前处理、后处理比较容易,不涉及复杂的科学计算,碰到这种模型,最多做个模型格式转化,然后用推理框架直接跑就可以了,这种移植成本很低。

而很大一部分模型会涉及复杂的前处理、后处理,包括大量的矩阵运算、图像处理等Python代码。这种情况一般的思路就是用Javascript语言将Python代码手工翻译一遍,这么做的问题是费时费力还容易出错。

Pyodide作为浏览器中的科学计算框架,很好的解决了这个问题:浏览器中运行原生的Python代码进行前、后处理,大量numpy、scipy的矩阵、张量等计算无需翻译为Javascript,为移植节省了很多工作。本文就基于pyodide框架,从理论和实战两个角度,帮助前端同学解决复杂模型的移植这一棘手问题。

二 原理篇

Pyodide是个可以在浏览器中跑的WebAssembly(wasm)应用。它基于CPython的源代码进行了扩展,使用emscripten编译成为wasm,同时也把一大堆科学计算相关的pypi包也编译成了wasm,这样就能在浏览器中解释执行python语句进行科学计算了。所以pyodide也必然遵循wasm的各种约束。Pyodide在浏览器中的位置如下图所示:

1 wasm内存布局

这是wasm线性内存的布局:

Data数据段是从0x400开始的, Function Table表也在其中,起始地址为memoryBase(Emscripten中默认为1024,即0x400),STACKTOP为栈地址起始,堆地址起始为STACK_MAX。而我们实际更关心的是Javascript内存与wasm内存的互相访问。

2 Javascript与Python的互访

浏览器基于安全方面的考虑,防止wasm程序把浏览器搞崩溃,通过把wasm运行在一个沙箱化的执行环境中,禁止了wasm程序访问Javascript内存,而Javascript代码却可以访问wasm内存。因为wasm内存本质上是一个巨大的ArrayBuffer,接受Javascript的管理。我们称之为“单向内存访问”。

作为一个wasm格式的普通程序,pyodide被调用起来后,当然只能直接访问wasm内存。

为了实现互访,pyodide引入了proxy,类似于指针:在Javascript侧,通过一个PyProxy对象来引用python内存里的对象;在Python侧,通过一个JsProxy对象来引用Javascript内存里的对象。

在Javascript侧生成一个PyProxy对象:

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

在Python侧生成一个JsProxy对象:

import js
from js import foo   # foo是Javascript里的一个全局对象

互访时的类型转换分为如下三个等级:

  • 【自动转换】对于简单类型,如数字、字符串、布尔等,会被自动拷贝内存值,此时产生的就不是Proxy、而是最终的值了。
  • 【半自动转换】非简单的内置类型,都需要通过to_js()、to_py()方式来显式转换:对于Python内置的list、dict、numpy.ndarray等对象,不属于简单类型,不会自动转换类型,必须通过pyodide.to_js()来转,相应的会被转成JS的list、map、TypedArray类型反过来也类似,通过to_py()方法,JS的TypedArray转为memoryview,list、map转为list、dict
  • 【手动转换】各种class、function和用户自定义类型,因为对方的语言没有对应的现成类型,所以只能以proxy的形式存在,需要通过运算符来间接操纵,就像操纵提线木偶一样。为了达到方便操纵的目的,pyodide对两种语言进行了语法模拟,用一种语言里的操作符模拟另一种语言的类似行为。例如:JS中的let a=new XXX(),在Python中就变为a=XXX.new()。

这里列举了一部分,详情可以查文档(见文章底部)。

Javascript的模块也可以引入到Python中,这样Python就能直接调用该模块的接口和方法了。例如,pyodide没有编译opencv包,可以使用opencv.js:

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

这对于pyodide缺失的pypi包是个很好的补充。

三 实践篇

我们从一个空白页面开始。使用浏览器打开测试页面(测试页面见文章底部)。

1 初始化python

为了方便观察运行过程,使用动态的方式加载所需js和执行python代码。打开浏览器控制台,依次运行以下语句:

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。当然也可以使用其他推理引擎
loadJS('https://test-bucket-duplicate.oss-cn-hangzhou.aliyuncs.com/public/onnxruntime/onnx.min.js',  function(){
    console.log('js load ok');
});

// 初始化python运行环境
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就安装完毕了,都位于内存文件系统中。我们可以查看一下python被安装到了哪里:

注意,这个文件系统是内存里虚拟出来的,刷新页面就丢失了。不过由于浏览器本身有缓存,所以刷新页面后从服务端再次加载pyodide的引导js和主体wasm还是比较快的,只要不清理浏览器缓存。

2 加载pypi包

在pyodide初始化完成后,python系统自带的标准模块可以直接import。第三方模块需要用micropip.install()安装:

  • pypi.org上的纯python包可以用micropip.install() 直接安装
  • 含有C语言扩展(编译为动态链接库)的wheel包,需要对照官方已编译包的列表在列表中的直接用micropip.install()安装不在这个列表里的,就需要自己手动编译后发布到服务器后再用micropip.install()安装。

下图展示了业内常用的两种编译为wasm的方式。

自己编译wasm package的方法可参考官方手册,大致步骤就是pull官方的编译基础镜像,把待编译包的setup.cfg文件放到模块目录里,再加上些hack的语句和配置(如果有的话),然后指定目标进行编译。编译成功后部署时,需要注意2点:

  • 设置允许跨域
  • 对于wasm格式的文件请求,响应Header里应当带上:"Content-type": "application/wasm"

下面是一个自建wasm服务器的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
          ';
        }

回到我们的推理实例, 现在用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包就已全部导入了。

3 opencv的使用

一般python里的图片数组都是从JS里传过来的,这里我们模拟构造一张图片,然后用opencv对其resize。上面提到过,pyodide官方的opencv还没编译出来。如果涉及到的opencv方法调用有其他pypi包的替代品,那是最好的:比如,cv.resize可以用Pillow库的PIL.resize代替(注意Pillow的resize速度比opencv的resize要慢);cv.threshold可以用numpy.where代替。 否则只能调用opencv.js的能力了。为了演示pyodide语法,这里都从opencv.js库里调用。

await pyodide.runPythonAsync(`
# 构造一个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))
# 改成调用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)
`);

传参原则:除了简单的数字、字符串类型可以直接传,其他类型都需要通过pyodide.to_js()转换后再传入。 返回值的获取也类似,除了简单的数字、字符串类型可以直接获取,其他类型都需要通过xx.to_py()转换后获取结果。

接着对一个mask检测其轮廓:

await pyodide.runPythonAsync(`
# 使用cv2.findContours来检测轮廓。假设mask为二维numpy数组,只有0、1两个值
# 原python代码:contours=cv2.findContours(mask, cv2.RETR_CCOMP,cv2.CHAIN_APPROX_NONE)
# 改成调用opencv.js:
contours_jsproxy=cv2.MatVector.new()   # cv2.Mat数组,对应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格式转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加载模型并进行推理,详细语法可参考onnx.js官方文档。其他js版的推理引擎也都可以参考各自的文档。

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

通过以上的操作,我们确保了一切都在python语法范围内进行,这样修改原始的Python文件就比较容易了:把不支持的函数替换成我们自定义的调用js的方法;原Python里的推理替换成调用js版的推理引擎;最后在Javascript主程序框架里加少许调用Python的胶水代码就完成了。

5 挂载持久存储文件系统

有时我们需要对一些数据持久保存,可以利用pyodide提供的持久化文件系统(其实是emscripten提供的),见手册(文章底部)。

// 创建挂载点
pyodide.FS.mkdir('/mnt');
// 挂载文件系统
pyodide.FS.mount(pyodide.FS.filesystems.IDBFS, {}, '/mnt');
// 写入一个文件
pyodide.FS.writeFile('/mnt/test.txt', 'hello world');
// 真正的保存文件到持久文件系统
pyodide.FS.syncfs(function (err) {
    console.log(err);
});

这样文件就持久保存了。即使当我们刷新页面后,仍可以通过挂载该文件系统来读出里面的内容:

// 创建挂载点
pyodide.FS.mkdir('/mnt');
// 挂载文件系统
pyodide.FS.mount(pyodide.FS.filesystems.IDBFS, {}, '/mnt');
// 写入一个文件
pyodide.FS.writeFile('/mnt/test.txt', 'hello world');
// 真正的保存文件到持久文件系统
pyodide.FS.syncfs(function (err) {
    console.log(err);
});

运行结果如下:

当然,以上语句可以在python中以Proxy的语法方式运行。

持久文件系统有很多用处。例如,可以帮我们在多线程(webworker)之间共享大数据;可以把模型文件持久存储到文件系统里,无需每次都通过网络加载。

6 打wheel包

单Python文件无需打包,直接当成一个巨大的字符串,交给pyodide.runPythonAsync()运行就好了。当有多个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运行环境加载和初始化时间有点儿长,视网络情况,几秒到几十秒都有可能。
  • pypi包支持的不完整。虽然pypi.org上的纯python包都可以直接使用,但涉及到C扩展写的包,如果官方还没编译出来,那就需要自己动手编译了。
  • 个别很常用的包,例如opencv,还没成功编译出来;模型推理框架一个都没有。不过还好可以通过相应的JS库来弥补。
  • 如果python中调用了js库的话:可能会产生一定的内存拷贝开销(从wasm内存到JS内存的来回拷贝)。尤其是大数组作为参数或返回值,在速度要求高的场合下,额外的内存拷贝开销就不能忽视了。python库的方法接口可能跟其对应的js库的接口参数、返回值格式不一致,有一定的适配工作量。

五 总结

尽管有上述种种缺陷,得益于代码移植的高效率和逻辑上1:1复刻的高可靠性保障,我们还是可以把这种方法运用到多种业务场景里,为推动机器学习技术的应用添砖加瓦。

链接:

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/

本文为阿里云原创内容,未经允许不得转载。

、bs4简介

BeautifulSoup,是python中的一个库,是一个可以从HTML或XML文件中提取数据的Python库;它能够通过提供一些简单的函数用来处理导航、搜索、修改分析树等功能。它是一个工具箱,通过解析文档为用户提供需要抓取的数据,因为简单,所以不需要多少代码就可以写出一个完整的应用程序。Beautiful Soup会帮你节省数小时甚至数天的工作时间。

Beautiful Soup自动将输入文档转换为Unicode编码,输出文档转换为utf-8编码。你不需要考虑编码方式,除非文档没有指定一个编码方式,这时,Beautiful Soup就不能自动识别编码方式了。然后,你仅仅需要说明一下原始编码方式就可以了。

Beautiful Soup已成为和lxmlhtml6lib一样出色的python解释器,为用户灵活地提供不同的解析策略或强劲的速度。

二、使用方法步骤

1.安装bs4

pip install bs4
mamba install bs4 -c conda-forge

2.首先导入bs4库

from bs4 import BeautifulSoup

3.利用requests打开网址

当然您也可以用其他方法打开网址。以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)

输出的结果就是网页的内容。

4.创造一个BeautifulSoup对象

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

两个参数:第一个参数是要解析的html文本,第二个参数是使用那种解析器,对于HTML来讲就是html.parser,这个是bs4自带的解析器。

如果一段HTML或XML文档格式不正确的话,那么在不同的解析器中返回的结果可能是不一样的。

解析器

使用方法

优势

python标准库

BeautifulSoup(html, “html.parser”)

1、Python的内置标准库
2、执行速度适中
3、文档容错能力强

lxml HTML

BeautifulSoup(html, “lxml”)

1.速度快
2.文档容错能力强

lxml XML

BeautifulSoup(html, [“lxml”, “xml”]) BeautifulSoup(html, “xml”)

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

html5lib

BeautifulSoup(html, “html5lib”)

1.做好的容错性
2.以浏览器的方式解析文档
3.生成HTML5的文档

5.对获取到的源码进行筛选和处理

然后通过这个对象来实现对获取到的源码进行筛选和处理

print(soup.prettify()) #格式化输出全部内容
print(soup.标签名)
#标签名有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对象的转换

将一段文档传入BeautifulSoup的构造方法,就能得到一个文档的对象

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>

解析过程:

文档被转换成Unicode,并且HTML实例都被转换成Unicode编码

BeautifulSoup选择最合适的解析器来解析这段文档,如果手动指定解析器,那么BeautifulSoup会选择指定解析器来解析指定文档

注意:默认解析器情况下,BeautifulSoup会将当前文档作为HTML格式解析,如果要解析XML文档,需要指定"xml"解析器

四、 对象的种类

BeautifulSoup将复杂的HTML文档解转换成一个复杂的树形结构,每个节点都是python对象,所有对象可以归纳为4种:Tag, NavigableString, BeautifulSoup, Comment

1.Tag(标签)

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(标签的值)

字符串常包含在tag内。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 对象表示的是一个文档的全部内容。大部分时候,可以把它当作 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标签的属性
# 方法一
soup.p.attrs(返回字典) or soup.p.attrs['class'](class返回列表,其余属性返回字符串)

# 方法二
soup.p['class'](class返回列表,其余属性返回字符串)

# 方法三
soup.p.get('class')(class返回列表,其余属性返回字符串)

(二)获取文本

# 获取标签的值的三种方法

soup.p.string

soup.p.text

soup.p.get.text()

注意:当标签的内容还有标签时,string方法返回为None,而其他两个方法获取纯文本内容

(三)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只能找到符合要求的第一个标签,他返回的时一个对象

(四)find_all方法

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

soup.find_all('a')
soup.find_all(['a','span']) #返回所有的a和span标签
soup.find_all('a', class_='xxx')
soup.find_all('a', id=compile(r'xxx'))
# 提取出前两个符合要求的
soup.find_all('a', limit=3)

(五)select方法

返回列表。类似CSS写法来筛选元素,标签名不加任何修饰,类名加点,id名加#

1. 通过标签名查找

soup.select('a')

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

2.通过类名查找

soup.select('.class')

3.通过id名查找

soup.select('#id')

4. 通过子标签查找

# 直接子元素(必须相邻)
soup.select('body > div >a')

# 间接子元素(不需相邻)
soup.select('body a')

5.组合查找

标签名、类名、id名进行组合,空格隔开

soup.select('a #id')

6.属性查找

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

六、CSS选择器

BS4 支持大部分的 CSS 选择器,比如常见的标签选择器、类选择器、id 选择器,以及层级选择器。Beautiful Soup 提供了一个 select() 方法,通过向该方法中添加选择器,就可以在 HTML 文档中搜索到与之对应的内容。

css的规则

标签名不加任何修饰,类名前加点,id名前加#,在这里用的方法大致是一样的,用到的方法是soup.select(),它的返回值是一个列表list

#通过标签名字
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"]))