Python手记-10:Beautiful Soup爬取豆瓣经典书单

目录

1. Beautiful Soup简介

2.  Beautiful Soup简单使用

2.1 对象种类

2.2 遍历文档树

2.2.1 子节点

2.2.2 父节点

2.2.3 兄弟节点

2.2.4 回退和前进

2.3 搜索文档树

2.3.1 过滤器

2.3.2 find_all(name, attrs, recursive, text, limit, kwargs)

2.3.3 搜索文档树的其他方法

2.3.4 CSS选择器

3. Beautiful Soup爬取豆瓣经典书单


1. Beautiful Soup简介

Beautiful Soup名字来源于《爱丽丝梦游仙境》,是一个可以从HTML或XML文件中提取数据的Python库,当前版本4.4.0,Beautiful Soup 3目前已经停止开发,官方推荐使用Beautiful Soup 4(简称BS4),官文指路:https://beautifulsoup.readthedocs.io/zh_CN/v4.4.0/,不得不说Beautiful Soup官文的可读性秒爆某lx**的。

Beautiful Soup最主要的功能是从网页抓取数据,三大功能系:

  • Beautiful Soup提供一些简单的、python式的函数用来处理导航、搜索、修改分析树等功能。它是一个工具箱,通过解析文档为用户提供需要抓取的数据,因为简单,所以不需要多少代码就可以写出一个完整的应用程序;
  • Beautiful Soup自动将输入文档转换为Unicode编码,输出文档转换为utf-8编码,但是,当文档没有指定编码并且Beautiful Soup未能自动识别到编码方式,这时就需要指定原始编码;
  • Beautiful Soup已成为和lxml、html6lib一样出色的python解释器,解析高效且为用户灵活地提供不同的解析策略。

Beautiful Soup支持Python标准库中的HTML解析器,还支持一些第三方的解析器:Python标准库、lxml HTML 解析器、lxml XML 解析器、html5lib,推荐使用lxml作为解析器,因为高效性。

下载:https://www.crummy.com/software/BeautifulSoup/

安装Beautiful Soup:

$ pip install beautifulsoup4

或者PyCharm:File→Settings→Project:Python Notes→Project Interpret→搜索“beautifulsoup4”→Install Package。

2.  Beautiful Soup简单使用

豆瓣《小王子》书目源码用Beautiful Soup练一下:

# -*- coding: utf-8 -*-
# @Author : ChengYu
# @File : beausoup_dbbook.py
# @Project: Python Notes
# @CreateTime : 2020/5/8 15:08:46

from bs4 import BeautifulSoup

html_doc = """<li class="subject-item"> <div class="pic"> <a 
class="nbg" href="https://book.douban.com/subject/1084336/" οnclick="moreurl(this,{i:'0',query:'',subject_id:'1084336', 
from:'book_subject_search'})"> <img class="" src="https://img3.doubanio.com/view/subject/s/public/s1103152.jpg" width 
="90"> </a> </div> <div class="info"> <h2 class=""> <a 
href="https://book.douban.com/subject/1084336/" title="小王子" οnclick="moreurl(this,{i:'0',query:'',subject_id:'1084336', 
from:'book_subject_search'})"> 小王子 </a> </h2> <div class="pub"> [法]圣埃克苏佩里/马振聘/人民文学出版社/2003-8/22.00元 </div> <div 
class="star clearfix"> <span class="allstar45"></span> <span class="rating_nums">9.0</span> <span class="pl"> ( 
561845人评价) </span> </div> <p>小王子是一个超凡脱俗的仙童,他住在一颗只比他大一丁点儿的小行星上。陪伴他的是一朵他非常喜爱的小玫瑰花。但玫瑰花的虚荣心伤害了小王子对她的感情。小王子告别小行...</p> 
<div class="ft"> <div class="collect-info"> </div> <div class="cart-actions"> <span class="buy-info"> <a 
href="https://book.douban.com/subject/1084336/buylinks">纸质版47.30元起</a> </span> </div> </div> </div> </li>"""
soup = BeautifulSoup(html_doc, 'html.parser')
# 获取<a>标签的链接
for link in soup.find_all('a'):
    print(link.get('href'))
# 获取文档中所有的文字内容
print(soup.get_text())
# 按照标准的缩进格式输出
print(soup.prettify())

“Run”结果:

