Flask MEIZI小项目实战

好孤独啊,背后再也没有那只傻猴子跟着自己了,你怎么回头都看不到他蹦蹦跳跳的影子了。心里有什么东西忽然坍塌了,她从高高在上的公主宝座上跌落尘埃。

项目目标

提供一个用户输入,等待用户输入关键词,然后后端程序对输入的参数跳转到妹子图网站,使用正则提前相关的网址和标题,然后返回匹配到的图片和标题。

主体项目结构

主体函数结构

实战

爬取函数

def meizi(id):
    headerss = {
        'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36'
    }
    r = requests.get(url=str('http://www.mzitu.com/search/' + str(id)),headers=headerss)
    rr = re.findall("data-original='(.*?)' /></a><span><a.*?href=\"(.*?)\" target=\"_blank\">(.*?)</a></span><span",r.content,re.S)
    return rr

该函数接受一个关键词参数,然后匹配出标题,主图和网址并且当成一个列表返回

主页函数

@app.route('/index/')
def index():
    return render_template('index.html')

使用render_template(‘index.html’)渲染主页

搜索函数

@app.route('/search/',methods=['POST','GET'])
def search():
    if request.method == 'POST':
        idx = request.form['idx']
        return render_template('result.html', data=meizi(idx))
    else:
        return render_template('404.html')

获取POST传递过来的参数,name的值为idx,然后把值传递给爬取函数,最后结果给result.html渲染

静态模板

主页

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>妹子图搜索</title>

</head>
<body>
    <p>开始搜索关键词吧~</p>
    <form method="POST" action="/search/" >
        关键词:<input type="text" name="idx">
        <br> <br>
        <button type="submit">点击搜索</button>

    </form>
</body>
</html>

结果

`<!DOCTYPE html>`
`<html lang="en">`
`<head>`
 `   <meta charset="UTF-8">`
 `   <title>返回信息</title>`
`</head>`
`<body>`

`{%` for `x,y,z in data `%}`
`<`img` `s`r`c`=`"{{` x `}}">`
 `   `<`br`>`
  `  `<`a` `href` = "{{ y }}">`
  `  <`br`>`
  `   `{{` z `}}`

  `  `<`br`>`
`{`% endfor %`}`

``
`</body>`
`</html>`

404

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>错误信息</title>
</head>
<body>

只接受POST方法

不接受GET方法


</body>
</html>

该项目是一次简单的实战,很多地方都很粗糙,但是可以后期去修改维护。

全部代码

# -*- coding: utf-8 -*-
# @Time    : 2018/7/17 0017 21:06
# @Author  : Langzi
# @Blog    : www.langzi.fun
# @File    : Mmzi.py
# @Software: PyCharm
import sys,time,re,requests
from flask import Flask,make_response,request,Response,render_template
reload(sys)
sys.setdefaultencoding('utf-8')

app = Flask(__name__,template_folder='templates')

def meizi(id):
    headerss = {
        'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36'
    }
    r = requests.get(url=str('http://www.mzitu.com/search/' + str(id)),headers=headerss)
    rr = re.findall("data-original='(.*?)' /></a><span><a.*?href=\"(.*?)\" target=\"_blank\">(.*?)</a></span><span",r.content,re.S)
    return rr


@app.route('/index/')
def index():
    return render_template('index.html')



@app.route('/search/',methods=['POST','GET'])
def search():
    if request.method == 'POST':
        idx = request.form['idx']
        return render_template('result.html', data=meizi(idx))
    else:
        return render_template('404.html')



if __name__ == '__main__':
    app.run(host='0.0.0.0',debug=True)

补充

妹子图有反扒机制,参考github上的这个代码可以绕过referrer-killer

把这个粗糙的小项目部署到服务器了,全部代码不足100行,以后想起来应该会继续更新优化的,比如先破解盗链,然后更加美观的输出,提供下载功能等等。

