爬虫教程---第三章:信息提取之BeautifulSoup

3.2 BeautifulSoup

lxml一样,BeautifulSoup也是一个HTML/XML的解析器,跟XPath的功能是一样的。

区别在哪里呢?lxml只会局部遍历,如果你想提取a 标签的内容,那么我们需要写出相关的xpath语法,此时lxml 只会对全部的a 标签进行遍历,找出满足条件的a 标签。

BeautifulSoup会载入整个文档,将文档内容组织成类似于DOM树的结构,然后我们可以根据它提供的API来作出相应的操作,此时BeautifulSoup会遍历整个树,找出满足条件的标签。因此它的开销比较大,速度慢。

使用BeautifulSoup 前需要先安装:pip install bs4

那现在来看一下几大解析工具对比:

解析工具解析速度使用难度
BeautifulSoup最慢最简单
lxml简单
正则最快最难

简单使用

# 第一步:从bs4中导入BeautifulSoup
from bs4 import BeautifulSoup
import requests

r = requests.get("http://python123.io/ws/demo.html")
demo = r.text

# 将内容组织成DOM树
soup = BeautifulSoup(demo, 'html.parser')  # 第二个参数是指定一个解析器
print(soup.text)  # 输出纯文本
print(soup.prettify())  # 用比较美化得方式打印出来

soup = beatifulsoup(text, pattern)

  • 参数text 为要提取的内容

  • 参数pattern为解析器(可选)

    • html.parser(默认)
    • html5lib(容错性最强)
    • lxml (平时使用最多)
    • xml

这里值得一提的是,bs4库将任何读入的html文件或字符串都转换为utf-8编码。


提升案例

from bs4 import BeautifulSoup
import requests

rp = requests.get("http://www.baidu.com")
text = rp.text
soup = BeautifulSoup(text, "lxml")

# 获取30个div标签
trs = soup.find_all('div', limit=30)  # 返回的是列表
for ts in trs:
    print(ts)
    print("-"*30)

# 获取第二个div标签
tr = soup.find_all('div',limit = 3)[1]
print(tr)

# 获取class等于head_wrapper的div标签
# trs = soup.find_all('div',class_='head_wrapper')  # 注意这里是class_ ,因为class在python中是关键字
# 上下这两种方式等价
trs = soup.find_all('div',attrs={'class': 'head_wrapper'})
for tr in trs:
    print(tr)

# 将所有class=lb,并且name=tj_login的a标签
trs = soup.find_all('a', attrs={'class': 'lb', 'name': 'tj_login'})
for tr in trs:
    print(tr)

# 所有a标签的href属性
alist = soup.find_all('a')
for a in alist:
    # 获取标签属性的方法
    href = a['href']
    print(href)

# 获取第一个tr下面的全部文本内容
# infos = tr.strings  # 包含空白符
infos = list(tr.stripped_strings)  # 去掉空白符
print(infos[0])

# 获取所有的职位信息(假设网址中有这个信息)
# trs  = soup.find_all('tr')[1:]  # 获取第一个以后的tr标签
# message = []  # 存放职位信息
# for tr in trs:
#     mess = {}  # 存放单条职位信息
#	  # 找到tr下面所有的td标签
#     tds = tr.find_all('td')
#
#     mess['title'] = tds[0].string  # 获取第一个td的内容
#     mess['category'] = tds[1].string  # 获取第二个td的内容
#     message.append(mess)
# print(message)

soup.find(tag) 查找 第一个 匹配的tag标签等价于soup.tag,对应的有soup.find_all(tag[,limit]) 查找 所有 tag标签,limit表示提取多少条。

string 获取某个标签下的直接内容。无法获取多行内容

strings (返回的是生成器,可以转换成list) 获取所有子孙非标签的字符串,有多行时获取不到。

stripped_strings 同上,但会去掉空白文本。

get_text() 获取所有子孙非标签的文本,返回的是字符串。


CSS选择器:select方式

此种方式通过CSS语法来进行提取特定标签。

