通过爬取下载斗图啦https://www.doutula.com/photo/list?page=0中的表情包,来练习爬虫

requests模块

获取网页

声明一个变量来引用requests的对象

response = requests.get(url, headers...)

此时输出response是一个html文档

<!DOCTYPE html>
<head>
        <link rel="stylesheet" type="text/css" href="//static.doutula.com/css/bootstrap.css?id=ecc6bb0e2e008ee4306a">
    <link rel="stylesheet" type="text/css" href="//static.doutula.com/css/main.css?id=583df7ef1aba1be8ec1b">
            <meta charset="UTF-8">
        <meta http-equiv="content-language" content="zh-CN">
        <meta name="renderer" content="webkit">
        <meta name="force-rendering" content="webkit">
        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
        <meta name="viewport" content="initial-scale=1, maximum-scale=3, minimum-scale=1, user-scalable=no">
        <link rel="shortcut icon" href="https://www.doutula.com/favicon.ico" type="image/x-icon">
        <meta name="HandheldFriendly" content="true">
        <meta name="apple-mobile-web-app-title" content="斗图啦">
        <meta name="apple-mobile-web-app-capable" content="yes">
        <meta name="format-detection" content="telphone=no, email=no">
        <meta name="keywords" content="斗图,斗图啦,斗图网,斗图大会,表情三巨头,蘑菇头,小学生,撕逼图片,搞笑斗图,熊猫表情,微信滑稽表情,猥琐表情,斗图大会开始了,斗图图片,斗图大赛,金馆长斗图表情,金馆长表情,金馆长斗图吧,金馆长qq表情包"/>
        <meta name="description" content="斗图啦,收集了成千上万的撕逼斗图表情包,在这里你可以快速找到想要的表情,通过在线表情制作可以快速生成自定义表情。"/>
        <title>最新斗图表情 - 斗图表情包 - 金馆长表情库 - 真正的斗图网站 - doutula.com</title>
        <meta http-equiv="Cache-Control" content="no-transform" />
        <meta http-equiv="Cache-Control" content="no-siteapp" />
        <style>
        @media  screen and (max-width: 768px)
        {
            .center-wrap.col-sm-9 {
                padding: 0;
            }

            .center-wrap.col-xs-12 {
                padding: 0;
            }
        }
        </style>
<script>
var _hmt = _hmt || [];
(function() {
  var hm = document.createElement("script");
  hm.src = "https://hm.baidu.com/hm.js?2fc12699c699441729d4b335ce117f40";
  var s = document.getElementsByTagName("script")[0];
  s.parentNode.insertBefore(hm, s);
})();
</script>
        <script type="text/javascript" src="//cpro.baidustatic.com/cpro/ui/cm.js" async="async" defer="defer" ></script>
        <script src="//dup.baidustatic.com/js/ds.js" async="async" defer="defer"></script>



    <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>-->
    <script>
        (adsbygoogle = window.adsbygoogle || []).push({
            google_ad_client: "ca-pub-8376044552838383",
            enable_page_level_ads: true
        });
    </script>
</head>
<body>
...
</body>
</html>

转换数据

通过BeautifulSoup是用来从HTMLXML中提取数据的Python