2018年8月2日06:31:37更新

这段时间学习了SQLAlchemy数据库创建,Blueprint蓝图,WTForms表单验证,还有一些前端的知识复习。这里重新优化一下代码,把结构层优化并且使用蓝图的概念,把前端显示优化一些。

具体代码打包放在这里

2018年8月4日03:25:16更新

一次大更新

  1. 前端界面优化
  2. 本来想把缩略样式图下载到本地,后来舍不得内存放弃了
  3. 很惭愧并没有用ORM模型操作数据库
  4. 用爬虫爬取了妹子图的所有信息保存到本地数据库
  5. 从数据库返回结果
  6. 提供下载功能,下载界面为一个python文件,用python3允许即可下载

具体代码打包放在这里

获取所有妹子图信息并且保存到数据库的代码

# -*- coding: utf-8 -*-
# @Time    : 2018/8/3 0003 22:09
# @Author  : Langzi
# @Blog    : www.langzi.fun
# @File    : 妹子信息.py
# @Software: PyCharm
import sys
import requests
import re
import pymysql
import contextlib
import threading
#定义上下文管理器,连接后自动关闭连接
reload(sys)
sys.setdefaultencoding('utf-8')

@contextlib.contextmanager
def mysql(host='127.0.0.1',user='root',passwd='root',db='meizi',port=3306,charset='utf8'):
    conn = pymysql.connect(host='127.0.0.1',user='root',passwd='root',db='meizi',port=3306,charset='utf8')
    cursor = conn.cursor()
    try:
        yield cursor
    finally:
        conn.commit()
        cursor.close()
        conn.close()

# # 执行sql
# with mysql() as cursor:
#    print(cursor)
#    row_count = cursor.execute("select * from tb7")
#    row_1 = cursor.fetchone()
#    print row_count, row_1

# 创建数据库
# create table data(
# id int primary key auto_increment,
# title varchar(80),
# url varchar(100),
# show_img varchar(80),
# all_img varchar(2000),
# index index_neme1 (title),
# index index_neme2 (url)
# )charset=utf8,engine=INNODB;
# )charset=utf8,engine=INNODB;
# ALTER TABLE `data` ADD UNIQUE (
# `title`
# )

def start():
    lock.acquire()
    headers = {
        'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36',
        'Referer':'www.mzitu.com'

    }
    url = 'http://www.mzitu.com/page/'
    for _ in range(1,188):
        list_dir=[]
        img_dir=[]
        try:
            rr = re.findall("data-original='(.*?)' /></a><span><a.*?href=\"(.*?)\" target=\"_blank\">(.*?)</a></span><span",requests.get(url=str(url+str(_)),timeout=5).content,re.S)
            for x,y,z in rr:
                count = re.search('><span>(\d\d)</span></a><a',requests.get(url=y,timeout=5).content,re.S).group().replace('<span>','').replace('</span></a><a','').replace('<','').replace('>','')
                for test_1 in range(1,int(count)):
                    url_1 = y+str('/')+str(test_1)
                    img = re.search('<img src="(.*?)" alt=',requests.get(url_1).content).group().replace('<img src="','').replace('" alt=','')
                    img_dir.append(img)
                print unicode(('Page:%s  Title:%s  URL:%s  count_img:%s')%(str(_), z, y,str(count)), 'utf-8')
                with mysql() as cursor:
                   sql = "insert into data(title,url,show_img,all_img) VALUES (%s,%s,%s,%s)"
                   cursor.execute(sql,(z,y,x,str(img_dir).replace("'",'|')))
        except Exception,e:
            print e
    lock.release()

lock =threading.Lock()
for x in range(10):
    t=threading.Thread(target=start).start()

2018年8月5日13:14:29

关于此项目的业务需求比较简单,用户发起一次请求输入关键词,随后数据库搜索匹配关键词,然后把结果反馈到页面中,提供一个下载功能,下载功能是一个Python爬虫文件,用户复制代码保存到本地,右键启动运行,自动把该结果保存到本地。