2.1 对象种类

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

(1)Tag 对象可以看成 HTML 中的标签,Tag有很多方法和属性,Tag中最重要的属性:name和attributes。

  • name 属性是Tag对象的标签名,比如获取标签p的内容:

print(soup.p)
#输出结果如下:
<p>小王子是一个超凡脱俗的仙童,他住在一颗只比他大一丁点儿的小行星上。陪伴他的是一朵他非常喜爱的小玫瑰花。但玫瑰花的虚荣心伤害了小王子对她的感情。小王子告别小行...</p>
  • attributes属性是 Tag 对象所包含的属性值,它是一个字典类型,一个tag可能有很多个属性,tag的属性的操作方法与字典相同:

print(soup.a.attrs)
#输出结果如下:
{'class': ['nbg'], 'href': 'https://book.douban.com/subject/1084336/', 'onclick': "moreurl(this,{i:'0',query:'',subject_id:'1084336', \nfrom:'book_subject_search'})"}

(2)NavigableString:字符串常被包含在tag内,Beautiful Soup用 NavigableString类来包装tag中的字符串,用 .string 即可获取标签内部的文字。

print(soup.p.string)
#输出结果如下:
小王子是一个超凡脱俗的仙童,他住在一颗只比他大一丁点儿的小行星上。陪伴他的是一朵他非常喜爱的小玫瑰花。但玫瑰花的虚荣心伤害了小王子对她的感情。小王子告别小行...

NavigableString 对象支持遍历文档树和搜索文档树中定义的大部分属性,一个字符串不能包含其它内容(tag能够包含字符串或是其它tag),字符串不支持 .contents 或 .string 属性或 find() 方法。

如果想在Beautiful Soup之外使用 NavigableString 对象,需要调用 unicode() 方法,将该对象转换成普通的Unicode字符串,否则就算Beautiful Soup已方法已经执行结束,该对象的输出也会带有对象的引用地址,这样会浪费内存。

(3)BeautifulSoup:表示的是一个文档的全部内容,大部分时候,可以把它当作Tag对象,它支持遍历文档树和搜索文档树中描述的大部分的方法,因为 BeautifulSoup对象并不是真正的HTML或XML的Tag,所以它没有name和attribute属性,但有时查看它的.name属性是很方便的,所以 BeautifulSoup对象包含了一个值为 “[document]” 的特殊属性.name。

print(type(soup.span))
print(soup.name)
print(soup.attrs)
#输出结果如下:
<class 'bs4.element.Tag'>
[document]
{}

(4)Comment:是一个特殊类型的 NavigableString对象,Tag、NavigableString、BeautifulSoup几乎覆盖了html和xml中的所有内容,但是还有一些特殊对象,比如文档的注释部分,在HTML文档中时,Comment对象会使用特殊的格式输出:

markup = "<b><!--Hey, buddy. Want to buy a used parser?--></b>"
soup = BeautifulSoup(markup)
print(soup.b.prettify())
# 输出结果如下:
<b>
 <!--Hey, buddy. Want to buy a used parser?-->
</b>
print(type(soup.b.string))
# 输出结果如下:
<class 'bs4.element.Comment'>

如果不需要HTML 页面中含有注释及特殊字符串的内容,可以判断筛掉:if type(soup.a.string) == bs4.element.Comment。

2.2 遍历文档树

2.2.1 子节点

一个Tag可能包含多个字符串或其它的Tag,这些都是这个Tag的子节点,Beautiful Soup提供了许多操作和遍历子节点的属性,操作文档树最简单的方法就是告诉它你想获取的tag的name,

  • 比如获取 <span> 标签:soup.span
  • 比如获取li标签下的a标签:soup.li.img
  • 但是通过点取属性的方式只能获得当前名字的第一个tag,比如得到所有的<a>标签:soup.find_all('a'),这关于 Searching the tree 中的find_all()方法。

(1).contents 和 .children

tag的.contents属性可以将tag的子节点以列表的方式输出:

print(soup.a.contents)
# 输出结果:
[' ', <img class="" src="https://img3.doubanio.com/view/subject/s/public/s1103152.jpg" width="90"/>, ' ']

如上,contents属性得到的结果是直接子节点的列表,children属性的返回结果是生成器类型,通过tag的.children生成器,可以对tag的子节点进行循环,例如本节案例html_doc源代码中a节点包含img子节点,用for循环可以输出相应的内容。

