爬虫之BeautifulSoup

python中爬虫常用模块“美丽汤”

模块功能介绍

从html或者xml格式文件中提取数据

安装

# 安装BeautifulSoup
pip install bs4
# 安装其他相关解析器
pip install lxml
pip install html5lib

模块引用

# 初始化
from bs4 import BeautifulSoup
# 方法一,直接打开文件(需要指定Beautiful的解析器为“html.parser”,否则解析不正常)
soup=BeautifulSoup(open("index.html",'r',encoding="utf-8"),"html.parser")
# 方法二,指定数据
resp="<html>data</html>"
soup=BeautifulSoup(resp, 'lxml')
# soup为BeautifulSoup 类型对象
print(type(soup))

基本元素

  • BeautifulSoup
    文档的全部内容。
  • Tag
    HTML的标签,最基本的信息组织单元,分别用<>和</>标名开头和结尾
  • Attributes
    标签的属性,字典形式组织,格式:.attrs
    soup.a.attrs #获取a标签的所有属性(注意到格式是字典)
  • NavigableString
    标签包含的文字,即标签内非属性字符串,<>...</>中字符串,格式:.string
    soup.a.string # a标签的非属性字符串信息,表示尖括号之间的那部分字符串
    .string和.text的区别

.string可以返回当前节点中的内容,但是当前节点包含子节点时,.string不知道要获取哪一个节点中的内容,故返回空
.text(或者.get_text())可以返回当前节点所包含的所有文本内容,包括当前节点的子孙节点
举例使用.text的情况:

  1. "中国中车( 601766 )"
  2. (+49) 228 251 927
  • Comment
    一种特殊的NavigableString类型,当标签中的NavigableString被注释时,则定义为该类型

标签搜索和过滤

基本方法

标签搜索有find_all() 和find() 两个基本的搜索方法,find_all() 方法会返回所有匹配关键字的标签列表,find()方法则只返回一个匹配结果

# 返回一个标签名为"a"的Tag
soup.find("a")
# 返回所有tag 列表
soup.find_all("a")
##    find_all方法可被简写
soup("a")
# 找出所有以b开头的标签
for tag in soup.find_all(re.compile("^b")):
  print(tag.name)
#找出列表中的所有标签
soup.find_all(["a", "p"])
# 查找标签名为p,class属性为"title"
soup.find_all("p", "title")
# 查找属性id为"link2"
soup.find_all(id="link2")
# 查找存在属性id的
soup.find_all(id=True)
# 设置多个筛选的属性
soup.find_all(href=re.compile("elsie"), id='link1')
# 通过 find_all() 方法的 attrs 参数定义一个字典参数来搜索包含特殊属性的tag
#    有些tag属性在搜索不能使用,比如HTML5中的 data-* 属性,同时name由于已经是find_all()方法中的一个参数名(代表tag的名字),
#    所以也不可通过tag中的name属性来搜索tag,但是可以通过 find_all() 方法的 attrs 参数定义一个字典参数来搜索包含特殊属性的tag
soup.find_all(attrs={"data-foo": "value"})
# 查找标签文字包含"sisters"
soup.find(string=re.compile("sisters"))
# 获取指定数量的结果
soup.find_all("a", limit=2)
# 自定义匹配方法
def has_class_but_no_id(tag):
  return tag.has_attr('class') and not tag.has_attr('id')
soup.find_all(has_class_but_no_id)
# 仅对属性使用自定义匹配方法
def not_lacie(href):
    return href and not re.compile("lacie").search(href)
soup.find_all(href=not_lacie)
# 调用tag的find_all()方法时,BeautifulSoup会检索当前tag的所有子孙节点,如果只想搜索tag的直接子节点,可以使用参数recursive=False 
soup.find_all("title", recursive=False)

扩展方法