result = BeautifulSoup(response, "html.parser)

此时result这个变量就是一个经过转换的soup对象
分别输出responseresult的数据类型

<class 'str'>
<class 'bs4.BeautifulSoup'>

提取数据

创建一个变量data用了存放从result中提取出来的信息

[<img alt="等他说完在电他" class="img-responsive lazy image_dta" data-backup="http://img.doutula.com/production/uploads/image/2020/07/31/20200731175866_BnSbGs.png" data-original="http://img.doutula.com/production/uploads/image/2020/07/31/20200731175866_BnSbGs.png" referrerpolicy="no-referrer" src="//static.doutula.com/img/loader.gif?33"/>,..., <img alt="那耶@好吃到飞起" class="img-responsive lazy image_dta" data-backup="http://img.doutula.com/production/uploads/image/2020/07/30/20200730078726_eJqKRZ.jpg" data-original="http://img.doutula.com/production/uploads/image/2020/07/30/20200730078726_eJqKRZ.jpg" referrerpolicy="no-referrer" src="//static.doutula.com/img/loader.gif?33"/>]

是一个包含多个img标签的列表,列表是一个python数据结构

筛选数据

遍历data这个list,分别用img_nameimg_url去引用data中的属性altdata-backup

for imgs in data:
    img_name = imgs['alt']
    img_url = imgs['data-backup']
    img = requests.get(img_url)
    print("正在下载" + "  " + img_name + "  " + img_url)
    try:
        f = open("C://Users/Koyang/Desktop/表情包/%s.png" % img_name, 'wb')
        f.write(img.content)
    except Exception as result:
        print(result)

到目前为止就已经完成了对斗图啦的表情包的爬取和批量下载

附上完整的代码

# --*-- coding : utf-8 --*--
# @Author : Koyang 
import requests
from bs4 import BeautifulSoup


class Doutu:
    def __init__(self):
        self.url = "https://www.doutula.com/photo/list?page=0"
        self.headers = {
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36"}

    def get_html(self):
        response = requests.get(self.url, headers=self.headers).text
        result = BeautifulSoup(response, "html.parser")
        data = result.find_all(attrs={"alt": True})

        # 对数据进行筛选和下载
        for imgs in data:
            img_name = imgs['alt']
            img_url = imgs['data-backup']
            img = requests.get(img_url)
            print("正在下载" + "  " + img_name + "  " + img_url)
            try:
                f = open("C://Users/Koyang/Desktop/表情包/%s.png" % img_name, 'wb')
                f.write(img.content)
            except Exception as result:
                print(result)


if __name__ == "__main__":
    test = Doutu()
    test.get_html()

附页

Beautiful解析器的区别

解析器 使用方法 优势 劣势
Python标准库 BeautifulSoup(markup, “html.parser”) Python的内置标准库执行速度适中文档容错能力强 Python 2.7.3 or 3.2.2)前 的版本中文档容错能力差
lxml HTML 解析器 BeautifulSoup(markup, “lxml”) 速度快文档容错能力强 需要安装C语言库
lxml XML 解析器 BeautifulSoup(markup, [“lxml”, “xml”])BeautifulSoup(markup, “xml”) 速度快唯一支持XML的解析器 需要安装C语言库
html5lib BeautifulSoup(markup, “html5lib”) 最好的容错性以浏览器的方式解析文档生成HTML5格式的文档 速度慢不依赖外部扩展

BeautifulSoup函数讲解

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

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

1)name 参数

name 参数可以查找所有名字为 name 的tag,字符串对象会被自动忽略掉

A.传字符串

最简单的过滤器是字符串.在搜索方法中传入一个字符串参数,Beautiful Soup会查找与字符串完整匹配的内容,下面的例子用于查找文档中所有的标签

12 soup.find_all(‘b’)# [The Dormouse’s story]
12 print soup.find_all(‘a’)#[, Lacie, Tillie]

B.传正则表达式

如果传入正则表达式作为参数,Beautiful Soup会通过正则表达式的 match() 来匹配内容.下面例子中找出所有以b开头的标签,这表示和标签都应该被找到

12345 import refor tag in soup.find_all(re.compile(“^b”)): print(tag.name)# body# b

C.传列表

如果传入列表参数,Beautiful Soup会将与列表中任一元素匹配的内容返回.下面代码找到文档中所有标签和标签

12345 soup.find_all([“a”, “b”])# [The Dormouse’s story,# Elsie,# Lacie,# Tillie]

D.传 True

True 可以匹配任何值,下面代码查找到所有的tag,但是不会返回字符串节点

1234567891011 for tag in soup.find_all(True): print(tag.name)# html# head# title# body# p# b# p# a# a

E.传方法

如果没有合适过滤器,那么还可以定义一个方法,方法只接受一个元素参数 [4] ,如果这个方法返回 True 表示当前元素匹配并且被找到,如果不是则反回 False

下面方法校验了当前元素,如果包含 class 属性却不包含 id 属性,那么将返回 True:

12 def has_class_but_no_id(tag): return tag.has_attr(‘class’) and not tag.has_attr(‘id’)

将这个方法作为参数传入 find_all() 方法,将得到所有

标签:

1234 soup.find_all(has_class_but_no_id)# [

The Dormouse’s story

,#

Once upon a time there were…

,#

]

2)keyword 参数

注意:如果一个指定名字的参数不是搜索内置的参数名,搜索时会把该参数当作指定名字tag的属性来搜索,如果包含一个名字为 id 的参数,Beautiful Soup会搜索每个tag的”id”属性

12 soup.find_all(id=’link2’)# [Lacie]

如果传入 href 参数,Beautiful Soup会搜索每个tag的”href”属性

12 soup.find_all(href=re.compile(“elsie”))# [Elsie]

