Beautiful Soup 是一个能够从HTML或XML文件中提取数据的Python库。它可以经过你喜欢的转换器实现惯用的文档导航、查找、修改文档的方式。Beautiful Soup会帮你节省数小时甚至数天的工做时间。Beautiful Soup 3 目前已经中止开发,官网推荐在如今的项目中使用Beautiful Soup 4。css
- 须要将pip源设置为国内源,阿里源、豆瓣源、网易源等 - windows (1)打开文件资源管理器(文件夹地址栏中) (2)地址栏上面输入 %appdata% (3)在这里面新建一个文件夹 pip (4)在pip文件夹里面新建一个文件叫作 pip.ini ,内容写以下便可 [global] timeout = 6000 index-url = https://mirrors.aliyun.com/pypi/simple/ trusted-host = mirrors.aliyun.com - linux (1)cd ~ (2)mkdir ~/.pip (3)vi ~/.pip/pip.conf (4)编辑内容,和windows如出一辙
#安装 Beautiful Soup pip install beautifulsoup4 #安装解析器 Beautiful Soup支持Python标准库中的HTML解析器,还支持一些第三方的解析器,其中一个是 lxml .根据操做系统不一样,能够选择下列方法来安装lxml: $ apt-get install Python-lxml $ easy_install lxml $ pip install lxml 另外一个可供选择的解析器是纯Python实现的 html5lib , html5lib的解析方式与浏览器相同,能够选择下列方法来安装html5lib: $ apt-get install Python-html5lib $ easy_install html5lib $ pip install html5lib
下表列出了主要的解析器,以及它们的优缺点,官网推荐使用lxml做为解析器,由于效率更高. 在Python2.7.3以前的版本和Python3中3.2.2以前的版本,必须安装lxml或html5lib, 由于那些Python版本的标准库中内置的HTML解析方法不够稳定。html
解析器 | 使用方法 | 优点 | 劣势 |
python标准库 | BeautifulSoup(markup, "html.parser") | Python的内置标准库html5 执行速度适中python 文档容错能力强linux |
Python 2.7.3 or 3.2.2)前 的版本中文档容错能力差正则表达式 |
lxml HTML 解析器 | BeautifulSoup(markup, "lxml") | 速度快express 文档容错能力强windows |
须要安装C语言库浏览器 |
lxml XML 解析器 | BeautifulSoup(markup,["lxml","xml"])网络 BeautifulSoup(markup, "xml") |
速度快 惟一支持XML的解析器 |
须要安装C语言库 |
html5lib | BeautifulSoup(markup, "html5lib") | 最好的容错性 以浏览器的方式解析文档 生成HTML5格式的文档 |
速度慢 不依赖外部扩展 |
详情查看中文文档:Beautiful Soup 4.2.0 文档
容错处理,文档的容错能力指的是在html代码不完整的状况下,使用该模块能够识别该错误。
使用BeautifulSoup解析不完整的html代码,可以获得一个 BeautifulSoup 的对象,并能按照标准的缩进格式的结构输出。
核心思想:将html文档转换为BeautifulSoup对象,调用该对象中的属性和方法进行html文档指定内容定位查找。
一、导包:from bs4 import BeautifulSoup 2、建立Beautiful对象: 若是html文档的来源是本地: Beautiful('open('本地的html文件')', 'lxml') 若是html是来源于网络: Beautiful('网络请求到的页面数据', 'lxml')
from bs4 import BeautifulSoup html_doc = """ <html><head><title>The Dormouse's story</title></head> <body> <p class="title"><b>The Dormouse's story</b></p> <p class="story">Once upon a time there were three little sisters; and their names were <a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>, <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>; and they lived at the bottom of a well.</p> <p class="story">...</p> """ soup=BeautifulSoup(html_doc,'lxml') #具备容错功能 res=soup.prettify() # 处理好缩进,结构化显示 print(res)
输出的结果补齐了缺失的html代码:
</body> </html>
遍历文档树即直接经过标签名字选择,特色是选择速度快,但若是存在多个相同的标签则只返回第一个。
html_doc = """ <html><head><title>The Dormouse's story</title></head> <body> asdf <div class="title"> <b>The Dormouse's story总共</b> <h1>f</h1> </div> <div class="story">Once upon a time there were three little sisters; and their names were <a class="sister0" id="link1">Els<span>f</span>ie</a>, <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>; and they lived at the bottom of a well.</div> ad<br/>sf <p class="story">...</p> </body> </html> """ # 一、用法 from bs4 import BeautifulSoup soup=BeautifulSoup(html_doc, features='lxml') #具备容错功能 # 2.name,根据标签名查找 # tag = soup.a # name = tag.name # 获取标签名称 # print(name) # 输出:a # tag.name = 'span' # 设置标签名称 # print(soup) """输出代码可看到第一个a标签修改成了span标签 <span class="sister0" id="link1">Els<span>f</span>ie</span>, <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> """ # 3.attrs,获取标签属性 # tag = soup.a # attrs = tag.attrs # 获取标签属性 # print(attrs) """ {'class': ['sister0'], 'id': 'link1'} """ # tag.attrs = {'ik':123} # 清空并设置标签属性 # tag.attrs['id'] = 'iiiii' # 添加标签属性 # print(soup) """ <a id="iiiii" ik="123">Els<span>f</span>ie</a>, """ # 4.string/text/get_text,获取标签的内容 # print(soup.p.string) # p下的文本只有一个时取到,不然为None # print(soup.p.string) # 拿到一个生成器对象,取到p下全部的文本内容 # print(soup.p.text) # 取到p下全部的文本内容 # for line in soup.div.stripped_strings: # 去掉空白 # print(line) """ 若是tag包含了多个子节点,tag就没法肯定.string 方法应该调用哪一个子节点的内容, .string 的输出结果是 None, 若是只有一个子节点那么就输出该子节点的文本,好比下面的这种结构,soup.p.string 返回为None, 但soup.p.strings和soup.p.get_text()就能够找到全部文本内容 """ # 5.children,全部子节点 # body = soup.find('body') # v = body.children # 获得一个迭代器,包含body下全部子节点 # for i, child in enumerate(v): # i只有9个 # print(i, child) # 6.descendants,获取子孙节点 # body = soup.find('body') # 获取子孙节点,body下全部的标签都会选择出来 # v = body.descendants # for i, child in enumerate(v): # i有30个 # print(i, child) # 7.嵌套选择 # print(soup.head.title.string) # The Dormouse's story # print(soup.body.a.string) # None # 8.parent/parents, 父节点/祖先节点 # parent = soup.a.parent # 获取a标签父节点 # print(parent) # <div class="story">...</div> # parents = soup.a.parents # 获取a标签全部祖先节点 # print(parents) # <generator object parents at 0x10403b200> # 9.next_sibling/previous_sibling,兄弟节点 n_s = soup.a.next_sibling # 下一个兄弟 print(n_s) p_s = soup.a.previous_sibling # 上一个兄弟 print(p_s) n_generator = list(soup.a.next_sibling) # 下面的兄弟们——》生成器对象 print(n_generator) p_generator = soup.a.previous_sibling # 上面的兄弟们——》生成器对象 print(p_generator)
BeautifulSoup定义了不少搜索方法,这里着重介绍2个:find()和find_all(),其余方法的参数和用法相似。
html_doc = """ <html><head><title>The Dormouse's story</title></head> <body> asdf <div class="title"> <b>The Dormouse's story总共</b> <h1>f</h1> </div> <div class="story">Once upon a time there were three little sisters; and their names were <a class="sister0" id="link1">Els<span>f</span>ie</a>, <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>; and they lived at the bottom of a well.</div> ad<br/>sf <p class="story">...</p> </body> </html> """ from bs4 import BeautifulSoup soup=BeautifulSoup(html_doc, features='lxml') #具备容错功能 # 五种过滤器: 字符串、正则表达式、列表、True、方法 # 一、字符串: 标签名 # print(soup.find_all('b')) """ [<b>The Dormouse's story总共</b>] """ # 二、正则表达式 # import re # print(soup.find_all(re.compile('^b$'))) # 找出b开头并结尾的标签 """ [<b>The Dormouse's story总共</b>] """ # 三、列表: 若是传入列表参数,Beautiful Soup会将与列表中任一元素匹配的内容返回.下面代码找到文档中全部<a>标签和<b>标签: # print(soup.find_all(['a', 'b'])) """ [<b>The Dormouse's story总共</b>, <a class="sister0" id="link1">Els<span>f</span>ie</a>...... """ # 四、True:能够匹配任何值,下面代码查找到全部的tag,可是不会返回字符串节点 # print(soup.find_all(True)) # for tag in soup.find_all(True): # print(tag.name) # html head title等 # 五、方法:若是没有合适过滤器,那么还能够定义一个方法,方法只接受一个元素参数, # 若是这个方法返回 True 表示当前元素匹配而且被找到,若是不是则反回 False def has_class_but_no_id(tag): return tag.has_attr("class") and not tag.has_attr("id") print(soup.find_all(has_class_but_no_id)) """ [<div class="title">...</div>, <div class="story">...</div>, <p class="story">...</p>] """
# 2、find_all() # 一、name: 搜索name参数的值可使任一类型的 过滤器 ,字符窜,正则表达式,列表,方法或是 True . # print(soup.find_all(name=re.compile('^t'))) """ [<title>The Dormouse's story</title>] """ # 二、keyword: key=value的形式,value能够是过滤器:字符串,正则表达式,列表,True. print(soup.find_all(id=re.compile('my'))) print(soup.find_all(href=re.compile('lacie'),id=re.compile('\d'))) # 注意类要用class_ print(soup.find_all(id=True)) # 查找有id属性的标签 # 有些tag属性在搜索不能使用,好比HTML5中的 data-* 属性: data_soup = BeautifulSoup('<div data-foo="value">foo!</div>','lxml') # data_soup.find_all(data-foo="value") #报错:SyntaxError: keyword can't be an expression # 可是能够经过 find_all() 方法的 attrs 参数定义一个字典参数来搜索包含特殊属性的tag: print(data_soup.find_all(attrs={"data-foo": "value"})) # [<div data-foo="value">foo!</div>] # 三、按照类名查找,注意关键字是class_,class_=value,value能够是五种选择器之一 print(soup.find_all('a',class_='sister')) # 查找类为sister的a标签 print(soup.find_all('a',class_='sister ssss')) # 查找类为sister和sss的a标签,顺序错误也匹配不成功 print(soup.find_all(class_=re.compile('^sis'))) # 查找类为sister的全部标签 # 四、attrs print(soup.find_all('p',attrs={'class':'story'})) # 五、text: 值能够是:字符,列表,True,正则 print(soup.find_all(text='Elsie')) print(soup.find_all('a',text='Elsie')) # 六、limit参数:若是文档树很大那么搜索会很慢.若是咱们不须要所有结果,可使用 limit 参数限制返回结果的数量. # 效果与SQL中的limit关键字相似,当搜索到的结果数量达到 limit 的限制时,就中止搜索返回结果 print(soup.find_all('a',limit=2)) # 获取前两个符合条件的a标签 # 七、recursive:调用tag的 find_all() 方法时,Beautiful Soup会检索当前tag的全部子孙节点, # 若是只想搜索tag的直接子节点,可使用参数 recursive=False . print(soup.html.find_all('a')) print(soup.html.find_all('a',recursive=False)) ''' 像调用 find_all() 同样调用tag find_all() 几乎是Beautiful Soup中最经常使用的搜索方法,因此咱们定义了它的简写方法. BeautifulSoup 对象和 tag 对象能够被看成一个方法来使用,这个方法的执行结果与调用这个对象的 find_all() 方法相同,下面两行代码是等价的: soup.find_all("a") soup("a") 这两行代码也是等价的: soup.title.find_all(text=True) soup.title(text=True) '''
find_all() 方法将返回文档中符合条件的全部tag,尽管有时候咱们只想获得一个结果。
好比文档中只有一个<body>标签,那么使用 find_all() 方法来查找<body>标签就不太合适, 使用 find_all 方法并设置 limit=1 参数不如直接使用 find() 方法。
下面两行代码是等价的:
print(soup.find_all("title", limit=1)) # [<title>The Dormouse's story</title>] print(soup.find("title")) # <title>The Dormouse's story</title>
惟一的区别是 find_all() 方法的返回结果是值包含一个元素的列表,而 find() 方法直接返回结果(返回符合条件的第一个标签).
find_all() 方法没有找到目标是返回 空列表, find() 方法找不到目标时,返回 None .
print(soup.find("nosuchtag")) """ None """
soup.head.title 是 tag的名字 方法的简写.这个简写的原理就是屡次调用当前tag的 find() 方法:
print(soup.head.title) # <title>The Dormouse's story</title> print(soup.find("head").find("title")) # <title>The Dormouse's story</title>
该模块提供了select方法来支持css,详见官网:CSS选择器
html_doc = """ <html><head><title>The Dormouse's story</title></head> <body> <p class="title"> <b>The Dormouse's story</b> Once upon a time there were three little sisters; and their names were <a href="http://example.com/elsie" class="sister" id="link1"> <span>Elsie</span> </a> <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>; <div class='panel-1'> <ul class='list' id='list-1'> <li class='element'>Foo</li> <li class='element'>Bar</li> <li class='element'>Jay</li> </ul> <ul class='list list-small' id='list-2'> <li class='element'><h1 class='yyyy'>Foo</h1></li> <li class='element xxx'>Bar</li> <li class='element'>Jay</li> </ul> </div> and they lived at the bottom of a well. </p> <p class="story">...</p> """ from bs4 import BeautifulSoup soup = BeautifulSoup(html_doc, 'lxml') # 一、CSS选择器 # print(soup.p.select('.sister')) # print(soup.select('.sister span')) """ [<a class="sister" href="http://example.com/elsie" id="link1"><span>Elsie</span></a>, <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>] [<span>Elsie</span>] """ # print(soup.select('#link1')) # print(soup.select('#link1 span')) """ [<a class="sister" href="http://example.com/elsie" id="link1"><span>Elsie</span></a>] [<span>Elsie</span>] """ # print(soup.select('#list-2 .element.xxx')) """ [<li class="element xxx">Bar</li>] """ # 能够一直select,但其实不必,一条select就能够了 # print(soup.select('#list-2')[0].select('.element')) """ [<li class="element"><h1 class="yyyy">Foo</h1></li>, <li class="element xxx">Bar</li>, <li class="element">Jay</li>] """ # 二、获取属性 # print(soup.select('#list-2 h1')[0].attrs) """ {'class': ['yyyy']} """ # 三、获取内容 print(soup.select('#list-2 h1')[0].get_text()) """ Foo """
注意:
(1)常见的选择器有:标签选择器、类选择器、id选择器、层级选择器。
(2)select选择器返回永远是列表,须要经过下标提取指定的对象。
(3)经过 class_ 参数搜索有指定CSS类名的tag
按照CSS类名搜索tag的功能很是实用,但标识CSS类名的关键字 class 在Python中是保留字,使用 class 作参数会致使语法错误.从Beautiful Soup的4.1.1版本开始,能够经过 class_ 参数搜索有指定CSS类名的tag:
soup.find_all("a", class_="sister") # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>, # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
class_ 参数一样接受不一样类型的 过滤器 ,字符串,正则表达式,方法或 True :
soup.find_all(class_=re.compile("itl")) # [<p class="title"><b>The Dormouse's story</b></p>] def has_six_characters(css_class): return css_class is not None and len(css_class) == 6 soup.find_all(class_=has_six_characters) # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>, # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
Beautiful Soup的强项是文档树的搜索,但同时也能够方便的修改文档树。
详见官网:修改文档树。
需求:爬取古诗文网中三国小说里的标题和内容。古诗文网三国演义网址
import requests from bs4 import BeautifulSoup url = "http://www.shicimingju.com/book/sanguoyanyi.html" headers = { "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36" } def get_content(url): """ 根据url获取页面中指定的标题所对应的文章内容 :param url: :return: """ content_page = requests.get(url=url, headers=headers).text # 指定文章内容解析 soup = BeautifulSoup(content_page, 'lxml') # 经过 class_ 参数搜索有指定CSS类名的tag div = soup.find('div', class_='chapter_content') return div.text page_text = requests.get(url=url, headers=headers).text # 数据解析 soup = BeautifulSoup(page_text, 'lxml') a_list = soup.select('.book-mulu > ul > li > a') # 层级表达式定位到li标签下的a标签 # print(a_list) """a_list存储的一系列的a标签对象 [<a href="/book/sanguoyanyi/1.html">第一回·宴桃园豪杰三结义 斩黄巾英雄首立功</a>, ..., <a href="/book/sanguoyanyi/120.html">第一百二十回·荐杜预老将献新谋 降孙皓三分归一统</a>] """ # print(type(a_list[0])) """ <class 'bs4.element.Tag'> # 注意:Tag类型的对象能够继续调用响应的解析属性和方法进行局部数据的解析 """ # 持久化存储 fp = open('./sanguo.txt', 'w', encoding='utf-8') for a in a_list: # 获取章节标题 title = a.string # Tag类型的对象能够继续调用响应的解析属性和方法进行局部数据的解析 # 获取章节url content_url = 'http://www.shicimingju.com' + a['href'] # 获取章节内容 content = get_content(content_url) # print(content) fp.write(title + ':' + content + "\n\n\n") print('写入一个章节内容')
一、推荐使用 lxml 解析库
二、讲了三种选择器:标签选择器,find与find_all,css选择器
1)标签选择器筛选功能弱,可是速度快;
2)建议使用find,find_all查询匹配单个结果或者多个结果;
3)若是对css选择器很是熟悉建议使用select。
三、记住经常使用的获取属性attrs和文本值get_text()的方法