重新梳理一下后端和重新构架一下前端代码

后端

使用tree命令直接获取目录树结构,默认不显示文件。如果要显示文件使用命令 tree /F。

反馈结果如下

卷 数据 的文件夹 PATH 列表
卷序列号为 000000E5 0002:D245
E:.
│  1.txt
│  
└─app # 根目录
    │  config.py     # 配置文件,开启Debug模式等等
    │  run.py           # 启动文件
    │  __init__.py    # 初始化文件,注册蓝图,初始化 app = Flask(__name__,template_folder=('web/templates'),static_folder=('web/static'))
    │  
    └─web        # 核心业务处理,model层
        │  check_forms.py         # 表单验证,暂时没用到
        │  cretae_data_pymysql.py    # 创建数据库模型,没用到
        │  Imginfo.py            # 妹子详细信息,没用到
        │  Mmzi.py            # 核心处理函数,接受参数,数据库查询,返回结果
        │  __init__.py            # 初始化,注册蓝图,然后执行Mmzi.py
        │  
        ├─static            # 静态文件
        │  ├─css
        │  │      bootstrap-theme.css
        │  │      bootstrap-theme.css.map
        │  │      bootstrap-theme.min.css
        │  │      bootstrap-theme.min.css.map
        │  │      bootstrap.css
        │  │      bootstrap.css.map
        │  │      bootstrap.min.css
        │  │      bootstrap.min.css.map
        │  │      
        │  ├─fonts
        │  │      glyphicons-halflings-regular.eot
        │  │      glyphicons-halflings-regular.svg
        │  │      glyphicons-halflings-regular.ttf
        │  │      glyphicons-halflings-regular.woff
        │  │      glyphicons-halflings-regular.woff2
        │  │      
        │  ├─image
        │  │      1.jpg
        │  │      2.jpg
        │  │      3.jpg
        │  │      4.jpg
        │  │      5.jpg
        │  │      6.jpg
        │  │      test.jpg
        │  │      
        │  └─js
        │          bootstrap.js
        │          bootstrap.min.js
        │          jquery.js
        │          npm.js
        │          
        └─templates        # 模板HTML文件
                404.html    # 404页面
                index.html    # 主页
                layout.html    # jinja2模板
                referrer-killer.js    # 用不到
                result.html    # 返回结果的页面
        down.html    #下载一些代码,
        downimg.html    # 下载图片

app目录

run.py

import sys
sys.path.append('..')
from app import create_app

reload(sys)
sys.setdefaultencoding('utf-8')
app = create_app()

if __name__ == '__main__':
    app.run(host='0.0.0.0',debug=app.config['DEBUG'])

单纯的启动

config.py

import sys
reload(sys)
sys.setdefaultencoding('utf-8')
DEBUG = True

单纯的配置

init.py

import sys
reload(sys)
sys.path.append('..')
from flask import Flask
sys.setdefaultencoding('utf-8')

def create_app():
    app = Flask(__name__,template_folder=('web/templates'),static_folder=('web/static'))
    app.config.from_object('config')
    start_Blueprint(app)
    return app

def start_Blueprint(app):
    from app.web import web
    app.register_blueprint(web)

初始化文件,create_app()函数作用是是初始化app对象,选择静态文件夹目录和模板文件夹目录。start_Blueprint(app)为注册蓝图,从web目录下的init.py中导入一个变量web,说起来有点拗口,两个web意义不一样。导入的变量web是一个蓝图对象。综上init文件的作用是初始化对象并且注册蓝图

web目录下

init.py

from flask import Blueprint
web = Blueprint('web',__name__)
from app.web import Mmzi
from app.web import Imginfo

注册蓝图,这里的web变量就是蓝图的名字,随后导入下面两个文件,因为两个文件中没有加上ifname=mian其实导入这两个文件就是相当于执行了这两个文件