import requests
from bs4 import BeautifulSoup

rp = requests.get("http://www.baidu.com")
text = rp.text
soup = BeautifulSoup(text, "lxml")

# 查找p标签  select功能跟find_all一样,就是不能同时筛选类名和id
print(soup.select('p'))

# 查找div下面的所有满足class=‘cp-feedback’的标签
print(soup.select('div .cp-feedback'))
# 查找div下面的所有满足id=‘cp’的标签
print(soup.select('div #cp'))
# 获取class=‘lg’的a标签 注意,中间没有空格
a = soup.select('a.lb')
# a = soup.select('a[class="lb"]') 等价于上面的写法
print(a)

# 查找name="tj_trnews"的a标签
print(soup.select('a[name="tj_trnews"]'))

# 查找div下的子p标签
print(soup.select('div > p'))

小结:

  • 中间有空格:表示在该标签下找满足筛选条件的标签
  • 中间没空格:表示查找满足筛选条件的该标签。
  • select功能跟find_all一样,返回的都是列表,但select无法同时筛选类名和id
  • > 表示在该标签的子元素中查找

四个常用对象

Beautiful Soup 将复杂的HTML文档转换成一个复杂的树形结构,每个节点都是python对象,这些对象可以分为4种:

  • Tag :就是一个个的HTML标签(拥有name、class等属性)。
  • NavigableString:继承自python本身的str类型。代表标签中的内容字符串。
  • BeautifulSoup:继承自Tag,本质上就是Tag。所以上面soup对象使用的find_all等方法实际上是Tag类中的方法,与此同时也说明了 为什么标签可以像soup一样直接使用find_all等方法 。它表示的是一个文档的全部内容。
  • Comment:特殊的NavigableString,表示注释的部分。

案例:

本次案例是用requests库跟bs4结合使用,然后爬取CSDN推荐文章列表的一些信息。

在这里插入图片描述
在这里插入图片描述

其他要爬取的内容的方式跟这两个方式差不多,这里就不一一截图出来了。。。

下面来看看代码吧。

import requests
from bs4 import BeautifulSoup

url = 'https://blog.csdn.net/nav/python'
# 伪装
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
                  'AppleWebKit/537.36 (KHTML, like Gecko) '
                  'Chrome/82.0.4077.0 Safari/537.36'
}

res = requests.get(url, headers=headers)
text = res.text
# 建立文档树
soup = BeautifulSoup(text, 'lxml')

# 提取所有文章的li
lis = soup.find_all('li', attrs={'class': 'clearfix', 'data-type': 'blog'})

# 遍历lis,单独对每个li进行提取
for li in lis:
    title_div = li.find('div', attrs={'class': 'title'})
    # 获取标题内容,因为stripped_strings返回的是迭代器所以要先转成list
    # "".join()的作用是把列表内容变成字符串
    title = "".join(list(title_div.find('a').stripped_strings))

    # 获取作者名称所在的dd标签
    name_dd = li.find('dd', attrs={'class': 'name'})
    # 获取作者名称
    name = "".join(list(name_dd.find('a').stripped_strings))

    # 获取阅读量所在的dd标签
    readNum_dd = li.find('dd', attrs={'class': 'read_num'})
    # 获取阅读量
    readNum = "".join(list(readNum_dd.stripped_strings))

    # 获取评论数所在的dd标签
    commonNum_dd = li.find('dd', attrs={'class': 'common_num'})
    # 获取评论数
    # 因为有些文章是没有评论的,所以会缺少此标签
    if commonNum_dd != None:
        commonNum = "".join(list(commonNum_dd.stripped_strings))
    else:
        commonNum = 0

    print('文章标题:{}\t作者名称:{}\t浏览量:{}\t评论数:{}'
          .format(title, name, readNum, commonNum))

当然,你还可以自己获取更多更好玩的内容,冲冲冲。
至于如何爬取每篇文章的内容详情,后续教程中将会讲到,一步一步来。
那这一节就到这里啦~~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值