7.10
1.多線程
- 程序需要維護(hù)許多共享的狀態(tài)(尤其是可變狀態(tài))唉韭,Python中的列表夜涕、字典、集合都是線程安全的属愤,所以使用線程而不是進(jìn)程維護(hù)共享狀態(tài)的代價相對較小女器。
- 程序會花費大量時間在I/O操作上,沒有太多并行計算的需求且不需占用太多的內(nèi)存住诸。
2.多進(jìn)程
- 程序執(zhí)行計算密集型任務(wù)(如:字節(jié)碼操作驾胆、數(shù)據(jù)處理涣澡、科學(xué)計算)。
- 程序的輸入可以并行的分成塊丧诺,并且可以將運算結(jié)果合并入桂。
- 程序在內(nèi)存使用方面沒有任何限制且不強依賴于I/O操作(如:讀寫文件、套接字等)驳阎。
3.異步編程:對于一次I/O操作(以讀操作為例)抗愁,數(shù)據(jù)會先被拷貝到操作系統(tǒng)內(nèi)核的緩沖區(qū)中,然后從操作系統(tǒng)內(nèi)核的緩沖區(qū)拷貝到應(yīng)用程序的緩沖區(qū)(這種方式稱為標(biāo)準(zhǔn)I/O或緩存I/O搞隐,大多數(shù)文件系統(tǒng)的默認(rèn)I/O都是這種方式),最后交給進(jìn)程远搪。
- 阻塞 I/O(blocking I/O):進(jìn)程發(fā)起讀操作劣纲,如果內(nèi)核數(shù)據(jù)尚未就緒,進(jìn)程會阻塞等待數(shù)據(jù)直到內(nèi)核數(shù)據(jù)就緒并拷貝到進(jìn)程的內(nèi)存中谁鳍。
- 非阻塞 I/O(non-blocking I/O):進(jìn)程發(fā)起讀操作癞季,如果內(nèi)核數(shù)據(jù)尚未就緒,進(jìn)程不阻塞而是收到內(nèi)核返回的錯誤信息倘潜,進(jìn)程收到錯誤信息可以再次發(fā)起讀操作绷柒,一旦內(nèi)核數(shù)據(jù)準(zhǔn)備就緒,就立即將數(shù)據(jù)拷貝到了用戶內(nèi)存中涮因,然后返回废睦。
- 多路I/O復(fù)用( I/O multiplexing):監(jiān)聽多個I/O對象,當(dāng)I/O對象有變化(數(shù)據(jù)就緒)的時候就通知用戶進(jìn)程养泡。多路I/O復(fù)用的優(yōu)勢并不在于單個I/O操作能處理得更快嗜湃,而是在于能處理更多的I/O操作。
- 異步 I/O(asynchronous I/O):進(jìn)程發(fā)起讀操作后就可以去做別的事情了澜掩,內(nèi)核收到異步讀操作后會立即返回购披,所以用戶進(jìn)程不阻塞,當(dāng)內(nèi)核數(shù)據(jù)準(zhǔn)備就緒時肩榕,內(nèi)核發(fā)送一個信號給用戶進(jìn)程刚陡,告訴它讀操作完成了。
4.編寫一個處理用戶請求的服務(wù)器程序:
- 每收到一個請求株汉,創(chuàng)建一個新的進(jìn)程筐乳,來處理該請求;
- 每收到一個請求乔妈,創(chuàng)建一個新的線程哥童,來處理該請求;
- 每收到一個請求褒翰,放入一個事件列表贮懈,讓主進(jìn)程通過非阻塞I/O方式來處理請求
7.11(Tornado框架)
1.最適合用來開發(fā)需要處理長連接和應(yīng)對高并發(fā)的Web應(yīng)用
2.例子tornadoweb.org
3.創(chuàng)建并激活虛擬環(huán)境
mkdir hello-tornado
cd hello-tornado
python3 -m venv venv
source venv/bin/activate
4.安裝Tornado
pip3 install tornado
5.編寫Web應(yīng)用
import tornado.ioloop
import tornado.web
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write('<h1>Hello, world!</h1>')
def main():
app = tornado.web.Application(handlers=[(r'/', MainHandler), ])
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
if __name__ == '__main__':
main()
6.運行并訪問應(yīng)用
python3 example01.py
注意這里端口成了8888
7.指定Web應(yīng)用的監(jiān)聽端口
import tornado.ioloop
import tornado.web
from tornado.options import define, options, parse_command_line
# 定義默認(rèn)端口
define('port', default=8000, type=int)
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write('<h1>Hello, world!</h1>')
def main():
parse_command_line()
app = tornado.web.Application(handlers=[(r'/', MainHandler), ])
app.listen(options.port)
tornado.ioloop.IOLoop.current().start()
if __name__ == '__main__':
main()
8.路由解析
py文件:
import os
import random
import tornado.ioloop
import tornado.web
from tornado.options import define, options, parse_command_line
# 定義默認(rèn)端口
define('port', default=8000, type=int)
class SayingHandler(tornado.web.RequestHandler):
"""自定義請求處理器"""
def get(self):
sayings = [
'世上沒有絕望的處境匀泊,只有對處境絕望的人',
'人生的道路在態(tài)度的岔口一分為二,從此通向成功或失敗',
'所謂措手不及朵你,不是說沒有時間準(zhǔn)備各聘,而是有時間的時候沒有準(zhǔn)備',
'那些你認(rèn)為不靠譜的人生里,充滿你沒有勇氣做的事',
'在自己喜歡的時間里抡医,按照自己喜歡的方式躲因,去做自己喜歡做的事,這便是自由',
'有些人不屬于自己忌傻,但是遇見了也彌足珍貴'
]
# 渲染index.html模板頁
self.render('index.html', message=random.choice(sayings))
class WeatherHandler(tornado.web.RequestHandler):
"""自定義請求處理器"""
def get(self, city):
# Tornado框架會自動處理百分號編碼的問題
weathers = {
'北京': {'temperature': '-4~4', 'pollution': '195 中度污染'},
'成都': {'temperature': '3~9', 'pollution': '53 良'},
'深圳': {'temperature': '20~25', 'pollution': '25 優(yōu)'},
'廣州': {'temperature': '18~23', 'pollution': '56 良'},
'上海': {'temperature': '6~8', 'pollution': '65 良'}
}
if city in weathers:
self.render('weather.html', city=city, weather=weathers[city])
else:
self.render('index.html', message=f'沒有{city}的天氣信息')
class ErrorHandler(tornado.web.RequestHandler):
"""自定義請求處理器"""
def get(self):
# 重定向到指定的路徑
self.redirect('/saying')
def main():
"""主函數(shù)"""
parse_command_line()
app = tornado.web.Application(
# handlers是按列表中的順序依次進(jìn)行匹配的
handlers=[
(r'/saying/?', SayingHandler),
(r'/weather/([^/]{2,})/?', WeatherHandler),
(r'/.+', ErrorHandler),
],
# 通過template_path參數(shù)設(shè)置模板頁的路徑
template_path=os.path.join(os.path.dirname(__file__), 'templates')
)
app.listen(options.port)
tornado.ioloop.IOLoop.current().start()
if __name__ == '__main__':
main()
html文件:
mkdir templates
cd templates
touch index.html
vim index.html
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Tornado基礎(chǔ)</title>
</head>
<body>
<h1>{{message}}</h1>
</body>
</html>
touch weather.html
vim weather.html
<!-- weather.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Tornado基礎(chǔ)</title>
</head>
<body>
<h1>{{city}}</h1>
<hr>
<h2>溫度:{{weather['temperature']}}攝氏度</h2>
<h2>污染指數(shù):{{weather['pollution']}}</h2>
</body>
</html>
運行后訪問127.0.0.1:8000/saying
和127.0.0.1:8000/weather/北京
等即可看到效果
9.請求處理器
py文件
import os
import re
import tornado.ioloop
import tornado.web
from tornado.options import define, options, parse_command_line
# 定義默認(rèn)端口
define('port', default=8000, type=int)
users = {}
class User(object):
"""用戶"""
def __init__(self, nickname, gender, birthday):
self.nickname = nickname
self.gender = gender
self.birthday = birthday
class MainHandler(tornado.web.RequestHandler):
"""自定義請求處理器"""
def get(self):
# 從Cookie中讀取用戶昵稱
nickname = self.get_cookie('nickname')
if nickname in users:
self.render('userinfo.html', user=users[nickname])
else:
self.render('userform.html', hint='請?zhí)顚憘€人信息')
class UserHandler(tornado.web.RequestHandler):
"""自定義請求處理器"""
def post(self):
# 從表單參數(shù)中讀取用戶昵稱大脉、性別和生日信息
nickname = self.get_body_argument('nickname').strip()
gender = self.get_body_argument('gender')
birthday = self.get_body_argument('birthday')
# 檢查用戶昵稱是否有效
if not re.fullmatch(r'\w{6,20}', nickname):
self.render('userform.html', hint='請輸入有效的昵稱')
elif nickname in users:
self.render('userform.html', hint='昵稱已經(jīng)被使用過')
else:
users[nickname] = User(nickname, gender, birthday)
# 將用戶昵稱寫入Cookie并設(shè)置有效期為7天
self.set_cookie('nickname', nickname, expires_days=7)
self.render('userinfo.html', user=users[nickname])
def main():
"""主函數(shù)"""
parse_command_line()
app = tornado.web.Application(
handlers=[
(r'/', MainHandler), (r'/register', UserHandler)
],
template_path=os.path.join(os.path.dirname(__file__), 'templates')
)
app.listen(options.port)
tornado.ioloop.IOLoop.current().start()
if __name__ == '__main__':
main()
html文件:
touch templates/userform.html
vim templates/userform.html
<!-- userform.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Tornado基礎(chǔ)</title>
<style>
.em { color: red; }
</style>
</head>
<body>
<h1>填寫用戶信息</h1>
<hr>
<p class="em">{{hint}}</p>
<form action="/register" method="post">
<p>
<label>昵稱:</label>
<input type="text" name="nickname">
(字母數(shù)字下劃線,6-20個字符)
</p>
<p>
<label>性別:</label>
<input type="radio" name="gender" value="男" checked>男
<input type="radio" name="gender" value="女">女
</p>
<p>
<label>生日:</label>
<input type="date" name="birthday" value="1990-01-01">
</p>
<p>
<input type="submit" value="確定">
</p>
</form>
</body>
</html>
touch templates/userinfo.html
vim templates/userinfo.html
<!-- userinfo.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Tornado基礎(chǔ)</title>
</head>
<body>
<h1>用戶信息</h1>
<hr>
<h2>昵稱:{{user.nickname}}</h2>
<h2>性別:{{user.gender}}</h2>
<h2>出生日期:{{user.birthday}}</h2>
</body>
</html>
運行即可水孩,注冊后即可見用戶信息頁(如下圖)
7.12(Tornado異步)
1.舊版本
- 添加@tornado.web.asynchronous裝飾器:
class AsyncReqHandler(RequestHandler):
@tornado.web.asynchronous
def get(self):
http = httpclient.AsyncHTTPClient()
http.fetch("http://example.com/", self._on_download)
def _on_download(self, response):
do_something_with_response(response)
self.render("template.html")
- 添加@tornado.gen.coroutine裝飾器:
class GenAsyncHandler(RequestHandler):
@tornado.gen.coroutine
def get(self):
http_client = AsyncHTTPClient()
response = yield http_client.fetch("http://example.com")
do_something_with_response(response)
self.render("template.html")
- 使用@return_future裝飾器:
@return_future
def future_func(arg1, arg2, callback):
# Do stuff (possibly asynchronous)
callback(result)
async def caller():
await future_func(arg1, arg2)
2.aiomysql基于pymysql封裝镰矿,實現(xiàn)了對MySQL操作的異步化;操作Redis可以使用aioredis俘种;訪問MongoDB可以使用motor
3.例子:
- 創(chuàng)建數(shù)據(jù)庫并添加數(shù)據(jù)
create database hrs default charset utf8;
use hrs;
/* 創(chuàng)建部門表 */
create table tb_dept
(
dno int not null comment '部門編號',
dname varchar(10) not null comment '部門名稱',
dloc varchar(20) not null comment '部門所在地',
primary key (dno)
);
insert into tb_dept values
(10, '會計部', '北京'),
(20, '研發(fā)部', '成都'),
(30, '銷售部', '重慶'),
(40, '運維部', '深圳');
- 查詢和新增部門兩個操作
import json
import aiomysql
import tornado
import tornado.web
from tornado.ioloop import IOLoop
from tornado.options import define, parse_command_line, options
define('port', default=8000, type=int)
async def connect_mysql():
return await aiomysql.connect(
host='120.77.222.217',
port=3306,
db='hrs',
user='root',
password='123456',
)
class HomeHandler(tornado.web.RequestHandler):
async def get(self, no):
async with self.settings['mysql'].cursor(aiomysql.DictCursor) as cursor:
await cursor.execute("select * from tb_dept where dno=%s", (no, ))
if cursor.rowcount == 0:
self.finish(json.dumps({
'code': 20001,
'mesg': f'沒有編號為{no}的部門'
}))
return
row = await cursor.fetchone()
self.finish(json.dumps(row))
async def post(self, *args, **kwargs):
no = self.get_argument('no')
name = self.get_argument('name')
loc = self.get_argument('loc')
conn = self.settings['mysql']
try:
async with conn.cursor() as cursor:
await cursor.execute('insert into tb_dept values (%s, %s, %s)',
(no, name, loc))
await conn.commit()
except aiomysql.MySQLError:
self.finish(json.dumps({
'code': 20002,
'mesg': '添加部門失敗請確認(rèn)部門信息'
}))
else:
self.set_status(201)
self.finish()
def make_app(config):
return tornado.web.Application(
handlers=[(r'/api/depts/(.*)', HomeHandler), ],
**config
)
def main():
parse_command_line()
app = make_app({
'debug': True,
'mysql': IOLoop.current().run_sync(connect_mysql)
})
app.listen(options.port)
IOLoop.current().start()
if __name__ == '__main__':
main()
7.13(WebSocket)
1.特點
- 建立在TCP協(xié)議之上秤标,服務(wù)器端的實現(xiàn)比較容易
- 與HTTP協(xié)議有著良好的兼容性,默認(rèn)端口是80(WS)和443(WSS)宙刘,通信握手階段采用HTTP協(xié)議苍姜,能通過各種 HTTP 代理服務(wù)器(不容易被防火墻阻攔)
- 數(shù)據(jù)格式比較輕量,性能開銷小悬包,通信高效
- 可以發(fā)送文本衙猪,也可以發(fā)送二進(jìn)制數(shù)據(jù)
- 沒有同源策略的限制,客戶端(瀏覽器)可以與任意服務(wù)器通信
2.處理來自WebSocket的請求(tornado.websocket.WebSocketHandler類)
方法 | 作用 |
---|---|
open(*args, **kwargs) | 建立新的WebSocket連接后布近,Tornado框架會調(diào)用該方法屈嗤,該方法的參數(shù)與RequestHandler的get方法的參數(shù)類似,這也就意味著在open方法中可以執(zhí)行獲取請求參數(shù)吊输、讀取Cookie信息這樣的操饶号。 |
on_message(message) | 建立WebSocket之后,當(dāng)收到來自客戶端的消息時季蚂,Tornado框架會調(diào)用該方法茫船,這樣就可以對收到的消息進(jìn)行對應(yīng)的處理,必須重寫這個方法 |
on_close() | 當(dāng)WebSocket被關(guān)閉時扭屁,Tornado框架會調(diào)用該方法算谈,在該方法中可以通過close_code和close_reason了解關(guān)閉的原因 |
write_message(message, binary=False) | 將指定的消息通過WebSocket發(fā)送給客戶端,可以傳遞utf-8字符序列或者字節(jié)序列料滥,如果message是一個字典然眼,將會執(zhí)行JSON序列化。正常情況下葵腹,該方法會返回一個Future對象高每;如果WebSocket被關(guān)閉了屿岂,將引發(fā)WebSocketClosedError |
set_nodelay(value) | 默認(rèn)情況下,因為TCP的Nagle算法會導(dǎo)致短小的消息被延遲發(fā)送鲸匿,在考慮到交互性的情況下就要通過將該方法的參數(shù)設(shè)置為True來避免延遲 |
close(code=None, reason=None) | 主動關(guān)閉WebSocket爷怀,可以指定狀態(tài)碼(詳見RFC 6455 7.4.1節(jié))和原因 |
3.WebSocket客戶端編程
- 創(chuàng)建WebSocket對象
var webSocket = new WebSocket('ws://localhost:8000/ws');
- 編寫回調(diào)函數(shù)
# 如果要綁定多個事件回調(diào)函數(shù),可以用addEventListener方法带欢。另外运授,通過事件對象的data屬性獲得的數(shù)據(jù)可能是字符串,也有可能是二進(jìn)制數(shù)據(jù)乔煞,可以通過webSocket對象的binaryType屬性(blob吁朦、arraybuffer)或者通過typeof、instanceof運算符檢查類型進(jìn)行判定
webSocket.onopen = function(evt) { webSocket.send('...'); };
webSocket.onmessage = function(evt) { console.log(evt.data); };
webSocket.onclose = function(evt) {};
webSocket.onerror = function(evt) {};
4.項目:Web聊天室
"""
handlers.py - 用戶登錄和聊天的處理器
"""
import tornado.web
import tornado.websocket
nicknames = set()
connections = {}
class LoginHandler(tornado.web.RequestHandler):
def get(self):
self.render('login.html', hint='')
def post(self):
nickname = self.get_argument('nickname')
if nickname in nicknames:
self.render('login.html', hint='昵稱已被使用渡贾,請更換昵稱')
self.set_secure_cookie('nickname', nickname)
self.render('chat.html')
class ChatHandler(tornado.websocket.WebSocketHandler):
def open(self):
nickname = self.get_secure_cookie('nickname').decode()
nicknames.add(nickname)
for conn in connections.values():
conn.write_message(f'~~~{nickname}進(jìn)入了聊天室~~~')
connections[nickname] = self
def on_message(self, message):
nickname = self.get_secure_cookie('nickname').decode()
for conn in connections.values():
if conn is not self:
conn.write_message(f'{nickname}說:{message}')
def on_close(self):
nickname = self.get_secure_cookie('nickname').decode()
del connections[nickname]
nicknames.remove(nickname)
for conn in connections.values():
conn.write_message(f'~~~{nickname}離開了聊天室~~~')
"""
run_chat_server.py - 聊天服務(wù)器
"""
import os
import tornado.web
import tornado.ioloop
from handlers import LoginHandler, ChatHandler
if __name__ == '__main__':
app = tornado.web.Application(
handlers=[(r'/login', LoginHandler), (r'/chat', ChatHandler)],
template_path=os.path.join(os.path.dirname(__file__), 'templates'),
static_path=os.path.join(os.path.dirname(__file__), 'static'),
cookie_secret='MWM2MzEyOWFlOWRiOWM2MGMzZThhYTk0ZDNlMDA0OTU=',
)
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
<!-- login.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Tornado聊天室</title>
<style>
.hint { color: red; font-size: 0.8em; }
</style>
</head>
<body>
<div>
<div id="container">
<h1>進(jìn)入聊天室</h1>
<hr>
<p class="hint">{{hint}}</p>
<form method="post" action="/login">
<label>昵稱:</label>
<input type="text" placeholder="請輸入你的昵稱" name="nickname">
<button type="submit">登錄</button>
</form>
</div>
</div>
</body>
</html>
<!-- chat.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Tornado聊天室</title>
</head>
<body>
<h1>聊天室</h1>
<hr>
<div>
<textarea id="contents" rows="20" cols="120" readonly></textarea>
</div>
<div class="send">
<input type="text" id="content" size="50">
<input type="button" id="send" value="發(fā)送">
</div>
<p>
<a id="quit" href="javascript:void(0);">退出聊天室</a>
</p>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
<script>
$(function() {
// 將內(nèi)容追加到指定的文本區(qū)
function appendContent($ta, message) {
var contents = $ta.val();
contents += '\n' + message;
$ta.val(contents);
$ta[0].scrollTop = $ta[0].scrollHeight;
}
// 通過WebSocket發(fā)送消息
function sendMessage() {
message = $('#content').val().trim();
if (message.length > 0) {
ws.send(message);
appendContent($('#contents'), '我說:' + message);
$('#content').val('');
}
}
// 創(chuàng)建WebSocket對象
var ws= new WebSocket('ws://localhost:8888/chat');
// 連接建立后執(zhí)行的回調(diào)函數(shù)
ws.onopen = function(evt) {
$('#contents').val('~~~歡迎您進(jìn)入聊天室~~~');
};
// 收到消息后執(zhí)行的回調(diào)函數(shù)
ws.onmessage = function(evt) {
appendContent($('#contents'), evt.data);
};
// 為發(fā)送按鈕綁定點擊事件回調(diào)函數(shù)
$('#send').on('click', sendMessage);
// 為文本框綁定按下回車事件回調(diào)函數(shù)
$('#content').on('keypress', function(evt) {
keycode = evt.keyCode || evt.which;
if (keycode == 13) {
sendMessage();
}
});
// 為退出聊天室超鏈接綁定點擊事件回調(diào)函數(shù)
$('#quit').on('click', function(evt) {
ws.close();
location.href = '/login';
});
});
</script>
</body>
</html>
運行run_chat_server.py
文件逗宜,打開127.0.0.1:8888/login
即可,效果如下圖:
7.14
項目實戰(zhàn)課件被刪了,我看了下剥啤,接下來的爬蟲和機器學(xué)習(xí)項目實戰(zhàn)都被刪了锦溪。不脯。府怯。
7.15(網(wǎng)絡(luò)爬蟲和相關(guān)工具)
1.網(wǎng)絡(luò)爬蟲概念
2.網(wǎng)絡(luò)爬蟲應(yīng)用領(lǐng)域
3.網(wǎng)絡(luò)爬蟲合法性(Robots協(xié)議,全稱是“網(wǎng)絡(luò)爬蟲排除標(biāo)準(zhǔn)”,大多數(shù)網(wǎng)站都會定義robots.txt文件)
4.Http回顧:
- HTTP請求(請求行+請求頭+空行+[消息體])
- HTTP響應(yīng)(響應(yīng)行+響應(yīng)頭+空行+消息體)
5.相關(guān)工具
- Chrome Developer Tools:谷歌瀏覽器內(nèi)置的開發(fā)者工具
- POSTMAN:功能強大的網(wǎng)頁調(diào)試與RESTful請求工具
- HTTPie:命令行HTTP客戶端
- BuiltWith:識別網(wǎng)站所用技術(shù)的工具
- python-whois:查詢網(wǎng)站所有者的工具
- robotparser:解析robots.txt的工具
6.一個簡單的爬蟲(數(shù)據(jù)采集(網(wǎng)頁下載)防楷、數(shù)據(jù)處理(網(wǎng)頁解析)和數(shù)據(jù)存儲(將有用的信息持久化))
7.步驟:
- 設(shè)定抓取目標(biāo)(種子頁面/起始頁面)并獲取網(wǎng)頁
- 當(dāng)服務(wù)器無法訪問時牺丙,按照指定的重試次數(shù)嘗試重新下載頁面
- 在需要的時候設(shè)置用戶代理或隱藏真實IP,否則可能無法訪問頁面
- 對獲取的頁面進(jìn)行必要的解碼操作然后抓取出需要的信息
- 在獲取的頁面中通過某種方式(如正則表達(dá)式)抽取出頁面中的鏈接信息
- 對鏈接進(jìn)行進(jìn)一步的處理(獲取頁面并重復(fù)上面的動作)
- 將有用的信息進(jìn)行持久化以備后續(xù)的處理
8.從“搜狐體育”上獲取NBA新聞標(biāo)題和鏈接的爬蟲
9.爬蟲注意事項
- 處理相對鏈接复局。有的時候我們從頁面中獲取的鏈接不是一個完整的絕對鏈接而是一個相對鏈接冲簿,這種情況下需要將其與URL前綴進(jìn)行拼接(urllib.parse中的urljoin()函數(shù)可以完成此項操作)
- 設(shè)置代理服務(wù)。有些網(wǎng)站會限制訪問的區(qū)域(例如美國的Netflix屏蔽了很多國家的訪問)亿昏,有些爬蟲需要隱藏自己的身份峦剔,在這種情況下可以設(shè)置使用代理服務(wù)器,可以通過urllib.request中的ProxyHandler來為請求設(shè)置代理角钩。
- 限制下載速度吝沫。如果我們的爬蟲獲取網(wǎng)頁的速度過快,可能就會面臨被封禁或者產(chǎn)生“損害動產(chǎn)”的風(fēng)險(這個可能會導(dǎo)致吃官司且敗訴)递礼,可以在兩次下載之間添加延時從而對爬蟲進(jìn)行限速惨险。
- 避免爬蟲陷阱。有些網(wǎng)站會動態(tài)生成頁面內(nèi)容脊髓,這會導(dǎo)致產(chǎn)生無限多的頁面(例如在線萬年歷通常會有無窮無盡的鏈接)辫愉。可以通過記錄到達(dá)當(dāng)前頁面經(jīng)過了多少個鏈接(鏈接深度)來解決該問題将硝,當(dāng)達(dá)到事先設(shè)定的最大深度時爬蟲就不再像隊列中添加該網(wǎng)頁中的鏈接了恭朗。
- SSL相關(guān)問題屏镊。在使用urlopen打開一個HTTPS鏈接時會驗證一次SSL證書,如果不做出處理會產(chǎn)生錯誤提示“SSL: CERTIFICATE_VERIFY_FAILED”冀墨,可以通過使用未經(jīng)驗證的上下文和設(shè)置全局的取消證書驗證兩種方式加以解決:
# 使用未經(jīng)驗證的上下文
import ssl
request = urllib.request.Request(url='...', headers={...})
context = ssl._create_unverified_context()
web_page = urllib.request.urlopen(request, context=context)
# 設(shè)置全局的取消證書驗證
import ssl
ssl._create_default_https_context = ssl._create_unverified_context
7.16(數(shù)據(jù)采集和解析)
1.每個步驟的第三方庫
- 下載數(shù)據(jù) - urllib / requests / aiohttp
- 解析數(shù)據(jù) - re / lxml / beautifulsoup4(bs4)/ pyquery
- 緩存和持久化 - pymysql / sqlalchemy / peewee/ redis / pymongo
- 生成數(shù)字簽名 - hashlib
- 序列化和壓縮 - pickle / json / zlib
- 調(diào)度器 - 進(jìn)程(multiprocessing) / 線程(threading) / 協(xié)程(coroutine)
2.使用requests獲取頁面
3.四種采集方式
- 使用正則表達(dá)式
- 使用XPath和Lxml(BeautifulSoup)
- PyQuery
實例 - 獲取知乎發(fā)現(xiàn)上的問題鏈接
from urllib.parse import urljoin
import re
import requests
from bs4 import BeautifulSoup
def main():
headers = {'user-agent': 'Baiduspider'}
proxies = {
'http': 'http://122.114.31.177:808'
}
base_url = 'https://www.zhihu.com/'
seed_url = urljoin(base_url, 'explore')
resp = requests.get(seed_url,
headers=headers,
proxies=proxies)
soup = BeautifulSoup(resp.text, 'lxml')
href_regex = re.compile(r'^/question')
link_set = set()
for a_tag in soup.find_all('a', {'href': href_regex}):
if 'href' in a_tag.attrs:
href = a_tag.attrs['href']
full_url = urljoin(base_url, href)
link_set.add(full_url)
print('Total %d question pages found.' % len(link_set))
if __name__ == '__main__':
main()
7.17(存儲數(shù)據(jù))
1.存儲海量數(shù)據(jù)(數(shù)據(jù)持久化的首選方案應(yīng)該是關(guān)系型數(shù)據(jù)庫,如果要存儲海量的低價值數(shù)據(jù)闸衫,文檔數(shù)據(jù)庫也是不錯的選擇,MongoDB是文檔數(shù)據(jù)庫中的佼佼者)
2.數(shù)據(jù)緩存(可以使用Redis來提供高速緩存服務(wù))
3.實例 - 緩存知乎發(fā)現(xiàn)上的鏈接和頁面代碼
from hashlib import sha1
from urllib.parse import urljoin
import pickle
import re
import requests
import zlib
from bs4 import BeautifulSoup
from redis import Redis
def main():
# 指定種子頁面
base_url = 'https://www.zhihu.com/'
seed_url = urljoin(base_url, 'explore')
# 創(chuàng)建Redis客戶端
client = Redis(host='1.2.3.4', port=6379, password='1qaz2wsx')
# 設(shè)置用戶代理(否則訪問會被拒絕)
headers = {'user-agent': 'Baiduspider'}
# 通過requests模塊發(fā)送GET請求并指定用戶代理
resp = requests.get(seed_url, headers=headers)
# 創(chuàng)建BeautifulSoup對象并指定使用lxml作為解析器
soup = BeautifulSoup(resp.text, 'lxml')
href_regex = re.compile(r'^/question')
# 將URL處理成SHA1摘要(長度固定更簡短)
hasher_proto = sha1()
# 查找所有href屬性以/question打頭的a標(biāo)簽
for a_tag in soup.find_all('a', {'href': href_regex}):
# 獲取a標(biāo)簽的href屬性值并組裝完整的URL
href = a_tag.attrs['href']
full_url = urljoin(base_url, href)
# 傳入URL生成SHA1摘要
hasher = hasher_proto.copy()
hasher.update(full_url.encode('utf-8'))
field_key = hasher.hexdigest()
# 如果Redis的鍵'zhihu'對應(yīng)的hash數(shù)據(jù)類型中沒有URL的摘要就訪問頁面并緩存
if not client.hexists('zhihu', field_key):
html_page = requests.get(full_url, headers=headers).text
# 對頁面進(jìn)行序列化和壓縮操作
zipped_page = zlib.compress(pickle.dumps(html_page))
# 使用hash數(shù)據(jù)類型保存URL摘要及其對應(yīng)的頁面代碼
client.hset('zhihu', field_key, zipped_page)
# 顯示總共緩存了多少個頁面
print('Total %d question pages found.' % client.hlen('zhihu'))
if __name__ == '__main__':
main()
7.19(7.18號睡過頭了)
1.協(xié)程(coroutine)通常又稱之為微線程或纖程,它是相互協(xié)作的一組子程序(函數(shù))诽嘉。所謂相互協(xié)作指的是在執(zhí)行函數(shù)A時蔚出,可以隨時中斷去執(zhí)行函數(shù)B,然后又中斷繼續(xù)執(zhí)行函數(shù)A虫腋。注意骄酗,這一過程并不是函數(shù)調(diào)用(因為沒有調(diào)用語句),整個過程看似像多線程悦冀,然而協(xié)程只有一個線程執(zhí)行趋翻。協(xié)程通過yield關(guān)鍵字和send()操作來轉(zhuǎn)移執(zhí)行權(quán),協(xié)程之間不是調(diào)用者與被調(diào)用者的關(guān)系盒蟆。
2.協(xié)程的優(yōu)勢:
- 執(zhí)行效率極高踏烙,因為子程序(函數(shù))切換不是線程切換,由程序自身控制历等,沒有切換線程的開銷讨惩。
- 不需要多線程的鎖機制,因為只有一個線程寒屯,也不存在競爭資源的問題荐捻,當(dāng)然也就不需要對資源加鎖保護(hù),因此執(zhí)行效率高很多寡夹。
3.示例代碼和實例(多線程爬取“手機搜狐網(wǎng)”所有頁面)
4.AIOHTTP這個第三方庫处面,實現(xiàn)了HTTP客戶端和HTTP服務(wù)器的功能,對異步操作提供了非常好的支持,官方文檔
7.20(解析動態(tài)內(nèi)容)
1.JavaScript逆向工程(只是看了API)
2.使用BeautifulSoup進(jìn)行錯誤示范
import requests
from bs4 import BeautifulSoup
def main():
resp = requests.get('https://v.taobao.com/v/content/live?catetype=704&from=taonvlang')
soup = BeautifulSoup(resp.text, 'lxml')
for img_tag in soup.select('img[src]'):
print(img_tag.attrs['src'])
if __name__ == '__main__':
main()
3.使用Selenium
pip3 install selenium
from bs4 import BeautifulSoup
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
def main():
driver = webdriver.Chrome()
driver.get('https://v.taobao.com/v/content/live?catetype=704&from=taonvlang')
soup = BeautifulSoup(driver.page_source, 'lxml')
for img_tag in soup.body.select('img[src]'):
print(img_tag.attrs['src'])
if __name__ == '__main__':
main()
此程序指定使用Chrome瀏覽器菩掏,需要到Selenium的官方網(wǎng)站找到瀏覽器驅(qū)動的下載鏈接并下載需要的驅(qū)動魂角,然后配置環(huán)境變量后方可正常運行
7.21
驗證碼的課件太簡單
7.22(Scrapy概述)
1.定義:Scrapy是Python開發(fā)的一個非常流行的網(wǎng)絡(luò)爬蟲框架
2.組件:
- Scrapy引擎(Engine):Scrapy引擎是用來控制整個系統(tǒng)的數(shù)據(jù)處理流程
- 調(diào)度器(Scheduler):調(diào)度器從Scrapy引擎接受請求并排序列入隊列,并在Scrapy引擎發(fā)出請求后返還給它們
- 下載器(Downloader):下載器的主要職責(zé)是抓取網(wǎng)頁并將網(wǎng)頁內(nèi)容返還給蜘蛛(Spiders)
- 蜘蛛(Spiders):蜘蛛是有Scrapy用戶自定義的用來解析網(wǎng)頁并抓取特定URL返回的內(nèi)容的類智绸,每個蜘蛛都能處理一個域名或一組域名野揪,簡單的說就是用來定義特定網(wǎng)站的抓取和解析規(guī)則
- 條目管道(Item Pipeline):條目管道的主要責(zé)任是負(fù)責(zé)處理有蜘蛛從網(wǎng)頁中抽取的數(shù)據(jù)條目,它的主要任務(wù)是清理传于、驗證和存儲數(shù)據(jù)囱挑。當(dāng)頁面被蜘蛛解析后,將被發(fā)送到條目管道沼溜,并經(jīng)過幾個特定的次序處理數(shù)據(jù)平挑。每個條目管道組件都是一個Python類,它們獲取了數(shù)據(jù)條目并執(zhí)行對數(shù)據(jù)條目進(jìn)行處理的方法,同時還需要確定是否需要在條目管道中繼續(xù)執(zhí)行下一步或是直接丟棄掉不處理通熄。條目管道通常執(zhí)行的任務(wù)有:清理HTML數(shù)據(jù)唆涝、驗證解析到的數(shù)據(jù)(檢查條目是否包含必要的字段)、檢查是不是重復(fù)數(shù)據(jù)(如果重復(fù)就丟棄)唇辨、將解析到的數(shù)據(jù)存儲到數(shù)據(jù)庫(關(guān)系型數(shù)據(jù)庫或NoSQL數(shù)據(jù)庫)中
- 中間件(Middlewares):中間件是介于Scrapy引擎和其他組件之間的一個鉤子框架廊酣,主要是為了提供自定義的代碼來拓展Scrapy的功能,包括下載器中間件和蜘蛛中間件
3.數(shù)據(jù)處理流程(整個數(shù)據(jù)處理流程由Scrapy引擎進(jìn)行控制)
- 引擎詢問蜘蛛需要處理哪個網(wǎng)站赏枚,并讓蜘蛛將第一個需要處理的URL交給它
- 引擎讓調(diào)度器將需要處理的URL放在隊列中
- 引擎從調(diào)度那獲取接下來進(jìn)行爬取的頁面
- 調(diào)度將下一個爬取的URL返回給引擎亡驰,引擎將它通過下載中間件發(fā)送到下載器
- 當(dāng)網(wǎng)頁被下載器下載完成以后,響應(yīng)內(nèi)容通過下載中間件被發(fā)送到引擎饿幅;如果下載失敗了凡辱,引擎會通知調(diào)度器記錄這個URL,待會再重新下載
- 引擎收到下載器的響應(yīng)并將它通過蜘蛛中間件發(fā)送到蜘蛛進(jìn)行處理
- 蜘蛛處理響應(yīng)并返回爬取到的數(shù)據(jù)條目栗恩,此外還要將需要跟進(jìn)的新的URL發(fā)送給引擎
- 引擎將抓取到的數(shù)據(jù)條目送入條目管道透乾,把新的URL發(fā)送給調(diào)度器放入隊列中
4.安裝scrapypip3 install scrapy
5.使用scrapy
- 在items.py文件中定義字段,字段用來保存數(shù)據(jù)
import scrapy
class DoubanItem(scrapy.Item):
name = scrapy.Field()
year = scrapy.Field()
score = scrapy.Field()
director = scrapy.Field()
classification = scrapy.Field()
actor = scrapy.Field()
- 在spiders文件夾中編寫自己的爬蟲
scrapy genspider movie movie.douban.com --template=crawl
import scrapy
from scrapy.selector import Selector
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from douban.items import DoubanItem
class MovieSpider(CrawlSpider):
name = 'movie'
allowed_domains = ['movie.douban.com']
start_urls = ['https://movie.douban.com/top250']
rules = (
Rule(LinkExtractor(allow=(r'https://movie.douban.com/top250\?start=\d+.*'))),
Rule(LinkExtractor(allow=(r'https://movie.douban.com/subject/\d+')), callback='parse_item'),
)
def parse_item(self, response):
sel = Selector(response)
item = DoubanItem()
item['name']=sel.xpath('//*[@id="content"]/h1/span[1]/text()').extract()
item['year']=sel.xpath('//*[@id="content"]/h1/span[2]/text()').re(r'\((\d+)\)')
item['score']=sel.xpath('//*[@id="interest_sectl"]/div/p[1]/strong/text()').extract()
item['director']=sel.xpath('//*[@id="info"]/span[1]/a/text()').extract()
item['classification']= sel.xpath('//span[@property="v:genre"]/text()').extract()
item['actor']= sel.xpath('//*[@id="info"]/span[3]/a[1]/text()').extract()
return item
- 運行爬蟲(scrapy crawl movie -o參數(shù)來指定文件名磕秤,可將數(shù)據(jù)導(dǎo)成數(shù)據(jù)導(dǎo)出成JSON乳乌、CSV、XML市咆、pickle汉操、marshal等格式)
- 在pipelines.py中完成對數(shù)據(jù)進(jìn)行持久化的操作(清理HTML數(shù)據(jù),驗證爬取的數(shù)據(jù);丟棄重復(fù)的不必要的內(nèi)容;將爬取的結(jié)果進(jìn)行持久化操作)
import pymongo
from scrapy.exceptions import DropItem
from scrapy.conf import settings
from scrapy import log
class DoubanPipeline(object):
def __init__(self):
connection = pymongo.MongoClient(settings['MONGODB_SERVER'], settings['MONGODB_PORT'])
db = connection[settings['MONGODB_DB']]
self.collection = db[settings['MONGODB_COLLECTION']]
def process_item(self, item, spider):
#Remove invalid data
valid = True
for data in item:
if not data:
valid = False
raise DropItem("Missing %s of blogpost from %s" %(data, item['url']))
if valid:
#Insert data into database
new_moive=[{
"name":item['name'][0],
"year":item['year'][0],
"score":item['score'],
"director":item['director'],
"classification":item['classification'],
"actor":item['actor']
}]
self.collection.insert(new_moive)
log.msg("Item wrote to MongoDB database %s/%s" %
(settings['MONGODB_DB'], settings['MONGODB_COLLECTION']),
level=log.DEBUG, spider=spider)
return item
- 修改settings.py文件對項目進(jìn)行配置
BOT_NAME = 'douban'
SPIDER_MODULES = ['douban.spiders']
NEWSPIDER_MODULE = 'douban.spiders'
USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_3) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.54 Safari/536.5'
ROBOTSTXT_OBEY = True
DOWNLOAD_DELAY = 3
RANDOMIZE_DOWNLOAD_DELAY = True
COOKIES_ENABLED = True
MONGODB_SERVER = '120.77.222.217'
MONGODB_PORT = 27017
MONGODB_DB = 'douban'
MONGODB_COLLECTION = 'movie'
ITEM_PIPELINES = {
'douban.pipelines.DoubanPipeline': 400,
}
LOG_LEVEL = 'DEBUG'
HTTPCACHE_ENABLED = True
HTTPCACHE_EXPIRATION_SECS = 0
HTTPCACHE_DIR = 'httpcache'
HTTPCACHE_IGNORE_HTTP_CODES = []
HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'
7.23
Spider的屬性和方法
1.name:爬蟲的名字床绪。
2.allowed_domains:允許爬取的域名客情,不在此范圍的鏈接不會被跟進(jìn)爬取其弊。
3.start_urls:起始URL列表癞己,當(dāng)我們沒有重寫start_requests()方法時,就會從這個列表開始爬取梭伐。
4.custom_settings:用來存放蜘蛛專屬配置的字典痹雅,這里的設(shè)置會覆蓋全局的設(shè)置。
5.crawler:由from_crawler()方法設(shè)置的和蜘蛛對應(yīng)的Crawler對象糊识,Crawler對象包含了很多項目組件绩社,利用它我們可以獲取項目的配置信息,如調(diào)用crawler.settings.get()方法赂苗。
6.settings:用來獲取爬蟲全局設(shè)置的變量愉耙。
7.start_requests():此方法用于生成初始請求,它返回一個可迭代對象拌滋。該方法默認(rèn)是使用GET請求訪問起始URL朴沿,如果起始URL需要使用POST請求來訪問就必須重寫這個方法。
8.parse():當(dāng)Response沒有指定回調(diào)函數(shù)時,該方法就會被調(diào)用赌渣,它負(fù)責(zé)處理Response對象并返回結(jié)果魏铅,從中提取出需要的數(shù)據(jù)和后續(xù)的請求,該方法需要返回類型為Request或Item的可迭代對象(生成器當(dāng)前也包含在其中坚芜,因此根據(jù)實際需要可以用return或yield來產(chǎn)生返回值)览芳。
9.closed():當(dāng)蜘蛛關(guān)閉時,該方法會被調(diào)用,通常用來做一些釋放資源的善后操作袄琳。
感覺這個課件有點虎頭蛇尾远舅。。屯仗。
7.24
Scrapy分布式實現(xiàn)
1.安裝Scrapy-Redis。
2.配置Redis服務(wù)器搔谴。
3.修改配置文件魁袜。
SCHEDULER = 'scrapy_redis.scheduler.Scheduler'
DUPEFILTER_CLASS = 'scrapy_redis.dupefilter.RFPDupeFilter'
REDIS_HOST = '1.2.3.4'
REDIS_PORT = 6379
REDIS_PASSWORD = '1qaz2wsx'
SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.FifoQueue'
SCHEDULER_PERSIST = True(通過持久化支持接續(xù)爬取)
SCHEDULER_FLUSH_ON_START = True(每次啟動時重新爬榷氐凇)
Scrapyd分布式部署
1.安裝Scrapyd
2.修改配置文件
mkdir /etc/scrapyd
vim /etc/scrapyd/scrapyd.conf
3.安裝Scrapyd-Client
將項目打包成Egg文件,通過addversion.json接口部署到Scrapyd上峰弹。
7.25
實戰(zhàn)無課件
7.26
機器學(xué)習(xí)的一般步驟:
1.數(shù)據(jù)收集
2.數(shù)據(jù)準(zhǔn)備
3.數(shù)據(jù)分析
4.訓(xùn)練算法
5.測試算法
6.應(yīng)用算法
7.27
Pandas的應(yīng)用(只有標(biāo)題無內(nèi)容)
7.28
NumPy和SciPy的應(yīng)用(只有標(biāo)題無內(nèi)容)
7.29(Matplotlib和數(shù)據(jù)可視化)
1.安裝matplotlibpip3 install matplotlib
2.繪制折線圖和散點圖
import matplotlib.pyplot as plt
def main():
# 保存x軸數(shù)據(jù)的列表
x_values = [x for x in range(1, 11)]
# 保存y軸數(shù)據(jù)的列表
y_values = [x ** 2 for x in range(1, 11)]
# 設(shè)置圖表的標(biāo)題以及x和y軸的說明
plt.title('Square Numbers')
plt.xlabel('Value', fontsize=18)
plt.ylabel('Square', fontsize=18)
# 設(shè)置刻度標(biāo)記的文字大小
plt.tick_params(axis='both', labelsize=16)
# 繪制折線圖
# plt.plot(x_values, y_values)
# 繪制散點圖
plt.axis([0, 12, 0, 120]) # 調(diào)整x軸和y軸的坐標(biāo)范圍
plt.plot(x_values, y_values, 'xr')
plt.show()
if __name__ == '__main__':
main()
3.繪制正弦曲線
import matplotlib.pyplot as plt
import numpy as np
def main():
# 指定采樣的范圍以及樣本的數(shù)量
x_values = np.linspace(0, 2 * np.pi, 1000)
# 計算每個樣本對應(yīng)的正弦值
y_values = np.sin(x_values)
# 繪制折線圖(線條形狀為--, 顏色為藍(lán)色)
plt.plot(x_values, y_values, '--b')
# 繪制折線圖(線條形狀為--, 顏色為紅色)
plt.plot(x_values, np.sin(2 * x_values), '--r')
plt.show()
if __name__ == '__main__':
main()
4.在兩個坐標(biāo)系上繪制出兩條曲線
import matplotlib.pyplot as plt
import numpy as np
def main():
# 將樣本數(shù)量減少為50個
x_values = np.linspace(0, 2 * np.pi, 50)
# 設(shè)置繪圖為2行1列活躍區(qū)為1區(qū)(第一個圖)
plt.subplot(2, 1, 1)
plt.plot(x_values, np.sin(x_values), 'o-b')
# 設(shè)置繪圖為2行1列活躍區(qū)為2區(qū)(第二個圖)
plt.subplot(2, 1, 2)
plt.plot(x_values, np.sin(2 * x_values), '.-r')
plt.show()
if __name__ == '__main__':
main()
5.繪制直方圖
import matplotlib.pyplot as plt
import numpy as np
def main():
# 通過random模塊的normal函數(shù)產(chǎn)生1000個正態(tài)分布的樣本
data = np.random.normal(10.0, 5.0, 1000)
# 繪制直方圖(直方的數(shù)量為10個)
plt.hist(data, 10)
plt.show()
if __name__ == '__main__':
main()
6.使用Pygal繪制矢量圖
from random import randint
import pygal
def roll_dice(n=1):
total = 0
for _ in range(n):
total += randint(1, 6)
return total
def main():
results = []
# 將兩顆色子搖10000次記錄點數(shù)
for _ in range(10000):
face = roll_dice(2)
results.append(face)
freqs = []
# 統(tǒng)計2~12點各出現(xiàn)了多少次
for value in range(2, 13):
freq = results.count(value)
freqs.append(freq)
# 繪制柱狀圖
hist = pygal.Bar()
hist.title = 'Result of rolling two dice'
hist.x_labels = [x for x in range(2, 13)]
hist.add('Frequency', freqs)
# 保存矢量圖
hist.render_to_file('result.svg')
if __name__ == '__main__':
main()
7.30
k最近鄰分類無課件
7.31
決策樹無課件
8.1
貝葉斯分類無課件
8.2
支持向量機無課件
8.3
K-均值聚類無課件
8.4
回歸分析無課件
8.5
大數(shù)據(jù)分析入門無課件
8.6
大數(shù)據(jù)分析進(jìn)階無課件
8.7
Tensorflow入門無課件
8.8
Tensorflow實戰(zhàn)無課件
8.9
推薦系統(tǒng)實戰(zhàn)無課件
8.10
團(tuán)隊項目開發(fā)準(zhǔn)備
1.傳統(tǒng)的溝通方式無法確定處理的優(yōu)先級(缺陷管理工具)
2.沒有能夠用于驗證的環(huán)境(實施持續(xù)交付)
3.用別名目錄管理項目分支及重新制作數(shù)據(jù)庫非常困難(實施版本控制)
4.不運行系統(tǒng)就無法察覺問題(實施持續(xù)集成,將團(tuán)隊成員的工作成果經(jīng)常芜果、持續(xù)的進(jìn)行構(gòu)建和測試)
5.覆蓋了其他成員修正的代碼(實施版本控制)
6.無法實施代碼重構(gòu)(大量的可重用的測試并實施持續(xù)集成)
7.不知道bug的修正日期無法追蹤退化(版本控制系統(tǒng)鞠呈、缺陷管理系統(tǒng)和持續(xù)集成之間需要交互,最好能夠和自動化部署工具集成到一起來使用)
8.發(fā)布過程太復(fù)雜(實施持續(xù)交付)
9.推薦工具
- 版本控制:git
- 缺陷管理:Redmine
- 持續(xù)集成:Jenkins和TravisCI
8.11
1.Docker簡介:Docker屬于對Linux容器技術(shù)的一種封裝(利用了Linux的namespace和cgroup技術(shù))右钾,它提供了簡單易用的容器使用接口蚁吝,是目前最流行的 Linux 容器解決方案。Docker將應(yīng)用程序與該程序的依賴打包在一個文件里面舀射,運行這個文件窘茁,就會生成一個虛擬容器。程序在這個虛擬容器里運行脆烟,就好像在真實的物理機上運行一樣山林。
2.安裝Docker:
- 確定操作系統(tǒng)內(nèi)核版本(CentOS 7要求64位,內(nèi)核版本3.10+邢羔;CentOS 6要求64位驼抹,內(nèi)核版本2.6+),可以通過
uname -r
確定Linux系統(tǒng)內(nèi)核版本拜鹤。 - 使用yum安裝Docker并啟動:
yum -y install docker
systemctl start docker
- 查看Docker的信息和版本
docker version
docker info
- 從Docker的鏡像倉庫下載名為hello-world的鏡像文件
docker pull hello-world
- 查看所有鏡像文件
docker images
- 通過鏡像文件創(chuàng)建并運行容器
docker container run --name 容器名 境像文件名
- 刪除這個容器
docker container rm mycontainer
- 刪除剛才鏡像文件
docker rmi 鏡像文件名
- 將服務(wù)器更換為國內(nèi)鏡像,修改 /etc/docker/daemon.js 文件
{
"registry-mirrors": [
"http://hub-mirror.c.163.com",
"https://registry.docker-cn.com"
]
}
3.使用Docker
- 安裝Nginx(在Docker中創(chuàng)建一個容器即可)
docker container run -d -p 80:80 --rm --name mynginx nginx
-d表示容器在后臺運行(不產(chǎn)生輸出到Shell)并顯示容器的ID框冀;-p是用來映射容器的端口到宿主機的端口,冒號前面是宿主機的端口敏簿,冒號后面是容器內(nèi)部使用的端口明也;--rm表示容器停止后自動刪除容器,例如執(zhí)行命令docker container stop mynginx后,容器就不復(fù)存在了诡右;--name后面的mynginx是自定義的容器名字安岂;在創(chuàng)建容器的過程中,需要用到nginx的鏡像文件帆吻,鏡像文件的下載是自動完成的域那,如果沒有指定版本號,默認(rèn)是最新版本(latest)
- 將自己的Web項目(頁面)部署到Nginx上猜煮,可以使用容器拷貝命令將指定路徑下所有的文件和文件夾拷貝到容器的指定目錄中
docker container cp /root/web/index.html mynginx:/usr/share/nginx/html
- 重新創(chuàng)建容器(要先停止當(dāng)前運行的容器)
docker container run -d -p 80:80 --rm --name mynginx --volume $PWD/html:/usr/share/nginx/html nginx
container是可以省略的次员,也就是說docker container run和docker run是一樣的,而docker container cp和docker cp是一樣的王带。此外淑蔚,命令中的--volume也可以縮寫為-v,就如同-d是--detach的縮寫愕撰,-p是--publish的縮寫刹衫。$PWD代表宿主系統(tǒng)當(dāng)前文件夾
- 查看運行中的容器
docker ps
- 啟動和停止容器
docker start mynginx
docker stop mynginx
- 查看所有容器
docker container ls -a
- 刪除容器(刪除正在運行中的容器,需要在rm后加-f選項)
docker rm -f mynginx
3.安裝MySQL - 先檢查一下有沒有MySQL的鏡像文件
docker search mysql
- 下載MySQL鏡像并指定鏡像的版本號
docker pull mysql:5.7
- 查看已經(jīng)下載的鏡像文件
docker images
- 創(chuàng)建并運行MySQL容器
docker run -d -p 3306:3306 --name mysql57 -v $PWD/mysql/conf:/etc/mysql/mysql.cnf.d -v $PWD/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 mysql:5.7
4.安裝Redis
5.構(gòu)建鏡像(docker commit不推薦,docker build命令和Dockerfile文件)
6.Dockerfile指令
7.容器編排
8.12
MySQL性能優(yōu)化
1.使用索引
- B-Tree索引
- HASH索引
- R-Tree索引(空間索引)
- Full-text索引(全文索引)
2.SQL優(yōu)化
- 通過show status了解各種SQL的執(zhí)行頻率
- 定位低效率的SQL語句 - 慢查詢?nèi)罩荆?code>show processlist)
- 通過
explain
了解SQL的執(zhí)行計劃 - 通過show profiles和show profile for query分析SQL
- 優(yōu)化CRUD操作
- 優(yōu)化insert語句
- 優(yōu)化order by語句
- 優(yōu)化group by語句
- 優(yōu)化嵌套查詢
- 優(yōu)化or條件
- 優(yōu)化分頁查詢
- 使用SQL提示
- USE INDEX
- IGNORE INDEX
- FORCE INDEX
3.配置優(yōu)化
- 調(diào)整max_connections
- 調(diào)整back_log
- 調(diào)整table_open_cache
- 調(diào)整thread_cache_size
調(diào)整innodb_lock_wait_timeout
4.架構(gòu)優(yōu)化
- 通過拆分提高表的訪問效率
- 垂直拆分
- 水平拆分
- 逆范式理論
- 數(shù)據(jù)表設(shè)計的規(guī)范程度稱之為范式(Normal Form)
- 1NF:列不能再拆分
- 2NF:所有的屬性都依賴于主鍵
- 3NF:所有的屬性都直接依賴于主鍵(消除傳遞依賴)
- BCNF:消除非平凡多值依賴
- 數(shù)據(jù)表設(shè)計的規(guī)范程度稱之為范式(Normal Form)
- 使用中間表提高統(tǒng)計查詢速度
- 主從復(fù)制和讀寫分離
- 配置MySQL集群
8.13
1.阮一峰老師的《理解RESTful架構(gòu)》以及《RESTful API設(shè)計指南》搞挣。
2.rukk文檔撰寫
8.14
關(guān)于Django的一些面試問答(https://github.com/jackfrued/Python-100-Days/blob/master/Day91-100/95.使用Django開發(fā)商業(yè)項目.md)
8.15
1.軟件測試是一種用來促進(jìn)鑒定軟件的正確性带迟、完整性、安全性和品質(zhì)的過程囱桨,也就是在規(guī)定的條件下對程序進(jìn)行操作以發(fā)現(xiàn)程序中的錯誤仓犬,衡量軟件的品質(zhì)并對其是否能滿足設(shè)計要求進(jìn)行評估的過程
2.測試的方法:
- 黑盒測試:測試應(yīng)用程序的功能,而不是其內(nèi)部結(jié)構(gòu)或運作舍肠。測試者不需具備應(yīng)用程序的代碼搀继、內(nèi)部結(jié)構(gòu)和編程語言的專門知識。測試者只需知道什么是系統(tǒng)應(yīng)該做的事翠语,即當(dāng)鍵入一個特定的輸入叽躯,可得到一定的輸出。測試案例是依應(yīng)用系統(tǒng)應(yīng)該做的功能啡专,照規(guī)范险毁、規(guī)格或要求等設(shè)計制圈。測試者選擇有效輸入和無效輸入來驗證是否正確的輸出们童。此測試方法可適合大部分的軟件測試,例如集成測試和系統(tǒng)測試
- 白盒測試:測試應(yīng)用程序的內(nèi)部結(jié)構(gòu)或運作鲸鹦,而不是測試應(yīng)用程序的功能(即黑箱測試)慧库。在白箱測試時,以編程語言的角度來設(shè)計測試案例馋嗜。測試者輸入數(shù)據(jù)驗證數(shù)據(jù)流在程序中的流動路徑齐板,并確定適當(dāng)?shù)妮敵觯愃茰y試電路中的節(jié)點
3.測試的種類
- 單元測試:對軟件組成單元進(jìn)行測試,其目的是檢驗軟件基本組成單位的正確性甘磨,測試的對象是軟件設(shè)計的最小單位 - 函數(shù)《Python必會的單元測試框架 - unittest》橡羞。
- 集成測試:將程序模塊采用適當(dāng)?shù)募刹呗越M裝起來,對系統(tǒng)的接口及集成后的功能進(jìn)行正確性檢測的測試工作济舆。其主要目的是檢查軟件單位之間的接口是否正確卿泽,集成測試的對象是已經(jīng)經(jīng)過單元測試的模塊。
- 系統(tǒng)測試:系統(tǒng)測試主要包括功能測試滋觉、界面測試签夭、可靠性測試、易用性測試椎侠、性能測試第租。
- 回歸測試:為了檢測代碼修改而引入的錯誤所進(jìn)行的測試活動∥壹停回歸測試是軟件維護(hù)階段的重要工作慎宾,有研究表明,回歸測試帶來的耗費占軟件生命周期的1/3總費用以上浅悉。
4.測試驅(qū)動開發(fā)
5.Selenium/Robot Framework:Selenium是實現(xiàn)Web應(yīng)用程序的功能測試以及集成測試自動化的瀏覽器驅(qū)動測試工具群.和使用瀏覽器的用戶相同璧诵,Selenium可以在瀏覽器進(jìn)行的鼠標(biāo)操作、在表單中輸入文字仇冯、驗證表單的值等之宿,利用這一點就可以將手動操作變成自動化操作。
8.16
8.17(項目部署上線指南)
1.上線前的檢查工作(python manage.py check --deploy)
2.將DEBUG設(shè)置為False并配置ALLOWED_HOSTS苛坚。
DEBUG = False
ALLOWED_HOSTS = ['*']
3.安全相關(guān)的配置
# 保持HTTPS連接的時間
SECURE_HSTS_SECONDS = 3600
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
# 自動重定向到安全連接
SECURE_SSL_REDIRECT = True
# 避免瀏覽器自作聰明推斷內(nèi)容類型
SECURE_CONTENT_TYPE_NOSNIFF = True
# 避免跨站腳本攻擊
SECURE_BROWSER_XSS_FILTER = True
# COOKIE只能通過HTTPS進(jìn)行傳輸
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
# 防止點擊劫持攻擊手段 - 修改HTTP協(xié)議響應(yīng)頭
# 當(dāng)前網(wǎng)站是不允許使用<iframe>標(biāo)簽進(jìn)行加載的
X_FRAME_OPTIONS = 'DENY'
4.敏感信息放到環(huán)境變量或文件中
SECRET_KEY = os.environ['SECRET_KEY']
DB_USER = os.environ['DB_USER']
DB_PASS = os.environ['DB_PASS']
REDIS_AUTH = os.environ['REDIS_AUTH']
5.注冊域名比被、解析域名以及購買權(quán)威機構(gòu)頒發(fā)的證書
6.常用開源軟件。
功能 | 開源方案 |
---|---|
版本控制工具 | Git泼舱、Mercurial等缀、SVN |
缺陷管理 | Redmine、Mantis |
負(fù)載均衡 | Nginx娇昙、LVS尺迂、HAProxy |
郵件服務(wù) | Postfix、Sendmail |
HTTP服務(wù) | Nginx冒掌、Apache |
消息隊列 | RabbitMQ噪裕、ZeroMQ、Redis |
文件系統(tǒng) | FastDFS |
基于位置服務(wù)(LBS) | MongoDB股毫、Redis |
監(jiān)控服務(wù) | Nagios膳音、Zabbix |
關(guān)系型數(shù)據(jù)庫 | MySQL、PostgreSQL |
非關(guān)系型數(shù)據(jù)庫 | MongoDB铃诬、Redis祭陷、Cassandra |
搜索引擎 | ElasticSearch苍凛、Solr |
緩存服務(wù) | Mamcached、Redis |
7.常用云服務(wù)
功能 | 可用的云服務(wù) |
---|---|
團(tuán)隊協(xié)作工具 | Teambition兵志、釘釘 |
代碼托管平臺 | Github醇蝴、Gitee、CODING |
郵件服務(wù) | SendCloud |
云存儲(CDN) | 七牛想罕、OSS哑蔫、LeanCloud、Bmob弧呐、又拍云闸迷、AWS |
移動端推送 | 極光、友盟俘枫、百度 |
即時通信 | 環(huán)信腥沽、融云 |
短信服務(wù) | 云片、極光鸠蚪、Luosimao今阳、又拍云 |
第三方登錄 | 友盟、ShareSDK |
網(wǎng)站監(jiān)控和統(tǒng)計 | 阿里云監(jiān)控茅信、監(jiān)控寶盾舌、百度云觀測、小鳥云 |
8.18
8.19
全文完蘸鲸。妖谴。。