# 所有父辈节点
ind_parents()
# 第一个父辈节点
find_parent()
# 之后的所有兄弟节点
find_next_siblings()
# 之后的第一个兄弟节点
find_next_sibling()
# 之前的所有兄弟节点
find_previous_siblings()
# 之前的第一个兄弟节点
find_previous_sibling()
# 之后的所有元素
find_all_next()
# 之后的第一个元素
find_next()
# 之前的所有元素
find_all_previous()
# 之前的第一个元素
find_previous()

CSS选择器

BeautifulSoup支持大部分的CSS选择器, 在Tag或BeautifulSoup对象的.select()方法中传入字符串参数, 即可使用CSS选择器的语法找到tag
标签直接写名称,class则用"."号加名称,id则使用"#"号加名称

# 所有a标签
soup.select("a")
# 逐层查找
soup.select("body a")
soup.select("html head title")
# tag标签下的直接子标签
soup.select("head > title")
soup.select("p > #link1")
# 所有匹配标签之后的兄弟标签
soup.select("#link1 ~ .sister")
# 匹配标签之后的第一个兄弟标签
soup.select("#link1 + .sister")
# 根据calss类名
soup.select(".sister")
soup.select("[class~=sister]")
# 根据id查找
soup.select("#link1")
soup.select("a#link1")
# 根据多个id查找
soup.select("#link1,#link2")
# 根据属性查找
soup.select('a[href]')
# 根据属性值查找
soup.select('a[href^="http://example.com/"]')
soup.select('a[href$="tillie"]')
soup.select('a[href*=".com/el"]')
# 只获取一个匹配结果
soup.select(".sister", limit=1)
# 只获取一个匹配结果
soup.select_one(".sister")

标签对象方法

标签属性

# 获取所有的 p标签对象
tags = soup.find_all("p")
# 获取第一个p标签对象
tag = soup.p
# 输出标签类型 
type(tag)
# 标签名
tag.name
# 标签属性
tag.attrs
# 标签属性class 的值
tag['class']
# 标签包含的文字内容,对象NavigableString 的内容
tag.string
# 返回标签内所有的文字内容
for string in tag.strings:
  print(repr(string))
# 返回标签内所有的文字内容, 并去掉空行
for string in tag.stripped_strings:
  print(repr(string))
# 获取到tag中包含的所有及包括子孙tag中的NavigableString内容,并以Unicode字符串格式输出
tag.get_text()
##    以"|"分隔
tag.get_text("|")
##    以"|"分隔,不输出空字符
tag.get_text("|", strip=True)
# 获取过滤出的a标签中href的链接
tag.select('a[target="_blank"]')[0].get("href")
# 获取子节点
##    返回第一层子节点的列表
tag.contents
##    返回第一层子节点的listiterator对象
tag.children 
for child in tag.children:
  print(child)
##    递归返回所有子节点
tag.descendants 
for child in tag.descendants:
  print(child)

获取父节点

# 返回第一层父节点标签
tag.parent 
# 递归得到元素的所有父辈节点
tag.parents 
##    父辈节点为空则打印父节点标签,否则打印名字
for parent in tag.parents:
  if parent is None:
    print(parent)
  else:
    print(parent.name)

获取兄弟节点

# 下一个兄弟元素
tag.next_sibling 
# 当前标签之后的所有兄弟元素
tag.next_siblings
for sibling in tag.next_siblings:
  print(repr(sibling))
# 上一个兄弟元素
tag.previous_sibling
# 当前标签之前的所有兄弟元素
tag.previous_siblings
for sibling in tag.previous_siblings:
  print(repr(sibling))

元素的遍历

BeautifulSoup中把每个tag定义为一个元素element,每个元素element被自上而下的在HTML中排列,可以通过遍历命令逐个显示标签

# 当前标签的下一个元素
tag.next_element
# 当前标签之后的所有元素
for element in tag.next_elements:
  print(repr(element))
# 当前标签的前一个元素
tag.previous_element
# 当前标签之前的所有元素
for element in tag.previous_elements:
  print(repr(element))

修改标签属性

