【Python基础】flask是一种爬虫框架吗

2021-01-08 10:00发布

3条回答
小磊子
2楼 · 2021-01-08 10:21

Flask是一个使用 Python 编写的轻量级 Web 应用框架。其 WSGI 工具箱采用 Werkzeug ,模板引擎则使用 Jinja2 。Flask使用 BSD 授权。

Flask也被称为 "microframework" ,因为它使用简单的核心,用 extension 增加其他功能。Flask没有默认使用的数据库、窗体验证工具。


kitidog2016
3楼 · 2021-01-11 18:31

Flask作为一个在Python领域较为出名的web框架,其页面构建采用了一种Python语法糖——修饰器,刚开始看到的时候,觉得Django简直是反Python之禅之大成!

铺垫

首先我们需要想一个经典的爬虫应用,然后再开始实现,然后瞬间就想到了各种爬虫入门都使用的妹子图例子(我为什么会瞬间想到?我明明很单纯的)。这个例子很简单,先打开一个页面,然后解析出所有图片的链接,最后利用链接保存图片。这个例子网上很多,函数的抽提以达到简化代码结构和复用的功效。但是这些重复的过程可能你在写下一个爬虫的时候还是会再写一次(如果你又一次引用了请别戳破,我只是觉得很少人会这样做,包括我),所以如果能够提取成一个库,那么这些工作就可以一劳永逸了。

开始构想

修饰器,其实际作用是将一个函数作为参数传入某个函数进行修饰,然后返回新的函数,此时再调用该函数,就是新的被修饰过的函数了。但是这样的理解不太适合于我们进行设计,打个不知道是否合适的比方,修饰器所需要传入的函数其实是大白胸前的那张卡,如果没有这张卡,大白就是不完整的,无法运行,但是这张卡插入了,大白就完整了,而且这张卡还决定了大白的属性。如此这般抽象到我们的想法当中,妹子图类似的爬虫里面,请求页面,保存图片这些操作都是一样的,就像大白充气的身体。而唯一不同的就是解析页面这个部分,可能这个网站的妹子图的链接在一个class的img标签内,但是另一个网站的妹子图在另一个class的img标签内,而这个解析的过程抽象出的函数,就是修饰器需要修饰的方法,即大白需要插入的卡,可能是红卡,可能是绿卡。来一张脑图

爬虫工作的基本流程

绿色的框框表示每次都是一样的操作,可以抽提为修饰器,而黄色的部分则是每次都不一样而需要修改的部分。

如果还不能理解,类比Flask框架,接收用户请求这部分可以看作我们这里请求页面这部分,而给用户返回结果的部分相当与我们保存图片这部分,中间唯一需要我们写的生成页面的部分就是我们这里的解析图片链接的部分,如果还不能理解,咳咳,直接上代码吧!

码代码

仔细想了想,我还是决定使用自顶向下的方法来讲一下这个代码,假使我们已经创建了一个我们理想中的爬虫框架,我们将其命名为spidry,其具有修饰器saveimages(类似Flask里面的app.route这样的东西)。

那么爬虫写出来如下:

# -*- coding:utf-8 -*-"""
@author: yangmqglobe
@file: test.py
@time: 2016/11/28
"""from spidry import saveimagesfrom spidry import response as respimport os


@saveimages(feature='json', sleep=3)# 使用修饰器修饰解析方法def bilibili():
    # 解析方法,生成包含需要保存图片url和路径的字典列表
    iconlist = [{'url': icon['icon'],
                 'path': 'icon/'+icon['title']+'.gif'}
                for icon in resp.json['fix']]
    return iconlistif __name__ == '__main__':
    if not os.path.exists("icon"):
        os.makedirs("icon")
    # 调用被修饰的方法!
    bilibili("http://www.bilibili.com/index/index-icon.json")
    print("done!")

如果写过Flask应用的童鞋应该对这样的语法应用不会很陌生,这里的response对象就是我们这个框架自动根据请求页面生成的请求返回对象,已经自动根据参数解析,类似Flask里面的session对象之类的。为了体现我等当代青年的高尚追求,这里我们用了一个其他的例子,下载B站右上角的动图,这段url会返回一个json,里面记录了所有动图的名称和地址,网站显示时使用一段js代码随机抽出一个显示,这里我们全部下载。feature参数指定我们需要如何解析返回的数据,这里设置为json,sleep参数为每下载一张图片暂停的时间,更多的参数我们在代码实现中自然会看到,这里暂且不提。
运行之:

fetch:http://www.bilibili.com/index/index-icon.json
save:icon/羽生结弦.gif
save:icon/僵尸.gif
save:icon/困.gif
save:icon/南瓜灯.gif
...此处省略..
save:icon/233333.gif
done!

然后在icon文件夹下就出现了所有的鬼畜小动图!
看到这里,是不是觉得这个框架会让爬虫变得非常简单,写起来就是那么自然、体贴、干爽、透气,独有的速效凹道和完美的吸收轨迹,让你再也不用为每个月的那几天感到焦虑和不安,再加上贴心的护翼设计,量多也不用当心。对不起,我调皮了(鸡汁地盗了一段话)。

修饰器类这样实现

当然啦,最重要还是如何实现修饰器,关于修饰器的基础知识,这里不再造轮子,大家可以去这里看这篇文章,我认为是讲得比较清楚也比较全的一篇。直接上代码:

# -*- coding:utf-8 -*-"""
@author: yangmqglobe
@file: saveimages.py
@time: 2016/11/29
"""from bs4 import BeautifulSoupfrom functools import wrapsfrom .spidry import responseimport requestsimport timeclass saveimages:
    """
    修饰器类
    """

    def __init__(self,
                 feature='html',
                 method='get',
                 sleep=0,
                 log=True,
                 **kwargs):
        """
        构造方法,初始化各种参数
        :param feature: 解析请求数据的方法,暂时分为html的soup和json
        :param method: 请求图片的方法
        :param sleep: 保存图片时每张图片的请求时间间隔
        :param log: 是否打印log
        :param kwargs: 其他的关键词参数,与requests库的参数相关
        """
        self.feature = feature
        self.method = method
        self.sleep = sleep
        self.log = log
        self.kwargs = kwargs    def __call__(self, fn):
        """
        类被作为修饰器调用时调用方法
        :param fn:传入的图片链接解析函数
        :return:
        """

        @wraps(fn)
        def wrapper(url, method='get', **kwargs):
            """
            修饰后的函数的实现
            :param url: 需要请求的页面地址
            :param method: 请求的方法
            :param kwargs: 其他请求参数
            """
            self._fetchpage(url, method, **kwargs)
            imglist = fn()  # 调用原始方法,获得图片列表
            for img in imglist:  # 循环保存图片
                self.saveimage(img)

        return wrapper    def _fetchpage(self, url, method, **kwargs):
        """
        请求页面并解析为相应的解析对象
        :param url:请求页面的url
        :param method:请求方法
        :param kwargs:其他请求尝试
        """
        if self.log:
            print('fetch:' + url)
        response.r = requests.request(method, url, **kwargs)
        response.text = response.r.text        if self.feature.lower() == 'html':  # 将结果解析为soup
            response.soup = BeautifulSoup(response.text, 'lxml')
            response.json = None
        elif self.feature.lower() == 'json':  # 将结果解析为json
            response.json = response.r.json()
            response.soup = None

    def saveimage(self, img):
        """
        保存图片函数
        :param img: 包含图片url和保存路径的字典
        """
        url = img['url']
        path = img['path']
        r = requests.request(self.method, url, **self.kwargs)
        with open(path, 'wb') as img:
            img.write(r.content)
        if self.log:
            print('save:' + path)
        time.sleep(self.sleep)

这个没什么好说的,几乎就是修饰器的内容,但是这里值得一提的是这里的respone对象,也是我们最终爬虫代码时调用的那个对象,这个对象实现起来其实也并不简单。

不简单的全局对象

前面说到了,这个response对象并不简单,我们在使用Flask的时候,你可能会引入session或者request对象,大致使用如下:

from flask import session, request
name = session['name']name = request.name

那么这个看似是一个全局变量的东西是如何定位到每次的的请求对象的?我们的这个response对象又该如何实现呢?

第一想法,使用全局对象,但是有一个问题,就是使用起来非常的麻烦,每次均需要声明其为globe,且其是静态的!然而Flask的对象并不是这样,这又是为什么呢?所以还是找了一圈资料,如果想深究,建议直接看Flask的源码,这里总结一下,flask的这个对象其实是使用了werkzeug库的LocalStack类,该类是标准库中threading包中的local类的一种封装,至于local类的使用,直接去看文档,其实际是一个线程唯一类,在同一线程中能够共享一些动态对象。这里我们也进行一些封装,实现如下:

# -*- coding:utf-8 -*-"""
@author: yangmqglobe
@file: spidry.py
@time: 2016/11/29
"""from threading import localfrom requests.models import Responsefrom bs4 import BeautifulSoupclass Spidry(local):
    def __init__(self):
        self.soup = BeautifulSoup(features='lxml')
        self.json = {}
        self.r = Response()
        self.text = ''response = Spidry()

可以看到,我们是否进行封装是无所谓的,只需要实例化local类的对象,我们就可以往里面塞各种对象同时进行共享,但是这里我还是进行了封装同时还假实例化了各个变量对应的类,只是为了在后面引用时能够获得代码提示而已,就这么简单却人性化,你来打我呀!

更多的扩展

虽然至此,我们最初的想法是已经成功了,但是如果只是能够进行图片保存,那么是否有点无趣了呢?其次,对于异常的处理,更多的参数设置,都还有待完善!其实这只是一种思路,我们还可以再继续添加将数据保存至数据库或文件,自动翻页等等修饰器,其次,还可以对现有的修饰器进行细分使其通用性更强,比如分开打开页面与保存图片的装饰器,以修饰器嵌套的方式来实现,这样代码的复用性将会更高!




相关问题推荐

  • 回答 3

    换行。比如,print hello\nworld效果就是helloworld\n就是一个换行符。\是转义的意思,'\n'是换行,'\t'是tab,'\\'是,\ 是在编写程序中句子太长百,人为换行后加上\但print出来是一整行。...

  • 回答 42

    十种常见排序算法一般分为以下几种:(1)非线性时间比较类排序:a. 交换类排序(快速排序、冒泡排序)b. 插入类排序(简单插入排序、希尔排序)c. 选择类排序(简单选择排序、堆排序)d. 归并排序(二路归并排序、多路归并排序)(2)线性时间非比较类排序:...

  • 回答 70
    已采纳

    前景很好,中国正在产业升级,工业机器人和人工智能方面都会是强烈的热点,而且正好是在3~5年以后的时间。难度,肯定高,要求你有创新的思维能力,高数中的微积分、数列等等必须得非常好,软件编程(基础的应用最广泛的语言:C/C++)必须得很好,微电子(数字电...

  • 回答 28

    迭代器与生成器的区别:(1)生成器:生成器本质上就是一个函数,它记住了上一次返回时在函数体中的位置。对生成器函数的第二次(或第n次)调用,跳转到函数上一次挂起的位置。而且记录了程序执行的上下文。生成器不仅记住了它的数据状态,生成器还记住了程序...

  • 回答 9

    python中title( )属于python中字符串函数,返回’标题化‘的字符串,就是单词的开头为大写,其余为小写

  • 回答 6

    第一种解释:代码中的cnt是count的简称,一种电脑计算机内部的数学函数的名字,在Excel办公软件中计算参数列表中的数字项的个数;在数据库( sq| server或者access )中可以用来统计符合条件的数据条数。函数COUNT在计数时,将把数值型的数字计算进去;但是...

  • 回答 1

    head是方法,所以需要取小括号,即dataset.head()显示的则是前5行。data[:, :-1]和data[:, -1]。另外,如果想通过位置取数据,请使用iloc,即dataset.iloc[:, :-1]和dataset.iloc[:, -1],前者表示的是取所有行,但不包括最后一列的数据,结果是个DataFrame。...

  • Python入门简单吗2021-09-23 13:21
    回答 45

    挺简单的,其实课程内容没有我们想象的那么难、像我之前同学,完全零基础,培训了半年,直接出来就工作了,人家还在北京大公司上班,一个月15k,实力老厉害了

  • 回答 4

    Python针对众多的类型,提供了众多的内建函数来处理(内建是相对于导入import来说的,后面学习到包package时,将会介绍),这些内建函数功用在于其往往可对多种类型对象进行类似的操作,即多种类型对象的共有的操作;如果某种操作只对特殊的某一类对象可行,Pyt...

  • 回答 8

     相当于 ... 这里不是注释

  • 回答 4

    还有FIXME

  • 回答 3

    python的两个库:xlrd和xlutils。 xlrd打开excel,但是打开的excel并不能直接写入数据,需要用xlutils主要是复制一份出来,实现后续的写入功能。

  • 回答 8

    单行注释:Python中的单行注释一般是以#开头的,#右边的文字都会被当做解释说明的内容,不会被当做执行的程序。为了保证代码的可读性,一般会在#后面加一两个空格然后在编写解释内容。示例:#  单行注释print(hello world)注释可以放在代码上面也可以放在代...

  • 回答 2

    主要是按行读取,然后就是写出判断逻辑来勘测行是否为注视行,空行,编码行其他的:import linecachefile=open('3_2.txt','r')linecount=len(file.readlines())linecache.getline('3_2.txt',linecount)这样做的过程中发现一个问题,...

  • 回答 4

    或许是里面有没被注释的代码

  • 回答 26

    自学的话要看个人情况,可以先在B站找一下视频看一下

没有解决我的问题,去提问