for child in soup.a.children:
    print(soup.a.children)
    print(child)
# 输出结果:
<list_iterator object at 0x000001E95CFB7A90>
 
<img class="" src="https://img3.doubanio.com/view/subject/s/public/s1103152.jpg" width="90"/>

 (2).descendants

接上面,.contents 和 .children属性仅包含tag的直接子节点,要得到所有的子孙节点的话,可以调用descendants属性,下面获取div下属所有的子节点:

for child in soup.div.descendants:
    print(child)
# 输出结果:
<a class="nbg" href="https://book.douban.com/subject/1084336/" οnclick="moreurl(this,{i:'0',query:'',subject_id:'1084336', 
from:'book_subject_search'})"> <img class="" src="https://img3.doubanio.com/view/subject/s/public/s1103152.jpg" width="90"/> </a>
 
<img class="" src="https://img3.doubanio.com/view/subject/s/public/s1103152.jpg" width="90"/>

(3).string

如果tag只有一个NavigableString 类型子节点,那么这个tag可以使用.string得到子节点,tag包含了多个子节点,tag就无法确定.string方法应该调用哪个子节点的内容,.string的输出结果是None:

print(soup.a.string)
# 输出结果:
None
print(soup.p.string)
#输出结果:
小王子是一个超凡脱俗的仙童,他住在一颗只比他大一丁点儿的小行星上。陪伴他的是一朵他非常喜爱的小玫瑰花。但玫瑰花的虚荣心伤害了小王子对她的感情。小王子告别小行...

(4).strings 和 .stripped_strings

tag中包含多个字符串,可以使用.strings来循环获取:

for string in soup.strings:
    print(repr(string))
# 输出结果:
' '
' '
' '
' '
' '
' '
' '
' '
' 小王子 '
' '
' '
' [法]圣埃克苏佩里/马振聘/人民文学出版社/2003-8/22.00元 '
' '
' '
' '
'9.0'
' '
' ( \n561845人评价) '
' '
' '
'小王子是一个超凡脱俗的仙童,他住在一颗只比他大一丁点儿的小行星上。陪伴他的是一朵他非常喜爱的小玫瑰花。但玫瑰花的虚荣心伤害了小王子对她的感情。小王子告别小行...'
'\n'
' '
' '
' '
' '
' '
'纸质版47.30元起'
' '
' '
' '
' '
' '

上面输出的字符串中包含了很多空格或空行,使用.stripped_strings可以去除多余空白内容,全部是空格的行会被忽略掉,段首和段末的空白会被删除:

for string in soup.stripped_strings:
    print(repr(string))
# 输出结果:
'小王子'
'[法]圣埃克苏佩里/马振聘/人民文学出版社/2003-8/22.00元'
'9.0'
'( \n561845人评价)'
'小王子是一个超凡脱俗的仙童,他住在一颗只比他大一丁点儿的小行星上。陪伴他的是一朵他非常喜爱的小玫瑰花。但玫瑰花的虚荣心伤害了小王子对她的感情。小王子告别小行...'
'纸质版47.30元起'

2.2.2 父节点

(1).parent:通过.parent属性来获取某个元素的父节点,如在html_doc源代码中,<div>标签是<a>标签的父节点:

print(soup.a.parent)
# 输出结果:
<div class="pic"> <a class="nbg" href="https://book.douban.com/subject/1084336/" οnclick="moreurl(this,{i:'0',query:'',subject_id:'1084336', 
from:'book_subject_search'})"> <img class="" src="https://img3.doubanio.com/view/subject/s/public/s1103152.jpg" width="90"/> </a> </div>

(2).parents:通过元素的.parents属性可以递归得到元素的所有父辈节点,比如 .parents 方法遍历了<a>标签到根节点的所有节点:

for parent in soup.a.parents:
    if parent is None:
        print(parent)
    else:
        print(parent.name)
#输出结果:
div
li
[document]

2.2.3 兄弟节点

(1).next_sibling 和 .previous_sibling:获取当前Tag的下/上一个节点,实际源代码中的tag的.next_sibling和 .previous_sibling通常是字符串或空白,返回结果是当前标签与上一个标签之间的顿号和换行符。

print(soup.a.next_sibling)
print(soup.a.previous_sibling)