Mmzi.py

# -*- coding: utf-8 -*-
# @Time    : 2018/7/17 0017 21:06
# @Author  : Langzi
# @Blog    : www.langzi.fun
# @File    : Mmzi.py
# @Software: PyCharm
import sys
sys.path.append('..')
reload(sys)
from flask import Flask,make_response,request,Response,render_template,url_for
import time,re
import pymysql
import requests
from flask import Blueprint
from . import web

import contextlib
sys.setdefaultencoding('utf-8')

@contextlib.contextmanager
def mysql(host='127.0.0.1', port=3306, user='root', passwd='root', db='meizi',charset='utf8'):
    conn = pymysql.connect(host=host, port=port, user=user, passwd=passwd, db=db, charset=charset)
    cursor = conn.cursor()
    try:
        yield cursor
    finally:
        conn.commit()
        cursor.close()
        conn.close()

def meizi(id):
    idx = id.replace("'",'').replace('"','').replace('-','').replace('\\','').replace('and','').replace('or','').replace('&','').replace('|','').replace(')','').replace('(','').replace('=','').replace('#','').replace('%','')
    if len(idx)>10 or idx =='' or idx ==' ':
        return None
    with mysql() as cursor:
        sql = "select * from data where title like '%" + idx + "%'"
        row_count = cursor.execute(sql)
        row_1 = cursor.fetchall()
        return row_1

def meizii(id):
    idx = id.replace("'",'').replace('"','').replace('-','').replace('\\','').replace('and','').replace('or','').replace('&','').replace('|','').replace(')','').replace('(','').replace('=','').replace('#','').replace('%','')
    if len(idx)>30 or idx =='' or idx ==' ' or idx.find('http')<0:
        return None
    with mysql() as cursor:
        sql = "select * from data where url ='" + idx + "'"
        row_count = cursor.execute(sql)
        row_1 = cursor.fetchall()
        return row_1


@web.route('/')
def index():
    return render_template('index.html')



@web.route('/search/',methods=['POST','GET'])
def search():
    if request.method == 'POST':
        idx = request.form['idx']
        data=meizi(idx)
        print data
        if data == [] or data == None or data=='':
            return render_template('404.html')
        else:
            return render_template('result.html', data=data)

else:
    idx = request.args.get('idx')
    data = meizi(idx)
    if data == [] or data == None or data=='':
        return render_template('404.html')
    else:
        return render_template('result.html', data=data)

@web.route('/code/')
def get_code():
    return render_template('down.html')


@web.route('/downimg/',methods=['POST','GET'])
def downimg():
    if request.method == 'POST':
        idx = str(request.form['name']).replace('+','').replace('=','').replace('%2c','')
        data = meizii(idx)
        if data == [] or data == None :
            return render_template('404.html')
        else:
            for a,b,c,d,e in data:
                print a,b,c
                e = e+'|]'
                return render_template('downimg.html', data_title=b,data_url=e)
    else:
        return render_template('404.html')

这里是核心处理功能,首先在web目录下的init.py中导入web,web就是蓝图的对象,使用蓝图的话路由的注册就要用@web.route(‘/‘)

关于数据库的业务并没有使用ORM模型,第一这个项目比较小没必要使用数据库模型操作数据库,其次是直接使用pymysql链接比较方便

可以看到meizi(id)和meizii(id)这两个函数返回的结果不一样,第一个返回很多结果因为接受的是参数查询,第二个是下载功能需要在数据库中找到标题和详细网址。

随后是根目录,自动跳转到index.html

然后是search路由,负责接受用户输入的参数,并且提交给meizi()处理

然后是code路由,随便写的,返回一个页面,该页面有一些无关紧要的内容

随后是downimg路由,负责在搜索结果后,点击下载按钮,把结果的title传递过来,随后提交meizii()处理,获取详细的图片地址

至此后端代码全部完成