soup = BeautifulSoup('<b  name="test" class="boldest">Extremely bold</b>')
# 获取b标签对象
tag = soup.b
# 修改对象标签的名字
tag.name = "blockquote"
# 修改对象的class
tag['class'] = 'verybold'
# 修改对象的id
tag['id'] = 1
# 修改对象中的字符串(标签内容)
tag.string = "New link text."
print(tag)
#  <blockquote class="verybold" id="1">New link text.</blockquote>

修改标签内容

soup = BeautifulSoup('<b class="boldest">Extremely bold</b>',"html.parser")
tag = soup.b
# 修改标签中字符串
tag.string = "New link text."

添加标签内容

# 直接使用append对标签a对象进行操作
from bs4 import BeautifulSoup
soup = BeautifulSoup("<a>Foo</a>", "html.parser")
tag = soup.a
tag.append("Bar")
print(tag)
# 或者使用NavigableString,对整个soup对象进行操作
from bs4 import NavigableString
soup = BeautifulSoup("<a>Foo</a>", "html.parser")
new_string = NavigableString("Bar")
tag.append(new_string)
print(tag)

添加注释

注释是一个特殊的NavigableString对象,可以通过append()方法进行添加

from bs4 import BeautifulSoup
from bs4 import Comment
soup=BeautifulSoup("<a>Foo</a>", "html.parser")
new_comment=soup.new_string("Nice to see you.", Comment)
soup.append(new_comment)
print(soup)

添加标签

添加标签方法有两种,一种是在指定标签的内部添加(append方法),另一种是在指定位置添加(insert、insert_before、insert_after方法)

append方法
soup=BeautifulSoup("<b></b>","html.parser")
tag=soup.b
new_tag=soup.new_tag("a", href="http://www.example.com",rel="external nofollow" )
new_tag.string = "Link text."
tag.append(new_tag)
print(tag)
insert方法

在当前标签子节点列表的指定位置插入对象(Tag或NavigableString)

soup = BeautifulSoup('<b><a href="http://example.com/" rel="external nofollow">I linked to<i>example.com</i></a></b>',"html.parser")
tag = soup.a
tag.insert(1, ",but did not endorse ")
print(tag)
insert_before() 和 insert_after()

在当前标签之前或之后的兄弟节点添加元素

soup = BeautifulSoup('<b><a href="http://example.com/" rel="external nofollow">I linked to<i>example.com</i></a></b>',"html.parser")
tag = soup.new_tag("i")
tag.string = "Don't"
soup.b.insert_before(tag)
print(soup)
wrap() 和 unwrap()

可以对指定的tag元素进行包装或解包,并返回包装后的结果

# 添加包装
##      对字符串添加标签b
soup=BeautifulSoup("<p>I wish I was bold.</p>","html.parser")
soup.p.string.wrap(soup.new_tag("b"))
print(soup)
##      对p标签外添加div标签
soup.p.wrap(soup.new_tag("div"))
print(soup)
# 拆解包装
##      去除div标签
soup.div.unwrap()
print(soup)

删除标签

soup = BeautifulSoup('<b><a href="http://example.com/" rel="external nofollow">I linked to<i>example.com</i></a></b>',"html.parser")
# 将i标签之外的所有标签删除,并返回为i_tag
i_tag=soup.i.extract()
print(i_tag)
print(soup)
# 将当前标签a及所有子节点从soup 中移除,无返回。
soup.a.decompose()
print(soup)

soup = BeautifulSoup('<b><a href="http://example.com/" rel="external nofollow">I linked to<i>example.com</i></a></b>',"html.parser")
# 将当前标签替换为指定的元素
tag = soup.i
new_tag = soup.new_tag("p")
new_tag.string = "Don't"
tag.replace_with(new_tag)
print(soup)
# 清除当前标签的所有子节点,即删除b标签所有内容
soup.b.clear()
print(soup)

其他方法

格式化输出

