从本章起,开始编写《鱼书》网站的实际业务。
从最小原点出发,逐步形成一个产品。这个最小原点,就是网站的核心功能——向他人赠送书籍。
一、书籍呈现形式、用户搜索情况
1.1 网站书籍的呈现形式
《鱼书》的定位:一个公益性的赠书网站,不是二手图书交易平台。
所以,网站上显示的书籍,赠书人只需要给出书籍的基本信息,直接拿现成的图书封面一用。
不用像闲鱼等平台一样,那么麻烦,要求每个赠书人把书亲自拍照,以呈现其实际模样、新旧损耗。
1.2 用户搜索的四种情况
既然网站的功能是向他人赠送书籍,那么赠送前提就要先确定要送哪本书,所以先要搜索。
1.精准搜索:黑客与画家
2.模糊搜索:黑客与
3.作者搜索:杜拉斯
4.ISBN编号搜索:9787531321880
二、书籍的搜索与查询
2.1 图书检索数据的来源
本网站依赖外部的数据源,需要调用外部的图书API。
1.老师提供的API
基地址:http://t.talelin.com
关键字搜索 | ISBN搜索 | |
---|---|---|
API | http://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的两种形式 | 特点 |
---|---|
ISBN13 | 13个0-9数字组成,一般是新书 |
ISBN10 | 10个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
Comments | NOTHING