前端

说明

之前的前端代码虽然写的挺好看的,但是相对来说不够大气,于是在2018年8月5日13:41:09全部重写构造前端代码

结构

经过深思熟虑,随后设计了半天最终敲定使用下面这个排版结构

随后可以布局了,最上面是导航栏,下面的内容可以放在一个容器里面,左边占5份右边占7份

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Mmeizi</title>
    <link rel="stylesheet" href="../css/bootstrap.css">
    <link rel="stylesheet" href="../css/main.css">
</head>
<body>
<div class="navbar">导航栏</div>
<div class="container">
    <div class="col-md-5">左边的小导航栏</div>
    <div class="col-md-7">中间的文章</div>
</div>

</body>
<script src="../js/jquery.js"></script>
<script src="../js/bootstrap.js"></script>
</html>

导航栏

<div class="navbar navbar-default">
    <div class="container">
            <div class="nav navbar-header">
        <div class="nav navbar-brand">
            Meizi图
        </div>
    </div>
    <ul class="nav navbar-nav">
    <li><a href="#">博客首页</a></li>
    <li><a href="#">Flask笔记</a></li>
    <li><a href="#">Meizi项目地址</a></li>
    </ul>
        <form class="navbar-form navbar-right has-success" method="POST" action="/search/" >
        <div class="input-group">
            <div class="input-group-addon">关键词:</div>
            <input type="text" class="form-control-lg" name="idx">
        </div>
        <button type="submit" class="btn btn-default btn-sm">搜索</button>
        </form>

    </div>
</div>

效果如下

如果想让导航栏的brand也就是meizi图那个logo变成图片的话,让这个图片变成index.html的背景色

修改自己的main.css

.navbar-brand{
    background-image: url(../img/logo.jpg);
    /*指定背景图片的位置*/
    width: 50px;
    /*设定宽度*/
    background-size: 60%;
    /*背景图片的大小*/
    background-repeat: no-repeat;
    /*不要重复,因为背景图过小是会在网页中重复自身填充页面的*/
    background-position: center center;
    /*背景图片的位置居中*/
}

然后在html中指定为背景,把原来的brand修改

<!--<div class="nav navbar-brand">-->
        <!--Meizi图-->
    <a href="index.html" class="nav navbar-brand"></a>
<!--</div>-->

最后的效果如下(感觉这个logo还不如不用….)

左侧栏

根据之前的布局,开始着手左边的布局

<div class="col-md-2">
    <div class="list-group side-bar">
        <a href="#" class="list-group-item active">可爱</a>
        <a href="#" class="list-group-item">清纯</a>
        <a href="#" class="list-group-item">清秀</a>
        <a href="#" class="list-group-item">清新</a>
        <a href="#" class="list-group-item">气质</a>
    </div>
</div>

把栅格占2份,5份还是太大了…side-bar是自己要定义的样式,因为bootstrap的样式不够好看…

在main.css中

.side-bar .list-group-item{
    border: 0;
    /*不要边框*/
    border-radius: 3px;
    /*为元素添加圆角边框*/
    margin-bottom: 5px;
    /*每一项都有5个像素的间隙*/

}

.side-bar .list-group-item.active{
    /*当左侧栏的某个元素被点击激活*/
    background-color: #4cae4c;
    /*那么背点击的元素的背景色就变成了绿色*/
}

左边的侧栏效果就出来了

右边侧栏

右边的侧栏我其实没想写什么东西,大概就是一个图片然后配上一个标题在配上评论之类的,假装是个新闻的缩略图

<div class="col-md-10">
<div class="news-list">
<div class="news-list-item clearfix">
    <div class="col-md-3">
        <img src="../img/1.jpg">
    </div>
    <div class="col-md-9">
        <a href="#" class="title">随便写个标题</a>
        <div class="info">
            <span>作者:langzi</span> ·
            <span>阅读:650</span> ·
            <span>收藏:320</span> ·
            <span>评论:1</span>
        </div>
    </div>