使用BeautifulSoup解析后,文档都被转换成了unicode,特殊字符也被转换为unicode,如果将文档转换成字符串,unicode编码会被编码成utf8.这样就无法正确显示HTML特殊字符了。 使用unicode时,BeautifulSoup还会智能的把“引号”转换成HTML或XML中的特殊字符

soup = BeautifulSoup('<b><a href="http://example.com/" rel="external nofollow">I linked to<i>example.com</i></a></b>',"html.parser")
tag = soup.a
# 格式化输出
print(tag.prettify())
print(tag.prettify("latin-1"))

文档编码

使用BeautifulSoup解析后,文档都被转换成了unicode,其使用了“编码自动检测”子库来识别当前文档编码并转换成unicode编码

from bs4 import BeautifulSoup #,"html.parser"

html='<b><a href="http://example.com/" rel="external nofollow">I linked to<i>example.com</i></a></b>'
# 默认编码
soup = BeautifulSoup(html)
print(soup.original_encoding) #告警可忽视

# 手动指定文档的编码
soup = BeautifulSoup(html, from_encoding="iso-8859-8")
print(soup.original_encoding) #告警可忽视

# 为提高“编码自动检测”的检测效率,也可以预先排除一些编码
soup = BeautifulSoup(html, exclude_encodings=["iso-8859-8"]) #告警可忽视


# 通过BeautifulSoup输出文档时, 不管输入文档是什么编码方式, 默认输出编码a均为utf8编码文档解析器
# BeautifulSoup目前支持的有 “lxml”(pip install lxml)、 “html5lib”(pip install html5lib)、 “html.parser”
soup = BeautifulSoup("<a><b /></a>")
print(soup)     # 输出: <html><body><a><b></b></a></body></html>
soup = BeautifulSoup("<a></p>", "lxml")
print(soup)     # 输出: <html><body><a></a></body></html>
soup = BeautifulSoup("<a></p>", "html5lib")
print(soup)     # 输出: <html><head></head><body><a><p></p></a></body></html>
soup = BeautifulSoup("<a></p>", "html.parser")
print(soup)     # 输出: <a></a>

实例

kanguowai网站所有分类网址离线爬取

from bs4 import BeautifulSoup
import csv

Webs=[["索引","名称","国家","类型","网址","描述"]]
for webindex in range(2,12133): #range(2,12133)
    try:
        html=BeautifulSoup(open("./site/{webindex}.html".format(webindex=webindex),'r',encoding="utf-8"),"html.parser")
    except:
        print(webindex)  # 打印空的web索引
        WebName="Null"
        WebCountry="Null"
        WebType="Null"
        WebLink="Null"
        WebDesc="Null"
    try:
        WebName=html.select('.baseinfo li h1')[0].text
    except:
        WebName = "Null"
    try:
        WebCountry=html.select('.baseinfo .linfo a')[0].text
    except:
        WebCountry="Null"
    try:
        WebType=html.select('.baseinfo .rinfo a')[0].text
    except:
        WebType="Null"
    try:
        WebLink=html.select('.baseinfo .siteurl a')[0].text
    except:
        WebLink="Null"
    try:
        WebDesc=html.select('.sitetext p')[0].text
    except:
        WebDesc="Null"
    Webs.append([webindex,WebName,WebCountry,WebType,WebLink,WebDesc])
# 写入csv
with open("webs.csv","w",newline='',encoding="utf-8") as f:
    out=csv.writer(f)
    for row in Webs:
        out.writerow(row)

kanguowai网站所有分类网址在线爬取

from bs4 import BeautifulSoup
import urllib.request
import csv