使用多个指定名字的参数可以同时过滤tag的多个属性

12 soup.find_all(href=re.compile(“elsie”), id=’link1’)# [three]

在这里我们想用 class 过滤,不过 class 是 python 的关键词,这怎么办?加个下划线就可以

1234 soup.find_all(“a”, class_=”sister”)# [Elsie,# Lacie,# Tillie]

有些tag属性在搜索不能使用,比如HTML5中的 data-* 属性

123 data_soup = BeautifulSoup(‘
foo!
‘)data_soup.find_all(data-foo=”value”)# SyntaxError: keyword can’t be an expression

但是可以通过 find_all() 方法的 attrs 参数定义一个字典参数来搜索包含特殊属性的tag

12 data_soup.find_all(attrs={“data-foo”: “value”})# [
foo!
]

3)text 参数

通过 text 参数可以搜搜文档中的字符串内容.与 name 参数的可选值一样, text 参数接受 字符串 , 正则表达式 , 列表, True

12345678 soup.find_all(text=”Elsie”)# [u’Elsie’] soup.find_all(text=[“Tillie”, “Elsie”, “Lacie”])# [u’Elsie’, u’Lacie’, u’Tillie’] soup.find_all(text=re.compile(“Dormouse”))[u”The Dormouse’s story”, u”The Dormouse’s story”]

4)limit 参数

find_all() 方法返回全部的搜索结构,如果文档树很大那么搜索会很慢.如果我们不需要全部结果,可以使用 limit 参数限制返回结果的数量.效果与SQL中的limit关键字类似,当搜索到的结果数量达到 limit 的限制时,就停止搜索返回结果.

文档树中有3个tag符合搜索条件,但结果只返回了2个,因为我们限制了返回数量

123 soup.find_all(“a”, limit=2)# [Elsie,# Lacie]

5)recursive 参数

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

一段简单的文档:

1234567 The Dormouse’s story

是否使用 recursive 参数的搜索结果:

12345 soup.html.find_all(“title”)# [The Dormouse’s story] soup.html.find_all(“title”, recursive=False)# []

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

它与 find_all() 方法唯一的区别是 find_all() 方法的返回结果是值包含一个元素的列表,而 find() 方法直接返回结果

(3)find_parents() find_parent()

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

(4)find_next_siblings() find_next_sibling()

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

(5)find_previous_siblings() find_previous_sibling()

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

(6)find_all_next() find_next()

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

(7)find_all_previous() 和 find_previous()

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

注:以上(2)(3)(4)(5)(6)(7)方法参数用法与 find_all() 完全相同,原理均类似,在此不再赘述。

CSS选择器

我们在写 CSS 时,标签名不加任何修饰,类名前加点,id名前加 #,在这里我们也可以利用类似的方法来筛选元素,用到的方法是 soup.select(),返回类型是 list

(1)通过标签名查找

12 print soup.select(‘title’) #[The Dormouse’s story]
12 print soup.select(‘a’)#[, Lacie, Tillie]
12 print soup.select(‘b’)#[The Dormouse’s story]

(2)通过类名查找

12 print soup.select(‘.sister’)#[, Lacie, Tillie]

(3)通过 id 名查找

12 print soup.select(‘#link1’)#[]

(4)组合查找

组合查找即和写 class 文件时,标签名与类名、id名进行的组合原理是一样的,例如查找 p 标签中,id 等于 link1的内容,二者需要用空格分开

12 print soup.select(‘p #link1’)#[]

直接子标签查找

12 print soup.select(“head > title”)#[The Dormouse’s story]

(5)属性查找

查找时还可以加入属性元素,属性需要用中括号括起来,注意属性和标签属于同一节点,所以中间不能加空格,否则会无法匹配到。

12 print soup.select(‘a[class=”sister”]’)#[, Lacie, Tillie]
12 print soup.select(‘a[href=”http://example.com/elsie"]')#[]

同样,属性仍然可以与上述查找方式组合,不在同一节点的空格隔开,同一节点的不加空格

12 print soup.select(‘p a[href=”http://example.com/elsie"]')#[]

以上的 select 方法返回的结果都是列表形式,可以遍历形式输出,然后用 get_text() 方法来获取它的内容。

123456 soup = BeautifulSoup(html, ‘lxml’)print type(soup.select(‘title’))print soup.select(‘title’)[0].get_text() for title in soup.select(‘title’): print title.get_text()

好,这就是另一种与 find_all 方法有异曲同工之妙的查找方法,是不是感觉很方便?