(2).next_siblings 和 .previous_siblings:获取当前Tag的下面/上面所有的兄弟节点,返回一个生成器。

for sibling in soup.div.next_siblings:
    print(repr(sibling))
for sibling in soup.div.previous_siblings:
    print(repr(sibling))
# 输出结果:
' '
<div class="info"> <h2 class=""> <a href="https://book.douban.com/subject/1084336/" onclick="moreurl(this,{i:'0',query:'',subject_id:'1084336', 
from:'book_subject_search'})" title="小王子"> 小王子 </a> </h2> <div class="pub"> [法]圣埃克苏佩里/马振聘/人民文学出版社/2003-8/22.00元 </div> <div class="star clearfix"> <span class="allstar45"></span> <span class="rating_nums">9.0</span> <span class="pl"> ( 
561845人评价) </span> </div> <p>小王子是一个超凡脱俗的仙童,他住在一颗只比他大一丁点儿的小行星上。陪伴他的是一朵他非常喜爱的小玫瑰花。但玫瑰花的虚荣心伤害了小王子对她的感情。小王子告别小行...</p>
<div class="ft"> <div class="collect-info"> </div> <div class="cart-actions"> <span class="buy-info"> <a href="https://book.douban.com/subject/1084336/buylinks">纸质版47.30元起</a> </span> </div> </div> </div>
' '
' '

2.2.4 回退和前进

(1).next_element 和 .previous_element

.next_element 属性指向解析过程中下一个被解析的对象(字符串或Tag),结果可能与.next_element 相同,但通常是不一样的;.previous_element属性刚好与.next_element 相反,它指向当前被解析的对象的前一个解析对象:

print(soup.p.next_element)
print(soup.p.previous_element)
# 输出结果:
小王子是一个超凡脱俗的仙童,他住在一颗只比他大一丁点儿的小行星上。陪伴他的是一朵他非常喜爱的小玫瑰花。但玫瑰花的虚荣心伤害了小王子对她的感情。小王子告别小行...
 

(2).next_elements 和 .previous_elements:返回一个生成器,可以向前/向后访问文档的解析内容:

for element in soup.span.next_elements:
    print(repr(element))

for element in soup.span.previous_elements:
    print(repr(element))

2.3 搜索文档树

Beautiful Soup定义了很多搜索方法,常用的:find(name, attrs, recursive, text, limit, kwargs)和find_all(name, attrs, recursive, text, limit, kwargs)。

2.3.1 过滤器

过滤器可以被用在tag的name中,节点的属性中,字符串中或混合使用。

  • 字符串:查找与字符串完全匹配的内容。
print(soup.find_all('a'))
# 输出结果:
[<a class="nbg" href="https://book.douban.com/subject/1084336/" onclick="moreurl(this,{i:'0',query:'',subject_id:'1084336', 
from:'book_subject_search'})"> <img class="" src="https://img3.doubanio.com/view/subject/s/public/s1103152.jpg" width="90"/> </a>, <a href="https://book.douban.com/subject/1084336/" onclick="moreurl(this,{i:'0',query:'',subject_id:'1084336', 
from:'book_subject_search'})" title="小王子"> 小王子 </a>, <a href="https://book.douban.com/subject/1084336/buylinks">纸质版47.30元起</a>]
  • 正则表达式:如果传入正则表达式作为参数,Beautiful Soup会通过正则表达式的match()来匹配内容。
print(soup.find_all(re.compile('^p')))
# 输出结果:
[<p>小王子是一个超凡脱俗的仙童,他住在一颗只比他大一丁点儿的小行星上。陪伴他的是一朵他非常喜爱的小玫瑰花。但玫瑰花的虚荣心伤害了小王子对她的感情。小王子告别小行...</p>]
  • 列表:如果传入列表参数,Beautiful Soup会将与列表中任一元素匹配的内容返回。
print(soup.find_all(['p', 'span']))
# 输出结果:
[<span class="allstar45"></span>, <span class="rating_nums">9.0</span>, <span class="pl"> ( 
561845人评价) </span>, <p>小王子是一个超凡脱俗的仙童,他住在一颗只比他大一丁点儿的小行星上。陪伴他的是一朵他非常喜爱的小玫瑰花。但玫瑰花的虚荣心伤害了小王子对她的感情。小王子告别小行...</p>, <span class="buy-info"> <a href="https://book.douban.com/subject/1084336/buylinks">纸质版47.30元起</a> </span>]
  • True:True 可以匹配任何值。
