Python Web开发
一、前言
本人学习Python是在一个公众号学习的,公众号的内容比较好,值得推荐。
微信公众号:Crossion的编程教室
此篇博客为学习该公众号的一个实战项目的笔记。
地址也贴出来:
教程地址:https://res.crossincode.com/wechat/someweb.html
教程Github地址:https://github.com/crossin/MovieSite
二、笔记
2.1、Python 实战(0):初识 web.py
网站django 用的比较多,web.py 很简单。
- 安装web.py
选择解释器一定选择pip位置的解释器,这样才能用刚才pip的库。所以推荐将PyCharm解释器一直设置为Python安装目录下的那一个。
- 新建code.py文件
import web
urls = (
'/', 'index'
)
class index:
def GET(self):
return "Hello, world!"
if __name__ == "__main__":
app = web.application(urls, globals())
app.run()
- 运行
教程中说:
不出意外的话,应该会看到输出:
http://0.0.0.0:8080/
而我这里报错,没事,来解决:
问题:
OSError: No socket could be created -- (('0.0.0.0', 8080): [WinError 10048] 通常每个套接字地址(协议/网络地址/端口)只允许使用一次。)
原因:默认8080端口被占用,直接在PyCharm点击运行,就会默认使用8080端口,所以换一个运行方式。
解决:在终端里指定端口运行:python code.py 8009
PS:本人电脑0.0.0.0好像不能访问,用127.0.0.1是ok的
成功:
每次在终端启动台麻烦,所以现在需要在程序中指定服务器端口,PyCharm可以直接启动:https://bbs.crossincode.com/forum.php?mod=viewthread&tid=6754&extra=page%3D1
2.2、Python 实战(1):在网页上显示信息
- 解释代码:
import web
导入 web.py 模块。
urls = (
'/', 'index'
)
这是指定网站 url 的匹配规则,左边是正则表达式,右边是对应处理函数的名称。
class index:
def GET(self):
return "Hello, world!"
这便是处理请求的函数 index。GET 和 POST 是 HTTP 的两种请求方式,一般来说,GET 用于请求网页,而 POST 多用于提交表单。
if __name__ == "__main__":
app = web.application(urls, globals())
app.run()
最后,当这个代码文件被执行时,我们将创建一个 application,它会按照我们定义的 url 规则进行对应的处理,并在后台一直运行,独自等待请求的到来。
- 继续添加功能
添加数组:
movies = [
{
'title': 'Forrest Gump',
'year': 1994,
},
{
'title': 'Titanic',
'year': 1997,
},
]
修改GET(这里返回直接是字符串,后面教程返回模板html):
def GET(self):
page = ''
for m in movies:
page += '%s (%d)\n' % (m['title'], m['year'])
return page
不需要重启服务
,直接刷新网页就行了
执行结果:
- 模板
web.py 的模板是让在 HTML 里写 Python
指定模板:
render = web.template.render('templates/')
调用模板(这次浏览器请求返回的就是html文件了):
def GET(self):
return render.index()
运行失败:
解决:
在Stack Overflow里找到解决办法,
在代码中添加:
from web.template import ALLOWED_AST_NODES
ALLOWED_AST_NODES.append('Constant')
问题解决(可能是因为Python版本
原因吧):
将信息传递给模板(多加一个参数,从后端传到前端):
def GET(self):
return render.index(movies)
在模板里,接收并使用传递进来的参数:
web.py 模板中的 $def with 表示这个模板中将要使用的变量。注意务必把它放在模板的第一行
。如果有多个参数,需要全部依次列在括号中。
$def with (movies)
<h1>Later's Movie Site</h1>
$movies
运行:
继续模板操作(语法和Python差不多):
$for movie in movies:
<li>
$movie['title'], $movie['year']
</li>
- 小结
模板就是前后端相关联所使用的,程序从后端执行,等待浏览器请求和数据的交互。后端通过模板将数据给Html页面,负责显示,后端再将这个页面返回给浏览器显示。
2.3、Python 实战(2):简单的数据库
数据库
Python 直接带有对 SQLite 的支持,我还是喜欢用MySql:https://www.cnblogs.com/benben-wu/p/10220452.html
需要用SQLite数据库的可以看原教程,我简单介绍一下MySql数据库的连接配置.
- 建立数据库及数据表,顺便写一些数据:
使用Navicat for MySQL
软件,
创建数据库数据表与教程同名,数据库文件是这样的:
CREATE TABLE `movie` (
`id` int(4) NOT NULL,
`title` char(50) NOT NULL,
`year` char(10) NOT NULL,
`country` char(50) NOT NULL,
`abstract` longtext NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
- 连接MySql:
db = pymysql.connect("localhost", "root", "root", "moviesite", cursorclass=pymysql.cursors.DictCursor)
cursor = db.cursor()
其中cursorclass=pymysql.cursors.DictCursor
将数据库返回值转换成List形式
,这样做是为了方便解析数据。
- 修改GET方法(记得把之前写的list去掉):
def GET(self):
cursor.execute("select * from movie")
movies = cursor.fetchall()
print(movies)
return render.index(movies)
- 模板增加属性:
$for movie in movies:
<li>
$movie['title'], $movie['year'], $movie['country'], $movie['abstract']
</li>
- 刷新网页:
注意:Python3需要import pymysql,MySql数据库名只能小写(虽然觉得不科学,但是我这里是这样的)
- 小结:
现在完成了一个最简单的GET请求任务,程序启动,浏览器进入GET方法,查询数据库数据,返回模板index.html
2.4、Python 实战(3):更多的页面
任务:通过豆瓣电影的 API 获取更多的电影数据
有一个首页,首页上有影片的列表,点击列表中的某一部影片可以进入其详细页面。
- 在 urls 里增加一页面条跳转:
urls = (
'/', 'index',
'/movie/(\d+)', 'movie',
)
d+ 是正则表达式,表示一个数字。加上了括号,是为了让这个匹配到的数字可以被程序获取,成为后面所指向的 movie 中对应方法的参数。
- 新增处理请求的类 movie:
class movie:
def GET(self, movie_id):
movie_id = int(movie_id)
movie = db.select('movie', where='id=$movie_id', vars=locals())[0]
return render.movie(movie)
当在浏览器中访问诸如 /movie/123 的地址时,请求被转到 movie 中的 GET 方法,而 123 就成为参数 movie_id。
首先,方法里拿到的 movie_id 是字符串,所以需要转成 int。where 条件是一个将被拿到数据库中执行的查询条件,需要是一个字符串。同模板中一样,这里 $movie_id 是取变量 movie_id 的值,而 vars=locals() 则表示 $ 符号所取变量的范围为所有本地变量。select 拿到的是一组查询结果,后面加上 [0] 才能拿到具体的一个数据对象。
- 新增此页面的模板 movie.html:
$def with (movie)
<h4><a href="/">返回首页</a></h4>
<h1>$movie['title']</h1>
<hr>
<p>年代:$movie['year']</p>
<p>国家:$movie['country']</p>
<p>$movie['abstract']</p>
- 优化一下index.html:
$def with (movies)
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Move Site</title>
</head>
<body>
<h1>Later's Movie Site</h1>
<p>影片列表:</p>
$for movie in movies:
<li>
$movie['title'], $movie['year'], $movie['country'], $movie['abstract']
</li>
</body>
</html>
在这个 <a>
标签中,movie 的 title 作为链接文字,id 拼接成跳转的地址。
- 运行结果:
- 小结:
学习了页面跳转所需要配置的步骤,而代码中的
urls = (
'/', 'index',
'/movie/(\d+)', 'movie',
)
就像路由表一样,浏览器的URL定向到不同的class,执行不同的GET操作。
2.5、Python 实战(4):搜一下
新增搜索功能
HTML 里有一个 form 标签,它的作用是创建一个表单,用来提交一些数据。诸如搜索、登录、评论等操作,都可以通过 form 标签来解决。
我们直接在浏览器里访问一个 url 地址是向服务器发送了一个 GET 请求。
而用 form,就可以选择使用 POST 请求,从而更方便更安全地传递数据。
思路:
在首页上通过 form 标签增加一个搜索框。当用户输入文字点击搜索后,会向服务器发送一个 POST 请求。服务器端的代码里拿到请求中的文字,在数据库里搜索标题中包含此文字的影片列表,返回给首页模板进行显示。
- 在index.html添加搜索框:
<form action="/" method="post">
<input type="text" name="title" />
<input type="submit" value="搜索" />
</form>
action 是请求的地址,这里选择向首页请求,method 表示方法是 POST。input 是表单中的元素,type=“text” 表示一个文本框,name=“title” 在服务器端处理数据时会用到。type=“submit” 表示一个提交按钮,value=“搜索” 是按钮上显示的文字。
- 添加POST方法:
def POST(self):
data = web.input()
condition = r'title like "%' + data.title + r'%"'
print("select * from movie where %s" % condition)
cursor.execute("select * from movie where %s" % condition)
movies = cursor.fetchall()
return render.index(movies)
- 运行结果:
- 小结:学习了POST方法的使用
2.6、Python 实战(5):拿来主义
获取API数据
API 和爬虫的区别在于,API 是内容提供方将信息整理好主动提供给你,数据有标准的格式,但使用时会受一定的限制;爬虫则是你直接从网页上的展现内容里去分析并提取你要的信息,一般来说是未经授权的。从实现上来说,API 会比爬虫简单许多,只要按照接口规范就很容易获取数据。
注意:
这个代码并不作为网站功能的一部,而是直接通过命令行运行。如果你想在网页上实现此功能,会有一个问题,就是抓取过程是个很耗时的事情,但一个网页请求并不能等待很久,如果一段时间未返回,这个请求就会关闭。
- 获取测试豆瓣 API
豆瓣API也换了:(https://api.douban.com/v2/movie/subject/25924056)
获取电影Top250:
https://douban.uieee.com/v2/movie/top250
访问参数:
start : 数据的开始项
count:单页条数
电影详情:
https://douban.uieee.com/v2/movie/subject/:id
访问参数:电影id
如:电影《小飞象》的电影id为:25924056,搜索此电影的详细信息
https://douban.uieee.com/v2/movie/subject/1292052
- 访问接口试试:
- 拓展movie数据表
id - 影片 id
title - 中文名
origin - 原名
url - 影片豆瓣链接
rating - 评分
image - 海报图片地址
directors - 导演
casts - 主演
year - 年代
genres - 类型
countries - 制片国家/地区
summary - 简介
数据库设计文件:
CREATE TABLE `movie` (
`id` int(4) NOT NULL,
`title` char(50) DEFAULT NULL,
`origin` char(100) DEFAULT NULL,
`url` char(50) DEFAULT NULL,
`rating` char(10) DEFAULT NULL,
`image` char(100) DEFAULT NULL,
`directors` char(100) DEFAULT NULL,
`casts` char(100) DEFAULT NULL,
`year` char(10) DEFAULT NULL,
`genres` char(10) DEFAULT NULL,
`countries` char(50) DEFAULT NULL,
`summary` longtext,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
这里直接用教程所提供的代码get_movie.py,但是需要修改一些地方,Python2到Python3的转换,以及数据库的修改。
- 修改包括BUG问题解决结束最终代码:
from urllib import request
import json
import time
from urllib.request import Request
import pymysql
# 连接数据库
db = pymysql.connect("localhost", "root", "root", "moviesite")
cursor = db.cursor()
# 数组存放id
movie_ids = []
for index in range(0, 250, 50):
print(index)
url = 'https://douban.uieee.com/v2/movie/top250?start=%d&count=50' % index
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'}
ret = Request(url, headers=headers)
response = request.urlopen(ret)
data = response.read()
# print data
data_json = json.loads(data)
movie250 = data_json['subjects']
for movie in movie250:
movie_ids.append(movie['id'])
print(movie['id'], movie['title'])
time.sleep(3)
print(movie_ids)
def add_movie(data):
movie = json.loads(data)
# sql 语句有点儿长
sql = '''INSERT INTO movie(id, title, origin, url, rating, image, directors, casts, year, genres, countries,
summary) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) '''
sqlvar = (movie['id'], movie['title'], movie['original_title'], movie['alt'], movie['rating']['average'], movie['images']['large'], ','.join([d['name'] for d in movie['directors']]), ','.join([c['name'] for c in movie['casts']]), movie['year'], ','.join(movie['genres']), ','.join(movie['countries']), movie['summary'])
print(sql)
cursor.execute(sql, sqlvar)
db.commit()
print(movie['title'])
count = 0
for mid in movie_ids:
print(count, mid)
url = 'https://douban.uieee.com/v2/movie/subject/%s' % mid
print(url)
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'}
ret = Request(url, headers=headers)
response = request.urlopen(ret)
data = response.read()
add_movie(data)
count += 1
time.sleep(3)
- 解决问题
遇到问题:
urllib.error.HTTPError: HTTP Error 418:
原因:是因为反爬虫机制。为什么提供了API还会有反爬虫机制呢?也许是全局反爬虫吧。
解决:加上header模拟浏览器就行了
参考博客:https://blog.csdn.net/sinat_37812785/article/details/104247874
遇到问题:
TypeError: %d format: a number is required, not str
MySQL的字符串格式化不是标准的python的字符串格式化,应当一直使用%s用于字符串格式化。
最后,之前的网站关于数据库部分需要小修改(修改index.html里面一个属性就没有了),可别忘了。
反爬虫机制可能屏蔽IP,调试次数多了,这个接口会出问题。
- 运行结果(最后获取Top250的详细数据还是很慢的,需要等好一会儿):
看着这么多数据到手还是挺开心的哇!
- 小结:
这部分主要是丰富我们的数据库,用程序的手段,对后续web开发无关痛痒。但是学到了爬虫入门还是很有收获的。
2.7、Python 实战(6):放开那只海豹
电影信息增加海报图片
海报图片地址我们在上一步已经获取并存在Mysql数据库里面了。现在需要做的就是访问该地址,下载并显示该图片。
这里下载图片是放在服务器的根目录下面的,并未存入数据库。所以操作起来比较简单。
- 下载图片方法:
def get_poster(id, url):
pic = urllib.urlopen(url).read()
file_name = 'poster/%d.jpg' % id
f = file(file_name, "wb")
f.write(pic)
f.close()
- 通过id抓取海报:
db = web.database(dbn='sqlite', db='MovieSite.db')
movies = db.select('movie')
count = 0
for movie in movies:
get_poster(movie.id, movie.image)
count += 1
print count, movie.title
time.sleep(2)
- get_poster.py完整代码:
import time
import urllib
from urllib import request
from urllib.request import Request
import pymysql
def get_poster(id, url):
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'}
ret = Request(url, headers=headers)
#pic = request.urlopen(ret)
pic = urllib.request.urlopen(ret).read()
file_name = 'static/poster/%d.jpg' % id
f = open(file_name, "wb")
f.write(pic)
f.close()
# MySql-以List形式输出
db = pymysql.connect("localhost", "root", "root", "moviesite", cursorclass=pymysql.cursors.DictCursor)
cursor = db.cursor()
cursor.execute("select * from movie")
movies = cursor.fetchall()
count = 0
for movie in movies:
get_poster(movie['id'], movie['image'])
count += 1
print(count, movie['title'])
time.sleep(2)
- 运行结果:
- 小结:
同上一篇教程,也属于爬虫,时间有点慢(加了sleep方法),图片存储到数据库不方便,而且这里也没有这个必要。
2.8、Python 实战(7):连连看
在模板中:
$ 符号开头的代码将会以 Python 的语法执行。需要特别注意的是,$ casts 之间有一个不可缺少的空格
,这个空格说明这里是定义了一个新的变量 casts,而不是获取变量 casts 的值。
- 在 url 里添加跳转规则:
urls = (
'/', 'index',
'/movie/(.*)', 'movie',
'/cast/(.*)', 'cast',
)
- cast处理方法(主演搜索):
class cast:
def GET(self, cast_name):
condition = r'casts like "%' + cast_name + r'%"'
movies = db.select('movie', where=condition)
return render.index(movies)
- 顺便加上导演搜索吧,基本一样:
class director:
def GET(self, director_name):
condition = r'directors like "%' + director_name + r'%"'
cursor.execute("select * from movie where %s" % condition)
movies = cursor.fetchall()
return render.index(movies)
- 运行结果(别在意乱码,无影响):
- 小结:无法可脱
2.9、Python 实战(8):心中有数
完善查询提示,增加影片数量反馈。
这一部分核心就是增加查询语句
cursor.execute('SELECT COUNT(*) AS COUNT FROM movie')
count = cursor.fetchall()[0]['COUNT']
其它没啥可说的啦
运行结果:
三、小结
我是一步一步学的,实际没花多少实际,但是理解很多,收获很多。当然实现的功能还是太少啦,后续会基于此继续新增完善和美化。
现在的网站已基本可以,后续可以修改的地方:美化模板页面,增加更多的功能,但是还是基于GET和POST两种方法。页面模板费时费力,还不一定做得好看,可以之间Down现成的。
用这个Python的Web框架感觉很简单,相比于java和PHP环境的配置,这个太方便了。这个项目也完整的开发了一个Web项目,包括前后端和数据库的整体,对自己的学习很有帮助。
爬虫也算是入门了?
同时可以运用到自己的项目中,尤其是IOT项目,能够接收传感器终端数据,显示与储存。也可以作为第三方云平台的对接应用服务器,自己的毕业设计也可以自己做服务端。
实验环境:
pycharm+mysql5+python3
感谢:@Crossin编程教室
四、开源
下载地址:
CSDN:PythonMovieWeb
Github:PythonMovieWeb