第三章 书籍数据、flask路由之一



从本章起,开始编写《鱼书》网站的实际业务。

从最小原点出发,逐步形成一个产品。这个最小原点,就是网站的核心功能——向他人赠送书籍。

一、书籍呈现形式、用户搜索情况

1.1 网站书籍的呈现形式

《鱼书》的定位:一个公益性的赠书网站,不是二手图书交易平台。

所以,网站上显示的书籍,赠书人只需要给出书籍的基本信息,直接拿现成的图书封面一用。

不用像闲鱼等平台一样,那么麻烦,要求每个赠书人把书亲自拍照,以呈现其实际模样、新旧损耗。

1.2 用户搜索的四种情况

既然网站的功能是向他人赠送书籍,那么赠送前提就要先确定要送哪本书,所以先要搜索。

1.精准搜索:黑客与画家

2.模糊搜索:黑客与

3.作者搜索:杜拉斯

4.ISBN编号搜索:9787531321880

二、书籍的搜索与查询

2.1 图书检索数据的来源

本网站依赖外部的数据源,需要调用外部的图书API。

1.老师提供的API

基地址:http://t.talelin.com

关键字搜索ISBN搜索
APIhttp://t.talelin.com/v2/book/search?q={}&start={}&count={}http://t.talelin.com/v2/book/isbn/{isbn}
含义q: 用于传递关键字

start:第几页
count:本页有多少条记录
后两者用于控制分页,不可能把所有记录一次性给返回回来
isbn: 用于传递ISBN编号
不同通过问号?后面的查询参数来传递放到URL路径中

2.豆瓣的API

除了上述老师提供的两个API,也可以用豆瓣的API:https://api.douban.com/v2/book

在课程里面,建议用老师提供的两个API。

后续课程结束可以再用豆瓣API练习。因为豆瓣API有访问频率限制,150次/h,超过了IP就会被封。老师的这个API允许访问频率比较高。

没有不控制访问频率的API。否则,你的API几下子就被别人搞瘫痪了。

其实,API也是用flask编写的,它与用flask编写网站没有太大的区别。只要搞清楚两者返回的是什么即可。

2.2 关键字搜索

根据上述两个API,用户搜索一次就需要传入四个参数:

q、start、count、isbn。

但是,参数太多,给用户带来很大麻烦。

1.参数合并

是否有必要让用户在客户端选择,是普通关键字搜索还是ISBN搜索?

没必要。

因为两个参数q和isbn的共性都是关键字搜索,所以可以简化合并为一个q,具体是哪一种,让代码自己去区分。没必要让用户点击分类再搜索。

同理,两个参数start和count也可以合并为一个page。

2.视图函数如何接受用户参数

如何在视图函数中,接受用户从客户端传递过来的参数?

flask中,接受用户从客户端传递过来的参数有很多种。

这里用最简单的方式:在url路径中接受。

from flask import Flask

app = Flask(__name__)

@app.route('/book/search/<q>/<page>')     # 在url路径中,q前后加尖括号<>,会被识别为一个参数,而不是固定的url字符串
def search(q,page):
   pass

3.判断关键字q类型

用户的参数q传递进来了,接下来就要调用API。可是,有两个API,调用哪个呢?

通过编写代码,来判断传进来的关键字q是普通关键字还是ISBN。

ISBN的两种形式特点
ISBN1313个0-9数字组成,一般是新书
ISBN1010个0-9数字组成,一般是老书
通常,中间还会夹杂一些短横线“ - ”
from flask import Flask

app = Flask(__name__)

@app.route('/book/search/<q>/<page>')
def search(q,page):
    isbn_or_key = 'key'     # 默认是普通关键字key
    
    if len(q) == 13 and q.isdigit():        # 如果关键字q长度是13,且全是数字   # 此时,python中没必要用循环遍历
        isbn_or_key = 'isbn'
        
    short_q = q.replace('-',' ')        # 如果放进下面太长了,所以单独提取出来
    if '-' in q and len( short_q) == 10 and  short_q.isdigit():
        isbn_or_key = 'isbn'

