Python Web开发

  • 一、前言
  • 二、笔记
  • 2.1、Python 实战(0):初识 web.py
  • 2.2、Python 实战(1):在网页上显示信息
  • 2.3、Python 实战(2):简单的数据库
  • 2.4、Python 实战(3):更多的页面
  • 2.5、Python 实战(4):搜一下
  • 2.6、Python 实战(5):拿来主义
  • 2.7、Python 实战(6):放开那只海豹
  • 2.8、Python 实战(7):连连看
  • 2.9、Python 实战(8):心中有数
  • 三、小结
  • 四、开源


一、前言

本人学习Python是在一个公众号学习的,公众号的内容比较好,值得推荐。
微信公众号:Crossion的编程教室 此篇博客为学习该公众号的一个实战项目的笔记。
地址也贴出来: 二、笔记

2.1、Python 实战(0):初识 web.py

网站django 用的比较多,web.py 很简单。

  1. 安装web.py

wxPython 是否成功 wxpython web_Python


选择解释器一定选择pip位置的解释器,这样才能用刚才pip的库。所以推荐将PyCharm解释器一直设置为Python安装目录下的那一个。

  1. 新建code.py文件
import web
 
urls = (
    '/', 'index'
)
 
class index:
    def GET(self):
        return "Hello, world!"
 
if __name__ == "__main__":
    app = web.application(urls, globals())
    app.run()
  1. 运行

教程中说:
不出意外的话,应该会看到输出:
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

wxPython 是否成功 wxpython web_Python_02

PS:本人电脑0.0.0.0好像不能访问,用127.0.0.1是ok的

成功:

wxPython 是否成功 wxpython web_数据_03


 

2.2、Python 实战(1):在网页上显示信息

  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 规则进行对应的处理,并在后台一直运行,独自等待请求的到来。

  1. 继续添加功能

添加数组:

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

不需要重启服务,直接刷新网页就行了

执行结果:

wxPython 是否成功 wxpython web_Python_04

  1. 模板

web.py 的模板是让在 HTML 里写 Python

指定模板:

render = web.template.render('templates/')

调用模板(这次浏览器请求返回的就是html文件了):

def GET(self):
	return render.index()

运行失败:

wxPython 是否成功 wxpython web_数据库_05

解决:

在Stack Overflow里找到解决办法,

在代码中添加:

from web.template import ALLOWED_AST_NODES
ALLOWED_AST_NODES.append('Constant')

问题解决(可能是因为Python版本原因吧):

wxPython 是否成功 wxpython web_数据_06

将信息传递给模板(多加一个参数,从后端传到前端):

def GET(self):
	return render.index(movies)

在模板里,接收并使用传递进来的参数:
web.py 模板中的 $def with 表示这个模板中将要使用的变量。注意务必把它放在模板的第一行。如果有多个参数,需要全部依次列在括号中。

$def with (movies)
<h1>Later's Movie Site</h1>
$movies

运行:

wxPython 是否成功 wxpython web_数据库_07

继续模板操作(语法和Python差不多):

$for movie in movies:
<li>
	$movie['title'], $movie['year']
</li>

wxPython 是否成功 wxpython web_数据_08

  1. 小结

模板就是前后端相关联所使用的,程序从后端执行,等待浏览器请求和数据的交互。后端通过模板将数据给Html页面,负责显示,后端再将这个页面返回给浏览器显示。

2.3、Python 实战(2):简单的数据库

数据库

Python 直接带有对 SQLite 的支持,我还是喜欢用MySql:
需要用SQLite数据库的可以看原教程,我简单介绍一下MySql数据库的连接配置.

  1. 建立数据库及数据表,顺便写一些数据:

使用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

wxPython 是否成功 wxpython web_数据库_09

  1. 连接MySql:
db = pymysql.connect("localhost", "root", "root", "moviesite",  cursorclass=pymysql.cursors.DictCursor)
cursor = db.cursor()

其中cursorclass=pymysql.cursors.DictCursor将数据库返回值转换成List形式,这样做是为了方便解析数据。

wxPython 是否成功 wxpython web_数据库_10

  1. 修改GET方法(记得把之前写的list去掉):
def GET(self):
	cursor.execute("select * from movie")
	movies = cursor.fetchall()
	print(movies)
	return render.index(movies)
  1. 模板增加属性:
$for movie in movies:
	<li>
		$movie['title'], $movie['year'], $movie['country'], $movie['abstract']
	</li>
  1. 刷新网页:

wxPython 是否成功 wxpython web_Python_11

wxPython 是否成功 wxpython web_数据库_12


注意:Python3需要import pymysql,MySql数据库名只能小写(虽然觉得不科学,但是我这里是这样的)

  1. 小结:

现在完成了一个最简单的GET请求任务,程序启动,浏览器进入GET方法,查询数据库数据,返回模板index.html

2.4、Python 实战(3):更多的页面

任务:通过豆瓣电影的 API 获取更多的电影数据
有一个首页,首页上有影片的列表,点击列表中的某一部影片可以进入其详细页面。

  1. 在 urls 里增加一页面条跳转:
urls = (
            '/', 'index',
            '/movie/(\d+)', 'movie',
 )

d+ 是正则表达式,表示一个数字。加上了括号,是为了让这个匹配到的数字可以被程序获取,成为后面所指向的 movie 中对应方法的参数。

  1. 新增处理请求的类 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] 才能拿到具体的一个数据对象。

  1. 新增此页面的模板 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>
  1. 优化一下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 拼接成跳转的地址。

  1. 运行结果:
  2. 小结:

学习了页面跳转所需要配置的步骤,而代码中的

urls = (
    '/', 'index',
    '/movie/(\d+)', 'movie',
)

就像路由表一样,浏览器的URL定向到不同的class,执行不同的GET操作。

