使用BeautifulSoup爬取想要的标签
- 精确爬取标签
- BeautifulSoup中的find()和find_all()方法
- BeautifulSoup中的对象
- 兄弟、子、父、后代标签的处理
- 抓取子标签和其他后代标签
- 抓取兄弟标签
- 抓取父标签
- 正则表达式
- 正则表达式和BeautifulSoup
- 获取属性
- Lambda表达式(匿名函数)
精确爬取标签
我们可以使用标签的CSS属性爬取择我们想要的一个或者多个标签,如class(类)属性、id属性、src属性等。
为了方便演示标签的选择,我们使用书中作者特别准备好的爬虫演示网站为例(http://www.pythonscraping.com/pages/warandpeace.html )。在这个网站中,人物的对话是红色的,对应的类属性为red,人物名的标签为绿色的,对应的类属性为green,并且对应的标签都是span标签,比如:
<span class="green">the prince</span>
<span class="red">First of all, dear friend, tell me how you are. Set your friend's
mind at rest,</span>
那么我们就可以通过类属性来轻松的抓取出现的任务的名字:
from bs4 import BeautifulSoup
from urllib.request import urlopen
html = urlopen("http://www.pythonscraping.com/pages/warandpeace.html")
bs = BeautifulSoup(html,"html.parser")
nameList = bs.find_all("span",{"class":"green"})
for name in nameList:
print(name.get_text())
#get_text()表示只获取标签中的文本,如果不使用该方法,
#则会打印完整的标签
BeautifulSoup中的find()和find_all()方法
find()方法和find_all()方法从字面意义上就可以看出区别,find用于抓取一个符合条件的标签,find_all()则用于抓取若干(默认为全部)个符合条件的标签,他们的定义如下:
find_all(tag,attributes,recursive,text,limit,keywords)
find(tag,attributes,recursive,text,keywords)
除了参数limit,两个方法的参数含义相同,所以之后都以find_all为例子进行说明:
tag:需要查找的标签名称(或者说类型),当只查找一种标签时以字符串类型传入,当查找多种标签的时候则以列表的形式传入,比如:
.find_all(["h1","h2","h3","h4","h5","h6"])
#查找所有标题标签
attributes:用字典封装,键对应需要查找的标签的属性,值对应于属性的值,比如:
.find_all("span",{"class":"green"})
#对应查找类属性的值为green的span标签
.find_all("span",{"class":{"green","red"}})
#对应查找类属性的值为green和red的span标签,注意对比二者的区别
recursive:传入一个bool变量,当传入True(默认)的时候,会抓取所有满足条件的标签,当为False的时候,只抓取一级标签,比如我们想要抓取p标签,对于下面这个p标签就不能被抓取到,因为它被包含在div标签中:
<div><p>123</p></div>
text:按标签中的文本来抓取内容,比如对于上面被包含在div中的p标签,我们就可以使用以下代码来进行抓取:
.find_all(text="123")
limit:最多抓取符合条件的标签的数目,从HTML代码最上方开始算起,当其值为1时,功能与find()方法等价
keyword:指定被抓取的标签的属性,比如:
.find_all(id="title",class_="text")
#寻找id为title。class为text的标签
#由于class为python中受保护的关键字,在指定类属性的
#时候需要写成class_
BeautifulSoup中的对象
BeautifulSoup对象:如之前展示的那样。
Tag对象:代表从HTML中抓取到的标签,通过find()或者find_all()方法得到,或者访问BeautifulSoup的属性得到。
NavigabelString对象:代表标签中的文字。
comment对象:用于表示HTML中的注释。
兄弟、子、父、后代标签的处理
这里我们以作者提供的虚拟购物网站为例进行说明:(http://www.pythonscraping.com/pages/page3.html )
抓取子标签和其他后代标签
在BeautifulSoup中,子标签表示父标签中包含的下一级标签,而后代标签则表示标签中包含的下一级标签、下下一级标签、下下下一级标签等等,也就是说一个标签的子标签是它的后代标签,而一个标签的后代标签不一定是它的子标签。如果我们需要查看一个标签对象的子标签,则可以使用其children属性来获得,比如:
from bs4 import BeautifulSoup
from urllib.request import urlopen
html = urlopen("http://www.pythonscraping.com/pages/page3.html")
bs = BeautifulSoup(html,"html.parser")
for children in bs.find("table",{"id":"giftList"}).children:
print(children)
从上面代码我们可以看到,首先我们使用了find方法,抓取到类型为table,id为giftList的标签,之后调用其children属性获取其子标签,由于其有多个子标签,所以我们使用for循环来对其子标签进行遍历。
另外我们还可以使用descendant属性来找出所有后代标签。
抓取兄弟标签
兄弟标签即同一级的标签,在BeautifulSoup中可以使用tag对象的next_siblings来抓取改标签之后的所有兄弟标签,也可以使用previous_siblings来抓取改标签之前的所有兄弟标签。同样的,还有next_sibling和previous_sibling,用来抓取后一个或者前一个兄弟标签。下面是一个例子:
from bs4 import BeautifulSoup
from urllib.request import urlopen
html = urlopen("http://www.pythonscraping.com/pages/page3.html")
bs = BeautifulSoup(html,"html.parser")
for sibling in bs.find("table",{"id":"giftList"}).tr.next_siblings:
print(sibling)
#抓取div中第一个tr标签之后的所有同级标签并遍历
抓取父标签
查找抓取父标签的功能并不适用,可以使用parent和parents来抓取当前标签的父标签,下面是一个例子:
from bs4 import BeautifulSoup
from urllib.request import urlopen
html = urlopen("http://www.pythonscraping.com/pages/page3.html")
bs = BeautifulSoup(html,"html.parser")
print(bs.find("img",
{"src":"../img/gifts/img1.jpg"}).
parent.previous_sibling.get_text())
最后一行代表先找到符合条件的img标签,之后选择img标签的父标签,之后在选择该父标签的之前的一个兄弟标签,获取其文本内容。
正则表达式
正则表达式在提取字符串时非常有用,它可以理解为一种字符串匹配规则,比如对于满足以下所有条件的字符串:
- 字母“a”至少出现一次
- 后面跟着“b”,重复五次
- 后面再跟着“c”,重复任意偶数次(包括零次)
- 最后一位是“d”或者“e”
满足以上规则的字符串有很多,比如“aaaabbbbbccccd”。使用正则表达式则可以表示以上的四个条件,可以表示为:
a+bbbbb(cc)*(d|e)
改正则表达式可以解释为:
- a+:+号代表前面的字符至少出现一次,如“a”或者“aaaa”
- bbbbb:5个b
- (cc)*:星号代表前面的字符至少出现0次或以上,由于加了括号,所以是两个c,所以代表c需要出现偶数次,如“cc”或者“cccc”
- (d|e):必须是d或者e,可以与其他符号如“+”号进行组合,表示可以出现一次以上的“d”或者“e”
下面是常用的正则表达式符号:
符号 | 含义 | 例子 | 可能的匹配结果 |
* | 匹配前面的字符、表达式或者括号里的字符0次或多次 | ab | aaabbb,aaa |
+ | 匹配前面的字符、表达式或者括号里的字符至少1次 | a+b+ | ab,aabb |
[] | 匹配中括号里的任意字符(相当于选一个) | [A-Z]* | APPLE,QWERTY |
() | 表达式编组(里面的表达式优先执行) | (ab) | aabaab,abaab |
{m,n} | 匹配前面的字符、表达式或者括号里的字符m次到n次 | a{2,3}b{2,3} | aabbb,aaabbb |
[^] | 匹配任意一个不再括号中的字符 | [^A-Z]* | abc1234,qwert |
| | 匹配任意一个由竖线分隔的字符、子表达式 | b(a|i|e)d | bid,bed,bad |
. | 匹配任意单个字符 | b.d | bad,bzd |
^ | 表示以前面的字符或表达式开头 | ^a | asdf,a |
\ | 转义字符(把具有特殊含义的字符转换为字面形式) | \. \| \ \ | .|\ |
$ | 表示从字符串的末端匹配 | [A-Z] *[a-z] *$ | ABCabc,zzzyx |
?! | 不包含 | ^((?![A-Z]).)$ | no-caps-here,a4e f!ne |
\d | 表示匹配任意一个数字 | \d+ | 1,123 |
\D | 表示匹配任意一个非数字 | \D+ | ab!c,AbS |
\s | 匹配空白字符(包括\t,\n,\r和空格) | \s | |
\w | 匹配0-9,A-Z,a-z和下划线 | \w+ | abvAWE123_ |
\w | \w的补集 | \W+ | ++++===- |
正则表达式和BeautifulSoup
使用正则表达式可以让我们在抓取网页中想要的标签时更加的方便,比如还是以之前的虚拟购物网站为例子,我们可以使用正则表达式来实现批量抓取文件路径类似的图片:
from bs4 import BeautifulSoup
from urllib.request import urlopen
import re
html = urlopen("http://www.pythonscraping.com/pages/page3.html")
bs = BeautifulSoup(html,"html.parser")
images = bs.find_all("img",
{"src":re.compile("\.\.\/img\/gifts\/img[0-9]*\.jpg")})
for image in images:
print(image["src"])
# ../img/gifts/img1.jpg
# ../img/gifts/img2.jpg
# ../img/gifts/img3.jpg
# ../img/gifts/img4.jpg
# ../img/gifts/img6.jpg
其中re模块中的compile函数用于将字符串转化为正则表达式。
获取属性
我们可以使用一个Tag对象的attrs属性来查看该标签的所有CSS属性,以我们之前使用爬虫抓取的图片标签为例:
for image in images:
print(image.attrs)
# {'src': '../img/gifts/img1.jpg'}
# {'src': '../img/gifts/img2.jpg'}
# {'src': '../img/gifts/img3.jpg'}
# {'src': '../img/gifts/img4.jpg'}
# {'src': '../img/gifts/img6.jpg'}
Lambda表达式(匿名函数)
Lambda表达式本质上是一个函数(称为匿名函数),对于find和find_all方法,允许我们将一个函数作为参数传入,该函数必须满足以下条件:
- 该函数传入的参数必须是标签对象
- 返回的值必须是bool量
之后find或者find_all函数便会将html文本中的每一个标签作为参数传入该函数,倘若返回的为True,则认为符合条件,将其以Tag对象返回,比如我们想要抓取具有两个CSS属性的标签,可以这么写:
bs.find_all(lambda tag:len(tag.attrs) == 2)