4.条件判断语句中多条件的编写原则

在上述由两个and和三个条件组成的条件判断语句中,三个条件的先后顺序,对代码的执行效率有影响吗?

有影响。

原则1:应该把很大可能出现假的条件放在前面。

因为一旦为假,后面的条件就不用执行了。

原则2:应该把耗时的操作的条件,放在后面。因为,越往后,运行的可能性就越低。

例如,查询数据库,比较消耗服务器资源。

2.3 视图函数的简单重构

1.判断代码写在搜索的视图函数下面,好吗?

不好。

  • 不简洁

    判断代码只是search搜素流程的很小的一部分,却已占用六行。太臃肿。

  • 复用性差。

    却是解决了search里面判断q关键字是ISBN还是key的问题,但是其他地方如果也需要这种判读呢?无法使用。

  • 不易读。

    别人阅读web代码,一般都是从视图函数或控制器开始看的。这样写,会强迫别人看具体的实现细节,而无法一眼就看出其功能。

应该:

将判断代码提取成一个函数,然后在search视图函数中去调用它,从而完成判断的功能。

在新建模块helper.py中:

def is_isbn_or_key(word):
    '''
        判断用户传递进来的关键字,是普通关键字,还是ISBN
    '''
    isbn_or_key = 'key'     # 默认是普通关键字key
    if len(word) == 13 and word.isdigit():   # 如果关键字q长度是13,且全是数字   # 此时,python中没必要用循环遍历
        isbn_or_key = 'isbn'
    short_word = word.replace('-', ' ')      # 如果放进下面太长了,所以单独提取出来
    if '-' in word and len(short_word) == 10 and short_word.isdigit():
        isbn_or_key = 'isbn'
    return isbn_or_key

fisher.py中入口文件中:

from flask import Flask
from helper import is_isbn_or_key

app = Flask(__name__)
app.config.from_object('config')

@app.route('/book/search/<q>/<page>')
def search(q,page):         
    is_isbn_or_key = is_isbn_or_key(q)       # 直接调用即可        # 函数名就体现了函数的功能,也不用注释了

视图函数中的代码必须是简洁、易读。

因为视图函数或控制器,是一个web项目开始某一个业务逻辑的起点(原点),阅读代码、维护代码,均从此开始。

如果又长又乱,维护很成问题。

2.如何阅读源代码?

原则:不要一次性的把所有的源代码的细节都看一遍,而是要分层看。

例如,第一次看search这个视图函数时,知道is_isbn_or_key()函数是做什么的就够了。没必要选中按F12跳进来,去看具体的实现细节。

也就是说,第一遍看的目的只是:理清楚整个源代码的结构、线性的线索。

三、访问鱼书API,获取和判断书籍数据

在判断完成之后,就可以访问调用鱼书API,获取书籍数据了。

1.如何在python中,访问API?

通过发送http请求。

python中有两种发送方式:

  • urllib:Python自带的模块,即from urllib import request,原生小爬虫时用过,难用。
  • requests:第三方的库(包),比较人性化、方便,推荐做项目时用这个。

    要像安装flask一样,用pipenv安装一下。

注意:

虚拟环境的启动、第三方包requests的安装,都必须在fisher.py目录下进行。

3.1 用requests发送http请求,以访问鱼书API

1.用requests如何发送http请求?

get()方法。

在新建模块http.py中:

import requests   # 导入第三方包

class HTTP:
    def get(self, url, return_json=True):       # url是请求的api地址
        r=requests.get(url)     # 调用requests包下面的get方法
        return r  

2.如何从r里拿到JSON格式的数据呢?

上面返回的r,是requests对此次请求结果的封装,里面有很多信息,比如状态码、headers头等。但是,大多数情况下,只想要返回的内容。