# 变量声明:
# 索引 webindex
# 名称 WebName
# 国家 WebCountry
# 类型 WebType
# 网站 WebLink
# 描述 WebDesc
Webs=[["索引","名称","国家","类型","网址","描述"]]
for webindex in range(2,12133): #range(2,12133)
    try:
        html=BeautifulSoup(urllib.request.urlopen("https://www.kanguowai.com/site/{webindex}.html".format(webindex=webindex)).read().decode("utf-8"),"html.parser")
    except:
        print(webindex)  # 打印空的web索引
        WebName="Null"
        WebCountry="Null"
        WebType="Null"
        WebLink="Null"
        WebDesc="Null"
    try:
        WebName=html.select('.baseinfo li h1')[0].text
    except:
        WebName = "Null"
    try:
        WebCountry=html.select('.baseinfo .linfo a')[0].text
    except:
        WebCountry="Null"
    try:
        WebType=html.select('.baseinfo .rinfo a')[0].text
    except:
        WebType="Null"
    try:
        WebLink=html.select('.baseinfo .siteurl a')[0].text
    except:
        WebLink="Null"
    try:
        WebDesc=html.select('.sitetext p')[0].text
    except:
        WebDesc="Null"
    Webs.append([webindex,WebName,WebCountry,WebType,WebLink,WebDesc])
# 写入csv
with open("websbuff.csv","w",newline='',encoding="utf-8") as f:
    out=csv.writer(f)
    for row in Webs:
        out.writerow(row)

egouz所有领事馆离线爬取

# 领事馆
from bs4 import BeautifulSoup
import csv,os

htmls=os.listdir("./consulate/yes")
Consulates=[["名称","机构类型","联系邮箱","联系电话","传真地址","工作时间","网站","大使(机构负责人)","联系地址"]]
for html in htmls:
    htmlitem=BeautifulSoup(open("./consulate/yes/{html}".format(html=html), 'r', encoding="utf-8"), "html.parser").select('.website-info .item-list .item')
    try:
        # 获取领事馆名称
        Name=htmlitem[0].select(".btn-quick")[0].text
        # 获取了领事馆类型
        Type=htmlitem[1].select(".text")[0].text
        Email=htmlitem[1].select(".text")[1].text
        Call=htmlitem[2].select(".text")[0].text
        Fax=htmlitem[2].select(".text")[1].text
        WorkTime=htmlitem[3].select(".text")[0].text
        Web=htmlitem[3].select(".text")[1].text
        People=htmlitem[4].select(".text")[0].text
        Address=htmlitem[5].select(".text")[0].text
        Consulates.append([Name,Type,Email,Call,Fax,WorkTime,Web,People,Address])
    except:
        print(html)
# 写入csv
with open("领事馆.csv","w",newline='',encoding="utf-8") as f:
    out=csv.writer(f)
    for row in Consulates:
        out.writerow(row)

egouz所有大使馆离线爬取

# 大使馆
from bs4 import BeautifulSoup
import csv,os

htmls=os.listdir("./embassy/yes")
Consulates=[["名称","机构类型","联系邮箱","联系电话","传真地址","工作时间","网站","大使(机构负责人)","联系地址"]]
for html in htmls:
    htmlitem=BeautifulSoup(open("./embassy/yes/{html}".format(html=html), 'r', encoding="utf-8"), "html.parser").select('.website-info .item-list .item')
    try:
        # 获取领事馆名称
        Name=htmlitem[0].select(".btn-quick")[0].text
        # 获取了领事馆类型
        Type=htmlitem[1].select(".text")[0].text
        Email=htmlitem[1].select(".text")[1].text
        Call=htmlitem[2].select(".text")[0].text
        Fax=htmlitem[2].select(".text")[1].text
        WorkTime=htmlitem[3].select(".text")[0].text
        Web=htmlitem[3].select(".text")[1].text
        People=htmlitem[4].select(".text")[0].text
        Address=htmlitem[5].select(".text")[0].text
        Consulates.append([Name,Type,Email,Call,Fax,WorkTime,Web,People,Address])
    except:
        print(html)
# 写入csv
with open("大使馆.csv","w",newline='',encoding="utf-8") as f:
    out=csv.writer(f)
    for row in Consulates:
        out.writerow(row)

参考链接:

Python下利用BeautifulSoup解析HTML的实现
python爬虫学习(一):BeautifulSoup库基础及一般元素提取方法