for tag in soup.find_all(True):
    print(tag.name)
# 输出结果:
li
div
a
img
div
h2
a
div
div
span
span
span
p
div
div
div
span
a
  • 方法:如果没有合适过滤器,那么还可以定义一个方法,方法只接受一个元素参数,如果这个方法返回True表示当前元素匹配并且被找到,如果不是则反回False如下校验当前元素如果包含title属性那么将返回True,将这个方法作为参数传入find_all()方法,将得到所有含有“title”的标签。
def has_title(tag):
    return tag.has_attr('title')


print(soup.find_all(has_title))
# 输出结果:
[<a href="https://book.douban.com/subject/1084336/" οnclick="moreurl(this,{i:'0',query:'',subject_id:'1084336', 
from:'book_subject_search'})" title="小王子"> 小王子 </a>]

另外 attrs 参数可以也作为过滤条件来获取内容,而 limit 参数是限制返回的条数。 

2.3.2 find_all(name, attrs, recursive, text, limit, kwargs)

find_all()方法搜索当前tag的所有tag子节点,并判断是否符合过滤器的条件。

(1)name参数:name参数可以查找所有名字为name的tag,字符串对象会被自动忽略掉,搜索name参数的值可以使任一类型的过滤器,字符串、正则表达式、列表、方法或是True。

print(soup.find_all('span'))
# 输出结果:
[<span class="allstar45"></span>, <span class="rating_nums">9.0</span>, <span class="pl"> ( 
561845人评价) </span>, <span class="buy-info"> <a href="https://book.douban.com/subject/1084336/buylinks">纸质版47.30元起</a> </span>]

(2)attrs参数:是用Python字典封装一个标签的若干属性和对应的属性值,如下找span标签class属性值为“buy-info”。

print(soup.find_all('span', {'class', 'buy-info'}))
print(soup.find_all('span', attrs={'class': 'buy-info'}))
# 输出结果:
[<span class="buy-info"> <a href="https://book.douban.com/subject/1084336/buylinks">纸质版47.30元起</a> </span>]

(3)recursive参数:调用tag的find_all()方法时,Beautiful Soup会检索当前tag的所有子孙节点,如果只想搜索tag的直接子节点,可以使用参数recursive=False。

print(soup.find_all("span", recursive=False))
# 输出结果:
[]

(4)text参数:是用标签的文本内容去匹配,而不是用标签的属性,通过 text 参数可以匹配文档中的字符串内容,与name参数的可选值一样,text 参数可以是字符串、正则表达式 、列表、True。

假如查找前面网页中包含“纸质版”内容的标签或者包含“小王子”内容的tag数量,可以把之前的 findAll 方法换成下面的代码:

print(soup.find_all(text=re.compile('纸质版')))
# 输出结果:
['纸质版47.30元起']
bookname = soup.findAll(text=re.compile("小王子"))
print(len(bookname))
# 输出结果:
2

(5)limit参数:find_all()方法返回全部的搜索结构,如果文档树很大那么搜索会很慢,如果不需要全部结果,可以使用limit参数限制返回结果的数量,效果与SQL中的limit关键字类似,当搜索到的结果数量达到limit的限制时,就停止搜索返回结果,如下:文档树中有3个tag符合搜索条件,但结果只返回了1个,因为限制了返回数量。

print(soup.find_all("a", limit=1))
# 输出结果:
[<a class="nbg" href="https://book.douban.com/subject/1084336/" οnclick="moreurl(this,{i:'0',query:'',subject_id:'1084336', 
from:'book_subject_search'})"> <img class="" src="https://img3.doubanio.com/view/subject/s/public/s1103152.jpg" width="90"/> </a>]

(6)kwargs参数:如果一个指定名字的参数不是搜索内置的参数名,搜索时会把该参数当作指定名字tag的属性来搜索,如果包含一个name为span的参数,Beautiful Soup会搜索每个tag的“span”属性。

print(soup.find_all(title="小王子"))
# 输出结果:
[<a href="https://book.douban.com/subject/1084336/" onclick="moreurl(this,{i:'0',query:'',subject_id:'1084336', 
from:'book_subject_search'})" title="小王子"> 小王子 </a>]