</div>
</div>
</div>

然后在mian.css中修改样式,news-list和news-list-item是我们自己定义的样式,clearfix是bootstrap自带的清除浮动的功能。在div里面有图片,标题,作者以及阅读量之类的。图片占用3份,并且要在css中修改图片的大小,title和info的样式也需要我们自己定义

修改mian.css

.news-list .title{
    /*news-list 下的 title样式*/
    display: block;
    /*display是设置元素显示的方式,block是一块状元素的方式显示*/
    font-size: 18px;
    /*字体的大小*/
    font-weight: bold;
    /*字体为粗体*/
    margin-bottom: 5px;
    /*与下面的内容相距5个像素*/
}
.news-list .info{
    color: #080808;
    /*颜色变成纯黑色*/
}
a:hover{
    /*当鼠标放在a标签上的时候*/
    text-decoration: #46b8da;
    /*鼠标就变成了蓝色*/
}
.news-list .title:hover{
    text-decoration: #46b8da;
}
.news-list-item{
    padding-top: 20px;
    /*顶部间距*/
    padding-buttom:20px;
}
.news-list-item:first-child{
    padding-top: 0px;
}

然后把html中的代码多复制积分,假装有很多新闻资讯

<div class="col-md-10">
    <div class="news-list">

        <div class="news-list-item clearfix">
            <div class="col-md-3">
                <img src="../img/1.jpg">
            </div>
            <div class="col-md-9">
                <a href="#" class="title">随便写个标题</a>
                <div class="info">
                    <span>作者:langzi</span> ·
                    <span>阅读:650</span> ·
                    <span>收藏:320</span> ·
                    <span>评论:1</span>
                </div>
            </div>
        </div>

                        <div class="news-list-item clearfix">
            <div class="col-md-3">
                <img src="../img/1.jpg">
            </div>
            <div class="col-md-9">
                <a href="#" class="title">随便写个标题</a>
                <div class="info">
                    <span>作者:langzi</span> ·
                    <span>阅读:650</span> ·
                    <span>收藏:320</span> ·
                    <span>评论:1</span>
                </div>
            </div>
        </div>                <div class="news-list-item clearfix">
            <div class="col-md-3">
                <img src="../img/1.jpg">
            </div>
            <div class="col-md-9">
                <a href="#" class="title">随便写个标题</a>
                <div class="info">
                    <span>作者:langzi</span> ·
                    <span>阅读:650</span> ·
                    <span>收藏:320</span> ·
                    <span>评论:1</span>
                </div>
            </div>
        </div>                <div class="news-list-item clearfix">
            <div class="col-md-3">
                <img src="../img/1.jpg">
            </div>
            <div class="col-md-9">
                <a href="#" class="title">随便写个标题</a>
                <div class="info">
                    <span>作者:langzi</span> ·
                    <span>阅读:650</span> ·
                    <span>收藏:320</span> ·
                    <span>评论:1</span>
                </div>
            </div>
        </div>

最终的效果如下

到这里就差不多了,然后重新整合一下前端和后端的代码

整合

对使用list-group side-bar样式内容修改

<a href="{{url_for('web.search',idx='可爱')}}" class="list-group-item active">可爱</a>

大概就是这种格式,然后再修改一下视图函数

完成的代码放在这里

测试

现在可以点击这里来测试

推荐一个学习Flask与Python的博客

坚持原创技术分享,您的支持将鼓励我继续创作!
------ 本文结束 ------

版权声明

LangZi_Blog's by Jy Xie is licensed under a Creative Commons BY-NC-ND 4.0 International License
由浪子LangZi创作并维护的Langzi_Blog's博客采用创作共用保留署名-非商业-禁止演绎4.0国际许可证
本文首发于Langzi_Blog's 博客( http://langzi.fun ),版权所有,侵权必究。

0%