外部API是restful标准的,它要求返回的结果一定是JSON格式的。本课程的两个API,也要求返回的结果也是JSON格式的。所以,我们可以在get方法中,直接返回JSON格式的数据。

直接调用json()方法。即return r.json(),这是对于API返回的结果是JSON格式的通用写法。

3.如果调用的API,其返回结果不是JSON格式,比如普通字符串呢?

可以通过参数return_json,来控制返回结果到底需不需要转换成JSON格式。

在模块http.py中:

import requests

class HTTP:
    def get(self, url, return_json=True):      
        r = requests.get(url)     

        if return_json:         # 增强了get函数的通用性
            return r.json()
        else:
            return r.text       # 将普通的字符串返回给你

4.用postman验证服务器返回的数据的正常与否

上面get()方法的健壮性还不够。

因为这只是假设服务器返回给了我们期望的结果。很多时候,服务器并不会按照我们的设想,来返回数据。

例如,对于ISBN搜索,如果ISBN编号对应的图书是存在的,那么,必然会返回图书的JSON格式的数据。

但是,如果是随便输入一个13位编号,服务器并没有找到这样的图书,那么,就不会返回给我们JSON格式的数据。

会出现404。

5.如何在代码里面鉴别,返回的不是正常数据的情况呢?

根据状态码来判断,比较方便。这普遍适用于所有restful标准的API。

所有开放的API都是restf标准的。例如,github给开发者提供的API就是restful的,且很标准。

在模块http.py中:

import requests

class HTTP:
    def get(self, url, return_json=True):       
        r = requests.get(url)     
        
        if r.status_code == 200:        # 最直接、最直白的写法,很罗嗦
            if return_json:
                return r.json()
            else:
                return r.text
        else:
            if return_json:
                return {}       # 空字典
            else:
                return ''       # 空字符串

简化:

import requests

class HTTP:
    def get(self, url, return_json=True):
        r = requests.get(url)

        if r.status_code != 200:
            return {} if return_json else ''        # 三元表达式
        return r.json() if return_json else r.text      # 也是一个三元表达式

3.2 简化嵌套if else的三个方法

1.利用三元表达式

2.利用两个return思想

if ...:
    return ...        # 正常流程的一种特殊处理
return ...        # 正常情况下函数的结果

一般情况下,一个函数建议只有一个return语句。

但是对于复杂函数,它会有很多个思维分支,此时,建议多用一个return就可视为一个函数分支的终结。从而集中注意力,去关注剩下的分支。保证思维清晰。

3.将里面的代码提取成一个函数

当里面的代码,极为复杂时很有用。

3.3 另一个http请求发送库urllib的用法

1.urllib的用法

urllib不太智能,也很麻烦。

要实现上面相同的功能:

from urllib import request, quote

@staticmethod
def get_with_request(url, json_return=True):
    url = quote(url, safe='/:?=&')     # 用quote对中文进行编码      # 但问号不能编码
    try:
        with request.urlopen(url) as r:     # r是一个响应对象
            result_str = r.read()       # 得到的是字节码
            result_str = str(result_str, encoding='utf-8')
        if json_return:
            return json.loads(result_str)
        else:
            return result_str
    except OSError as e:
        print(e.reason)
        if json_return:
            return {}
        else:
            return None

写爬虫,如果不用专业的爬虫框架scapy,那么用requests+beau soap经典组合是比较好的。

当然,如果要写并发、多线程的爬虫,scapy是更好的选择。

2.静态方法与新式类

在模块http.py中:

import requests

class HTTP:        # 不用继承父类,python3都是新式类
    
    @staticmethod       # 静态方法,与类、对象,都没有关系      # 既没有用到类变量,也没有用到实例变量
    def get(url, return_json=True):
        r = requests.get(url)

        if r.status_code != 200:
            return {} if return_json else ''        
        return r.json() if return_json else r.text      

声明:Jerry's Blog|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - 第三章 书籍数据、flask路由之一


Follow excellence, and success will chase you.