注意:用 keyword 偶尔会出现问题,如下在用 class 属性查找标签的时候,因为 class 是 Python 中受保护的关键字,所以一般只采用前2个参数tag、attributes即可。

print(soup.find_all(class="pub"))
# 输出结果:
  File "G:/pycharm/Python Notes/venv/beausoup_dbbook.py", line 91
    print(soup.find_all(class="pub"))
                        ^
SyntaxError: invalid syntax

2.3.3 搜索文档树的其他方法

(1)find( name , attrs , recursive , text , **kwargs )

find_all() 方法将返回文档中符合条件的所有tag,而 find() 方法返回符合条件的第一个Tag结果,比如源码中只有一个<span>标签,那么使用find_all() 方法来查找<span>标签就大材小用了,使用find_all() 方法并设置limit参数不如直接使用find()方法,下面两行代码是等价的:

print(soup.find_all("a", limit=1))
print(soup.find('a'))
# 输出结果:
[<a class="nbg" href="https://book.douban.com/subject/1084336/" οnclick="moreurl(this,{i:'0',query:'',subject_id:'1084336', 
from:'book_subject_search'})"> <img class="" src="https://img3.doubanio.com/view/subject/s/public/s1103152.jpg" width="90"/> </a>]
<a class="nbg" href="https://book.douban.com/subject/1084336/" οnclick="moreurl(this,{i:'0',query:'',subject_id:'1084336', 
from:'book_subject_search'})"> <img class="" src="https://img3.doubanio.com/view/subject/s/public/s1103152.jpg" width="90"/> </a>

(2)find_parents()和find_parent()

find_all() 和 find() 只搜索当前节点的所有子节点,孙子节点等, find_parents() 和 find_parent() 用来搜索当前节点的父辈节点,搜索方法与普通tag的搜索方法相同,搜索源代码包含的内容:

print(soup.find('span', {'class', 'buy-info'}))
print(soup.find('span', {'class', 'buy-info'}).find_parent("div"))
print(soup.find('span', {'class', 'buy-info'}).find_parents("h2"))
# 输出结果:
<span class="buy-info"> <a href="https://book.douban.com/subject/1084336/buylinks">纸质版47.30元起</a> </span>
<div class="cart-actions"> <span class="buy-info"> <a href="https://book.douban.com/subject/1084336/buylinks">纸质版47.30元起</a> </span> </div>
[]

(3)find_next_siblings()和find_next_sibling()

这2个方法通过 .next_siblings 属性对当 tag 的所有后面解析的兄弟 tag 节点进行迭代,find_next_siblings() 方法返回所有符合条件的后面的兄弟节点,find_next_sibling() 只返回符合条件的后面的第一个tag节点。

print(soup.a.find_next_siblings("a"))
print(soup.find("p").find_next_sibling("span"))

(4)find_previous_siblings()和find_previous_sibling()

这2个方法通过 .previous_siblings 属性对当前 tag 的前面解析的兄弟 tag 节点进行迭代,find_previous_siblings()方法返回所有符合条件的前面的兄弟节点,find_previous_sibling() 方法返回第一个符合条件的前面的兄弟节点。

(5)find_all_next() 和 find_next()

这2个方法通过 .next_elements 属性对当前 tag 的之后的 tag 和字符串进行迭代, find_all_next() 方法返回所有符合条件的节点, find_next() 方法返回第一个符合条件的节点。

(6)find_all_previous() 和 find_previous()

这2个方法通过 .previous_elements 属性对当前节点前面的 tag 和字符串进行迭代, find_all_previous() 方法返回所有符合条件的节点, find_previous()方法返回第一个符合条件的节点。

2.3.4 CSS选择器

Beautiful Soup支持大部分的CSS选择器 http://www.w3.org/TR/CSS2/selector.html,在Tag或BeautifulSoup对象的.select()方法中传入字符串参数,即可使用CSS选择器的语法找到Tag。

(1)通过tag标签逐层查找

print(soup.select("p"))
print(soup.select("li span"))
# 输出结果:
[<p>小王子是一个超凡脱俗的仙童,他住在一颗只比他大一丁点儿的小行星上。陪伴他的是一朵他非常喜爱的小玫瑰花。但玫瑰花的虚荣心伤害了小王子对她的感情。小王子告别小行...</p>]
[<span class="allstar45"></span>, <span class="rating_nums">9.0</span>, <span class="pl"> ( 
561845人评价) </span>, <span class="buy-info"> <a href="https://book.douban.com/subject/1084336/buylinks">纸质版47.30元起</a> </span>]

