好孤独啊,背后再也没有那只傻猴子跟着自己了,你怎么回头都看不到他蹦蹦跳跳的影子了。心里有什么东西忽然坍塌了,她从高高在上的公主宝座上跌落尘埃。
项目目标
提供一个用户输入,等待用户输入关键词,然后后端程序对输入的参数跳转到妹子图网站,使用正则提前相关的网址和标题,然后返回匹配到的图片和标题。
主体项目结构
主体函数结构
实战
爬取函数
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.sxadmin.github.io
# @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](https://
github.com/jpgerek/referrer-killer)
把这个粗糙的小项目部署到服务器了,全部代码不足100行,以后想起来应该会继续更新优化的,比如先破解盗链,然后更加美观的输出,提供下载功能等等。
2018年8月2日06:31:37更新
这段时间学习了SQLAlchemy数据库创建,Blueprint蓝图,WTForms表单验证,还有一些前端的知识复习。这里重新优化一下代码,把结构层优化并且使用蓝图的概念,把前端显示优化一些。
具体代码打包放在这里
2018年8月4日03:25:16更新
一次大更新
- 前端界面优化
- 本来想把缩略样式图下载到本地,后来舍不得内存放弃了
- 很惭愧并没有用ORM模型操作数据库
- 用爬虫爬取了妹子图的所有信息保存到本地数据库
- 从数据库返回结果
- 提供下载功能,下载界面为一个python文件,用python3允许即可下载
具体代码打包放在这里
获取所有妹子图信息并且保存到数据库的代码
# -*- coding: utf-8 -*-
# @Time : 2018/8/3 0003 22:09
# @Author : Langzi
# @Blog : www.sxadmin.github.io
# @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.sxadmin.github.io
# @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>
大概就是这种格式,然后再修改一下视图函数
完成的代码放在这里
测试
现在可以点击这里来测试