zoukankan      html  css  js  c++  java
  • python爬虫(一)---BeautufulSoup

    一、介绍

    Beautiful Soup 是 python 的一个库,最主要的功能是从网页抓取数据。官方解释如下:

    Beautiful Soup 提供一些简单的、python 式的函数用来处理导航、搜索、修改分析树等功能。它是一个工具箱,通过解析文档为用户提供需要抓取的数据,因为简单,所以不需要多少代码就可以写出一个完整的应用程序。 Beautiful Soup 自动将输入文档转换为 Unicode 编码,输出文档转换为 utf-8 编码。你不需要考虑编码方式,除非文档没有指定一个编码方式,这时,Beautiful Soup 就不能自动识别编码方式了。然后,你仅仅需要说明一下原始编码方式就可以了。 Beautiful Soup 已成为和 lxml、html6lib 一样出色的 python 解释器,为用户灵活地提供不同的解析策略或强劲的速度。

    二、安装

    pip install beautifulsoup4

    三、使用

    以一个简单例子讲解beautifulsoup使用。按F12或是右键单击选择检查可以打开开发者工具查看页面html,以下是网页

    BeautifulSoup的使用
    from bs4 import BeautifulSoup
    import requests
    
    url = "https://python123.io/ws/demo.html"
    
    r = requests.get(url)
    text = r.text
    
    soup = BeautifulSoup(text, "html.parser")
    print(soup.prettify())   #打印美化过的html代码
    
    print(soup.title)   #打印title标签下的内容
    print(soup.body)  #打印body标签下的内容
    print(soup.p)   #只打印了第一个p标签下的内容
    print(soup.find_all("p"))   #打印所有p标签,返回一个列表
    print(soup.find("p"))    #默认打印第一个p标签
    print(soup.find("p",class_ = "course")) #根据属性找到第二个p标签,因为class是pyhon关键字,所以需要加上_区分
    
    print(soup.find("a",id = "link2"))   #找到id=“link2”的a标签

    四、四大对象

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

    • Tag
    • NavigableString
    • BeautifulSoup
    • Comment

    1.Tag对象

    1)soup加上标签可以找到该标签的内容,但只能获取所有内容的第一个符合的标签

    title = soup.title
    print(title)
    p = soup.p
    print(p)  #2个p标签只打印了第一个
    print(type(p))   #返回的类型

     2)Tag的两个重属性,是 name 和 attrs

    print(soup.name)   #[document]
    print(soup.title.name)   #打印标签名称,返回一个str
    print(soup.p.name)
    # print(type(soup.p.name))    #<class 'str'>
    
    print(soup.title.attrs)   #打印标签属性,返回一个字典
    print(soup.p.attrs)   #<class 'dict'>
    # print(type(soup.p.attrs)) 

     3)获取属性值

    #2种方法获取属性值,返回一个列表
    print(soup.p["class"])   
    print(soup.p.get("class"))
    print(type(soup.p.get("class"))) 

     4)修改属性内容或删除

    soup.p["class"] = "newtitle"  #修改属性值
    print(soup.p)
    soup.p.string = "newstring"  #修改标签的内容;b标签也没有了
    print(soup.p)
    del soup.p["class"]    #删除属性
    print(soup.p)
    # del soup.p.text   #报错AttributeError: can't delete attribute;del soup.p.string一样

     

    2.NavigableString 标签内文本内容

    # string和text都可以获取标签的文本内容,但是类型不一样
    print(soup.p.string)
    # print(type(soup.p.string))   #<class 'bs4.element.NavigableString'>
    print(soup.p.text)
    # print(type(soup.p.text))   #<class 'str'>

     跨级获取文本内容

    #head下面有一个title标签,获取title标签的文本
    print(soup.head.string)
    print(soup.head.text)
    #第二个p标签下面有文本,还有2个a标签
    p2 = soup.find("p", class_ = "course")
    print(p2.string)  #None
    print("--------")
    print(p2.text)  #p标签下的所有文本

     

    3.BeautifulSoup 

    BeautifulSoup对象表示的是一个文档的全部内容。大部分时候,可以把它当作 Tag 对象,是一个特殊的 Tag,我们可以获取它的类型,名称,以及属性

    print(soup)
    print("-----------------------------------------------------------")
    print(soup.prettify())
    print("-----------------------------------------------------------")
    print(type(soup))
    print(soup.name)
    print(soup.attrs)

     

    4.Comment

    Comment 对象是一个特殊类型的 NavigableString 对象,其输出的内容不包括注释符号

    f = open("demo.html", "r", encoding = "UTF-8")   #不加encoding报错 UnicodeDecodeError: 'gbk' codec can't decode byte 0x80 in position 250: illegal multibyte sequence
    text = f.read()
    f.close()
    soup = BeautifulSoup(text, "html.parser")
    print(soup)
    print("---------------------------------------------")
    p2 = soup.find("p", class_ = "comment")   
    print(p2.attrs)
    print("---------------------------------------------")
    print(p2.string) #这个P标签的内注释要直接跟在p标签后,如果换行后再写注释,会返回None
    print(type(p2.string))
    print("---------------------------------------------")
    print(p2.text)   #打印空行

     p标签里的内容实际上是注释,但是如果我们利用 .string 来输出它的内容,我们发现它已经把注释符号去掉了,所以这可能会给我们带来不必要的麻烦。 另外我们打印输出下它的类型,发现它是一个 Comment 类型,所以,我们在使用前最好做一下判断

    import bs4
    
    if type(p2.string) == bs4.element.Comment:
        print(p2.string)    # 这是一个注释

    五、遍历文档树

    1.直接子节点

    • contents   #返回一个列表
    • children    #返回一个列表迭代器
    print(soup.body.contents)   #返回一个列表   返回的列表中每个p标签前后都有一个
    ?
    print(soup.body.children)   #返回一个列表迭代器
    for i in soup.body.children:   #返回的列表中每个p标签前后都有一个
    ?
      print(i)

     2.所有子孙节点

    • .descendants 可以对所有 tag 的子孙节点进行递归循环,返回一个生成器对象,我们需要遍历获取其中的内容
    print(soup.body.descendants)   #<generator object descendants at 0x0000020755C44C50>
    for tag in soup.body.descendants:
        print(tag)

     3.多个内容

    • strings   #获取标签下的所有文本内容,返回一个生成器
    • stripped_strings   #获取标签下的所有文本内容,返回一个生成器,strings方法获取的文本内容可能会有空行和空格,stripped_strings会删除所有的空白内容
    print(soup.body.string)   #None  body下面有多个标签
    print(soup.body.strings)  #返回一个生成器
    for s in soup.body.strings:
        print(s) 
    
    print("________________________________________________")
    print(soup.body.stripped_strings)   #返回一个生成器
    for s in soup.body.stripped_strings:
        print(s)

     4.父节点

    • parent  #直接父节点,返回一个Tag对象,可以获取其.name,.attrs等值
    • parents   #返回所有的父节点(父节点的父节点直至整个soup对象),返回一个生成器对象
    print(soup.a.parent)
    print(type(soup.a.parent))
    print(soup.a.parent.name)
    print(soup.a.parent.attrs)
    print("________________________________________________")
    print(soup.a.parents)
    print(type(soup.a.parents))
    for i in soup.a.parents:
        print(i)
        print(i.name)

    5.兄弟节点

    兄弟节点可以理解为与本节点处在同一级的节点,.next_sibling 属性获取了该节点的下一个兄弟节点,.previous_sibling 则与之相反,如果节点不存在,则返回 None。 注意:实际文档中的 tag 的 .next_sibling 和 .previous_sibling 属性通常是字符串或空白,因为空白或者换行也可以被视作一个节点,所以得到的结果可能是空白或者换行

    • next_siblilng  #后一个兄弟节点,返回Tag对象或是NavigableString对象
    • previous_sibling   #前一个兄弟节点,返回Tag对象或是NavigableString对象
    • next_siblings    #后面的所有兄弟节点,返回一个生成器
    • previuos_sibilings   #前面的所有兄弟节点,返回一个生成器
    print("____________1_________________")
    print(soup.p.next_sibling)
    print("____________2_________________")
    print(soup.a.next_sibling)
    print("____________3_________________")
    print(type(soup.a.next_sibling))
    print("____________4_________________")
    print(soup.p.previous_sibling)
    print("____________5_________________")
    print(soup.a.previous_sibling)
    print("____________6_________________")
    print(soup.p.next_siblings)  #后面的所有兄弟节点
    for i in soup.p.next_siblings:    #打印了2个空行和第二个p
        print(i)
        print(type(i))

    6.前后节点

    • next_element     #后一个节点
    • previous_element    #前一个节点
    • next_elements   #后面的所有节点
    • previous_elements    #前面的所有节点
    print("---------------1-------------------")
    print(soup.head.next_element)
    print(type(soup.head.next_element))
    print("---------------2-------------------")
    print(soup.title.next_element)
    print("---------------3-------------------")
    print(soup.title.string.next_element)
    print("---------------4-------------------")
    print(soup.p.next_element)
    print("---------------5-------------------")
    print(soup.head.previous_element)
    print("---------------6-------------------")
    print(soup.a.previous_element)
    print("---------------7-------------------")
    print(soup.head.next_elements)
    for i in soup.head.next_elements:
        print(i)
    print("---------------8-------------------")
    print(soup.a.previous_elements)
    for i in soup.a.previous_elements:
        print(i)

     

     

     

    7.关于 和空行问题

    soup = BeautifulSoup(open("demo2.html",encoding = "UTF-8"), "html.parser")
    print(soup)

    #没有空行的
    print(soup.body.contents) 
    print("----------------------")
    for child in soup.body.children:
        print(child)
    print("----------------------")
    #有空行
    for child in soup.head.children:
        print(child)
    print("----------------------")
    print(soup.head.contents)

    六、搜索文档树

    1.方法

    • find_all()   #搜索当前 tag 的所有 tag 子节点,并判断是否符合过滤器的条件 ;返回所有符合条件的tag节点,返回一个列表
    • fjind()     #搜索当前tag的所有tag子节点,返回符合当前过滤条件的第一个tag
    • find_parent()   #搜索当前Tag的所有父节点,并判断是否符合过滤器的条件;返回符合当前过滤条件的第一个tag
    • find_parents()   #搜索当前Tag的所有父节点,并判断是否符合过滤器的条件 ;返回所有符合条件的tag节点,返回一个列表
    • find_next_sibling()  #搜索当前节点的后面所有兄弟节点,并判断是否符合过滤条件;返回符合当前过滤条件的第一个tag
    • find_next_siblings()   #搜索当前节点的后面所有兄弟节点,并判断是否符合过滤条件;返回所有符合条件的tag节点,返回一个列表
    • find_previous_sibling()   #搜索当前节点的后面所有兄弟节点,并判断是否符合过滤条件;返回符合当前过滤条件的第一个tag
    • find_previous_siblings()   #搜索当前节点的后面所有兄弟节点,并判断是否符合过滤条件;返回所有符合条件的tag节点,返回一个列表
    • find_next()   # .next_elements 属性对当前 tag 的之后的 tag 和字符串进行迭代;返回符合条件的点击一个节点
    • find_all_next()   #.next_elements 属性对当前 tag 的之后的 tag 和字符串进行迭代;返回所有符合条件的tag节点,返回一个列表
    • find_previous()   #.previous_elements 属性对当前节点前面的 tag 和字符串进行迭代;返回符合条件的点击一个节点
    • find_all_previous()   #.previous_elements 属性对当前节点前面的 tag 和字符串进行迭代;;返回所有符合条件的tag节点,返回一个列表
    print(soup.a.find_parent("body"))   #a节点的所有父节点中找body节点
    print(soup.a.find_parents())  #a节点的所有父节点(

    2.参数

    以上方法都可以用相同的参数,以find_all()为例

    find_all(name, attrs, recurssive, text, **kwargs)

    1)name参数, name 参数匹配tag.name, 字符串对象会被自动忽略掉

    • 传字符串,Beautiful Soup 会查找与字符串完整匹配的标签名称
    • 传正则表达式, 如果传入正则表达式作为参数,Beautiful Soup 会通过正则表达式的 match () 来匹配内容。下面例子中找出所有以 b 开头的标签
    • 传列表 ,如果传入列表参数,Beautiful Soup 会将与列表中任一元素匹配的内容返回
    • 传 True, True 可以匹配任何值,下面代码查找到所有的 tag, 但是不会返回字符串节点
    • 传方法,如果没有合适过滤器,那么还可以定义一个方法,方法只接受一个元素参数  , 如果这个方法返回 True 表示当前元素匹配并且被找到
    for tag in soup.find_all("b"):   #字符串参数
        print(tag)
    print("----------------------")
    for tag in soup.find_all(re.compile("b")):   #正则表达式参数
        print(tag.name)  #打印标签名称
    print("----------------------")
    for tag in soup.find_all(["a","b"]):   #列表参数
        print(tag)
    print("----------------------")
    for tag in soup.find_all(True):   #True参数
        print(tag.name)
    print("----------------------")
    def f(tag):  
        return tag.has_attr("class") and not tag.has_attr("id")  #tag有class属性但是没有id属性
    for tag in soup.find_all(f):   #自定义方法参数
        print(tag.name)

     2)attrs参数,attrs参数匹配tag.attrs

    for i in soup.find_all(class_ = "title"):  #根据class属性搜索
        print(i)
    print("----------------------")
    for i in soup.find_all(id = "link2"):  #根据id属性搜索
        print(i)
    print("----------------------")
    for i in soup.find_all(href = "http://www.icourse163.org/course/BIT-1001870001"):   #根据href属性搜索
        print(i)
    print("----------------------")
    soup = BeautifulSoup('<p test = "111">测试</p>',"html.parser")
    print(soup.find_all(attrs = {"test":"111"}))  #用于一些不能直接搜索的属性

    3)text 参数, 通过 text 参数可以搜搜文档中的字符串内容。与 name 参数的可选值一样,text 参数接受 字符串,正则表达式,列表,True;列表中的元素为NavigableString对象

    print(soup.find_all(text = "The demo"))  #需要完全匹配
    print("----------------------")
    s = soup.find_all(text = "The demo python introduces several python courses.")
    print(s)
    for i in s:
        print(i.parent)
        print(type(i))

    4)limit参数,如果我们不需要全部结果,可以使用 limit 参数限制返回结果的数量。当搜索到的结果数量达到 limit 的限制时,就停止搜索返回结果

    print(soup.find_all("p",limit = 1))

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

    print(soup.body.find_all("a"))
    print(soup.body.find_all("a",recursive = False))

    七、CSS选择器

    soup.select(),返回类型是 list;根据属性查找时,类名前加点,id 名前加 #

    soup.select_one(),返回符合筛选条件的第一个;返回一个tag对象

    1.根据标签名查找

    print(soup.select("p"))
    print("----------------------")
    print(soup.select_one("p"))

    2.根据属性查找

    print(soup.select(".title"))  #通过class属性查找
    print(soup.select("#link2"))   #通过id属性查找
    print(soup.select('[class = "title"]'))   #通过class属性查找

    3.组合查找

    print(soup.select("p .title"))  #只能匹配到p的子标签的属性,不能匹配到p标签自己的属性
    print(soup.select("p #link2"))
    print("----------------------")
    print(soup.select('p[class="title"]'))  #只能匹配到p标签自己的属性,不能匹配到子标签的属性
    print(soup.select('p[id="link2"]'))
    print("----------------------")
    print(soup.select('p>a'))   #2个标签必须是父子关系,不能是爷孙关系
    print(soup.select('body>a'))
    print("----------------------")
    print(soup.select('p a[id="link2"]'))   #组合使用

    八、例子

    1.爬取论语,并保存为TXT文件

     

    from bs4 import BeautifulSoup
    import requests
    
    baseurl = "https://www.shicimingju.com"
    text = requests.get(baseurl+"/book/lunyu.html").text
    
    soup = BeautifulSoup(text, "html.parser")
    book = soup.h1.text  #获取书名
    li_list = soup.find("div",class_ = "book-mulu").ul.find_all("li")  #目录所在的所有li标签
    for li in li_list:
        link = li.a.get("href")  #获取章节地址
        title = li.a.text   #获取章节名
        t = requests.get(baseurl+link).text
        soup2 = BeautifulSoup(t,"html.parser")
        p = soup2.find("div", class_ = "chapter_content").text
        with open ("E:\"+book+".txt", "a",encoding = "UTF-8") as f:   #如果不加encoding,会报错UnicodeEncodeError: 'gbk' codec can't encode character 'xXX' in position XX: illegal multibyte sequence
            f.write(title) #章节名
            f.write(p)  #写入章节内容
            f.write("
    ")   #一章空一行

    2.图片之家下载图片

    from bs4 import BeautifulSoup
    import requests
    
    url = "https://www.tupianzj.com/sheying/fengjing/"
    text = requests.get(url).text
    soup = BeautifulSoup(text, "html.parser")
    li_list = soup.find("ul", class_ = "list_con_box_ul").children #先根据class属性找到ul标签,再找儿子标签li
    print(list(li_list))   #打印出来的列表首尾有“
    ”
    for i in list(li_list)[1:-1]:   #去掉首尾的"
    "
        img_url = i.a.img.get("src")   #获取图片地址
        c = requests.get(img_url).content   #图片是字节码形式解析
        with open("E:\图片\"+img_url[-11:], "wb") as f:
            f.write(c)
    print("第一页图片下载完成")
  • 相关阅读:
    pycharm 对mysql的可视化操作
    pycharm连接linux创建django工程
    linux上安装pycharm
    pycharm激活码
    Windows下安装pip
    migrate设置
    python相对目录的基本用法(一)
    pycharm设置连接github
    在shell终端操作oracle数据库的常用命令
    在windows中把一个文件夹打成war包
  • 原文地址:https://www.cnblogs.com/he-202007/p/14008672.html
Copyright © 2011-2022 走看看