(2)找到某个tag标签下的直接子标签

print(soup.select("a > img"))
print(soup.select("li > a"))
# 输出结果:
[<img class="" src="https://img3.doubanio.com/view/subject/s/public/s1103152.jpg" width="90"/>]
[]

(3)通过CSS的类名查找

print(soup.select(".buy-info"))
print(soup.select("[class~=nbg]"))
# 输出结果:
[<span class="buy-info"> <a href="https://book.douban.com/subject/1084336/buylinks">纸质版47.30元起</a> </span>]
[<a class="nbg" href="https://book.douban.com/subject/1084336/" οnclick="moreurl(this,{i:'0',query:'',subject_id:'1084336', 
from:'book_subject_search'})"> <img class="" src="https://img3.doubanio.com/view/subject/s/public/s1103152.jpg" width="90"/> </a>]

(4)通过属性查找:属性需要用中括号括起来,属性和标签属于同一节点,所以中间不能加空格,否则会无法匹配到:

print(soup.select('a[href]'))
# 输出结果:
[<a class="nbg" href="https://book.douban.com/subject/1084336/" οnclick="moreurl(this,{i:'0',query:'',subject_id:'1084336', 
from:'book_subject_search'})"> <img class="" src="https://img3.doubanio.com/view/subject/s/public/s1103152.jpg" width="90"/> </a>, <a href="https://book.douban.com/subject/1084336/" οnclick="moreurl(this,{i:'0',query:'',subject_id:'1084336', 
from:'book_subject_search'})" title="小王子"> 小王子 </a>, <a href="https://book.douban.com/subject/1084336/buylinks">纸质版47.30元起</a>]

print(soup.select('a[class="nbg"]'))
# 输出结果:
[<a class="nbg" href="https://book.douban.com/subject/1084336/" οnclick="moreurl(this,{i:'0',query:'',subject_id:'1084336', 
from:'book_subject_search'})"> <img class="" src="https://img3.doubanio.com/view/subject/s/public/s1103152.jpg" width="90"/> </a>]

3. Beautiful Soup爬取豆瓣经典书单

试试爬取豆瓣图书“经典”标签页的书单信息,简单应用:

# -*- coding: utf-8 -*-
# @Author : ChengYu
# @File : beausoup_dbbook.py
# @Project: Python Notes
# @CreateTime : 2020/5/8 15:08:46
import requests
import csv
from bs4 import BeautifulSoup

# 加上headers用来告诉网站这是通过一个浏览器进行的访问
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
                         'Chrome/81.0.4044.122 Safari/537.36'}
# 初始化csv文件
with open('DBbooks.csv', 'w', newline='', encoding='utf-8') as csvfile:
    writer = csv.writer(csvfile)
    writer.writerow(['序号', '书名', '作者/出版信息/价格', '评分', '评价数', '简介', '购买渠道'])
    count = 0
    for page in range(0, 10, 1):
        # https://book.douban.com/tag/%E7%BB%8F%E5%85%B8
        # https://book.douban.com/tag/%E7%BB%8F%E5%85%B8?start=0&type=T
        # https://book.douban.com/tag/%E7%BB%8F%E5%85%B8?start=20&type=T
        # https://book.douban.com/tag/%E7%BB%8F%E5%85%B8?start=40&type=T
        url = 'https://book.douban.com/tag/%E7%BB%8F%E5%85%B8?start=' + str(page * 20) + '&type=T'
        # 获取网页源码
        res = requests.get(url, headers=headers).text
        soup = BeautifulSoup(res, 'html.parser')
        # 从所有class为subject-item中提取图书信息
        for bookdata in soup.find_all(attrs={'class': 'subject-item'}):
            # 使用 .stripped_strings 生成器,获得文本列表后手动处理列表
            bookitem = [text for text in bookdata.stripped_strings]
            count += 1
            bookitem = [count] + bookitem
            print(bookitem)
            # 写入csv文件
            writer.writerow(bookitem)
    csvfile.close()

 

“Run”结果部分展示:

路漫漫,扯下口罩呼口气,2020年05月11日END。

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章