2.5、Python 实战(4):搜一下

新增搜索功能

HTML 里有一个 form 标签,它的作用是创建一个表单,用来提交一些数据。诸如搜索、登录、评论等操作,都可以通过 form 标签来解决。

我们直接在浏览器里访问一个 url 地址是向服务器发送了一个 GET 请求。
而用 form,就可以选择使用 POST 请求,从而更方便更安全地传递数据。

思路:
在首页上通过 form 标签增加一个搜索框。当用户输入文字点击搜索后,会向服务器发送一个 POST 请求。服务器端的代码里拿到请求中的文字,在数据库里搜索标题中包含此文字的影片列表,返回给首页模板进行显示。

  1. 在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=“搜索” 是按钮上显示的文字。

  1. 添加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)
  1. 运行结果:
  2. 小结:学习了POST方法的使用

2.6、Python 实战(5):拿来主义

获取API数据

API 和爬虫的区别在于,API 是内容提供方将信息整理好主动提供给你,数据有标准的格式,但使用时会受一定的限制;爬虫则是你直接从网页上的展现内容里去分析并提取你要的信息,一般来说是未经授权的。从实现上来说,API 会比爬虫简单许多,只要按照接口规范就很容易获取数据。

注意:
这个代码并不作为网站功能的一部,而是直接通过命令行运行。如果你想在网页上实现此功能,会有一个问题,就是抓取过程是个很耗时的事情,但一个网页请求并不能等待很久,如果一段时间未返回,这个请求就会关闭。

  1. 获取测试豆瓣 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

  1. 访问接口试试:

wxPython 是否成功 wxpython web_Python_13

wxPython 是否成功 wxpython web_数据库_14

  1. 拓展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的转换,以及数据库的修改。

  1. 修改包括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)
  1. 解决问题

遇到问题:
urllib.error.HTTPError: HTTP Error 418: 原因:是因为反爬虫机制。为什么提供了API还会有反爬虫机制呢?也许是全局反爬虫吧。
解决:加上header模拟浏览器就行了
参考博客:

遇到问题:
TypeError: %d format: a number is required, not str MySQL的字符串格式化不是标准的python的字符串格式化,应当一直使用%s用于字符串格式化。

最后,之前的网站关于数据库部分需要小修改(修改index.html里面一个属性就没有了),可别忘了。
反爬虫机制可能屏蔽IP,调试次数多了,这个接口会出问题。

  1. 运行结果(最后获取Top250的详细数据还是很慢的,需要等好一会儿):

wxPython 是否成功 wxpython web_数据_15

wxPython 是否成功 wxpython web_Python_16

wxPython 是否成功 wxpython web_Python_17

wxPython 是否成功 wxpython web_wxPython 是否成功_18

看着这么多数据到手还是挺开心的哇!

  1. 小结:

这部分主要是丰富我们的数据库,用程序的手段,对后续web开发无关痛痒。但是学到了爬虫入门还是很有收获的。

2.7、Python 实战(6):放开那只海豹

电影信息增加海报图片

海报图片地址我们在上一步已经获取并存在Mysql数据库里面了。现在需要做的就是访问该地址,下载并显示该图片。

这里下载图片是放在服务器的根目录下面的,并未存入数据库。所以操作起来比较简单。

  1. 下载图片方法:
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()
  1. 通过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)
  1. 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)
  1. 运行结果:

wxPython 是否成功 wxpython web_Python_19

wxPython 是否成功 wxpython web_数据_20

wxPython 是否成功 wxpython web_数据库_21

  1. 小结:

同上一篇教程,也属于爬虫,时间有点慢(加了sleep方法),图片存储到数据库不方便,而且这里也没有这个必要。

2.8、Python 实战(7):连连看

在模板中:
$ 符号开头的代码将会以 Python 的语法执行。需要特别注意的是,$ casts 之间有一个不可缺少的空格,这个空格说明这里是定义了一个新的变量 casts,而不是获取变量 casts 的值。

  1. 在 url 里添加跳转规则:
urls = (
	'/', 'index',
	'/movie/(.*)', 'movie',
	'/cast/(.*)', 'cast',
)
  1. 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)
  1. 顺便加上导演搜索吧,基本一样:
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)
  1. 运行结果(别在意乱码,无影响):

wxPython 是否成功 wxpython web_wxPython 是否成功_22

wxPython 是否成功 wxpython web_wxPython 是否成功_23

  1. 小结:无法可脱

2.9、Python 实战(8):心中有数

完善查询提示,增加影片数量反馈。

这一部分核心就是增加查询语句

cursor.execute('SELECT COUNT(*) AS COUNT FROM movie')
count = cursor.fetchall()[0]['COUNT']

其它没啥可说的啦

运行结果:

wxPython 是否成功 wxpython web_Python_24

wxPython 是否成功 wxpython web_wxPython 是否成功_25

wxPython 是否成功 wxpython web_数据库_26

三、小结

我是一步一步学的,实际没花多少实际,但是理解很多,收获很多。当然实现的功能还是太少啦,后续会基于此继续新增完善和美化。

wxPython 是否成功 wxpython web_数据库_27


现在的网站已基本可以,后续可以修改的地方:美化模板页面,增加更多的功能,但是还是基于GET和POST两种方法。页面模板费时费力,还不一定做得好看,可以之间Down现成的。

用这个Python的Web框架感觉很简单,相比于java和PHP环境的配置,这个太方便了。这个项目也完整的开发了一个Web项目,包括前后端和数据库的整体,对自己的学习很有帮助。

爬虫也算是入门了?

同时可以运用到自己的项目中,尤其是IOT项目,能够接收传感器终端数据,显示与储存。也可以作为第三方云平台的对接应用服务器,自己的毕业设计也可以自己做服务端。