在线时间 1630 小时 最后登录 2024-1-29 注册时间 2017-5-16 听众数 82 收听数 1 能力 120 分 体力 563287 点 威望 12 点 阅读权限 255 积分 174209 相册 1 日志 0 记录 0 帖子 5313 主题 5273 精华 3 分享 0 好友 163
TA的每日心情 开心 2021-8-11 17:59
签到天数: 17 天
[LV.4]偶尔看看III
网络挑战赛参赛者
网络挑战赛参赛者
自我介绍 本人女,毕业于内蒙古科技大学,担任文职专业,毕业专业英语。
群组 : 2018美赛大象算法课程
群组 : 2018美赛护航培训课程
群组 : 2019年 数学中国站长建
群组 : 2019年数据分析师课程
群组 : 2018年大象老师国赛优
& f9 p& a3 o+ w Python爬虫常用库总结 5 k$ |1 n7 e5 \. T( t7 E
文章目录 / x5 _. O4 d! D* H6 A, l
requests / k) x1 C, P1 a. ~
requests基础
0 D/ I# C% T6 i8 L* c8 b3 m$ u requests模块发送get请求 * S8 C; R( O4 Z+ @# x+ O, \1 O
response响应对象 ! r: e$ R) F8 B; |, w7 v9 c
response.text 和response.content的区别 / K; @) d8 P( u$ R
解决中文乱码
5 ]/ }# w, ^6 r& M Q7 A5 c4 x) u response响应对象的其它常用属性或方法 7 i0 Z1 Q* K) k D
requests实操 7 G3 G% ^6 O5 u, L
requests模块发送请求 ; B p3 q* h! B$ U
发送带参数的请求 ) }4 d3 A/ M, r- [( R- Z( `
超时参数timeout的使用
; x, s+ h' |, S, {+ b0 J requests发送post请求的方法 . f2 N8 i! ^ b v H
BeautifulSoup / n% T1 [1 }% i
常见解释器的优缺点 ' U1 l8 Q; t& Z% |1 b
常用操作 - L; V7 W% Z1 Y6 C5 N4 A
几个简单的浏览结构化数据的方法
' o% s% V$ b' O1 d* M- }% k$ K5 h 从文档中找到所有的< a>标签的链接 : Q2 I* _! S, o- `! d8 a3 K2 D
在文档中获取所有的文字内容 - b# T/ e( J; a5 B" [+ U3 I
通过标签和属性获取
; M( s' d( a( }- F' s Name属性 , H) d* A: P# t, q |' }
多个属性
6 W# p" J, z8 u/ b9 z8 g 多值属性
3 c, T4 i% z+ l 可以遍历的字符串 8 F3 z; @2 X8 F, f! _+ k
注释及特殊字符串
# `1 ~ a$ J5 W( s( B 遍历文档树
1 f- L* e4 U/ O% I 子节点 / N y0 d0 q5 `. o
find_all方法
. v. K% U% x! S .contents和.children , z1 \3 _: r: E9 b
selenium
7 i" z# i( }! h( N selenium介绍
7 G) L3 I7 U! A" b- B) b: r chrome浏览器的运行效果 3 I, Y- j3 L0 b) @
phantomjs无界面浏览器的运行效果 ; \4 t& W# }# P8 j N; }
selenium的作用和工作原理
7 ]+ ^# g+ F8 |4 \" a selenium的安装以及简单使用
5 I5 z' q- J. |# q4 ?9 n9 g selenium的简单使用 % W( u3 x0 c' B/ T! j& W# f
lxml
- {$ l) e6 v: P6 @ v _) X" Q( w 记得安装快速第三方库,Python经常需要安装第三方库,原始的下载速度很慢,使用国内的镜像就很快啦
0 |& F5 L% T( n" _ & \3 q% k1 {% N$ D" z5 M, B! }
! u+ k9 o" Q6 d4 w1 {1 `9 O pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple 包名
, w& D" W4 `2 f1 _. ] 1 " Y8 s) P5 w4 v: ~% [; D
快速下载模块
* P* w l/ I! L: o% ]. V
7 O: H, _% v0 b! [' N4 A
U; H& i0 @: P8 T9 v$ Y 官方网址: ! E4 f/ U4 w: ?$ {9 B
, h( g8 ^* x& I* K5 e6 E) k0 [ 2 {$ b8 R) X3 j( L& d
Requests: 让 HTTP 服务人类
I, E0 d* ?! K( R4 F# | Beautiful Soup 4.4.0 文档
" c6 g# P4 Q5 n+ [. |# k! K& G4 s8 r Selenium官网
3 @0 h( C, O! V5 V3 |) `; }0 D lxml - XML and HTML with Python
[% } P3 ]. O requests 9 j5 H# e, p3 {/ k3 N- K/ y; o' n. J
requests官方文档 https://docs.python-requests.org/zh_CN/latest/ ( p& D' u' j9 Y& ~ T- c
1 \6 ~2 k3 K$ J+ N! U
3 F! y6 B2 }, c4 p
! b% Y% j( A# _+ K, P9 d
8 D3 h% b/ Q% j+ V9 \6 x) l
) c4 J/ |$ }$ B! Y
8 ^, G6 F! Y* B0 X: y0 H4 B) U 进行爬虫,首先要对网址进行请求,这个时候就要用刀我们的requests模块了。requests是python的一个HTTP客户端库,跟urllib,urllib2类似。与urllib,urllib2相比,requests模块语法更加简单。正如他的官网所说:
, t# M. V9 M1 E4 a9 |. b) P & I( _1 n/ {( L: v
9 T0 I) a# u, c- Z
1 p$ X( ]" s0 i' H& `3 E
% D3 ]& }3 y) a9 d9 w8 d requests模块介绍
2 j9 _ }$ |! g' n5 h
7 O+ K, I P# ? / |) `; M( Q" c# C2 [0 I
发送http请求,获取响应数据
5 b. S# j; X+ W0 T" A3 a, E0 a1 f' H# a , v8 f3 i5 N! F1 F" v" \+ ^9 f
9 b- {2 Q' J. T! Q* E) @5 u
requests模块是一个第三方模块,需要在你的python(虚拟)环境中额外安装 : f. N; |2 B' W
+ Z! j! ~/ j5 f3 M$ L0 P
- u x8 F6 W- p* }* D" R pip/pip3 install requests
- O2 {* J( N. S: p$ q, k6 ^9 _
8 g1 B+ W* L1 k0 F) V9 t 4 t k4 ~% s6 \ N- G8 ^& ~* K
requests基础
6 X) s6 c0 M9 o0 S& e; D requests模块发送get请求 $ J& P7 `+ q& z, J
#https://beishan.blog.csdn.net/
# ]9 C5 J' C! r9 h" K/ n import requests 4 ]; f, s, R0 t2 |
# 目标url 9 |- w2 W( R% q, Z, m, r
url = 'https://www.baidu.com'
* E+ w1 _4 @" c( `; M/ l/ _ # 向目标url发送get请求 8 `* c8 P! e8 x/ }; G
response = requests.get(url) 0 p# u* [4 T9 F. ^6 K9 @3 C
# 打印响应内容 - ]# K1 u2 _$ f9 v- V
print(response.text)
' g4 ^5 x* r4 G 1
) S8 O3 }+ O; E( L3 J, F! T 2
! o& m; E7 j5 c: ^7 f+ i. A* W3 { 3 ) G. Z' ~* X1 I* k2 I5 J8 l
4 + y2 X7 @! m% Q4 |
5 8 b; _8 ]8 T( X% R0 _/ k4 z. ~
6 ) u" D" P5 V2 [* b, k+ M2 [1 [# t
7 5 U1 E/ h% K; W B4 b( `4 X" ~
8 % Z2 w: I' a- T- n/ e
response响应对象
4 S3 p% N% _: z# A+ G; ^- x 观察上边代码运行结果发现,有好多乱码;这是因为编解码使用的字符集不同早造成的;我们尝试使用下边的办法来解决中文乱码问题 ' m( g3 E2 i5 M/ K- [0 {; ?1 b
5 F8 W0 D8 s, f & ?. r- U1 f$ Q8 U! S' X
import requests
1 G/ ]7 P7 t3 G' W5 d url = 'https://www.baidu.com'
]3 ]# |) p( ?2 A. R. R6 G1 O # 向目标url发送get请求 8 \' R: Q2 C1 a( E7 l$ E& W; G
response = requests.get(url) $ K; Y, I! I# c/ O$ k; I
# 打印响应内容 $ Y! F/ b! e$ D9 n
# print(response.text) 0 Z1 F1 J" ~* Q+ l1 t- H
print(response.content.decode()) # 注意这里! . a% f9 ~. U4 x% ^; h4 v
1
, y$ \' P8 Q( q/ |% n W& \ 2
0 K' [8 E+ e% r 3
* g+ L$ u: ^$ U' B/ s 4 2 g0 \" ~& p5 P3 x2 W+ a
5 + Y- K! n0 s% F$ z
6 ) U1 x8 a! n0 W9 P6 M
7 + J; t [% }( P7 A
response.text是requests模块按照chardet模块推测出的编码字符集进行解码的结果 . ]6 Y9 p) |& u. ?; ^ E
网络传输的字符串都是bytes类型的,所以response.text = response.content.decode(‘推测出的编码字符集’)
. f ~* q, j$ x7 N# V3 i: k 我们可以在网页源码中搜索charset,尝试参考该编码字符集,注意存在不准确的情况 % f% S3 ?% L, z% w* V' @
response.text 和response.content的区别
t/ L# @) u0 n* w; ~: O- k response.text
2 y* b4 d: S" A' n0 R 类型:str
7 e$ k! V; q4 X& E 解码类型: requests模块自动根据HTTP 头部对响应的编码作出有根据的推测,推测的文本编码
* B6 y7 R( H) Q0 F/ C" k& B5 o response.content
$ J! D( i+ t8 o4 |7 v 类型:bytes 3 e/ J& f+ y2 }
解码类型: 没有指定 ( p2 c+ B: |7 w, R" w$ h( G9 @ k
解决中文乱码
% C1 u0 F: {. v1 ^ 通过对response.content进行decode,来解决中文乱码
4 v/ m5 N2 H, e2 u
8 n# H$ n4 I+ u/ M2 A4 T ; l6 h* W1 v' _' h5 b
response.content.decode() 默认utf-8
. I) M& {1 e6 X9 y/ U response.content.decode("GBK")
& }& f9 @8 f: u6 ?! J; s+ ^; _ 常见的编码字符集 ( y7 Q3 r* T& @4 G8 C- n' ~2 T
utf-8
, X4 Y# c+ Q: L, \ B. m gbk 5 d/ [/ @$ d! t2 Y; j
gb2312
' L# [3 M$ r0 J6 R& p& M# M. f ascii (读音:阿斯克码) 4 ]% k8 w/ m- H& W
iso-8859-1 ' \8 ?# k; M9 G& H2 n
response响应对象的其它常用属性或方法
3 N! S$ X7 x0 P #https://beishan.blog.csdn.net/
8 t _, _$ z* J7 B d6 X6 `: t' x # 1.2.3-response其它常用属性 ) E. B ~* q K0 T2 E: |9 z
import requests
, R4 a) Y% z$ ?
$ m/ r6 Q: V: J4 w; e. g4 X4 Q , O& b5 r) { P1 w- B
# 目标url 3 L4 w' `7 t7 ?$ m: u! v+ W5 v ~
url = 'https://www.baidu.com' , {8 M4 \ J1 e( \ | p
2 u% y' U: b W8 }
. z3 z9 I- p5 k, w& |; T
# 向目标url发送get请求
% C+ `; S% } m# q0 U5 c7 m response = requests.get(url) * b( y X" o( U8 G- M$ `
( Z& z K( B3 N0 W4 R1 ~$ m, n- U8 u1 Y ; l" Z5 s/ K- t8 J9 P$ _
# 打印响应内容 ( z# e# F5 u, ?; w1 E0 m# z
# print(response.text)
2 V; u$ U2 V$ P) v) z+ o # print(response.content.decode()) # 注意这里! * f; ]2 y5 u5 u* F
print(response.url) # 打印响应的url
" l- ~% B$ V6 g. K print(response.status_code) # 打印响应的状态码
s2 U0 [! X8 q, }/ t" Z* G) u; D# e* i print(response.request.headers) # 打印响应对象的请求头 ; a! Q& i& Y; E: {+ o
print(response.headers) # 打印响应头 . `7 L( u: u& |" P6 C
print(response.request._cookies) # 打印请求携带的cookies / {: ?& e2 W; p/ M3 Y+ g' k% ?
print(response.cookies) # 打印响应中携带的cookies
. F0 N; J7 ^9 t' e7 O) F2 q z 1 0 A" {4 o/ ?8 X e+ A- M
2
: L% R- w2 D9 j" b0 P0 T! d 3 - _" h1 d+ n( Z4 @
4 1 I" l. ^/ I: m$ p3 r. V
5 5 r6 G* j5 q! h3 U1 T7 ^; @
6
* Z* k% P( x" a: ] F$ Z 7
6 y8 \3 J/ _& e& A# W 8
, x: D2 s" D0 }9 ` 9 " u. c: N7 x* v2 \; A9 @: X
10 ( H" ]6 |, G& A9 L
11
/ X& _3 A' ~7 d% v 12
3 H( ^; T5 A- T; p4 a O5 _ 13
) ` \' }9 @7 J* H2 [$ [ 14
5 v& W. B9 [% O5 ]. X) g7 | 15
! g$ }% b: ?7 T( b- |/ }) A. o 16
/ D) P7 F4 } d, d; E$ U 17
" o0 V8 ~8 d% L" M& z$ B5 D7 O0 F* `, d 18
6 e( q) W9 a1 h8 d3 L) _ 19
2 M; ~8 T0 q5 Z requests实操 1 `$ \- c- w$ f' l
requests模块发送请求 x" g0 Z; Z5 ~% K6 g
发送带header的请求 7 e0 {; R) {5 t/ ~! x7 a
: [/ Y3 c: W( j9 u7 r
, A: p6 y1 k" ~) j. d 我们先写一个获取百度首页的代码 2 e3 D4 M9 _* b' S* V
+ J: e4 E5 P' i1 V- E, w8 t; s
* T7 k( i; G: R1 X" e% S# C import requests
, E0 S$ [; ?0 d- ]9 `" P5 I( {- k url = 'https://www.baidu.com'
0 f! e( P" H% r. g# V( o response = requests.get(url) * I) Q) D+ X$ ?
print(response.content.decode())
2 d+ K1 ~& y- G # 打印响应对应请求的请求头信息
" ^1 Q5 g G" g* | print(response.request.headers)
" ?( H+ s+ t3 P& p4 U9 g 1
- w7 t i V: h+ G 2
" R+ t; I$ z5 T& `( F 3
% h) u$ W8 T0 D 4
1 i" J! d. O/ T" p1 F4 V 5 - x* L3 h! E. B6 `; N1 ], i1 O
6 5 h1 O! E! e6 l
从浏览器中复制User-Agent,构造headers字典;完成下面的代码后,运行代码查看结果 & s" O9 j* ?6 {6 }; ~( b( {
% @8 e" x& M; _! Z
& K. n8 _0 W% `
import requests 5 M' I- X7 T" v/ A C2 o' a
6 c# G, w3 r& m2 i5 _% e0 p 8 P7 ~* z2 [, Y3 e3 ?
url = 'https://www.baidu.com'
3 N5 Q" K. L* F( X# K+ L , v7 P+ b ]1 h! L8 T8 q U8 u" Y
2 I9 E; Y9 ?5 t headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36"}
$ R+ S" m' n e+ k3 n; c0 Q - U# s h# j6 F/ J
8 w" C+ u1 {7 ?/ x* z/ I* J # 在请求头中带上User-Agent,模拟浏览器发送请求
' d. I" ] ^' G2 s response = requests.get(url, headers=headers)
6 F/ l$ o% F. n# s0 ]2 P3 v
; n& M* W/ c0 g$ S
8 y9 M' @8 |# s" |, g print(response.content)
5 p/ q6 t! b5 p; R! C3 \ . Z9 N3 Q3 g" R4 x3 Y
" s; P" C/ F! [) a4 R$ L # 打印请求头信息
6 i' K/ q% K" l3 |, Q print(response.request.headers) J5 |. L( Y& t j
1
4 ?/ D t# b) c: H3 h; u 2
; E q p( B' k4 n7 p0 f 3
0 W4 ^' S- v6 d/ q 4
( k9 l8 j, A, s+ K 5 8 A/ H7 r+ Y, ?$ ~- o1 {
6
* }3 L; [- Y8 b6 i' P+ U9 ? 7 ' ^, q, `0 s8 S
8 & B E% v- r0 G" E7 `( _7 [5 Q% P
9
8 I: v' T6 e6 G& |& v/ U 10 ) ]; q4 |! ^7 F
11
- @4 d' B% ]% p 12
! _7 Y* F/ g& ?3 H; e. [6 w 13
: p7 Q, a1 m$ O" n 发送带参数的请求 ( L/ k' `5 C2 n
我们在使用百度搜索的时候经常发现url地址中会有一个 ?,那么该问号后边的就是请求参数,又叫做查询字符串
, O- A% b, `9 C/ a* R( K( Z0 P; \
' t; v% ^3 @/ }& T# g0 _1 C
7 K5 ~9 i7 L4 s 在url携带参数,直接对含有参数的url发起请求
; w; y: f. X' c; |/ }: i- G # X- e- u9 N# f& Q& [3 A2 e
9 f/ Y3 K* u* {7 T, Q+ v
import requests
# J; j# k) f$ [6 _' P7 S, y ' v' Y' ~; m+ s* k; _4 f9 \6 Y% V8 A
p, E- G7 ^6 o) N# d headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36"}
, {9 y: p7 n; B$ n, b ) x5 W- }( f. N5 Z5 G* }7 ]
8 _8 g( T( ~4 E3 [ url = 'https://www.baidu.com/s?wd=python'
) b3 P! [8 e+ v5 b+ R* ?3 h* c + i8 W& \) K4 J+ l6 ]
4 S* n* N) J" U3 N$ `- a& O9 w response = requests.get(url, headers=headers)
! L0 T }; ?: N2 ]- N
- w3 Y* [" ^) Y- G' J6 U# m 7 p! `$ l9 A% _! v
1
) w( s' a6 _& a$ a 2
+ |6 u! X' {9 L- C W0 r* ~ 3
( U! f4 T# M. \ 4
5 o: \% v* p1 D* [' u 5 9 k6 q- F" V! B3 }: f7 z
6
: @" s! w7 U: u8 a) r/ I* i% X$ c 7
' | @3 ?6 m/ d3 q3 t3 u 8
5 w1 b4 D. R6 X! ]% r& J7 S 通过params携带参数字典 " u$ ^2 _8 J' w, [6 v6 G1 m9 t
6 o& [1 N7 h: f0 K ( z/ ]* ^6 c" A7 d+ t. _
1.构建请求参数字典 9 R; b2 _ q, I& Z$ i
j4 |1 D l- @% U. ?, y
3 A) E3 B7 B8 J 2.向接口发送请求的时候带上参数字典,参数字典设置给params
2 S9 Z7 j3 I% v6 f, m( G) a/ z
/ f0 b3 H `7 ?/ Y# F- w 5 Q; W7 O& {( W
import requests " F. T- ~( u2 ^
4 Q1 L/ M2 E4 H- \ ]- I) j n
% `% M. e( S: F
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36"} + S8 Q- q& H2 ^1 @
( n0 x: T, T) {, H, W* m
' U4 M# T: Y+ [0 k # 这是目标url $ ^5 o& a/ m2 y
# url = 'https://www.baidu.com/s?wd=python'
8 g. w8 Y7 {' W% E+ n5 [* \! B $ u( t! ?3 \8 S( ]4 a
: Q) ^- L: j7 O+ Y8 o # 最后有没有问号结果都一样
* ?$ g( e/ L) J' k/ J0 `1 Y$ a url = 'https://www.baidu.com/s?' $ }1 d4 p6 T! h% R* t/ L; N) B' N: Y4 w. T
" A+ l& l' P2 ^' q
! ^$ v- K; V2 d8 w5 p1 q # 请求参数是一个字典 即wd=python 5 s- j$ K2 a4 B! g& R6 v% C
kw = {'wd': 'python'} 1 ?8 a0 [. {( m5 `7 G. l
8 k- |+ `! K* G5 v5 n8 q - d" t& q* O4 s. x2 m
# 带上请求参数发起请求,获取响应 0 E& P/ q8 U- G# v- {# h
response = requests.get(url, headers=headers, params=kw) - P% N* e2 i7 X( R1 `+ j
4 Y2 T( v3 e7 [
a+ ?* B/ O) @+ ^" h& d print(response.content)
' M' f* {/ F A! L- J2 z/ N 1
6 W2 |- T8 e r2 ?2 d Z 2 * i0 L3 @8 a, w9 D0 x# w
3
, H: E% i6 ^6 O/ J4 x( ~; n 4 ' M, z9 S6 [' K, c, Q/ j0 `2 B; q
5 7 ?% t! ^3 D7 e* R
6 0 y" A- p9 x# H- T; X8 [. A
7
/ O- Q3 K% [( W5 ?7 u 8 0 |5 E% g, @" n# y6 w. r
9
) f X+ R, x+ N; c* Z( o 10 . O8 P* U! g! G
11 # l3 l1 Y. }: b9 L
12
: U2 q7 ]: w2 Q7 w* j4 U 13 5 g' o* Q" V. z- W" f
14
2 m7 y" I" r( b, M/ q 15 / P5 s0 [6 n# ~6 ~4 f
16
4 y( C5 ]( [- q 17 % S) c6 G% N7 G& {3 \& d& {7 F
从浏览器中复制User-Agent和Cookie
+ t: v$ o# K5 E3 a 浏览器中的请求头字段和值与headers参数中必须一致
8 W, C5 G- x# C& } headers请求参数字典中的Cookie键对应的值是字符串
: R5 b9 n% t+ `) E, B6 }: U- } import requests + M. r5 k5 t" H
7 k( C1 h. u ]3 M9 w
6 B7 `! G' W. n& u$ z) ]& I, A; y9 P url = 'https://github.com/USER_NAME' ) S, {5 x! |! C7 _. A. P& N
8 b3 P. M: h7 l+ d& X, [( Z' L4 g# K+ K
6 _/ y, G# C7 l( O! R # 构造请求头字典 1 L8 {' P# A" I( _# M X, g& f9 K
headers = {
+ H- [* {+ G s# a. \+ ` # 从浏览器中复制过来的User-Agent
5 Q% ~! ^1 y9 W0 H! R# y 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36',
. Z3 J; F/ m c& f( M # 从浏览器中复制过来的Cookie % y k# \/ m, ?0 x
'Cookie': 'xxx这里是复制过来的cookie字符串' " X" E2 V8 S4 p. C' w* M
} + j$ t+ _+ z5 q$ c- Z5 J
0 w! v0 [6 x+ o- Y' `0 e
0 F* t; _5 @3 s$ K # 请求头参数字典中携带cookie字符串
( V7 u6 X# Z. u. Y resp = requests.get(url, headers=headers) + z( k, [4 U0 I5 p0 N
/ ^ {- E8 _. c H9 t) h) x ; K% _* F6 _' O
print(resp.text) % s1 I' Z# I) Z- @
1 9 V$ X8 L2 m: x, m8 A$ k2 Q4 F! k
2
9 r" D" u0 g! N) `1 e* P$ ^$ z 3
9 U0 V& k, C0 h$ X 4 " D! b9 o N; G
5
1 h$ N7 c. p) ] t 6
- b# I: R0 K+ f 7
: a$ D* \; n3 h2 G+ s 8
6 R: N7 M9 X1 \ G5 p6 Y2 { 9
/ P+ y( C& k9 j- c 10 7 x9 s* W8 B1 a( q) a8 b# |1 t
11
8 M/ f/ x4 O. v$ t7 { 12
/ r8 ~+ @6 p1 S3 P$ f' N$ J4 [* q 13
0 E1 I& R1 z- R0 s/ C 14 8 }4 B' t4 W; p/ m- u
15 0 B% i: `9 y- y& W' l# K
16
t/ I2 u8 X X% U 超时参数timeout的使用
" w4 \* Y: K" O2 ^ 在平时网上冲浪的过程中,我们经常会遇到网络波动,这个时候,一个请求等了很久可能任然没有结果。
* |1 U- N) \" N3 R
6 g+ x' p3 z8 ~& G 1 R; q% n/ A6 l; W6 F6 H6 ^
在爬虫中,一个请求很久没有结果,就会让整个项目的效率变得非常低,这个时候我们就需要对请求进行强制要求,让他必须在特定的时间内返回结果,否则就报错。
' k# Y( Z9 @: S: F* L" p$ c. ^ ) Y, @! K: `+ L& V0 P( \3 m0 Z
! O) W& A2 W; T+ L
超时参数timeout的使用方法
, W* {* {2 g( _ g0 D$ T & D+ e' @9 h$ K' ]
* K6 {; ^) z9 ^0 q
response = requests.get(url, timeout=3)
9 a$ W1 h6 m* y s- J! O0 @9 P$ e
' |7 c/ r0 ~5 w+ V
timeout=3表示:发送请求后,3秒钟内返回响应,否则就抛出异常 " a) T) J: V P+ \+ w" b( O
* c9 |* r; d3 O1 `, c; Z
! c {6 @: C. u( Z% C6 K0 | import requests
5 ?( |+ t" D/ s
7 i& R* h8 s0 A: [ 0 G. J) [8 j a) }
0 {. }1 u6 a4 {- N9 y2 ?' y
5 {; h# g l8 g6 U; s" I, P
url = 'https://twitter.com' - I' ?2 }/ s. l
response = requests.get(url, timeout=3) # 设置超时时间 $ E& G, c+ G3 K; u- ~/ l
$ z. d- ~, e, Z1 N' n; D% ~ u0 ^
/ @# C* B9 J: }( [# \/ U 1 ' {+ n- ~5 X; v* p% a9 {% ?6 Z9 R
2
! L* p' O2 U' F1 }( U 3
' X# N, [" t Q/ c: V! { 4
: X& z# h" Y5 |" v" d1 X 5
# a& G- m' U0 j, K$ e 6 , J5 k1 g! b4 o9 c9 W \9 ~8 a
requests发送post请求的方法 , Y( m) C6 _/ V! o
response = requests.post(url, data) ; p( r4 E Y4 Y! e& P8 o
2 G8 D7 W1 M% w8 ?9 b
5 C$ Z# r$ K/ A6 W data参数接收一个字典 + P- l" z% I; J: M
( N9 X) R9 Z* b+ @5 {* e1 t7 d/ W
5 `1 x0 m0 b5 L4 h6 ?6 M requests模块发送post请求函数的其它参数和发送get请求的参数完全一致
1 X2 I. P n" p( i; { # s2 X' L, e; F3 ~* r
* o% c$ |% {4 O2 l- Q1 _
BeautifulSoup
# y: Q* q# x( Q( b. l BeautifulSoup官方文档 https://beautifulsoup.readthedocs.io/zh_CN/v4.4.0/ H- m5 h4 x9 e& a' K) [
5 Y9 T; a5 a ^
/ m- I7 i p7 J; b+ B
Beautiful Soup 是一个可以从HTML或XML文件中提取数据的Python库.它能够通过你喜欢的转换器实现惯用的文档导航,查找,修改文档的方式.Beautiful Soup会帮你节省数小时甚至数天的工作时间.
! h8 i( q s* l6 [( a
8 C3 n' z! T4 \* a) J5 v
$ s, }2 V5 p `( M" c( H7 \( ?1 B 6 H! T% F% a5 I a
$ q7 M# b4 ?0 \% D: ~1 \
2 M, ~- c; I' d# g$ w * Q9 ?' X3 G0 [# ^* I# S
文章目录
9 ^: P/ f) a( p% O' g$ y requests 6 [/ e2 _) t) l8 s# C
requests基础
, Q' K- y& f6 m$ g requests模块发送get请求
, S6 w" x% [. R response响应对象 6 ?5 T- z( b8 c5 x. `4 a( t
response.text 和response.content的区别 , K* F J. X% S D
解决中文乱码 ' z) C$ C( H: C! Q# {% ^! d
response响应对象的其它常用属性或方法 - z0 f% d2 I( C" }6 @" ]% X* N
requests实操 ' i* ?6 f4 W4 K8 v' t
requests模块发送请求
& N6 w1 e* |8 J 发送带参数的请求
) A- V/ ?+ m I1 @$ t 超时参数timeout的使用
" w% X: b4 f- N( S. g5 m+ Y; y. l requests发送post请求的方法
M# k; R/ Z. G8 T/ _+ p BeautifulSoup
5 g! z1 Y! P6 W) g: b X9 ~% ^ 常见解释器的优缺点
( j7 y' I9 q* a+ { 常用操作
) q) P7 H3 l9 G3 E) Z8 _1 ^5 A6 f 几个简单的浏览结构化数据的方法
3 {5 I7 X" P. w7 h$ J* k 从文档中找到所有的< a>标签的链接
3 n/ z/ l5 Z. z% M1 N1 c 在文档中获取所有的文字内容
6 q3 R$ j( L* U, q- S# S 通过标签和属性获取 0 L$ z+ I. m$ Q5 v
Name属性
2 ]+ b0 E8 M0 {1 e+ r# @ 多个属性
( X# m& R7 O+ x/ k* a# V$ A" I 多值属性 8 w+ G9 _6 { @- l
可以遍历的字符串 7 d% W& @0 y3 | u( u
注释及特殊字符串 " H/ d3 q; ~! F( `* O) W
遍历文档树 ' Q! V( c k/ {( \% N6 i+ f
子节点 6 `+ b- }, n4 R. O7 G
find_all方法
& H, b7 F* N* N, Z .contents和.children 9 L) E& U5 Z/ v2 a ?; a. r$ z
selenium % a2 b, K$ N' t8 e' i
selenium介绍
$ x) G/ }5 b1 {* _6 `9 d chrome浏览器的运行效果 ) Z$ k- o/ ^. `3 `# O ~
phantomjs无界面浏览器的运行效果 S- H+ t9 G$ M6 x& M# b2 ]; r
selenium的作用和工作原理
, l8 b0 M+ q. W# i" w3 t& l" W2 Z3 x selenium的安装以及简单使用 8 {4 m* ?0 |! ]- J9 @$ R
selenium的简单使用 " E" Q. J- d& L' i1 o
lxml ; @# f. z/ y6 k" \5 x$ D' f
常见解释器的优缺点
3 A) u# i B% ]) q, y9 F& @ 2 j4 M& h" \* E6 f& g6 C8 M
& A3 E3 V/ J) E5 s% `& }: f# d
' D$ x1 u h. X. x. q- V0 e1 F5 P
; {* n7 R9 G; p( ? ^: p$ \ 常用操作
2 |% `% v3 N) V8 Z 安装方法
! [" i7 w0 U8 }' z9 k7 F! O
* }2 F# d. E$ T0 p5 x- @
5 R8 M1 \, ?# {4 R3 ^- T pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple beautifulsoup4 ( a: K3 b0 z Q
1
4 T& d4 s/ p9 S% W$ L) g 导入即可
* l+ f% X; C+ ^ + A, V7 i* D' k- C% z
: y, X0 j0 j3 M5 [" A
from bs4 import BeautifulSoup
5 h1 |9 A3 x- z 1
) W7 U. O6 ]7 h. Z5 A- j html_doc = """ , A( @( y0 Z p) Y! j- D3 p
<html><head><title>The Dormouse's story</title></head>
6 q4 C5 ^; O! G/ @7 J( E7 f5 ^9 Z <body> 0 B6 p# e$ Z# U* F$ K3 [- X
<p class="title"><b>The Dormouse's story</b></p> 9 |$ Z# C% r0 M, D2 Z/ v
, h1 j) ~# C, V2 B( y
! b& R0 X! M$ G% L
<p class="story">Once upon a time there were three little sisters; and their names were 5 K* W- s: b6 B$ w- q4 f# L
<a class="sister" id="link1">Elsie</a>,
, ]- m5 p d2 z1 A# P( F8 j <a class="sister" id="link2">Lacie</a> and 8 C; H$ j) y2 ?* p0 s' [
<a class="sister" id="link3">Tillie</a>;
5 G+ ]) s5 R8 T5 ^3 {# | and they lived at the bottom of a well.</p> * z D, E1 t7 a& l
3 h8 t, \: B! g( }& G4 O1 I. I! N
. l! ]' a; W7 z+ ~' \ <p class="story">...</p>
" }, ?+ E4 g; n. b4 z' ~ """
+ W7 ?. q2 }; G" F/ m u 1 ! @# ~3 ^6 n& A, P% B
2 5 x* e% W9 A& g) b( }1 z4 t/ \- Z
3
# W: u5 m9 o( g# Y# U* j$ s, d 4
7 {/ x2 G4 a" i( e, l 5
% C a2 f! W+ R2 v4 x/ r 6
, y6 v% I' I( [5 P7 Y 7 9 }: M5 s M) b- T+ }( e
8
% L9 A: f* k8 u 9 ( g" I- ]3 y, J/ ~7 x* f
10
3 R7 Y7 H* l. m% Q# K& T 11 . @$ h; B' Q) R' x
12 + E* @* f- [: C9 I k8 h5 _
13 4 }4 J/ H; M) f( ?! T. }
soup = BeautifulSoup(html_doc,"lxml") # S( q9 J- G' k1 x: o: g9 I6 _
1 6 p2 b7 n; d/ _# O+ V: U
几个简单的浏览结构化数据的方法 # h+ ^$ G/ ~' d0 S8 E
soup.title * D* |& c& ~3 s' J
1 $ W( u/ h, v2 `* k7 K/ v
<title>The Dormouse's story</title>
* K9 |6 Q2 }4 }' ?/ p1 h+ G$ p 1
) t6 i5 o+ O4 P7 Z( K, L; m soup.title.name 6 c6 f5 k {& Y% M$ H
1 # i, Z& J' u' b8 x0 y T: d$ a
'title' ( C E2 {+ l" K+ a& Y4 n( E7 c0 B# p
1
* n* u v) A4 b) D% Q soup.title.string 3 I3 a2 s5 B- T
1 e% n( S9 Y( l
"The Dormouse's story"
8 X. H" { C9 z2 J1 w 1
" w6 t* c; `4 J2 w$ M9 p% ^ soup.title.text
; |4 d( h: ]. m0 r8 u' w' y0 s 1 9 ^" T: e# N( Q7 X( Y
"The Dormouse's story" , A& N ^3 p, n/ {6 W( q! F, ~( a
1 ; P* E9 d6 H) o
soup.title.parent.name ) e V% A# F9 n7 v( I6 M& Z4 Y- p
1 : M$ a$ {. L8 S$ i. Z E( y% @
'head'
. s8 f/ `$ g; f: w' K 1
& h6 s4 N% s# ]8 J; \# b! I soup.p 7 c* o, m! V T4 @$ i( Q$ Q. \
1
" O4 |' v8 m4 X& i8 x* l$ q( r <p class="title"><b>The Dormouse's story</b></p>
; d" s! D3 m! c" s$ E- E( K1 ~ 1 0 Z8 B9 ~: z9 g: ?
soup.p.name 1 s/ A0 `5 t: I
1 % j9 Q3 Z; ~/ B l- D0 X. F! O7 |
'p' 9 F) }# I* {; C! Q1 E0 ^; \
1
( ~! x5 \! q$ K. O% H soup.p["class"]
7 d6 W; r3 d6 N) O 1 1 M) `4 O$ K! _/ |, r) K1 ?3 `
['title']
) y* i; I' b: m! E( J& U0 T" A 1 ) G- M5 Z5 b0 d! ~4 S3 b- w
soup.a
! M3 K$ O, S) u& M 1 : x" m+ C4 `1 d$ O8 Q- g9 D
<a class="sister" id="link1">Elsie</a> 9 K) K# E" ^( x# e
1
: {- ~2 [( Y+ j+ \/ x+ b" X; d soup.find("a") # S6 [" V+ d* J6 G' |5 e- ?/ H
1
' ?/ F2 x4 p, S! \0 ` <a class="sister" id="link1">Elsie</a> # _2 Z. ~: d4 Q6 y
1
4 g" Q L. Z& ` soup.find_all("a")
2 T- h6 ~7 {5 q. o 1
) {* p' F/ r9 t) @ [<a class="sister" id="link1">Elsie</a>,
! n% u! D* J' B7 S6 N <a class="sister" id="link2">Lacie</a>,
+ f8 Y5 {" U) W, ]! C <a class="sister" id="link3">Tillie</a>] , J* O) ?) r) V* y: }
1
) H) ]+ X ]+ L* H8 Y. q a 2
: @0 r# R. o2 i" L _0 y& A" u: j4 W 3
% O9 P+ j0 C' y" q+ g% N" b) v1 a 从文档中找到所有的< a>标签的链接 1 c" a) C' F8 y0 t
for link in soup.find_all("a"): / j. U$ V1 S% C
print(link.get("href")) % F# P2 v7 Y$ |% V5 o2 Y! N
1
5 z' [2 n+ N( Y7 B 2
) N d$ b8 \* a- p0 i" l8 y1 o http://example.com/elsie : w3 [. h1 ?0 W8 E% G& N( ]& ^" Y
http://example.com/lacie
4 |# W8 v" \' M http://example.com/tillie
. @" m. S; o: Y' ` 1
- ^1 g8 |6 ]8 E 2 * K% A0 K; V# }+ l1 w, Y/ d+ M% U
3
. K( n: K# K- b1 p 在文档中获取所有的文字内容
. B) ]9 Q+ E0 B) g3 F6 S$ @ print(soup.get_text())
& I" S- S$ W1 i4 Q7 X9 w4 K) S 1
o, U+ i# j# N# i* |; G6 z; I: R The Dormouse's story 8 f7 w# \+ W% S4 a+ D& T
- Q3 s- O* N/ N( t
% v4 @$ c$ k9 Z" o* I0 T. U7 R6 i% P The Dormouse's story 6 r! g; ~& U9 J/ j4 e4 Z
Once upon a time there were three little sisters; and their names were
# }) g: N, m* L* a Elsie, v7 ?/ R2 ]5 x, } g# `, c6 {/ X
Lacie and ' U. T9 s P! O: h$ Q7 n7 ~
Tillie; ( Y2 `4 l0 X& q7 v D5 [
and they lived at the bottom of a well.
+ j' _1 s, D2 B+ b" X3 N* S" U ... # u: x8 j! R( N! R
1 - F( b/ n2 W; [. f0 z3 z
2 9 ^9 }: a# I% E6 G% J2 }
3
7 `* j2 v& n6 a9 z* a& ^ 4 ! U; L0 u4 w6 R; N# I, k
5 5 Q1 G3 ]$ L2 n3 |
6
, z7 N+ a/ q0 i* ^* U0 g% ^ 7 & q! T X8 b. z( M' ~
8
. Z0 p' V) b4 i0 T/ e 9
5 t# ~8 q1 a) a6 o7 w9 K ( \/ I: n: D% c' U, f9 C
% y) u! l2 |6 N. D) W. `) ~- U 1 e8 H. p+ s) y3 O( \; @# t1 Z
通过标签和属性获取 8 A2 n5 C, i: m
Tag有很多方法和属性,在 遍历文档树 和 搜索文档树 中有详细解释.现在介绍一下tag中最重要的属性: name和attributes : z* X. w5 \' d2 `
soup = BeautifulSoup('<b class="boldest">Extremely bold</b>') , _$ t# u* I4 }8 ~0 x3 g
tag = soup.b
4 @0 t9 N& Q0 v( H1 a( \ tag
# T0 o! e9 |) m, ~ 1 9 T2 P' p( ~% J* o6 m# L K
2 5 S: I& Z! P6 }" z# m/ N
3
' @1 |: r4 U6 V( E$ V& e <b class="boldest">Extremely bold</b> 0 G& u8 w4 @5 r& S; e* r; o0 `
1
9 G, X9 W9 a0 i; ]6 L type(tag)
' Y* U# F# U" v9 v1 D% m, q 1
: o- m+ F9 z7 c3 y" @2 C; U bs4.element.Tag 3 V, D5 i" R0 U j5 _
1 7 S6 k7 h3 |; w( K
Name属性
! J# r) f# w8 ^, R% C8 q 每个tag都有自己的名字,通过 .name 来获取:
9 f4 c- t) [7 i7 F. Y tag.name
) k9 T2 E' c) D& s 1 2 I# ]( j/ H) A( `5 ~
'b'
$ m" m' m4 V: ~- @# ? 1
3 f+ H) F, O7 d- {2 E: s 如果改变了tag的name,那将影响所有通过当前Beautiful Soup对象生成的HTML文档 ( U5 T4 h ~! i! l( v/ {
tag.name = "blockquote"
1 I# z- l. n+ r3 E tag
e/ y7 r$ G* o. q" ? 1
8 ~& a* X+ C: a! b/ M: o/ [ 2 , x [) K( ^+ Q- c! A7 B( |, u* Z
<blockquote class="boldest">Extremely bold</blockquote>
# O9 O4 |8 O: J/ T 1
0 Q2 j3 L" z' e3 Y D 多个属性 * C# a9 G3 f- ~6 G, z
一个tag可能有很多个属性.tag 有一个 “class” 的属性,值为 “boldest” . tag的属性的操作方法与字典相同: % K* D0 R8 n! G9 A( R- q" T: c
tag["class"]
4 V4 Y1 O9 V8 y 1
0 g' F+ \% J* a, h! u ['boldest']
( Z: Z1 _4 a' h 1 * y; U& a/ P: I6 s/ J/ o& ]
tag.attrs & V1 D3 V1 @8 @
1
# B* i/ b/ O+ A {'class': ['boldest']}
, j; g! U! E& D' q3 S8 v 1 ! W( h9 _! [+ O9 @9 ~$ e/ G: A! C2 y
tag的属性可以被添加,删除或修改. 再说一次, tag的属性操作方法与字典一样
. U8 k' J; }9 `# j tag["class"] = "verybold" - I+ l: W; }, q, @) N" ?- ^
tag["id"] = 1
) ~' X- e. @& p+ { tag
Y" Z5 [1 F8 p9 e/ I1 P! |4 u 1
2 h7 S9 ?( h6 D& y* T2 Q* T) w- }! \ I0 j 2
! d, C1 I! `$ q' P 3 ' N8 a- y0 B2 Q% ~
<blockquote class="verybold" id="1">Extremely bold</blockquote> ) r6 A% X5 `: a1 M) j" e, @
1 & |' C0 e1 y! A
del tag["class"] 3 E ^5 B8 I4 G4 X6 |- X+ `% z/ T! F
tag
: }7 o6 F# l# l; u: O 1 g: v# j3 K* k2 [
2 4 N/ Y' ]9 ?' z! L* Q: Z. E7 O
<blockquote id="1">Extremely bold</blockquote>
# u) M% s1 x, ~ 1 , f; L. u7 p, `3 l
多值属性 2 g7 P6 i, \: t
css_soup = BeautifulSoup('<p class="body strikeout"></p>') 4 }, Q, P* G' a! H; x7 k( j6 D; p
css_soup.p['class']
! Z5 P5 [* \, w: r3 K3 _0 x4 i- x 1
" ?' y, d: }/ i4 O 2
' _! M: D; p7 ]) k# r9 G) D ['body', 'strikeout']
. v4 A3 K- T% j 1
8 x. K( P9 \- k5 F2 f9 z$ W css_soup = BeautifulSoup('<p class="body"></p>')
' k8 I6 ^3 ]6 m$ U' z' ~ css_soup.p['class'] 1 D4 }2 G' s$ a
1 2 c5 x. G4 Q v. Z/ a9 b) t9 _
2
7 d6 e+ z4 m/ l) Y ['body'] * y* c5 x1 F! i: ]
1 ' ?# s8 R2 ]8 c$ F$ p6 J% @
可以遍历的字符串 7 W- r: C& Q. L' t
字符串常被包含在tag内.Beautiful Soup用 NavigableString 类来包装tag中的字符串: ! q( l( _; J9 Q$ L( C, Q: i. E2 O
tag.string " o& C! n/ X/ z6 z' J) X" _( N, |
1 % L4 Z) j" v2 j! k8 D6 I; [
'Extremely bold'
! G. Y, c2 U0 t" e 1
& s' b) B! l* g& y type(tag.string) 5 g& b1 b a* x9 u ~
1 4 @& W# @* o j) k- l7 b
bs4.element.NavigableString
- I2 J9 T9 p* t 1 ; `9 `8 r5 X: @0 v
一个 NavigableString 字符串与Python中的Unicode字符串相同,
5 A- W7 f/ T Z% E 并且还支持包含在遍历文档树 和 搜索文档树 中的一些特性.
' r4 ~" M: |2 s 通过 unicode() 方法可以直接将 NavigableString 对象转换成Unicode字符串: ' k3 f/ r. j0 L) V
+ n, O$ l- F1 }( i) k
* k+ z- W- y4 `2 K tag中包含的字符串不能编辑,但是可以被替换成其他的字符串,用replace_with()方法
3 \1 w* D1 [3 J0 \, N$ s T0 N
. @: V3 D- V3 l* k0 ~( s 5 i$ h' O0 Z, J6 F
tag.string.replace_with("No longer bold") & @3 S% B' T; i) }5 R6 K
tag
5 Z8 T- [: U: T @ 1 " ^/ r/ p( Y# k4 g- s2 `5 K8 x
2
* P' [$ r7 t G* R4 z. d <blockquote id="1">No longer bold</blockquote>
6 v& F3 u5 S: B1 H5 v: m 1 8 v' n) T4 \0 G6 O( V
注释及特殊字符串 9 h7 @$ b3 m8 f& q4 A {) u
文档的注释部分 3 H$ L2 K( j) d: S% w+ m* g2 p
markup = "<b><!--Hey, buddy. Want to buy a used parser?--></b>" - S, s: N+ V% L# s- q' t1 b
soup = BeautifulSoup(markup)
* C" S6 ]) m! q5 L' O$ }! m comment = soup.b.string % v- |2 m' x& D
comment 0 C( Z- `$ V3 _3 T2 p& K0 C
1
2 y/ w/ d$ y- ?- u8 {3 { 2 0 M* W2 E1 G$ J1 u# L
3
. A$ g9 m8 |5 i8 m5 i: q$ T8 G. L 4
0 B6 V! r- i) U8 L5 p H7 X0 E 'Hey, buddy. Want to buy a used parser?' 4 k! Q3 a9 n! I% V4 v- c7 d
1
: s4 L+ W( }$ I& R1 p* { type(comment)
. X( l6 E4 T+ _; d! J9 u 1
# o+ E2 O& m: M9 ^5 f: J* O bs4.element.Comment
' Y& c( k* }3 |) y9 r 1 : ^* z6 g5 C9 H8 \* v% @2 J
Comment 对象是一个特殊类型的 NavigableString 对象: . q; Q) z) R7 H1 V0 }/ \0 o9 B4 n
comment
4 W! X3 r( G8 P; J$ k% f 1
% B( K% T& Y2 z H! W$ M7 I 'Hey, buddy. Want to buy a used parser?'
+ F! R M( [, ]' f2 Y 1
; y- j$ y1 h+ x& b) B, N i! ~ 但是当它出现在HTML文档中时, Comment 对象会使用特殊的格式输出: 8 I$ L6 D+ e2 {0 ^4 Y
# ]5 E; z& D5 q5 W: j, N
- Z0 \6 S5 H2 k( T print(soup.prettify()) . K2 _. i7 }7 N5 j. B. c4 H
1 4 S" l& v y# n! d9 s! w
<html>
$ |" o1 C; u/ ^# ]& G, U: c6 M <body>
( I: Q" P5 ?! {8 B <b> * T- Y7 O5 k: z. ^* `: O# ]
<!--Hey, buddy. Want to buy a used parser?-->
; e" ^4 k: |$ X5 v </b> 0 R) [5 O! F5 Q' K d7 q5 c9 |: S
</body>
) w: b* {8 h+ |) d8 w </html> 8 W+ z/ L# q A+ _4 X& E
1 / n; ^) X5 O s. U+ x7 E4 R8 L( B* |
2
; H. E1 S" [3 Z/ d 3 , y2 p0 a: x- ~: q
4
7 l r# K+ B' n2 s( x8 E 5
* E8 r2 s9 n6 d2 P# S; |" H% l6 t 6 ; L! v E1 D; s
7 % l+ L+ y! S) w: Y' R* b
from bs4 import CData
M# I3 t+ d0 f cdata = CData("A CDATA block")
: J" r9 V. g/ a( a3 V7 ] comment.replace_with(cdata)
% R# l: @0 B9 V- H V$ R7 p print(soup.b.prettify())
, E4 H' P) S; q3 K$ j. D 1
3 N3 E; e# y% k" a4 I2 W 2 - t7 Z5 f7 d2 Z: k c
3
8 P$ P1 i9 J% Z( f: t1 ]+ ] 4
$ B1 b$ N+ r o: I. f( ~: t <b> $ N2 q. `" s9 G
<![CDATA[A CDATA block]]>
. _) b& O" c5 P4 Y/ p- m* g </b> " R. E; L. a' Z3 `& ~$ ~
1 , [# W- Z8 G" y% }
2
4 W, O( k! y. S" }9 r; \4 { 3 * O* Z. i; x9 O8 Y% @' g: c- b" @ E
遍历文档树 / m: r8 V" I) M% Y* Y4 a
html_doc = """ . c4 |) i4 p r
<html><head><title>The Dormouse's story</title></head> ) [! A5 ^, t+ I: R. @
<body> # d) i- F& x- x( v6 ^: m/ H
<p class="title"><b>The Dormouse's story</b></p> ) T! ^* v; M+ @$ W( I o
8 C q* n2 _4 g. \+ _
3 i/ T: f' ]( F$ r9 j. P4 W' u <p class="story">Once upon a time there were three little sisters; and their names were
8 `, D B$ s) U <a class="sister" id="link1">Elsie</a>,
) c2 r6 G- V) x <a class="sister" id="link2">Lacie</a> and
; ^) D5 r3 x$ | <a class="sister" id="link3">Tillie</a>; 2 d0 l" C' K4 L. Y% u/ x% Q
and they lived at the bottom of a well.</p> G* p' ]- e6 r* n0 ^+ A
* _( P \0 @, J: G ! T& d' \4 {. `6 x" J
<p class="story">...</p>
$ ?6 H' D* |) g' B2 G+ @5 B6 T """ 7 h/ [/ I2 o0 n0 L/ s6 }
1 9 @4 h6 k% h* Y: d; {
2 - Z! p5 R- C. u9 ]
3
' L i' _$ L' l' w0 F 4
" ~ X, H0 M4 f+ c9 j5 e 5 , j- ^3 f" x% m* X
6 - H, Q7 d& f# \; f: P
7
0 a0 u2 s0 D: T3 F2 X t9 `/ F 8
# `* T+ O& p3 }% p" [0 L+ K 9
3 s, X$ k, g# M0 k4 M+ k7 W 10 ) n( j: `( t9 I
11
9 ~# g8 U n; v0 ] 12
+ I! Z1 E6 g2 A* l6 |$ I 13 + `( g) z( [3 O2 r( F
from bs4 import BeautifulSoup
( G0 G' k Y& y) M3 q: `( Y 1
" s$ V. N- S1 A& z! b& s) @8 h7 @ soup = BeautifulSoup(html_doc,"html.parser")
- M! v$ j" t1 R) e7 p 1 1 Y( ], T2 h" F
子节点
0 h& ^ e6 \2 d- b* l1 e( H 一个Tag可能包含多个字符串或其它的Tag,这些都是这个Tag的子节点.Beautiful Soup提供了许多操作和遍历子节点的属性.
3 }8 X* y3 E% H1 t3 {* q7 n4 i8 q( p2 s + f: e( y4 K& E' D, P
7 ^1 l9 P; Q% V+ t. ~ soup.head
. B- Y+ V& p: s( O1 Q K 1
$ T4 j, j# b$ l1 ~/ [ <head><title>The Dormouse's story</title></head>
4 e2 U1 f$ [* X9 D4 s 1
; B0 D$ Z, u% V- u7 L6 @ soup.title
4 s% _8 c7 I$ H9 K2 p4 {# W& } 1
* l9 [; P0 ~8 n$ k: ` <title>The Dormouse's story</title> * ]9 h! \) B4 d4 R3 @- o5 ]
1 " g$ t" y4 d0 v! d/ ?
这是个获取tag的小窍门,可以在文档树的tag中多次调用这个方法.下面的代码可以获取标签中的第一个标签:
2 e% P }5 c! H' s) F6 P; ?
# L' `0 P9 O0 k5 D/ @1 [' d. x ! j: J3 \9 V5 P- [- K
soup.body.b
5 P9 S' Q3 x2 A9 x 1 + c& M2 F0 L2 q' J8 V
<b>The Dormouse's story</b>
% |* |* ]6 }; j" Y5 d0 A: J 1
$ C5 h) K0 O3 _' u+ v# B 通过点取属性的方式只能获得当前名字的第一个tag: : ]2 _/ a7 ]$ k$ Z
$ y6 r( \( ]5 V4 ]% A* J2 Y
6 a1 B' Z @) [# [* `: @; t soup.a ) ?" |' w4 L. U+ R
1 ! J7 \- {) a2 a" D, v9 K
<a class="sister" id="link1">Elsie</a>
: `7 X) l# {; _5 b 1 / U: R8 j9 ]! p& P( J' `
find_all方法 7 w3 k2 o& [1 ?, n9 a
如果想要得到所有的标签,或是通过名字得到比一个tag更多的内容的时候,就需要用到 Searching the tree 中描述的方法,比如: find_all() + `6 `* b$ P1 {: x) ]' s" o
% c9 m* s& H5 v8 z9 t1 S b4 a 7 U& m4 S$ W8 i1 I% o# w* M( c
soup.find_all("a") ! h# s. t7 b$ F! }; ?9 L3 [- r
1 c$ ]9 i: \9 l! Z# C) ^. y
[<a class="sister" id="link1">Elsie</a>, + s N# S" B- w! v! e
<a class="sister" id="link2">Lacie</a>, * R$ F" Q& D+ T1 L% w2 C
<a class="sister" id="link3">Tillie</a>] # h4 Q+ w& O8 @# i
1 $ G' ~( F5 j. @
2 / f! l1 }2 y6 m) J! ^
3
~) e4 c n) ^' w7 w .contents和.children
9 G1 u3 Z- b: i; t head_tag = soup.head $ I! p) w1 p( H" y7 K
head_tag
% |' F9 Z" I2 Q, K 1 2 x1 z/ p0 w' t. ]5 l
2 2 c* f, `9 x8 ]+ D. e& k% ~
<head><title>The Dormouse's story</title></head> 1 P# X. ?' P8 _ F _
1 ( x3 h) L5 H2 X
head_tag.contents
0 r* P# T5 u- D x 1
9 {( r4 c' c: H3 n1 u1 K [<title>The Dormouse's story</title>]
/ {4 y: E* N7 Y7 Q7 a V 1 ' q3 a K4 {2 ]2 ~7 J
head_tag.contents[0]
$ P- c. i3 Z/ j+ n 1
/ d7 h( w& E2 K; D1 K# [ <title>The Dormouse's story</title> 8 [2 J- v! ?+ u/ d
1 2 H& k8 @' E1 d1 p) r
head_tag.contents[0].contents
7 z; c" d# y* ? ^. U5 X 1
4 h- c; b, `3 u7 G6 a5 K/ s ["The Dormouse's story"]
. h. H' n/ n3 _- w 1
, E. R* C; p! Z4 F8 H: ]6 y* T# Z selenium ( C0 O3 h" j" n, [( x; `
6 ?; x2 g; k1 ^% G4 t; [
3 H0 T5 }+ s) [. `6 d- c & I# E% U* g* q8 M8 h/ Y+ D
3 l" s# L) y" F' Z* m1 {9 e selenium官方文档 https://www.selenium.dev/selenium/docs/api/py/api.html
% r6 s4 g# M" u9 P0 _ [& B- u 7 R( O7 I3 b0 y, m# U" t: X% ~
; g& p, n2 \7 R% n selenium介绍
6 }9 _: V' J6 x chrome浏览器的运行效果 & C# n" t8 o* d) u$ U
在下载好chromedriver以及安装好selenium模块后,执行下列代码并观察运行的过程 , f) r3 B! E. d% x$ {
0 E! K' T6 R" @, `
/ q2 R7 h0 F! w9 g
from selenium import webdriver _# p/ X) k/ ~# L6 `5 O* v6 b3 Q; a
! x" y0 Z, n0 ~( Y# N
& R+ k: R0 \. G( x
# 如果driver没有添加到了环境变量,则需要将driver的绝对路径赋值给executable_path参数
# k0 g, g* p4 {5 r# | # driver = webdriver.Chrome(executable_path='/home/worker/Desktop/driver/chromedriver') ) c5 f$ [9 D0 z/ o
' y) h# s/ |! n, M- r
( G3 G' g: \/ [7 l2 V5 P$ Q
# 如果driver添加了环境变量则不需要设置executable_path 5 X) o/ @ Q% q
driver = webdriver.Chrome()
& a: t s: U5 \; U2 K$ ? 1 K2 @$ a7 h& R
4 W/ }1 n! f6 ^ a# y' W: S
# 向一个url发起请求 : u: f: H* P H- k1 k7 F1 L& O
driver.get("http://www.itcast.cn/")
7 i U E* E3 L5 b( u$ ` & B6 x' S7 [ X$ ~6 q
: z! g9 t# T$ e6 q$ O- t # 把网页保存为图片,69版本以上的谷歌浏览器将无法使用截图功能 + f/ b$ G4 Z/ E
# driver.save_screenshot("itcast.png") 7 {* m' K$ @9 `4 _+ {
0 X# I7 ` X6 _9 e$ k% ~2 a
3 N N8 ]3 k% p! }$ @
print(driver.title) # 打印页面的标题 ) B! S" k7 o, X1 Y. u4 d$ ~& \
: @% ]2 r# h% I/ T: z
6 Z5 b/ M5 A$ D8 O # 退出模拟浏览器 9 r* O9 @) e" c6 X4 ~3 i0 D
driver.quit() # 一定要退出!不退出会有残留进程!
/ ^. c5 G1 X/ p" {4 N# E& m/ ~ 1
! U- w' C& m8 b9 H- Y 2
) I" ^! G8 A. D' K7 C7 h 3 , @& e/ J3 I! x
4
% e5 h% V' Z6 G [ 5 2 z/ u5 i) O( m8 G* x3 J/ t% U
6 # J, n0 y, F( i z* F0 i1 [( v! Z
7
! v! V3 c/ n; b& v7 w ?5 r7 C 8 8 G9 n" r: P2 I5 H- s
9 ! J7 N/ ^: ?! ~
10 6 ^) |2 w; H. x9 s8 o. L4 u
11 - h7 W/ T6 L. k# Y* [- c
12 ' b# B/ w* M6 h8 I) D
13 ( g% S4 m7 N7 H2 D& N/ U: `
14
4 Q. o( S" X4 @, Z 15
. G" j" `1 G; m5 T4 n 16
$ j5 U! ?- O& u3 i L! T 17 4 T) W( K$ u2 W C/ p. D
18
2 _% E4 F4 r6 { `0 J! L phantomjs无界面浏览器的运行效果
' L0 m/ h. G, n5 M PhantomJS 是一个基于Webkit的“无界面”(headless)浏览器,它会把网站加载到内存并执行页面上的 JavaScript。下载地址:http://phantomjs.org/download.html
, i3 o' k1 Q9 x, K" i 2 h5 i6 d& D& {7 B& ^
; {/ l+ Z7 n7 k" y+ o a) H2 G. h7 \ from selenium import webdriver
, N- R+ A% O7 L0 o $ i" O# W5 M7 E
3 e$ k/ N, T; S3 D5 h& J # 指定driver的绝对路径
& A" L$ o6 H+ N3 Z: g1 @, O driver = webdriver.PhantomJS(executable_path='/home/worker/Desktop/driver/phantomjs')
- \, z5 u- f: w3 T2 ^+ U8 l5 ~ # driver = webdriver.Chrome(executable_path='/home/worker/Desktop/driver/chromedriver') 4 U. f: [1 R' ?+ r! }5 j( ^0 a
: t9 s) a" y4 a( L, G0 h+ o 9 ~* h0 S7 K2 K( J& J3 \6 Q
# 向一个url发起请求
3 H j/ U6 }( C1 n4 Z5 j0 x driver.get("http://www.itcast.cn/")
6 D+ _7 u2 Q% k1 H
+ R0 _ o2 b. z9 ] Q- {( ~3 O6 C. L
# 把网页保存为图片
0 p6 z( E. x2 z5 ?1 L. u7 f driver.save_screenshot("itcast.png")
2 ]+ E" \/ e- e$ t9 n" o
, X7 T3 X6 K7 R, }7 [$ c" G * C0 _- `: C3 C# S% M: ]/ m+ U
# 退出模拟浏览器
) Y; B4 V2 e: [- r# ^; l driver.quit() # 一定要退出!不退出会有残留进程! + Y) B% @: x R& u8 z+ }
1 6 y( I2 m( W# c
2 , L6 m, c" X6 i4 s) w
3 0 Z; d9 j \' z# d: I
4 ; k! M0 p* |, Y' U
5
+ J) X) Q/ i/ E6 Z$ m5 w 6 $ I" _2 ]6 P. B8 _ _; D: J5 O
7 / C- `9 z. L& Y L
8
4 p4 I |1 l% N 9
0 \. o( V, ~/ | 10
! {% V' ]8 O0 }7 x% @( U 11
+ d; l$ `' J" X$ P+ x 12 5 H+ i3 G" P& N7 Z/ A7 f
13
% g* p: G* s' @ 14 2 r& g0 P# q! |4 m Q
无头浏览器与有头浏览器的使用场景 + C5 I1 w3 I3 R% W% M m9 \
, l8 N" P/ N; L+ I6 G- r; i' T& o
( a) X" `& @* S- d" F% A: _ 通常在开发过程中我们需要查看运行过程中的各种情况所以通常使用有头浏览器 ; a% o, e& Z: B4 W
在项目完成进行部署的时候,通常平台采用的系统都是服务器版的操作系统,服务器版的操作系统必须使用无头浏览器才能正常运行 / f* n9 u4 H, `! t
selenium的作用和工作原理
8 M2 i: \7 Q/ h" s. Z 利用浏览器原生的API,封装成一套更加面向对象的Selenium WebDriver API,直接操作浏览器页面里的元素,甚至操作浏览器本身(截屏,窗口大小,启动,关闭,安装插件,配置证书之类的) / ?$ S& M4 H- N2 A3 E( X) G
& p0 T, Q2 A1 r3 A4 c) C9 i; K
$ r7 c6 ~' T4 v8 a1 M5 {
selenium的安装以及简单使用 ! w' |+ ?$ I3 b4 w3 p$ e
以edge浏览器为例 参见这个blog哦,驱动chrome浏览器同理 # ~- n" L9 p d( v3 C
selenium驱动edge浏览器 7 ~: m2 M2 `+ o: _" v
& x! f* K+ Y% P, d, T) l% X3 i
# t7 c4 ?2 n8 a( i; G chromedriver环境的配置 - a. |. J- v' ?/ K; Q
windows环境下需要将 chromedriver.exe 所在的目录设置为path环境变量中的路径
; v( ^$ e! k2 q r linux/mac环境下,将 chromedriver 所在的目录设置到系统的PATH环境值中 - a! }8 [8 r" ~7 K; {; q& W! V
selenium的简单使用
8 p& U, `/ i- O. l' M ?0 U. E# H' M 接下来我们就通过代码来模拟百度搜索 3 [: P+ k( W) g1 X1 y. E7 z' u3 W
( ?: C- e1 X9 l( A: ^4 @
, N2 A, q6 i) I3 E& v% s) F- @- f import time
1 `% K$ ~/ X7 T3 B& p0 y/ y from selenium import webdriver
; B0 [6 U- ?1 e. x 2 J! l0 s( o- R( D
( w* t% b% g) q; d3 Y # 通过指定chromedriver的路径来实例化driver对象,chromedriver放在当前目录。
4 f( T/ Z0 `" X1 k: {7 B' |* W # driver = webdriver.Chrome(executable_path='./chromedriver')
2 ?9 O+ C/ K: k9 [# ` # chromedriver已经添加环境变量
8 c6 f/ M: y- F driver = webdriver.Chrome() 0 \+ _# F/ Q! r; O9 E$ q
# K5 v j. W5 s7 Y- @
2 _; \- K- t2 n; Z# J T; q # 控制浏览器访问url地址 # W- M: B& {1 _8 {* O. H
driver.get("https://www.baidu.com/")
9 Y% B5 R1 d9 l" F+ s
; }! E9 j0 Q9 [ ) v X1 c' Z' m2 U5 Q* D
# 在百度搜索框中搜索'python'
7 q1 z% e# o- D driver.find_element_by_id('kw').send_keys('python')
" j6 {* M( S* d: d; ^ # 点击'百度搜索' ; k% @* w v+ C( W' L; C
driver.find_element_by_id('su').click()
1 c' t w& }8 C / {: k: M5 s2 {3 P) V
- t, z0 f3 p4 t
time.sleep(6)
( r% z6 \3 D R/ j2 W" y4 F # 退出浏览器
. o- \9 w( ]/ i2 x driver.quit() 9 q4 Z ~/ s' z% E5 s6 {& T- \9 r
1
' d+ Y: T8 `- q9 T 2 ' a; J+ Z2 r: Z, o! q; h
3
& \1 ]" ?) l+ ^' ~* _$ X$ q 4
$ g! l* s0 w9 P8 ~% | 5 $ `3 |7 ]1 e% Y4 `* H
6 & i/ O6 J0 d+ h5 r5 x
7 * d: O9 P; w- t i
8 % I; ]! Z/ A2 M) k/ M( e. A
9 ) i2 m/ @. y5 w3 i: F* `, N( {* \: L
10
4 ~, z- C; G/ g8 R* q 11
; F; Q x& g$ J5 Q- v 12
+ s3 e& G: {7 S: x! |- ~ 13
; D* v* t3 B0 } 14
) N8 j$ W: n/ K$ V( d 15
+ \6 Y0 E- ]5 G% h 16 3 V7 q: H6 C' w( T
17
$ j, g( F U% U 18 $ O! z4 e8 ~: f/ C8 k2 G7 U( D% i
19 : S, B+ M+ u0 j% A! _
webdriver.Chrome(executable_path='./chromedriver')中executable参数指定的是下载好的chromedriver文件的路径 . `1 |+ F2 d& b/ v2 [
driver.find_element_by_id('kw').send_keys('python')定位id属性值是’kw’的标签,并向其中输入字符串’python’ # C. j. ~0 w8 X! i! \% _4 ^
driver.find_element_by_id('su').click()定位id属性值是su的标签,并点击
9 {" `4 ^& l7 Y. M8 A3 P click函数作用是:触发标签的js的click事件
6 ?- D* M6 A5 Y/ D( z 值是’kw’的标签,并向其中输入字符串’python’ % \* \% T5 O6 T2 y/ F" N" D# @% N, S6 ~
1 [6 S% m4 F9 q9 \/ N+ Y* U
( u2 L7 l$ B9 z4 O/ D driver.find_element_by_id('su').click()定位id属性值是su的标签,并点击 4 j9 i/ Z! t+ X9 \1 D: ~
click函数作用是:触发标签的js的click事件
( @5 `6 j( x+ } 使用xpath来提取数据,爬取数据的简单语法。 # d: O( L* I y r' z; a
. w+ @8 J' U+ H
6 r8 E, I9 o4 ^, x+ M
lxml $ p! r# m [/ {' j
( ~& `( b5 `" F0 O5 w! x' M
3 \: R$ P ~1 `# V& M
, g; S y) T$ ^$ M 1 G2 v- p* l- b
requests官方文档 https://lxml.de/ 1 N' ]: D' T, |
7 Z& K3 ?! {* t% Z
0 N8 j8 m& ]# C$ s, A& _# x pip install lxml ! ?) \2 \6 `: H% z9 K8 i
1
! H3 v3 k) ? a- i0 K 导入模块 J# ~. T- b) ?) h
from lxml import etree
) ~7 Z4 ?/ |; z5 [" Y* @) [2 ^ 1
1 F" m8 m" N4 X0 l5 z7 ` 利用xpath获取text或者href内容
8 v J; N* c6 P /li/a/@href 这样取的应该是href的内容 ' d" L9 _1 @$ [: `
/li/a/text() 这样取得是text内容 6 [5 Z5 l' p: E- M0 a
1 : ]3 w+ G' ?4 n4 l' W5 ?
2
# U3 |! z6 L1 k% f etree的使用 , u2 a" z$ \4 d" p, i
h=etree.HTML(response.text)#response.text是网页的源码 " W' M* R) I( | D5 i; ~( e
h.xpath('//img') #寻找所有的img结点, ) B) H& v& k4 n9 |0 o3 i
h.xpath('//div').xpath('.//img')#寻找所有div下的所有img结点
3 U% l* w- P1 l3 P+ F9 W& y. B 1 ; E# c8 [3 M6 T7 A& C0 x
2 4 W. }. j3 \. R$ ~
3 9 b9 h. q8 C: P% G# v5 P) g$ L+ ]
xpath的语法 1 {; |/ N* u' i9 L
符号
1 m9 [. e% E8 ` g. t% U7 h2 p XPath 使用路径表达式在 XML 文档中选取节点。节点是通过沿着路径或者 step 来选取的。 6 l+ S# ~+ S2 v( D5 ~! K
! }2 S) q& I5 u9 P, D/ y
) H: C' ^2 A9 o, H$ G( [
表达式 描述
; s; F* D1 Z0 K P4 [! i3 J6 D / 从根节点选取
[% d# L/ r) ^$ b5 w4 s3 ?* a // 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。
) d% V# n! R+ K7 }9 f( M+ T . 选取当前节点。
9 Q1 U/ Q6 f6 S8 ^) H% S . . 选取当前节点的父节点。
; u# ^5 i/ I# T+ j0 { @ 选取属性。 ! f1 s% }& i' e) c7 [, @
| 在两个中结点中选择 ) ^# W/ a J, u8 I4 X* P4 e
() 用()来包含|
- j0 n& ], b& R @0 u3 E0 w7 `* G * 包含所有元素 ! X( C. W$ X9 v* N) K
not 取反
" P0 H9 R& ^9 ?3 W! V& f 实例
9 Z E7 ?6 M, V Y: o- c 9 v% _' ]0 J b# N
/ y. ^8 B! e M D/ C 路径表达式 结果
% n+ C0 Z: g/ V# q; }$ U. J bookstore 选取 bookstore 元素的所有子节点。 4 k# b. G% B$ P3 I, d- |4 i
/bookstore 选取根元素 bookstore。注释:假如路径起始于正斜杠( / ),则此路径始终代表到某元素的绝对路径!
) o8 q- [0 x/ P9 o5 G bookstore/book 选取属于 bookstore 的子元素的所有 book 元素。
( ?6 n& x' L3 q( _ //book 选取所有 book 子元素,而不管它们在文档中的位置。 ! r% a/ d$ k; J& s" Y" e2 F
bookstore//book 选择属于 bookstore 元素的后代的所有 book 元素,而不管它们位于 bookstore 之下的什么位置。 # X( ]% d, z# B$ N" m6 o
//@lang 选取名为 lang 的所有属性。
+ R% |6 Q9 o9 ^ //*[@class] 选取带有class属性的所有元素 * W9 i/ y( w: z J# w
//div[@*] 匹配任意属性的div元素 7 y. M0 D3 B8 `+ N
//a[not(@class)] 匹配没有class属性的a元素
& p, c* B a% |7 X7 n" _# v 谓语
6 ? `, q: { d. c0 k( w 带谓语的路径表达式
8 w* ]! n) R# g+ g' A) [
4 [! a. W+ S8 s% C# A$ s, v2 q) c* ^4 h + s( ?* a, F3 s! R! p' m1 [
路径表达式 结果
8 ?7 H7 G+ [) l; h" V /bookstore/book[1] 选取属于 bookstore 子元素的第一个 book 元素。
- R2 L' T3 m1 y2 {7 ^: m" v /bookstore/book[last()] 选取属于 bookstore 子元素的最后一个 book 元素。 {; P; \9 B4 ^- w: a
/bookstore/book[last()-1] 选取属于 bookstore 子元素的倒数第二个 book 元素。
5 b7 B1 c3 V) U- {/ m- z2 O /bookstore/book[position()< 3] 选取最前面的两个属于 bookstore 元素的子元素的 book 元素。 8 ]3 q6 x4 p, q
//title[@lang] 选取所有拥有名为 lang 的属性的 title 元素。
) c- a/ T5 D, i# ]- F //title[@lang=‘eng’] 选取所有 title 元素,且这些元素拥有值为 eng 的 lang 属性。 . U9 N7 c$ `+ n0 [9 v2 V7 p
/bookstore/book[price>35.00] 选取 bookstore 元素的所有 book 元素,且其中的 price 元素的值须大于 35.00。 ; p j" V. _# B& k5 h6 O9 ^. Q6 K
/bookstore/book[price>35.00]/title 选取 bookstore 元素中的 book 元素的所有 title 元素,且其中的 price 元素的值须大于 35.00。 / D4 H$ W' ^% {1 k7 v5 \
———————————————— 2 b, ^# V4 R8 e
版权声明:本文为CSDN博主「北山啦」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 ( P+ ~. O& q/ G( c5 [( S: X: F
原文链接:https://blog.csdn.net/qq_45176548/article/details/118187068
9 S. o" L* `: G5 Q) T 1 k) \7 M5 Z) \! U) A! T' ?
0 }8 }; M& o% D" t. _
zan