第七色在线视频,2021少妇久久久久久久久久,亚洲欧洲精品成人久久av18,亚洲国产精品特色大片观看完整版,孙宇晨将参加特朗普的晚宴

為了賬號安全,請及時綁定郵箱和手機立即綁定
2.2 pyenv

純 pyenv 是可以看做一個 Python 版本管理器,可以同時管理多個 Python 版本共存。 簡單地說,pyenv 可以根據(jù)需求使用戶在系統(tǒng)里安裝和管理多個 Python 版本。它的基本功能如下:配置當(dāng)前用戶的 Python 的版本;配置當(dāng)前 shell 的 python 版本;配置某個項目(目錄及子目錄)的 Python 版本;配置多個虛擬環(huán)境。注意:pyenv 的工作原理就是利用系統(tǒng)環(huán)境變量 PATH 的優(yōu)先級,劫持 Python 的命令到 pyenv 上,根據(jù)用戶所在的環(huán)境或目錄,使用不同版本的 Python 。2.2.1 pyenv 的安裝、更新和卸載安裝: 在 Linux 系統(tǒng)上安裝 pyenv 的方式非常簡單,由官方提供的安裝 bash 腳本,也可以直接下載源碼即可。具體操作如下:# 安裝git$ yum install git -y$ git clone https://github.com/pyenv/pyenv.git ~/.pyenv# 安裝 pyenv-virtualenv$ git clone https://github.com/pyenv/pyenv-virtualenv.git ~/.pyenv/plugins/pyenv-virtualenv$ cat ~/.bashrc...# 在~/.bashrc最后加上如下3行export PATH="~/.pyenv/bin:$PATH"eval "$(pyenv init -)"eval "$(pyenv virtualenv-init -)"# 使配置生效$ source ~/.bashrc# 查看pyenv的版本$ pyenv version更新: 因為是使用 git 安裝的 pyenv,所以更新只需要繼續(xù)用 git 拉最新的代碼即可。更新 pyenv 步驟如下:cd ~/.pyenv 或者 cd $(pyenv root)git pull卸載: 由于 pyenv 把一切都放在~/.pyenv 下了,所以卸載很方便,兩個步驟就行了:刪除 ~/.bashrc 中添加的環(huán)境變量;刪除 ~/.pyenv 目錄及其下面所有文件。rm -rf ~/.pyenv` 或者 `rm -rf $(pyenv root)Tips:pyenv 并不支持 Windows,因為作者大大不想浪費時間去兼容 Windows 平臺。2.2.2 pyenv 的命令pyenv 的常用命令如下:pyenv commands : 查看 pyenv 工具支持的命令;pyenv local:python 的局部設(shè)置,當(dāng)前目錄生效,加上 --unset 選項表示取消;pyenv global:python 的全局設(shè)置,整個系統(tǒng)生效;pyenv shell:指定當(dāng)前 shell 使用的 Python , 可以使用 --unset 取消指定 ;pyenv --version: 查看 pyenv 的版本;pyenv version:顯示當(dāng)前活動的 Python 版本。# 系統(tǒng)環(huán)境[root@server ~]# pyenv versionsystem (set by /root/.pyenv/version)# 激活創(chuàng)建的虛擬環(huán)境[root@server ~]# pyenv activate env-3.8.1pyenv-virtualenv: prompt changing will be removed from future release. configure `export PYENV_VIRTUALENV_DISABLE_PROMPT=1' to simulate the behavior.# 再次查看(env-3.8.1) [root@server ~]# pyenv versionenv-3.8.1 (set by PYENV_VERSION environment variable)pyenv versions:查看所有的虛擬環(huán)境;pyenv install --list: 查看所有可以安裝的 Python 版本;pyenv install:安裝對應(yīng)版本的 Python 作為虛擬環(huán)境的解釋器。這一步通常第一次需要聯(lián)網(wǎng)下載對應(yīng)的 Python 包,會比較慢;pyenv uninstall:卸載某個 Python 版本。pyenv-virtualenv 工具的常用命令:pyenv virtualenv python版本號 name :創(chuàng)建一個虛擬環(huán)境,,命名為 name, 創(chuàng)建好后可通過 pyenv versions 來查看;pyenv activate name :切換到 name 虛擬環(huán)境中 ;pyenv deactivate :退出虛擬環(huán)境 ;pyenv virtualenv-delete name :刪除名為 name 的虛擬環(huán)境;

2.2 Map 的屬性和方法

Map 提供的屬性和方法從增、刪、改、查幾個方面入手,主要有以下 5 種:方法名描述set接收鍵值對,向 Map 實例中添加元素get傳入指定的 key 獲取 Map 實例上的值delete傳入指定的 key 刪除 Map 實例中對應(yīng)的值clear清空 Map 實例has傳入指定的 key 查找在 Map 實例中是否存在size屬性,返回 Map 實例的長度Map 提供 size 屬性可以獲取 Map 實例上的長度var map = new Map([["x", 1], ["y", 2], ["z", 3]]);console.log(map.size) // 3set() 方法為 Map 實例添加或更新一個指定了鍵(key)和值(value)的鍵值對。myMap.set(key, value);通常情況下不會一開始就初始化值,而是動態(tài)地添加,或更新 Map 時需要用到 set 方法,可以新增和修改 Map 實例的值。而且 key 值可以是任意類型的,查看如下示例:var map = new Map();var str = 'string';var obj = {};var arr = [];var fun = function() {};map.set(str, '鍵的類型字符串');map.set(obj, '鍵的類型對象');map.set(arr, '鍵的類型數(shù)組');map.set(fun, '鍵的類型函數(shù)');上面的代碼中,我們定義了不同類型的變量,使用這些變量為 map 添加數(shù)據(jù)。相比 Object 對象,擴展性更強了。另外還可以鏈?zhǔn)教砑渔I值對:var map = new Map();map.set('a', 1).set('b', 2).set('c', 3);console.log(map); // Map(3) {"a" => 1, "b" => 2, "c" => 3}使用鏈?zhǔn)教砑渔I值對的方式比較簡潔,如果需要添加多個值,建議使用這樣的方式去添加。get() 方法是接收一個指定的鍵(key)返回一個 Map 對象中與這個指定鍵相關(guān)聯(lián)的值,如果找不到這個鍵則返回 undefined。myMap.get(key);使用上面的示例,可以通過 get 方法獲取對應(yīng)的值:console.log(map.get('string')); // "鍵的類型字符串"console.log(map.get(str)); // "鍵的類型字符串"console.log(map.get(obj)); // "鍵的類型對象"console.log(map.get(arr)); // "鍵的類型數(shù)組"console.log(map.get(fun)); // "鍵的類型數(shù)組"上面的代碼可以看出,我們可以直接使用鍵的值去獲取 Map 實例上對應(yīng)的值,也可以通過定義變量的方式去獲取。has() 方法是用于判斷指定的鍵是否存在,并返回一個 bool 值,如果指定元素存在于 Map 中,則返回 true,否則返回 false。myMap.has(key);實例:var map = new Map();map.set("a", 11);map.has("a"); // truemap.has("b"); // falsedelete() 方法用于移除 Map 實例上的指定元素,如果 Map 對象中存在該元素,則移除它并返回 true;否則如果該元素不存在則返回 false。myMap.delete(key);實例:var map = new Map();map.set("a", 11);map.delete("a"); // truemap.has("a"); // falseclear() 方法會移除 Map 對象中的所有元素,返回 undefined。myMap.clear(key);實例:var map = new Map();map.set("a", 11);map.clear(); // 返回 undefined這里需要注意的是 clear() 返回的值是 undefined 而不是 true 所以如果在判斷結(jié)果的時候需要注意這一點。

2. 基于 Scrapy 框架的頭條熱點新聞數(shù)據(jù)爬取

還是按照我們以前的套路來進(jìn)行,第一步是使用 startproject 命令創(chuàng)建熱點新聞項目:[root@server ~]# cd scrapy-test/[root@server scrapy-test]# pyenv activate scrapy-testpyenv-virtualenv: prompt changing will be removed from future release. configure `export PYENV_VIRTUALENV_DISABLE_PROMPT=1' to simulate the behavior.(scrapy-test) [root@server scrapy-test]# scrapy startproject toutiao_hotnewsNew Scrapy project 'toutiao_hotnews', using template directory '/root/.pyenv/versions/3.8.1/envs/scrapy-test/lib/python3.8/site-packages/scrapy/templates/project', created in: /root/scrapy-test/toutiao_hotnewsYou can start your first spider with: cd toutiao_hotnews scrapy genspider example example.com(scrapy-test) [root@server scrapy-test]#接著,根據(jù)我們要抓取的新聞數(shù)據(jù)字段,先定義好 Item:import scrapyclass ToutiaoHotnewsItem(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field() title = scrapy.Field() abstract = scrapy.Field() source = scrapy.Field() source_url = scrapy.Field() comments_count = scrapy.Field() behot_time = scrapy.Field()有了 Item 之后,我們需要新建一個 Spider,可以使用 genspider 命令生成,也可以手工編寫一個 Python 文件,代碼內(nèi)容如下:# 代碼位置:toutiao_hotnews/toutiao_hotnews/spiders/hotnews.pyimport copyimport hashlibfrom urllib.parse import urlencodeimport jsonimport timefrom scrapy import Request, Spiderfrom toutiao_hotnews.items import ToutiaoHotnewsItemhotnews_url = "https://www.toutiao.com/api/pc/feed/?"params = { 'category': 'news_hot', 'utm_source': 'toutiao', 'widen': 1, 'max_behot_time': '', 'max_behot_time_tmp': '', 'as': '', 'cp': ''}headers = { 'referer': 'https://www.toutiao.com/ch/news_hot/', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36'}cookies = {'tt_webid':'6856365980324382215'} max_behot_time = '0'def get_as_cp(): # 該函數(shù)主要是為了獲取as和cp參數(shù),程序參考今日頭條中的加密js文件:home_4abea46.js zz = {} now = round(time.time()) e = hex(int(now)).upper()[2:] a = hashlib.md5() a.update(str(int(now)).encode('utf-8')) i = a.hexdigest().upper() if len(e) != 8: zz = {'as':'479BB4B7254C150', 'cp':'7E0AC8874BB0985'} return zz n = i[:5] a = i[-5:] r = '' s = '' for i in range(5): s = s + n[i] + e[i] for j in range(5): r = r + e[j + 3] + a[j] zz ={ 'as': 'A1' + s + e[-3:], 'cp': e[0:3] + r + 'E1' } return zzclass HotnewsSpider(Spider): name = 'hotnews' allowed_domains = ['www.toutiao.com'] start_urls = ['http://www.toutiao.com/'] # 記錄次數(shù),注意停止 count = 0 def _get_url(self, max_behot_time): new_params = copy.deepcopy(params) zz = get_as_cp() new_params['as'] = zz['as'] new_params['cp'] = zz['cp'] new_params['max_behot_time'] = max_behot_time new_params['max_behot_time_tmp'] = max_behot_time return "{}{}".format(hotnews_url, urlencode(new_params)) def start_requests(self): """ 第一次爬取 """ request_url = self._get_url(max_behot_time) self.logger.info(f"we get the request url : {request_url}") yield Request(request_url, headers=headers, cookies=cookies, callback=self.parse) def parse(self, response): """ 根據(jù)得到的結(jié)果得到獲取下一次請求的結(jié)果 """ self.count += 1 datas = json.loads(response.text) data = datas['data'] for d in data: item = ToutiaoHotnewsItem() item['title'] = d['title'] item['abstract'] = d.get('abstract', '') item['source'] = d['source'] item['source_url'] = d['source_url'] item['comments_count'] = d.get('comments_count', 0) item['behot_time'] = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(d['behot_time'])) self.logger.info(f'得到的item={item}') yield item if self.count < self.settings['REFRESH_COUNT']: max_behot_time = datas['next']['max_behot_time'] self.logger.info("we get the next max_behot_time: {}, and the count is {}".format(max_behot_time, self.count)) yield Request(self._get_url(max_behot_time), headers=headers, cookies=cookies)這里的代碼之前一樣,第一次構(gòu)造 Request 請求在 start_requests() 方法中,接下來在根據(jù)每次請求結(jié)果中獲取 max_behot_time 值再進(jìn)行下一次請求。另外我使用了全局計算變量 count 來模擬刷新的次數(shù),控制請求熱點新聞次數(shù),防止無限請求下去。此外,Scrapy logger 在每個 spider 實例中提供了一個可以訪問和使用的實例,我們再需要打印日志的地方直接使用 self.logger 即可,它對應(yīng)日志的配置如下:# 代碼位置:toutiao_hotnews/settings.py# 注意設(shè)置下下載延時DOWNLOAD_DELAY = 5# ...#是否啟動日志記錄,默認(rèn)TrueLOG_ENABLED = True LOG_ENCODING = 'UTF-8'#日志輸出文件,如果為NONE,就打印到控制臺LOG_FILE = 'toutiao_hotnews.log'#日志級別,默認(rèn)DEBUGLOG_LEVEL = 'INFO'# 日志日期格式 LOG_DATEFORMAT = "%Y-%m-%d %H:%M:%S"#日志標(biāo)準(zhǔn)輸出,默認(rèn)False,如果True所有標(biāo)準(zhǔn)輸出都將寫入日志中,比如代碼中的print輸出也會被寫入到LOG_STDOUT = False接下來是 Item Pipelines 部分,這次我們將抓取到的新聞保存到 MySQL 數(shù)據(jù)庫中。此外,我們還有一個需求就是選擇當(dāng)前最新的10條新聞發(fā)送到本人郵件,這樣每天早上就能定時收到最新的頭條新聞,豈不美哉。首先我想給自己的郵件發(fā)送 HTML 格式的數(shù)據(jù),然后列出最新的10條新聞,因此第一步是是準(zhǔn)備好模板熱點新聞的模板頁面,具體模板頁面如下:# 代碼位置: toutiao_hotnews/html_template.pyhotnews_template_html = """<!DOCTYPE html><html><head> <title>頭條熱點新聞一覽</title></head><style type="text/css"></style><body><div class="container"><h3 style="margin-bottom: 10px">頭條熱點新聞一覽</h3>$news_list</div></body></html>"""要注意一點,Scrapy 的郵箱功能只能發(fā)送文本內(nèi)容,不能發(fā)送 HTML 內(nèi)容。為了能支持發(fā)送 HTML 內(nèi)容,我繼承了原先的 MailSender 類,并對原先的 send() 方法稍做改動:# 代碼位置: mail.pyimport logging from email import encoders as Encodersfrom email.mime.base import MIMEBasefrom email.mime.multipart import MIMEMultipartfrom email.mime.nonmultipart import MIMENonMultipartfrom email.mime.text import MIMETextfrom email.utils import COMMASPACE, formatdatefrom scrapy.mail import MailSenderfrom scrapy.utils.misc import arg_to_iterlogger = logging.getLogger(__name__)class HtmlMailSender(MailSender): def send(self, to, subject, body, cc=None, mimetype='text/plain', charset=None, _callback=None): from twisted.internet import reactor #####去掉了與attachs參數(shù)相關(guān)的判斷語句,其余代碼不變############# msg = MIMEText(body, 'html', 'utf-8') ########################################################## to = list(arg_to_iter(to)) cc = list(arg_to_iter(cc)) msg['From'] = self.mailfrom msg['To'] = COMMASPACE.join(to) msg['Date'] = formatdate(localtime=True) msg['Subject'] = subject rcpts = to[:] if cc: rcpts.extend(cc) msg['Cc'] = COMMASPACE.join(cc) if charset: msg.set_charset(charset) if _callback: _callback(to=to, subject=subject, body=body, cc=cc, attach=attachs, msg=msg) if self.debug: logger.debug('Debug mail sent OK: To=%(mailto)s Cc=%(mailcc)s ' 'Subject="%(mailsubject)s" Attachs=%(mailattachs)d', {'mailto': to, 'mailcc': cc, 'mailsubject': subject, 'mailattachs': len(attachs)}) return dfd = self._sendmail(rcpts, msg.as_string().encode(charset or 'utf-8')) dfd.addCallbacks( callback=self._sent_ok, errback=self._sent_failed, callbackArgs=[to, cc, subject, len(attachs)], errbackArgs=[to, cc, subject, len(attachs)], ) reactor.addSystemEventTrigger('before', 'shutdown', lambda: dfd) return dfd緊接著就是我們的 pipelines.py 文件中的代碼:import loggingfrom string import Templatefrom itemadapter import ItemAdapterimport pymysqlfrom toutiao_hotnews.mail import HtmlMailSenderfrom toutiao_hotnews.items import ToutiaoHotnewsItemfrom toutiao_hotnews.html_template import hotnews_template_htmlfrom toutiao_hotnews import settingsclass ToutiaoHotnewsPipeline: logger = logging.getLogger('pipelines_log') def open_spider(self, spider): # 使用自己的MailSender類 self.mailer = HtmlMailSender().from_settings(spider.settings) # 初始化連接數(shù)據(jù)庫 self.db = pymysql.connect( host=spider.settings.get('MYSQL_HOST', 'localhost'), user=spider.settings.get('MYSQL_USER', 'root'), password=spider.settings.get('MYSQL_PASS', '123456'), port=spider.settings.get('MYSQL_PORT', 3306), db=spider.settings.get('MYSQL_DB_NAME', 'mysql'), charset='utf8' ) self.cursor = self.db.cursor() def process_item(self, item, spider): # 插入sql語句 sql = "insert into toutiao_hotnews(title, abstract, source, source_url, comments_count, behot_time) values (%s, %s, %s, %s, %s, %s)" if item and isinstance(item, ToutiaoHotnewsItem): self.cursor.execute(sql, (item['title'], item['abstract'], item['source'], item['source_url'], item['comments_count'], item['behot_time'])) return item def query_data(self, sql): data = {} try: self.cursor.execute(sql) data = self.cursor.fetchall() except Exception as e: logging.error('database operate error:{}'.format(str(e))) self.db.rollback() return data def close_spider(self, spider): sql = "select title, source_url, behot_time from toutiao_hotnews where 1=1 order by behot_time limit 10" # 獲取10條最新的熱點新聞 data = self.query_data(sql) news_list = "" # 生成html文本主體 for i in range(len(data)): news_list += "<div><span>{}、<a href=https://www.toutiao.com{}>{} [{}]</a></span></div>".format(i + 1, data[i][1], data[i][0], data[i][2]) msg_content = Template(hotnews_template_html).substitute({"news_list": news_list}) self.db.commit() self.cursor.close() self.db.close() self.logger.info("最后統(tǒng)一發(fā)送郵件") # 必須加return,不然會報錯 return self.mailer.send(to=["2894577759@qq.com"], subject="這是一個測試", body=msg_content, cc=["2894577759@qq.com"])這里我們會將 MySQL 的配置統(tǒng)一放到 settings.py 文件中,然后使用 spider.settings 來讀取響應(yīng)的信息。其中 open_spider() 方法用于初始化連接數(shù)據(jù)庫,process_item() 方法用于生成 SQL 語句并提交插入動作,最后的 close_spider() 方法用于提交數(shù)據(jù)庫執(zhí)行動作、關(guān)閉數(shù)據(jù)庫連接以及發(fā)送統(tǒng)一新聞熱點郵件。下面是我們將這個 Pipeline 在 settings.py 中開啟以及配置數(shù)據(jù)庫信息、郵件服務(wù)器信息,同時也要注意關(guān)閉遵守 Robot 協(xié)議,這樣爬蟲才能正常執(zhí)行。ROBOTSTXT_OBEY = False# 啟動對應(yīng)的pipelineITEM_PIPELINES = { 'toutiao_hotnews.pipelines.ToutiaoHotnewsPipeline': 300,}# 數(shù)據(jù)庫配置MYSQL_HOST = "180.76.152.113"MYSQL_PORT = 9002MYSQL_USER = "store"MYSQL_PASS = "數(shù)據(jù)庫密碼"MYSQL_DB_NAME = "ceph_check"# 郵箱配置MAIL_HOST = 'smtp.qq.com'MAIL_PORT = 25MAIL_FROM = '2894577759@qq.com'MAIL_PASS = '你的授權(quán)碼'MAIL_USER = '2894577759@qq.com'來看看我們這個頭條新聞爬蟲的爬取效果,視頻演示如下:83

2.3 實現(xiàn)

核心利用 sed 代碼:#!/bin/bash# auth:kaliarch# func:sys info check# version:v1.0# sys:centos6.x/7.x# 判斷用戶是否為root用戶,如果非root則提示需要root用戶執(zhí)行腳本[ $(id -u) -gt 0 ] && echo "請用root用戶執(zhí)行此腳本!" && exit 1sysversion=$(rpm -q centos-release|cut -d- -f3)line="-------------------------------------------------"# 創(chuàng)建日志目錄[ -d logs ] || mkdir logs# 定義日志文件sys_check_file="logs/$(ip a show dev eth0|grep -w inet|awk '{print $2}'|awk -F '/' '{print $1}')-`date +%Y%m%d`.txt"# 獲取系統(tǒng)cpu信息function get_cpu_info() { Physical_CPUs=$(grep "physical id" /proc/cpuinfo| sort | uniq | wc -l) Virt_CPUs=$(grep "processor" /proc/cpuinfo | wc -l) CPU_Kernels=$(grep "cores" /proc/cpuinfo|uniq| awk -F ': ' '{print $2}') CPU_Type=$(grep "model name" /proc/cpuinfo | awk -F ': ' '{print $2}' | sort | uniq) CPU_Arch=$(uname -m)cat <<EOF | column -t CPU信息:物理CPU個數(shù): $Physical_CPUs邏輯CPU個數(shù): $Virt_CPUs每CPU核心數(shù): $CPU_KernelsCPU型號: $CPU_TypeCPU架構(gòu): $CPU_ArchEOF}# 獲取系統(tǒng)內(nèi)存信息function get_mem_info() { check_mem=$(free -m) MemTotal=$(grep MemTotal /proc/meminfo| awk '{print $2}') #KB MemFree=$(grep MemFree /proc/meminfo| awk '{print $2}') #KB let MemUsed=MemTotal-MemFree MemPercent=$(awk "BEGIN {if($MemTotal==0){printf 100}else{printf \"%.2f\",$MemUsed*100/$MemTotal}}") report_MemTotal="$((MemTotal/1024))""MB" #內(nèi)存總?cè)萘?MB) report_MemFree="$((MemFree/1024))""MB" #內(nèi)存剩余(MB) report_MemUsedPercent="$(awk "BEGIN {if($MemTotal==0){printf 100}else{printf \"%.2f\",$MemUsed*100/$MemTotal}}")""%" #內(nèi)存使用率%cat <<EOF內(nèi)存信息:${check_mem}EOF}# 獲取系統(tǒng)網(wǎng)絡(luò)信息function get_net_info() { pri_ipadd=$(ip a show dev eth0|grep -w inet|awk '{print $2}'|awk -F '/' '{print $1}') pub_ipadd=$(curl ifconfig.me -s) gateway=$(ip route | grep default | awk '{print $3}') mac_info=$(ip link| egrep -v "lo"|grep link|awk '{print $2}') dns_config=$(egrep -v "^$|^#" /etc/resolv.conf) route_info=$(route -n)cat <<EOF | column -t IP信息:系統(tǒng)公網(wǎng)地址: ${pub_ipadd}系統(tǒng)私網(wǎng)地址: ${pri_ipadd}網(wǎng)關(guān)地址: ${gateway}MAC地址: ${mac_info}路由信息:${route_info}DNS 信息:${dns_config}EOF}# 獲取系統(tǒng)磁盤信息function get_disk_info() { disk_info=$(fdisk -l|grep "Disk /dev"|cut -d, -f1) disk_use=$(df -hTP|awk '$2!="tmpfs"{print}') disk_inode=$(df -hiP|awk '$1!="tmpfs"{print}')cat <<EOF磁盤信息:${disk_info}磁盤使用:${disk_use}inode信息:${disk_inode}EOF}# 獲取系統(tǒng)信息function get_systatus_info() { sys_os=$(uname -o) sys_release=$(cat /etc/redhat-release) sys_kernel=$(uname -r) sys_hostname=$(hostname) sys_selinux=$(getenforce) sys_lang=$(echo $LANG) sys_lastreboot=$(who -b | awk '{print $3,$4}') sys_runtime=$(uptime |awk '{print $3,$4}'|cut -d, -f1) sys_time=$(date) sys_load=$(uptime |cut -d: -f5)cat <<EOF | column -t 系統(tǒng)信息:系統(tǒng): ${sys_os}發(fā)行版本: ${sys_release}系統(tǒng)內(nèi)核: ${sys_kernel}主機名: ${sys_hostname}selinux狀態(tài): ${sys_selinux}系統(tǒng)語言: ${sys_lang}系統(tǒng)當(dāng)前時間: ${sys_time}系統(tǒng)最后重啟時間: ${sys_lastreboot}系統(tǒng)運行時間: ${sys_runtime}系統(tǒng)負(fù)載: ${sys_load}EOF}# 獲取服務(wù)信息function get_service_info() { port_listen=$(netstat -lntup|grep -v "Active Internet") kernel_config=$(sysctl -p 2>/dev/null) if [ ${sysversion} -gt 6 ];then service_config=$(systemctl list-unit-files --type=service --state=enabled|grep "enabled") run_service=$(systemctl list-units --type=service --state=running |grep ".service") else service_config=$(/sbin/chkconfig | grep -E ":on|:啟用" |column -t) run_service=$(/sbin/service --status-all|grep -E "running") ficat <<EOF服務(wù)啟動配置:${service_config}${line}運行的服務(wù):${run_service}${line}監(jiān)聽端口:${port_listen}${line}內(nèi)核參考配置:${kernel_config}}# 獲取系統(tǒng)用戶信息function get_sys_user() { login_user=$(awk -F: '{if ($NF=="/bin/bash") print $0}' /etc/passwd) ssh_config=$(egrep -v "^#|^$" /etc/ssh/sshd_config) sudo_config=$(egrep -v "^#|^$" /etc/sudoers |grep -v "^Defaults") host_config=$(egrep -v "^#|^$" /etc/hosts) crond_config=$(for cronuser in /var/spool/cron/* ;do ls ${cronuser} 2>/dev/null|cut -d/ -f5;egrep -v "^$|^#" ${cronuser} 2>/dev/null;echo "";done)cat <<EOF系統(tǒng)登錄用戶:${login_user}${line}ssh 配置信息:${ssh_config}${line}sudo 配置用戶:${sudo_config}${line}定時任務(wù)配置:${crond_config}${line}hosts 信息:${host_config}EOF}# 獲取進(jìn)程信息function process_top_info() { top_title=$(top -b n1|head -7|tail -1) cpu_top10=$(top b -n1 | head -17 | tail -10) mem_top10=$(top -b n1|head -17|tail -10|sort -k10 -r)cat <<EOFCPU占用top10:${top_title}${cpu_top10}內(nèi)存占用top10:${top_title}${mem_top10}EOF}# 信息匯總function sys_check() { get_cpu_info echo ${line} get_mem_info echo ${line} get_net_info echo ${line} get_disk_info echo ${line} get_systatus_info echo ${line} get_service_info echo ${line} get_sys_user echo ${line} process_top_info}sys_check > ${sys_check_file}

1. Django 的文件上傳實驗

同樣,話不多說,我們先通過兩個上傳的例子來看看 Django 的上傳功能。實驗1:簡單文件上傳準(zhǔn)備本地文件,upload.txt,上傳到服務(wù)器的 /root/test/django 目錄下;準(zhǔn)備模板文件,顯示上傳按鈕:<form method="post" action="/hello/file_upload/" enctype="multipart/form-data"> {% csrf_token %} {{ forms }}<br> <input type="submit" value="提交"></form>完成 Form 表單以及視圖函數(shù)的編寫:class FileUploadForm(forms.Form): file = forms.FileField(label="文件上傳")def handle_uploaded_file(f): save_path = os.path.join('/root/test/django', f.name) with open(save_path, 'wb+') as fp: for chunk in f.chunks(): fp.write(chunk)@csrf_exemptdef file_upload(request, *args, **kwargs): error_msg = "" if request.method == 'POST': forms = FileUploadForm(request.POST,request.FILES) if forms.is_valid(): handle_uploaded_file(request.FILES['file']) return HttpResponse('上傳成功') error_msg = "異常" else: forms = FileUploadForm() return render(request,'test_file_upload.html',{'forms':forms, "error_msg": error_msg})編寫 URLConf 配置:urlpatterns = [ # ... # 文件上傳測試 path('file_upload/', views.file_upload)]只需要這樣幾步,一個簡單的文件上傳就完成了。接下來啟動服務(wù)進(jìn)行測試,參考如下的操作:17實驗2:使用模型(model) 處理上傳的文件第一步,先設(shè)置 settings.py 中的 MEDIA_ROOT,這個設(shè)置上傳文件保存的根目錄;# first_django_app/settings.py# ...MEDIA_ROOT = '/root/test/'# ...第二步,準(zhǔn)備文件上傳模型類# hello_app/models.py# ...class FileModel(models.Model): name = models.CharField('上傳文件名', max_length=20) upload_file = models.FileField(upload_to='django')注意:這個 upload_to 參數(shù)和 settings.py 中的 MEDIA_ROOT 屬性值一起確定文件上傳的目錄。它可以有很多種形式,比如寫成upload_to='django/%Y/%m/%d' 這樣的。此外,該參數(shù)可以接收方法名,該方法返回的是上傳文件的目錄。第三步,我們必須要生成這個對應(yīng)的表,使用如下命令:(django-manual) [root@server first_django_app]# python manage.py makemigrations hello_app(django-manual) [root@server first_django_app]# python manage.py migrate hello_app執(zhí)行完成這兩步之后,在數(shù)據(jù)庫里面,我們就生成了相應(yīng)的表。默認(rèn)的表面是[應(yīng)用名_模型類名小寫],即hello_app__filemodel。第三步, 準(zhǔn)備相應(yīng)的視圖函數(shù);# hello_app/views.py# ... def file_upload2(request, *args, **kwargs): if request.method == 'POST': upload_file = request.FILES['upload_file'] FileModel.objects.create(name=upload_file.name, upload_file=upload_file) return HttpResponse('上傳成功') return render(request,'test_file_upload2.html',{})# ...(django-manual) [root@server first_django_app]# cat templates/test_file_upload2.html {% load staticfiles %}<form method="post" action="/hello/file_upload2/" enctype="multipart/form-data"> {% csrf_token %} <label>選擇上傳文件:</label><input type="file" name="file"> <div><input type="submit" value="提交" style="margin-top:10px"></div></form>注意:這里和之前保存文件方式略有不同,直接使用對應(yīng)模型實例的保存數(shù)據(jù)方法即可,文件將會自動上傳到指定目錄下且會在數(shù)據(jù)庫中添加一條記錄。編寫對應(yīng)的 URLconf 配置,如下:# hello_app/urls.py# ...urlpatterns = [ # ... # 文件上傳測試 path('file_upload2/', views.file_upload2)]接下來,就是常規(guī)的啟動服務(wù),然后頁面上測試。參考如下:18實驗3:多文件上傳實驗實現(xiàn)一次上傳多個文件也比較簡單,我們只需要改動前端的一行代碼,就可以支持一次性上傳多個文件。改動前端代碼如下:<!--原來的語句 <label>選擇上傳文件:</label><input type="file" name="file"> --><label>選擇上傳文件:</label><input type="file" name="files" multiple="">接下來,簡單調(diào)整下視圖函數(shù):def file_upload2(request, *args, **kwargs): if request.method == 'POST': # 獲取文件列表 upload_files = request.FILES.getlist('files') # 遍歷文件并保存 for f in upload_files: FileModel.objects.create(name=f.name, upload_file=f) return HttpResponse('上傳成功') return render(request,'test_file_upload2.html',{})最后看我們的啟動服務(wù)和測試接口過程如下:19

3. Docker 安裝

在不同的操作系統(tǒng)中都可以安裝 Docker ,本節(jié)內(nèi)容中只演示 Ubuntu 環(huán)境下的 Docker 安裝。本次安裝演示的 Ubuntu 版本為 Ubuntu 20.04.1 LTS 。apt 更換國內(nèi)源在安裝應(yīng)用之前,我們需要把 apt 更換為國內(nèi)源,這里我們選擇阿里云的 mirros.aliyun.com。# 備份 apt 源列表文件sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak# 更換源為 mirros.aliyun.comsudo sed -i s@/archive.ubuntu.com/@/mirrors.aliyun.com/@g /etc/apt/sources.list# 更新源sudo apt-get cleansudo apt-get update更換完畢后,我們還需要安裝 apt 的一些工具,如 https,curl 等。安裝 apt 依賴包sudo apt-get -y install apt-transport-https ca-certificates curl gnupg-agent software-properties-common獲取 GPG 密鑰證書我們這里使用阿里云的鏡像來獲取 GPG 密鑰:curl -fsSL http://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo apt-key add -獲取成功會返回 OK ,我們使用 apt-key finger 命令查看:apt-key finger# 輸出密鑰信息/etc/apt/trusted.gpg--------------------pub rsa4096 2017-02-22 [SCEA]9DC8 5822 9FC7 DD38 854A E2D8 8D81 803C 0EBF CD88uid [ unknown] Docker Release (CE deb) <docker@docker.com>sub rsa4096 2017-02-22 [S]密鑰添加成功后,我們就可以開始后續(xù)的安裝了。添加 Docker 源為了更快速的安裝 Docker,這里我們添加阿里云的 Docker 源,首先我們先使用 lsb_release -a 命令獲取當(dāng)前系統(tǒng)的 Codename:lsb_release -a# 輸出系統(tǒng)信息No LSB modules are available.Distributor ID: UbuntuDescription: Ubuntu 20.04.1 LTSRelease: 20.04Codename: focal本系統(tǒng)的 Codename 也就是版本代碼為 focal,我們在添加 Docker 源的時候就會使用這個版本:# 添加 docker-ce 源,系統(tǒng)為 ubuntu,系統(tǒng)版本為 focal, stable 為 docker 穩(wěn)定版。sudo add-apt-repository "deb [arch=amd64] http://mirrors.aliyun.com/docker-ce/linux/ubuntu focal stable"執(zhí)行完畢后,我們需要更新 apt 源:sudo apt-get updateDocker 安裝更新完源后,我們就可以開始安裝 Docker 組件了:sudo apt-get install docker-ce docker-ce-cli containerd.io執(zhí)行這一行命令需要一點時間,稍等片刻。完成后我們就可以來查看 Docker 是否安裝成功了。查看 Docker 版本使用 docker -v 來查看 Docker 版本:docker -v# 輸出 docker 版本信息Docker version 19.03.13, build 4484c46d9d看到版本信息輸出就說明我們的 Docker 源安裝成功了。Tips: 如果安裝失敗,需要注意系統(tǒng)的版本和添加的 Docker 源是否能使用。安裝成功后,我們來添加 Docker Image 鏡像源。添加 Docker Image 鏡像源使用阿里云的 Docker Image 鏡像源,需要登錄阿里云官網(wǎng)開啟 容器鏡像服務(wù):https://cr.console.aliyun.com/cn-hangzhou/instances/mirrors添加 Docker Image 鏡像源為阿里云鏡像,這里同學(xué)們使用自己賬號的加速器地址即可:sudo mkdir -p /etc/dockersudo tee /etc/docker/daemon.json <<-'EOF'{"registry-mirrors": ["https://xxxxxx.mirror.aliyuncs.com"]}EOF添加完畢后,我們就可以啟動我們的 Docker 服務(wù)了。啟動 Dockerservice docker start# 輸出啟動信息* Starting Docker: docker 啟動完成,接下來我們進(jìn)行測試。Docker 測試執(zhí)行測試命令:docker run hello-world輸出:Unable to find image 'hello-world:latest' locallylatest: Pulling from library/hello-world0e03bdcc26d7: Pull complete Digest: sha256:4cf9c47f86df71d48364001ede3a4fcd85ae80ce02ebad74156906caff5378bcStatus: Downloaded newer image for hello-world:latestHello from Docker!This message shows that your installation appears to be working correctly.To generate this message, Docker took the following steps:1. The Docker client contacted the Docker daemon.2. The Docker daemon pulled the "hello-world" image from the Docker Hub.(amd64)3. The Docker daemon created a new container from that image which runs theexecutable that produces the output you are currently reading.4. The Docker daemon streamed that output to the Docker client, which sent itto your terminal.To try something more ambitious, you can run an Ubuntu container with:$ docker run -it ubuntu bashShare images, automate workflows, and more with a free Docker ID:https://hub.docker.com/For more examples and ideas, visit:https://docs.docker.com/get-started/我們看到這段信息就說明,我們的 Docker 測試成功了。那么接下來,我們就可以使用 Docker 來安裝 Zookeeper 服務(wù)。

2.3 下載器的 _download() 分析

我們回到 Downloader 類上繼續(xù)學(xué)習(xí),該下載器類中最核心的有如下三個方法:_enqueue_request():請求入隊;_process_queue():處理隊列中的請求;_download():下載網(wǎng)頁;從代碼中很明顯可以看到,三個函數(shù)的關(guān)系如下:Downloader中三個核心函數(shù)關(guān)系我們來重點看看這個 _download() 方法:_download()方法分析看圖中注釋部分,_download() 方法先創(chuàng)建一個下載的 deferred,注意這里的方法正是 self.handlers 的 download_request() 方法,這是網(wǎng)頁下載的主要語句。 接下來又使用 deferred 的 addCallback() 方法添加一個回調(diào)函數(shù):_downloaded()。很明顯,_downloaded() 就是下載完成后調(diào)用的方法,其中 response 就是下載的結(jié)果,也就是后續(xù)會返回給 spider 中的 parse() 方法的那個。我們可以簡單做個實驗,看看是不是真的會在這里打印出響應(yīng)的結(jié)果:創(chuàng)建一個名為 test_downloader 的 scrapy 的項目:[root@server2 scrapy-test]# scrapy startproject test_downloader生成一個名為 downloader 的 spider:# 進(jìn)入到spider目錄[root@server2 scrapy-test]# cd test_downloader/test_downloader/spiders/# 新建一個spider文件[root@server2 spiders]# scrapy genspider downloader idcbgp.cn/wiki/[root@server2 spiders]# cat downloader.py import scrapyclass DownloaderSpider(scrapy.Spider): name = 'downloader' allowed_domains = ['idcbgp.cn/wiki/'] start_urls = ['http://idcbgp.cn/wiki/'] def parse(self, response): pass我們添加幾個配置,將 scrapy 的日志打到文件中,避免影響我們打印一些結(jié)果:# test_download/settings.py# ...#是否啟動日志記錄,默認(rèn)TrueLOG_ENABLED = True LOG_ENCODING = 'UTF-8'#日志輸出文件,如果為NONE,就打印到控制臺LOG_FILE = 'downloader.log'#日志級別,默認(rèn)DEBUGLOG_LEVEL = 'INFO'# 日志日期格式 LOG_DATEFORMAT = "%Y-%m-%d %H:%M:%S"#日志標(biāo)準(zhǔn)輸出,默認(rèn)False,如果True所有標(biāo)準(zhǔn)輸出都將寫入日志中,比如代碼中的print輸出也會被寫入到LOG_STDOUT = False最重要的步驟來啦,我們在 scrapy 的源碼 scrapy/core/downloader/__init__.py 的中添加一些代碼,用于查看下載器獲取的結(jié)果:我們來對添加的這部分代碼進(jìn)行下說明:# ...class Downloader: # ... def _download(self, slot, request, spider): print('下載請求:{}, {}'.format(request.url, spider.name)) # The order is very important for the following deferreds. Do not change! # 1. Create the download deferred dfd = mustbe_deferred(self.handlers.download_request, request, spider) # 2. Notify response_downloaded listeners about the recent download # before querying queue for next request def _downloaded(response): self.signals.send_catch_log(signal=signals.response_downloaded, response=response, request=request, spider=spider) ###############################新增代碼######################################## print('__downloaded()中 response 結(jié)果類型:{}'.format(type(response))) import gzip from io import BytesIO from scrapy.http.response.text import TextResponse if isinstance(response, TextResponse): text = response.text else: # 解壓縮文本,這部分會在后續(xù)的下載中間件中被處理,傳給parse()方法時會變成解壓后的數(shù)據(jù) f = gzip.GzipFile(fileobj = BytesIO(response.body)) text = f.read().decode('utf-8') print('得到結(jié)果:{}'.format(text[:3000])) ############################################################################ return response但就我們新建的項目而言,只是簡單的爬取慕課網(wǎng)的 wiki 頁面,獲取相應(yīng)的頁面數(shù)據(jù)。由于我們沒有禁止 robot 協(xié)議,所以項目第一次會爬取 /robots.txt 地址,檢查 wiki 頁面是否允許爬??;接下來才會爬取 wiki 頁面。測試發(fā)現(xiàn),第一次請求 /robots.txt 地址時,在 _downloaded() 中得到的結(jié)果直接就是 TextResponse 實例,我們可以用 response.text 方式直接拿到結(jié)果;但是第二次請求 http://idcbgp.cn/wiki/ 時,返回的結(jié)果是經(jīng)過壓縮的,其結(jié)果的前三個字節(jié)碼為:b'\x1f\x8b\x08' 開頭的 ,說明它是 gzip 壓縮過的數(shù)據(jù)。為了能查看相應(yīng)的數(shù)據(jù),我們可以在這里解碼查看,對應(yīng)的就是上面的 else 部分代碼。我們現(xiàn)在來進(jìn)行演示:118

1. 實戰(zhàn):直接構(gòu)建鏡像

首先 我們需要新建一個目錄 dockerfiledir,用于存放 Dockerfile 文件。mkdir dockerfiledir# 在這個目錄下新建個空文件 Dockerfile,之后填充內(nèi)容 touch dockerfiledir/Dockerfile新建一個目錄code,用來存放flask和c的源代碼。mkdir code將之前 app.py 和 helloworld.c 兩個源碼文件放入到 code 目錄下,當(dāng)前的目錄結(jié)構(gòu)應(yīng)該是這樣的:進(jìn)入 dockerfiledir 目錄,編輯 Dockerfile 文件:# 從 ubuntu系統(tǒng)鏡像開始構(gòu)建FROM ubuntu # 標(biāo)記鏡像維護(hù)者信息MAINTAINER user <user@imooc.com># 切換到鏡像的/app目錄,不存在則新建此目錄WORKDIR /app# 將 宿主機的文件拷貝到容器中COPY ../code/app.py .COPY ../code/helloworld.c .# 安裝依賴 編譯helloworldRUN apt update >/dev/null 2>&1 && \ apt install -y gcc python3-flask python3-redis >/dev/null 2>&1 && \ cc /app/helloworld.c -o /usr/bin/helloworld# 設(shè)定執(zhí)行用戶為userRUN useradd userUSER user# 設(shè)定flask所需的環(huán)境變量ENV FLASK_APP app# 默認(rèn)啟動執(zhí)行的命令CMD ["flask", "run", "-h", "0.0.0.0"]# 將flask的默認(rèn)端口暴露出來EXPOSE 5000然后執(zhí)行:docker build .出現(xiàn)如下報錯:COPY failed: Forbidden path outside the build context: ../code/app.py ()解決這個問題,需要引入一個重要的概念——構(gòu)建上下文。docker build .命令在執(zhí)行時,當(dāng)前目錄.被指定成了構(gòu)建上下文,此目錄中的所有文件或目錄都將被發(fā)送到 Docker 引擎中去,Dockerfile中的切換目錄和復(fù)制文件等操作只會對上下文中的內(nèi)容生效。Tips:在默認(rèn)情況下,如果不額外指定 Dockerfile 的話,會將構(gòu)建上下文對應(yīng)的目錄下 Dockerfile 的文件作為 Dockerfile。但這只是默認(rèn)行為,實際上 Dockerfile 的文件名并不要求必須為 Dockerfile,而且并不要求必須位于上下文目錄中,比如可以用 -f ../demo.txt參數(shù)指定父級目錄的demo.txt文件作為 Dockerfile。一般來說,我們習(xí)慣使用默認(rèn)的文件名 Dockerfile,將其置于鏡像構(gòu)建上下文目錄.中。我們需要將 code 目錄納入到上下文中,一個直接的方法是,調(diào)整dockerfile中的COPY指令的路徑。# 將 .. 改為 .COPY ./code/app.py .COPY ./code/helloworld.c .然后將 code 所在的目錄指定為構(gòu)建上下文。由于我們當(dāng)前的目錄是 dockerfiledir,所以我們執(zhí)行:docker build -f ./Dockerfile ..如果你留意查看構(gòu)建過程,會發(fā)現(xiàn)類似這樣的提示:Sending build context to Docker daemon 421.309 MB如果..目錄除了code和dockerfiledir,還包含其他的文件或目錄,docker build也會將這個數(shù)據(jù)傳輸給Docker,這會增加構(gòu)建時間。避免這種情況,有兩種解決方法:使用.dockerignore文件:在構(gòu)建上下文的目錄下新建一個.dockerignore文件來指定在傳遞給 docker 時需要忽略掉的文件或文件夾。.dockerignore 文件的排除模式語法和 Git 的 .gitignore 文件相似。使用一個干凈的目錄作為構(gòu)建上下文(推薦):使用 Dockerfile 構(gòu)建鏡像時最好是將 Dockerfile 放置在一個新建的空目錄下。然后將構(gòu)建鏡像所需要的文件添加到該目錄中。在我們當(dāng)前的示例中,將code目錄移入dockerfiledir。mv ../code .現(xiàn)在的目錄層級如下:執(zhí)行 docker build -t myhello . 執(zhí)行構(gòu)建即可獲得我們的自定義鏡像 myhello。使用鏡像 myhello 創(chuàng)建 myhello 容器:# 這里使用--net=host,方便使用之前章節(jié)中部署的redis容器服務(wù),與之進(jìn)行數(shù)據(jù)交換docker run -dit --net=host --name myhello myhello 確保部署之前的 redis 容器正常啟動,然后在 Docker 宿主機的瀏覽器中訪問http://127.0.0.1:5000:說明 myhello 中的 flask 應(yīng)用已經(jīng)正常運行了。接下來,我們再運行測試一下編譯的 helloworld。docker exec myhello /usr/bin/helloworld得到輸出:Hello, World!Tips: myhello容器已經(jīng)完成任務(wù),記得執(zhí)行docker rm -f myhello刪除它.

2. 案例

有了上面的基礎(chǔ)之后,我們來完成一個簡單的權(quán)限管理案例。首先添加3條告警記錄,用于作為后面測試數(shù)據(jù):(django-manual) [root@server first_django_app]# python manage.py shellPython 3.8.1 (default, Dec 24 2019, 17:04:00) [GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linuxType "help", "copyright", "credits" or "license" for more information.(InteractiveConsole)>>> from hello_app.models import AlarmMessage>>> m1 = AlarmMessage(alarm_title='服務(wù)異常告警', alarm_content='后臺認(rèn)證管理服務(wù)不存在,請檢查', level=0)>>> m1.save()>>> m2 = AlarmMessage(alarm_title='內(nèi)存告警', alarm_content='主機host-001內(nèi)存使用率100%,請檢查', level=1)>>> m2.save()>>> m3 = AlarmMessage(alarm_title='主機掉線', alarm_content='主機host-001已掉線,請檢查', level=2)>>> m3.save()我們完成一個登錄頁面,同樣是利用 session 保存登錄狀態(tài)。針對三個不同用戶登錄,我們會顯示不同的結(jié)果:普通用戶直接提示沒有權(quán)限登錄、平臺員工登錄后顯示告警列表、平臺負(fù)責(zé)人登錄后除了顯示告警列表外,每個記錄對應(yīng)有個刪除按鈕。首先來看模板頁面,主要有兩個:登錄頁面 (test_form2.html) 和告警信息 (test_permission.html) 列表頁面。{# 代碼位置: template/test_form2.html #}{% load staticfiles %}<link rel="stylesheet" type="text/css" href="{% static 'css/main.css' %}" />{% if not success %}<form action="/hello/test_form_view2/" method="POST">{% csrf_token %}<div><span>{{ form.name.label }}:</span>{{ form.name }}<div><span>{{ form.password.label }}:</span>{{ form.password }}<div>{{ form.save_login }}{{ form.save_login.label }}</div><div><input class="input-text input-red" type="submit" value="登錄" style="width: 214px"/></div>{% if err_msg %}<div><label class="color-red">{{ err_msg }}</label</div>{% endif %}</form>{% else %} <p>登錄成功</p>{% endif %}{# 代碼位置: template/test_permission.html #}{% load staticfiles %}<p>登錄用戶:{{ username }}<a href="/hello/logout/" style="margin-left:30px">登出</a></p><div><table border="1" class="member-table"> <thead> <tr> <th></th> <th>告警編號</th> <th>告警標(biāo)題</th> <th>告警級別</th> <th>告警內(nèi)容</th> <th>產(chǎn)生時間</th> {% if perms.hello_app.delete_alarm_message %} <th>操作</th> {% endif %} </tr> </thead> <tbody> {% for message in messages %} <tr> <td></td> <td>{{ message.id }}</td> <td>{{ message.alarm_title }}</td> <td>{{ message.level }}</td> <td>{{ message.alarm_content }}</td> <td>{{ message.created_at|date:"Y-m-d H:m:s" }}</td> {% if perms.hello_app.delete_alarm_message %} <td><a href="/hello/alarm_delete/{{ message.id }}">刪除</a></td> {% endif %} </tr> {% endfor %} </tbody></table><div ><div class="page"></div></div></div>我們準(zhǔn)備視圖函數(shù),這個視圖函數(shù)是在前面的基礎(chǔ)上進(jìn)行了改造,代碼如下:# 代碼位置:hello_app/views.py# ...class TestFormView2(TemplateView): template_name = 'test_form2.html' def get(self, request, *args, **kwargs): # print('進(jìn)入get()請求') success = False err_msg = '' form = LoginForm() if request.session.get('has_login', False): logined_user = User.objects.all().get(id=int(request.session['user_id'])) # 判斷登錄的用戶是否有權(quán)查看告警頁面 if logined_user.has_perm('hello_app.view_alarm_message'): # 這一步不能掉,request中添加用戶信息 request.user = user messages = AlarmMessage.objects.all() return render(request, "test_permission.html", {"username":logined_user.username, "messages": messages}) else: success = False err_msg = "用戶無權(quán)訪問" return self.render_to_response(context={'success': success, 'err_msg': err_msg, 'form': form}) def post(self, request, *args, **kwargs): # print('進(jìn)入post請求') form = LoginForm(request.POST) success = True err_msg = "" if form.is_valid(): login_data = form.clean() name = login_data['name'] password = login_data['password'] # 登錄認(rèn)證 user = authenticate(username=name, password=password) if not user: success = False err_msg = "用戶名密碼不正確" # 判斷登錄用戶是否有權(quán)限 elif user.has_perm('hello_app.view_alarm_message'): request.user = user request.session['has_login'] = True request.session['user_id'] = user.id # 設(shè)置100s后過期 request.session.set_expiry(100) messages = AlarmMessage.objects.all() return render(request, "test_permission.html", {"username": user.username, "messages": messages}) else: success = False err_msg = "用戶無權(quán)訪問" else: success = False err_msg = form.errors['password'][0] return self.render_to_response(context={'success': success, 'err_msg': err_msg, 'form': form})def logout(request, *args, **kwargs): if 'has_login' in request.session: del request.session["has_login"] if 'user_id' in request.session: del request.session["user_id"] request.session.flush() return redirect('/hello/test_form_view2/')我們這里除了改造原來的登錄請求外,還多加了一個 logout() 方法用于用戶登出操作。接下來準(zhǔn)備好 URLConf 配置如下:# 代碼位置:hello_app/urls.py# ...urlpatterns = [ # ... path('test_form_view2/', views.TestFormView2.as_view(), name='test_form_view2'), path('logout/', views.logout, name='logout'),]最后,我們啟動我們的工程,來看看實際的效果,具體演示演示如下:24

1. django-celery 框架實戰(zhàn)

我們使用 django-celery 框架完成2個小實驗,簡單體驗下 celery 框架的功能。首先準(zhǔn)備好實驗環(huán)境,包括安裝 django-celery 等依賴的模塊以及完成初始配置代碼。按照以下步驟進(jìn)行:安裝依賴模塊:包括 django-celery 等;(django-manual) [root@server first_django_app]# pip install django-celery注意: 如果想要使用 Django 的 ORM 來存儲結(jié)果,想要安裝 django-celery-results 插件;安裝好 redis 服務(wù),使用源碼或者 yum 安裝即可;(django-manual) [root@server first_django_app]# yum install redis推薦使用源碼安裝方式,可以安裝高版本的 redis。最后我在本機上安裝了 redis 5.0 版本的服務(wù),如下:(django-manual) [root@server first_django_app]# redis-server --versionRedis server v=5.0.7 sha=00000000:0 malloc=jemalloc-5.1.0 bits=64 build=5cb8e5612a04130d(django-manual) [root@server first_django_app]# systemctl status redis● redis.service - Redis Loaded: loaded (/etc/systemd/system/redis.service; disabled; vendor preset: disabled) Drop-In: /etc/systemd/system/redis.service.d └─limit.conf Active: active (running) since 一 2020-01-13 22:41:09 CST; 3 months 28 days ago Main PID: 1707 (redis-server) CGroup: /system.slice/redis.service └─1707 /usr/local/bin/redis-server 127.0.0.1:6379新增一個處理異步任務(wù)的應(yīng)用:async_tasks,另外設(shè)置相應(yīng)的 settings.py 中的配置:(django-manual) [root@server first_django_app]# django-admin startapp async_tasks在 async_tasks 目錄下準(zhǔn)備一個 tasks.py 文件在,這個代碼里面會放一些需要異步處理的任務(wù):# 代碼位置:async_tasks/tasks.py# Create your tasks herefrom __future__ import absolute_import, unicode_literalsimport timefrom celery import shared_task@shared_taskdef add(x, y): time.sleep(10) return x + y最后,為了能啟動 celery 服務(wù),我們需要在 Django 項目下 settings.py 的同級目錄下添加一個 celery.py 文件 (必須這樣命名) :# 代碼位置:first_django_app/celery.pyfrom __future__ import absolute_import, unicode_literals import osfrom celery import Celeryfrom django.conf import settings# 設(shè)置環(huán)境變量os.environ.setdefault("DJANGO_SETTINGS_MODULE", "first_django_app.settings")#創(chuàng)建celery應(yīng)用app = Celery('first_django_app')app.config_from_object('django.conf:settings', namespace='CELERY')#如果在工程的應(yīng)用中創(chuàng)建了 tasks.py 模塊,那么Celery應(yīng)用就會自動去檢索創(chuàng)建的任務(wù)。app.autodiscover_tasks(lambda:settings.INSTALLED_APPS)# 代碼位置:first_django_app/__init__.pyfrom __future__ import absolute_import# This will make sure the app is always imported when# Django starts so that shared_task will use this app.from .celery import app as celery_app接下里我們在 settings.py 中增加 celery 的相關(guān)配置,包括設(shè)置 broker 等# 代碼位置:first_django_app/settings.pyINSTALLED_APPS = [ # ... 'djcelery', # 注冊應(yīng)用 'hello_app', 'async_tasks'] import djcelerydjcelery.setup_loader()CELERY_TIMEZONE='Asia/Shanghai'CELERY_BROKER_URL='redis://127.0.0.1:6379/0'CELERY_RESULT_BACKEND = 'redis://127.0.0.1:6379/0' # BACKEND配置,這里使用redisCELERY_RESULT_SERIALIZER = 'json' CELERY_IMPORTS = ('async_tasks.tasks')CELERYBEAT_SCHEDULER = 'djcelery.schedulers.DatabaseScheduler' ###測試環(huán)境。下面第一個以交互式方式啟動 celery,后續(xù)則是以守護(hù)進(jìn)程方式啟動。# 交互式方式啟動(django-manual) [root@server first_django_app]# celery -A first_django_app worker -l info...# 以守護(hù)進(jìn)程方式啟動(django-manual) [root@server first_django_app]# celery multi start w1 -A first_django_app --concurrency=4 -l info --logfile=/root/test/celerylog.log --pidfile=celerypid.pidcelery multi v4.4.2 (cliffs)> Starting nodes...> w1@server: OK(django-manual) [root@server first_django_app]# celery multi stop w1 -A first_django_app -l infocelery multi v4.4.2 (cliffs)> w1@server: DOWN啟動服務(wù)之后,我們在另一個連接窗口進(jìn)行測試,使用 Django 的 shell 交互模式調(diào)用 celery 的 task。(django-manual) [root@server first_django_app]# python manage.py shellPython 3.8.1 (default, Dec 24 2019, 17:04:00) [GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linuxType "help", "copyright", "credits" or "license" for more information.(InteractiveConsole)>>> from async_tasks.tasks import add>>> add.delay(8, 10)<AsyncResult: c41bee82-f17d-4556-8c48-b246b36822b8># 當(dāng)然在等待10s之后,我們也能通過如下方式獲取結(jié)果>>> from celery.result import AsyncResult>>> res = AsyncResult("c41bee82-f17d-4556-8c48-b246b36822b8")>>> res.result18執(zhí)行該異步任務(wù)后,我們在 redis 中查看執(zhí)行的結(jié)果。剛開始是空的,等到10s 之后,結(jié)果就會寫入 redis 中,默認(rèn)的 key 是 celery-task-meta-[任務(wù)id]:[root@server ~]# redis-cli127.0.0.1:6379> get celery-task-meta-c41bee82-f17d-4556-8c48-b246b36822b8(nil)# 10s過后,結(jié)果才會出來127.0.0.1:6379> get celery-task-meta-c41bee82-f17d-4556-8c48-b246b36822b8"{\"status\": \"SUCCESS\", \"result\": 18, \"traceback\": null, \"children\": [], \"date_done\": \"2020-05-12T09:51:53.716003\", \"task_id\": \"c41bee82-f17d-4556-8c48-b246b36822b8\"}"有了上面的基礎(chǔ)后,我們就可以實現(xiàn)兩個簡單的工作了。實驗1:異步發(fā)送郵件。準(zhǔn)備好郵件發(fā)送代碼,放到 async_tasks/utils.py 中。# 代碼位置:async_tasks/utils.pyimport smtplibfrom email.mime.text import MIMETextfrom email.header import Header# 發(fā)送方郵箱mail_from = '2894577759@qq.com' # 大家可以使用自己的郵箱作為發(fā)送郵箱passwd = 'xxxxx' def send_email(subject_text, content='', receivers=['2894577759@qq.com']): send_html = "<html><head></head><body><h3>{}</h3></body></html>".format(content) msg = MIMEText(send_html, 'html', 'utf-8') # 放入郵件主題 msg['Subject'] = subject_text msg['From'] = mail_from try: s = smtplib.SMTP_SSL("smtp.qq.com", 465) # 登錄到郵箱 s.login(mail_from, passwd) # 發(fā)送郵件:發(fā)送方,收件方,要發(fā)送的消息 s.sendmail(mail_from, receivers, msg.as_string()) print("發(fā)送郵件成功") except s.SMTPException as e: print("發(fā)送郵件失敗:{},請手工查看生成的巡檢報告".format(str(e))) return False finally: s.quit() return Trueif __name__ == "__main__": send_email('Django-Celery測試', "慕課網(wǎng)的朋友們,大家好!")可以直接使用 python 測試,結(jié)果如下:準(zhǔn)備異步發(fā)送郵件的 task:# 代碼位置: async_tasks/tasks.pyfrom celery import shared_taskfrom async_tasks.utils import send_emailfrom first_django_app import celery_app@celery_app.taskdef send_email_task(title, content="慕課網(wǎng)的兄弟們,大家好!"): time.sleep(10) return send_email(title, content)準(zhǔn)備好視圖函數(shù),比較簡單,直接使用異步處理即可:# 代碼位置:async_tasks/views.pyfrom django.http.response import HttpResponsefrom async_tasks.tasks import send_email_task# Create your views here.def send_email(request, *args, **kwargs): try: # res = send_email_task.apply_async((), countdown=10) res = send_email_task.delay('這是來自django-celery的測試', '慕課網(wǎng)的朋友們,大家好,我是xxx,來自xxx!') except Exception as e: print('異常:{}'.format(str(e))) return HttpResponse('發(fā)送郵件成功,請耐心等待')準(zhǔn)備好 URLConf 配置,注意我們這里新建了一個 app,所以需要在主入口也要加上 URLConf 配置,此外還需要在該 app 下新建對應(yīng)的 urls.py 文件并在其中添加相應(yīng)的映射關(guān)系:# 代碼位置:first_django_app/urls.py# ...# 所有url入口urlpatterns = [ url('admin/', admin.site.urls), url('hello/', include('hello_app.urls')), # 添加async_tasks應(yīng)用的映射入口 url('celery/', include('async_tasks.urls')), url('hello_world/', hello_world), path('api-auth/', include('rest_framework.urls', namespace='rest_framework'))]# 代碼位置:async_tasks/urls.pyfrom django.urls import pathfrom async_tasks import viewsurlpatterns = [ path('send_email/', views.send_email),]在項目目錄下,啟動 celery 服務(wù)(最好以守護(hù)進(jìn)程方式),這一步非常重要,千萬不能漏掉?。?django-manual) [root@server first_django_app]# celery multi start w1 -A first_django_app --concurrency=4 -l info --logfile=/root/test/celerylog.log --pidfile=celerypid.pid(django-manual) [root@server first_django_app]# ps -ef | grep celeryroot 7592 23339 0 23:35 pts/0 00:00:00 grep --color=auto celeryroot 18503 1 0 22:49 ? 00:00:06 /root/.pyenv/versions/3.8.1/envs/django-manual/bin/python3.8 -m celery worker -A first_django_app --concurrency=4 -l info --logfile=/root/test/celerylog.log --pidfile=celerypid.pid --hostname=w1@serverroot 18851 18503 0 22:49 ? 00:00:00 /root/.pyenv/versions/3.8.1/envs/django-manual/bin/python3.8 -m celery worker -A first_django_app --concurrency=4 -l info --logfile=/root/test/celerylog.log --pidfile=celerypid.pid --hostname=w1@serverroot 18875 18503 0 22:49 ? 00:00:00 /root/.pyenv/versions/3.8.1/envs/django-manual/bin/python3.8 -m celery worker -A first_django_app --concurrency=4 -l info --logfile=/root/test/celerylog.log --pidfile=celerypid.pid --hostname=w1@serverroot 18890 18503 0 22:49 ? 00:00:00 /root/.pyenv/versions/3.8.1/envs/django-manual/bin/python3.8 -m celery worker -A first_django_app --concurrency=4 -l info --logfile=/root/test/celerylog.log --pidfile=celerypid.pid --hostname=w1@serverroot 18903 18503 0 22:49 ? 00:00:00 /root/.pyenv/versions/3.8.1/envs/django-manual/bin/python3.8 -m celery worker -A first_django_app --concurrency=4 -l info --logfile=/root/test/celerylog.log --pidfile=celerypid.pid --hostname=w1@server最后,我們啟動服務(wù),然后請求這個異步的接口,效果如下視頻所示。可以看到,請求會立馬返回,而任務(wù)會交給 celery 去執(zhí)行,并最后將結(jié)果放到 redis 服務(wù)中。效果演示:26實驗2:設(shè)置定時任務(wù)。我們來在 Django 工程中使用 celery 設(shè)置兩個定時任務(wù),一個任務(wù)用于在某個目錄下每隔3秒生成一個日志文件,另一個定時任務(wù)則每分鐘清除該目錄下的所有日志文件。如果想要在 Django 中使用定時任務(wù)功能需要靠 beat 完成任務(wù)發(fā)送功能,當(dāng)在 Django 中使用定時任務(wù)時,需要安裝 django-celery-beat 插件。然后在 settings.py 的 INSTALLED_APPS 值中添加 'django_celery_beat' 應(yīng)用。最后該應(yīng)用還提供了一些表,我們使用 migrate 命令在我們數(shù)據(jù)庫中遷移這些表,操作如下:(django-manual) [root@server first_django_app]# pip install django-celery-beat...(django-manual) [root@server first_django_app]# python manage.py migrate django_celery_beatOperations to perform: Apply all migrations: django_celery_beatRunning migrations: Applying django_celery_beat.0001_initial... OK Applying django_celery_beat.0002_auto_20161118_0346... OK Applying django_celery_beat.0003_auto_20161209_0049... OK Applying django_celery_beat.0004_auto_20170221_0000... OK Applying django_celery_beat.0005_add_solarschedule_events_choices... OK Applying django_celery_beat.0006_auto_20180322_0932... OK Applying django_celery_beat.0007_auto_20180521_0826... OK Applying django_celery_beat.0008_auto_20180914_1922... OK Applying django_celery_beat.0006_auto_20180210_1226... OK Applying django_celery_beat.0006_periodictask_priority... OK Applying django_celery_beat.0009_periodictask_headers... OK Applying django_celery_beat.0010_auto_20190429_0326... OK Applying django_celery_beat.0011_auto_20190508_0153... OK Applying django_celery_beat.0012_periodictask_expire_seconds... OK來添加兩個定時任務(wù),在 settings.py 文件中:# 代碼位置:first_django_app/settings.py# ...from celery.schedules import crontabCELERY_BEAT_SCHEDULE = { # 定時任務(wù)1:生成日志文件 'task1': { 'task': 'async_tasks.tasks.echo_hello', 'schedule': 3, # 每3秒執(zhí)行一次 }, # 定時任務(wù)2:刪除日志文件 'task2': { 'task': 'async_tasks.tasks.clear_logs', 'schedule': crontab(minute='*/1'), 'args': ('/root/test/logs', ) }}首先啟動 celery 服務(wù),然后啟動celery-beat 服務(wù),這樣定時任務(wù)就可以看到效果了。(django-manual) [root@server first_django_app]# celery -A first_django_app worker -l info(django-manual) [root@server first_django_app]# celery -A first_django_app beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler就簡單這樣子,我們就相應(yīng)于設(shè)置了定時任務(wù)。我們簡單看看演示效果:27由于篇幅限制,就不再看如何在 Django 內(nèi)置的 admin 管理系統(tǒng)中演示如何添加定時任務(wù)了,這個工作就當(dāng)作課后練習(xí)。

1. 創(chuàng)建第一個Django應(yīng)用程序

在創(chuàng)建第一個 Django 應(yīng)用程序之前,我們需要使用 pyenv 工具創(chuàng)建相應(yīng)的虛擬環(huán)境,操作如下:新建一個統(tǒng)一的目錄,用于存放 Django 工程代碼:[root@server ~]# mkdir django-manual[root@server ~]# cd django-manual/進(jìn)入虛擬環(huán)境,然后建立 django-manual 虛擬環(huán)境。一般而言每個 Django 工程會創(chuàng)建一個虛擬環(huán)境,這樣避免各個 Python 項目之間發(fā)生包沖突。建立好虛擬環(huán)境之后,激活虛擬環(huán)境。操作如下:[root@server django-manual]# pyenv versions system* 3.8.1 (set by /root/.pyenv/version) 3.8.1/envs/env-3.8.1 env-3.8.1 # 新建django-manual虛擬環(huán)境[root@server django-manual]# pyenv virtualenv 3.8.1 django-manualLooking in links: /tmp/tmpllz1yd5eRequirement already satisfied: setuptools in /root/.pyenv/versions/3.8.1/envs/django-manual/lib/python3.8/site-packages (41.2.0)Requirement already satisfied: pip in /root/.pyenv/versions/3.8.1/envs/django-manual/lib/python3.8/site-packages (19.2.3)# 手動新建的虛擬環(huán)境[root@server django-manual]# pyenv activate django-manualpyenv-virtualenv: prompt changing will be removed from future release. configure `export PYENV_VIRTUALENV_DISABLE_PROMPT=1' to simulate the behavior.(django-manual) [root@server django-manual]#接下來,我們需要安裝 Django 2.2.11 版本(提示: django 3.0 最近發(fā)布了,但是還處于初步完善階段,所以本次介紹以 Django 2.2.11 版本為準(zhǔn)):(django-manual) [root@server django-manual]# pip install django==2.2.11 -i https://pypi.tuna.tsinghua.edu.cn/simpleLooking in indexes: https://pypi.tuna.tsinghua.edu.cn/simpleCollecting django==2.2.11 Downloading https://pypi.tuna.tsinghua.edu.cn/packages/be/76/7ccbcf52366590ca76997ce7860308b257b79962a4e4fada5353f72d7be5/Django-2.2.11-py3-none-any.whl (7.5MB) |████████████████████████████████| 7.5MB 71kB/s Requirement already satisfied: sqlparse in /root/.pyenv/versions/3.8.1/envs/django-manual/lib/python3.8/site-packages (from django==2.2.11) (0.3.1)Requirement already satisfied: pytz in /root/.pyenv/versions/3.8.1/envs/django-manual/lib/python3.8/site-packages (from django==2.2.11) (2019.3)Installing collected packages: djangoSuccessfully installed django-2.2.11WARNING: You are using pip version 19.2.3, however version 20.0.2 is available.You should consider upgrading via the 'pip install --upgrade pip' command.(django-manual) [root@server django-manual]# python -c "import django; print(django.__version__)"2.2.11這樣子,虛擬環(huán)境中就安裝好了 Django 2.2.11。Django 提供 django-admin 命令來幫助我們創(chuàng)建項目和應(yīng)用,我們只需要使用 django-admin 命令即可快速創(chuàng)建我們的第一個 Django 項目:(django-manual) [root@server django-manual]# django-admin startproject first_django_app(django-manual) [root@server django-manual]# (django-manual) [root@server django-manual]# tree ..└── first_django_app ├── first_django_app │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py └── manage.py2 directories, 5 filesTips:盡量在 Linux 平臺上完成實驗,在 Windows 下操作在安裝 mysqlclient 模塊是會稍微有些工作要做。Django 項目可以由多個應(yīng)用(app)組成,每個應(yīng)用是一個邏輯上劃分,即將某一個功能模塊劃歸到這個應(yīng)用。創(chuàng)建一個應(yīng)用使用 django-admin starapp 應(yīng)用名即可:(django-manual) [root@server django-manual]# cd first_django_app/(django-manual) [root@server first_django_app]# django-admin startapp hello_app(django-manual) [root@server first_django_app]# tree ..├── first_django_app│ ├── __init__.py│ ├── settings.py│ ├── urls.py│ └── wsgi.py├── hello_app│ ├── admin.py│ ├── apps.py│ ├── __init__.py│ ├── migrations│ │ └── __init__.py│ ├── models.py│ ├── tests.py│ └── views.py└── manage.py3 directories, 12 files可以看到,在使用 django-admin 執(zhí)行創(chuàng)建 hello_app 應(yīng)用后,該命令給我們生成了 hello_app 以及若干代碼文件。為了能讓 Django 項目運行起來,我們需要調(diào)整下 settings.py 文件中的配置:# settings.py 中默認(rèn)使用 sqlite3...DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), }}...# 現(xiàn)在調(diào)整成 mysql 數(shù)據(jù)庫,讀者需要自行準(zhǔn)備mysql服務(wù)DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'django_manual', 'USER': 'store', 'PASSWORD': 'xxxxxxxxx', 'HOST': '180.76.152.113', 'PORT': '9000', }}有了數(shù)據(jù)庫支持,還需要在 Django 那邊安裝 mysql 相關(guān)的模塊包。通常安裝的是 mysqlclient 模塊:# 安裝相應(yīng)的依賴包(django-manual) [root@server first_django_app]# yum install mysql-devel -y(django-manual) [root@server first_django_app]# pip install mysqlclient -i https://pypi.tuna.tsinghua.edu.cn/simpleLooking in indexes: https://pypi.tuna.tsinghua.edu.cn/simpleCollecting mysqlclient Downloading https://pypi.tuna.tsinghua.edu.cn/packages/d0/97/7326248ac8d5049968bf4ec708a5d3d4806e412a42e74160d7f266a3e03a/mysqlclient-1.4.6.tar.gz (85kB) |████████████████████████████████| 92kB 22.2MB/s Installing collected packages: mysqlclient Running setup.py install for mysqlclient ... doneSuccessfully installed mysqlclient-1.4.6最后一件事情,在啟動 Django 服務(wù)之前,必須要先創(chuàng)建數(shù)據(jù)庫。Django 服務(wù)默認(rèn)并不會幫我們創(chuàng)建好數(shù)據(jù)庫,我們必須手工建好數(shù)據(jù)庫,然后再啟動 Django 服務(wù):[root@server ~]# mysql -u store -pxxxxxxxxx -h 180.76.152.113 -P9000Welcome to the MariaDB monitor. Commands end with ; or \g.Your MySQL connection id is 37328Server version: 5.7.26 MySQL Community Server (GPL)Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.MySQL [(none)]> CREATE DATABASE IF NOT EXISTS django_manual DEFAULT CHARSET utf8;Query OK, 1 row affected (0.00 sec)MySQL [(none)]> show databases;+--------------------+| Database |+--------------------+| information_schema || alarms || dashboard || django_manual || graph || mysql || performance_schema || sys || uic |+--------------------+15 rows in set (0.00 sec)MySQL [(none)]> exit;Bye# ---------------------------------------------------------------------------------------(django-manual) [root@server first_django_app]# python manage.py runserver 0.0.0.0:8888Watching for file changes with StatReloaderPerforming system checks...System check identified no issues (0 silenced).You have 17 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.Run 'python manage.py migrate' to apply them.March 13, 2020 - 07:29:06Django version 2.2.11, using settings 'first_django_app.settings'Starting development server at http://0.0.0.0:8888/Quit the server with CONTROL-C.在完成上述這些步驟后,基本的工程就搭建起來了。接下來我們從外面訪問這個端口結(jié)果如下:第一次訪問 django 服務(wù)這個是 Django 在配置中做的一個白名單機制,它有一個 ALLOWED_HOSTS 配置參數(shù),它用來設(shè)置訪問服務(wù)的白名單。如果想要允許任何主機訪問,直接設(shè)置如下:(django-manual) [root@server first_django_app]# cat first_django_app/settings.py...DEBUG = TrueALLOWED_HOSTS = ['*']...另外,默認(rèn) setting.py 中的 DEBUG 參數(shù)為 True。正因為如此,請求報錯才會有如此詳細(xì)的提示。在正真上線部署時候,這個參數(shù)一定要關(guān)閉。如果我設(shè)置如下參數(shù)再次從外部請求該 Django 服務(wù)時,瀏覽器的輸出結(jié)果如下圖所示??梢钥吹?,除了顯示一個冷冰冰 400 錯誤,無任何提示。這樣屏蔽錯誤信息,防止有人從錯誤結(jié)果中推斷服務(wù)漏洞,達(dá)到滲透的目的。(django-manual) [root@server first_django_app]# cat first_django_app/settings.py...DEBUG = FalseALLOWED_HOSTS = ['127.0.0.1']...設(shè)置 Debug=False 的錯誤輸出我們重新設(shè)置好 DEBUG 和 ALLOWED_HOSTS 參數(shù)后,再次請求 Django 服務(wù),可以得到 Dajngo 內(nèi)置的歡迎頁面,提示我們服務(wù)已經(jīng)正常啟動和運行。正常訪問 Django 服務(wù)現(xiàn)在,我們寫一個最簡單的 Hello, World 字符串輸出到頁面上。改動 first_django_app/first_django_app/url.py 文件,這個文件是所有 url 請求路由的入口,所有的映射關(guān)系都會先通過這里:(django-manual) [root@server first_django_app]# pwd/root/django-manual/first_django_app(django-manual) [root@server first_django_app]# cat first_django_app/urls.py """注釋性文本,省略"""from django.contrib import adminfrom django.urls import path## 新導(dǎo)入模塊from django.http import HttpResponse## 視圖函數(shù)def hello_world(*args, **kwargs): return HttpResponse("Hello, world.", content_type="text/plain")urlpatterns = [ path('admin/', admin.site.urls), ####添加的url映射,由上面的hello_world()函數(shù)處理 path('hello/', hello_world),]再次啟動 Django 服務(wù),訪問 8888 端口的 /hello/ 路徑,可以看到頁面出現(xiàn) “Hello, world.” 這樣的字符,說明我們的第一個 URL 接口完成。頁面輸出 Hello,World.

1. Django ORM 模型的增刪改查操作

話不多說,直接進(jìn)入 django 的交互命令模式:[root@server ~]# pyenv activate django-manual pyenv-virtualenv: prompt changing will be removed from future release. configure `export PYENV_VIRTUALENV_DISABLE_PROMPT=1' to simulate the behavior.(django-manual) [root@server ~]# cd django-manual/first_django_app/(django-manual) [root@server first_django_app]# python manage.py shellPython 3.8.1 (default, Dec 24 2019, 17:04:00) [GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linuxType "help", "copyright", "credits" or "license" for more information.(InteractiveConsole)>>>前面在 hello_app 應(yīng)用目錄下的 models.py 中定義了 Member 模型類,導(dǎo)入進(jìn)來。然后我們實例化 Member 類,并給實例的屬性賦值,最后調(diào)用模型類的 save() 方法,將該實例保存到表中:>>> from hello_app.models import Member>>> from hello_app.models import Member>>> m1 = Member()>>> m1.name = 'spyinx'>>> m1.age = 29>>> m1.sex = 0>>> m1.occupation = "程序員">>> m1.phone_num = '18054293763'>>> m1.city = 'guangzhou'>>> m1.save()通過 mysql 客戶端可以查看該保存的記錄,如下:[root@server first_django_app]# mysql -u store -pstore.123@ -h 180.76.152.113 -P 9002Welcome to the MariaDB monitor. Commands end with ; or \g.Your MySQL connection id is 73555Server version: 5.7.26 MySQL Community Server (GPL)Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.MySQL [(none)]> use django_manualReading table information for completion of table and column namesYou can turn off this feature to get a quicker startup with -ADatabase changedMySQL [django_manual]> select * from member where 1=1\G;*************************** 1. row *************************** id: 1 name: spyinx age: 29 sex: 0 occupation: 程序員 phone_num: 18054293763 email: city: guangzhouregister_date: 2020-04-05 07:30:45.0433771 row in set (0.00 sec)ERROR: No query specifiedMySQL [django_manual]>接下來是查詢的操作介紹,為了能更好的演示查詢操作,我們通過如下代碼在 member 中添加100條記錄:from datetime import datetimeimport randomimport MySQLdboccupations = ['web', 'server', 'ops', 'security', 'teacher', 'ui', 'product', 'leader']cities = ['beijing', 'guangzhou', 'shenzhen', 'shanghai', 'wuhan']def gen_phone_num(): phone_num = "18" for i in range(9): phone_num += str(random.randint(0, 9)) return phone_numconn = MySQLdb.connect(host='180.76.152.113', port=9002, user='store', passwd='store.123@', db='django_manual')conn.autocommit(True)data = (('spyinx-%d' % i, \ random.randint(20, 40), \ random.randint(0, 1), \ occupations[random.randint(0, len(occupations) - 1)], \ gen_phone_num(), \ '22%d@qq.com' % i, \ cities[random.randint(0, len(cities) - 1)], \ datetime.now().strftime("%Y-%m-%d %H:%M:%S")) for i in range(100))try: cursor = conn.cursor() cursor.executemany('insert into member(`name`, `age`, `sex`, `occupation`, `phone_num`, `email`, `city`, `register_date`) values (%s, %s, %s, %s, %s, %s, %s, %s);', data) print('批量插入完成')except Exception as e: print('插入異常,執(zhí)行回滾動作: {}'.format(str(e))) conn.rollback()finally: if conn: conn.close()執(zhí)行 python 代碼后,我們通過 mysql 客戶端確認(rèn)100條數(shù)據(jù)已經(jīng)成功插入到數(shù)據(jù)庫中:MySQL [django_manual]> select count(*) from member where 1=1\G;*************************** 1. row ***************************count(*): 1011 row in set (0.00 sec)我們執(zhí)行如下操作:>>> type(Member.objects)<class 'django.db.models.manager.Manager'>>>> Member.objects.get(name='spyinx')<Member: <spyinx, 18054293763>>>>> Member.objects.all().count()101>>> type(Member.objects.all())<class 'django.db.models.query.QuerySet'>上面的語句中 Member.objects.get(name='spyinx') 中,objects 是一個特殊的屬性,通過它來查詢數(shù)據(jù)庫,它是模型的一個 Manager。首先來看看這個 Manager 類提供的常用方法:all():查詢所有結(jié)果,返回的類型為 QuerySet 實例;filter(**kwargs):根據(jù)條件過濾查詢結(jié)果,返回的類型為 QuerySet 實例;get(**kwargs):返回與所給篩選條件相匹配的記錄,只返回一個結(jié)果。如果符合篩選條件的記錄超過一個或者沒有都會拋出錯誤,返回的類型為模型對象實例;exclude(**kwargs):和 filter() 方法正好相反,篩選出不匹配的結(jié)果,返回的類型為 QuerySet 實例;values(*args):返回一個ValueQuerySet,一個特殊的QuerySet,運行后得到的并不是一系列 model 的實例化對象,而是一個可迭代的字典序列;values_list(*args):它與 values() 類似,只不過 values_list() 返回的是一個元組序列,而 values() 返回的是一個字典序列;order_by(*args):對結(jié)果按照傳入的字段進(jìn)行排序,返回的類型為 QuerySet 實例;reverse():對查詢結(jié)果反向排序,返回的類型為 QuerySet 實例;distinct():去掉查詢結(jié)果中重復(fù)的部分,返回的類型為 QuerySet 實例;count():返回數(shù)據(jù)庫中匹配查詢的記錄數(shù),返回類型為 int;first():返回第一條記錄,結(jié)果為模型對象實例;;last():返回最后一條記錄,結(jié)果為模型對象實例;exists():如果 QuerySet 包含數(shù)據(jù),就返回 True,否則返回 False。如果上述這些方法的返回結(jié)果是一個 QuerySet 實例,那么它也同樣具有上面這些方法,因此可以繼續(xù)調(diào)用,形成鏈?zhǔn)秸{(diào)用,示例如下:>>> Member.objects.all().count()101>>> Member.objects.all().reverse().first()<Member: <spyinx-99, 18022422977>>此外,在 filter() 方法中還有一些比較神奇的雙下劃線輔助我們進(jìn)一步過濾結(jié)果:MySQL [django_manual]> select id, name, phone_num from member where name like 'spyinx-2%';+----+-----------+-------------+| id | name | phone_num |+----+-----------+-------------+| 24 | spyinx-2 | 18627420378 || 42 | spyinx-20 | 18687483216 || 43 | spyinx-21 | 18338528387 || 44 | spyinx-22 | 18702966393 || 45 | spyinx-23 | 18386787195 || 46 | spyinx-24 | 18003292724 || 47 | spyinx-25 | 18160946579 || 48 | spyinx-26 | 18517339819 || 49 | spyinx-27 | 18575613014 || 50 | spyinx-28 | 18869175798 || 51 | spyinx-29 | 18603950130 |+----+-----------+-------------+11 rows in set (0.00 sec)>>> Member.objects.all().filter(name__contains='spyinx-2')<QuerySet [<Member: <spyinx-2, 18627420378>>, <Member: <spyinx-20, 18687483216>>, <Member: <spyinx-21, 18338528387>>, <Member: <spyinx-22, 18702966393>>, <Member: <spyinx-23, 18386787195>>, <Member: <spyinx-24, 18003292724>>, <Member: <spyinx-25, 18160946579>>, <Member: <spyinx-26, 18517339819>>, <Member: <spyinx-27, 18575613014>>, <Member: <spyinx-28, 18869175798>>, <Member: <spyinx-29, 18603950130>>]>>>> Member.objects.all().filter(name__contains='spyinx-2').filter(id__lt=47, id__gt=42)<QuerySet [<Member: <spyinx-21, 18338528387>>, <Member: <spyinx-22, 18702966393>>, <Member: <spyinx-23, 18386787195>>, <Member: <spyinx-24, 18003292724>>]>這種雙下劃線的過濾字段有:contains/icontains:過濾字段的值包含某個字符串的結(jié)果;in:和 SQL 語句中的 in 類似,過濾字段的值在某個列表內(nèi)的結(jié)果,比如:>>> Member.objects.all().filter(name__contains='spyinx-2').filter(id__in=[42, 43])<QuerySet [<Member: <spyinx-20, 18687483216>>, <Member: <spyinx-21, 18338528387>>]>lt/gt:過濾字段值小于或者大于某個值的結(jié)果;range:過濾字段值在某個范圍內(nèi)的結(jié)果;>>> Member.objects.all().filter(name__contains='spyinx-2').filter(id__range=[48, 50])<QuerySet [<Member: <spyinx-26, 18517339819>>, <Member: <spyinx-27, 18575613014>>, <Member: <spyinx-28, 18869175798>>]>startswith/istartswith:匹配字段的值以某個字符串開始,前面的 i 標(biāo)識是否區(qū)分大小寫;endswith/iendswiths:匹配字段的值以某個字符串結(jié)束;>>> Member.objects.all().filter(id__gt=90).filter(name__endswith='2')<QuerySet [<Member: <spyinx-72, 18749521006>>, <Member: <spyinx-82, 18970386795>>, <Member: <spyinx-92, 18324708274>>]>F查詢和Q查詢前面我們構(gòu)造的過濾器都只是將字段值與某個常量做比較。如果我們要對兩個字段的值做比較,就需要使用 Django 提供 F() 來做這樣的比較。F() 的實例可以在查詢中引用字段,來比較同一個 model 實例中兩個不同字段的值:>>> from django.db.models import Q# 找出id值大于age*4的記錄>>> Member.objects.all().filter(id__gt=F('age')*4)<QuerySet [<Member: <spyinx-70, 18918359267>>, <Member: <spyinx-77, 18393464230>>, <Member: <spyinx-90, 18272147421>>, <Member: <spyinx-91, 18756265752>>, <Member: <spyinx-92, 18324708274>>, <Member: <spyinx-97, 18154031313>>]># 將所有記錄中age字段的值加1>>> Member.objects.all().update(age=F('age')+1)101此外,前面的多個 filter() 方法實現(xiàn)的是過濾條件的 “AND” 操作,如果想實現(xiàn)過濾條件 “OR” 操作呢,就需要使用到 Django 為我們提供的 Q() 方法:>>> from django.db.models import Q# 過濾條件的 OR 操作>>> Member.objects.all().filter(Q(name='spyinx-22') | Q(name='spyinx-11'))<QuerySet [<Member: <spyinx-11, 18919885274>>, <Member: <spyinx-22, 18702966393>>]># 過濾條件的 AND 操作>>> Member.objects.all().filter(Q(name__contains='spyinx-2') & Q(name__endswith='2'))<QuerySet [<Member: <spyinx-2, 18627420378>>, <Member: <spyinx-22, 18702966393>>]>對于記錄的更新和刪除操作,我們同樣有對應(yīng)的 update() 方法以及 delete() 方法:# 刪除name=spyinx的記錄>>> Member.objects.all().filter(Q(name='spyinx')).delete()(1, {'hello_app.Member': 1})>>> Member.objects.all().count()100# 所有記錄的年齡字段加1>>> Member.objects.all().update(age=F('age')+1)101

1. Scrapy 的 <code>settings.py</code> 配置

從前面的學(xué)習(xí)中我們知道,settings.py 是 Scrapy 使用 startproject 命令生成的,這里的配置會默認(rèn)覆蓋 Scrapy 內(nèi)置的配置項,這些默認(rèn)的配置項都位于 Scrapy 的 scrapy/settings/default_settings.py 中:Scrapy的默認(rèn)配置文件我們來看看 default_settings.py 中的一些默認(rèn)配置項。AJAXCRAWL_ENABLED:通用爬取經(jīng)常會抓取大量的 index 頁面;AjaxCrawlMiddleware 能幫助我們正確地爬取,AJAXCRAWL_ENABLED 配置正是開啟該中間件的開關(guān)。由于有些性能問題,且對于特定爬蟲沒有什么意義,該中間默認(rèn)關(guān)閉;自動限速擴展 (AutoThrottle):這類配置主要是以 Scrapy 爬蟲以及正在抓取網(wǎng)站的負(fù)載來自動優(yōu)化爬取速度。它能自動調(diào)整 Scrapy 達(dá)到最佳的爬取速度,使用者無需自己設(shè)置下載延遲,只要設(shè)置好最大并發(fā)請求數(shù)即可。來看看有關(guān)該擴展的配置項:AUTOTHROTTLE_ENABLED = False # 默認(rèn)關(guān)閉 AUTOTHROTTLE_DEBUG = False # 關(guān)閉調(diào)試 AUTOTHROTTLE_MAX_DELAY = 60.0 # 最高下載延遲 AUTOTHROTTLE_START_DELAY = 5.0 # 初始化下載延遲 AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0 # Scrapy 同時請求目標(biāo)網(wǎng)站的平均請求數(shù)下面四個配置用于設(shè)置爬蟲自動關(guān)閉條件:CLOSESPIDER_TIMEOUT:一個整數(shù)值,單位為秒。如果一個 spider 在指定的秒數(shù)后仍在運行, 它將以 closespider_timeout 的原因被自動關(guān)閉。 如果值設(shè)置為0 (或者沒有設(shè)置),spiders 不會因為超時而關(guān)閉;CLOSESPIDER_ITEMCOUNT:一個整數(shù)值,指定條目的個數(shù)。如果 spider 爬取條目數(shù)超過了設(shè)置的值, 并且這些條目通過 item pipelines 傳遞,spider 將會以 closespider_itemcount 的原因被自動關(guān)閉;CLOSESPIDER_PAGECOUNT:一個整數(shù)值,指定最大的抓取響應(yīng) (reponses) 數(shù)。 如果 spider 抓取數(shù)超過指定的值,則會以 closespider_pagecount 的原因自動關(guān)閉。 如果設(shè)置為0(或者未設(shè)置),spiders不會因為抓取的響應(yīng)數(shù)而關(guān)閉;CLOSESPIDER_ERRORCOUNT:一個整數(shù)值,指定spider可以接受的最大錯誤數(shù)。 如果spider生成多于該數(shù)目的錯誤,它將以 closespider_errorcount 的原因關(guān)閉。 如果設(shè)置為0(或者未設(shè)置),spiders不會因為發(fā)生錯誤過多而關(guān)閉;以上四個參數(shù)在 default_settings.py 中設(shè)置的默認(rèn)值都是0并發(fā)相關(guān),的設(shè)置會較大影響 Scrapy 爬蟲的性能。下面是默認(rèn)的配置值,其中都已經(jīng)進(jìn)行了詳細(xì)的注釋說明:# pipelines中并發(fā)處理items數(shù)CONCURRENT_ITEMS = 100# scrapy中并發(fā)下載請求數(shù)CONCURRENT_REQUESTS = 16# 對任何單個域執(zhí)行的并發(fā)請求的最大數(shù)量CONCURRENT_REQUESTS_PER_DOMAIN = 8# 將對任何單個IP執(zhí)行的并發(fā)請求的最大數(shù)量。如果非零CONCURRENT_REQUESTS_PER_IP = 0Cookie相關(guān)配置:# 是否啟用cookiesmiddleware。如果關(guān)閉,cookies將不會發(fā)送給web serverCOOKIES_ENABLED = True# 如果啟用,Scrapy將記錄所有在request(cookie 請求頭)發(fā)送的cookies及response接收到的cookies(set-cookie接收頭),這也會間接影響性能,因此默認(rèn)關(guān)閉。COOKIES_DEBUG = False請求深度相關(guān)配置,比如 DEPTH_LIMIT 設(shè)置請求允許的最大深度。如果為 0 ,則表示不受限;DEPTH_STATS_VERBOSE 參數(shù)控制是否收集詳細(xì)的深度統(tǒng)計信息;如果啟用此選項,則在統(tǒng)計信息中收集每個深度的請求數(shù)。DEPTH_PRIORITY 參數(shù)用于根據(jù)深度調(diào)整請求優(yōu)先級。來看看他們的默認(rèn)設(shè)置:DEPTH_LIMIT = 0DEPTH_STATS_VERBOSE = FalseDEPTH_PRIORITY = 0DNS 相關(guān)配置。DNSCACHE_ENABLED 用于控制是否啟用 DNS 緩存,DNSCACHE_SIZE參數(shù)設(shè)置緩存大小,DNS_TIMEOUT 處理 DNS 查詢超時時間;我們來具體看看 default_settings.py 中的默認(rèn)配置:DNSCACHE_ENABLED = TrueDNSCACHE_SIZE = 10000# 緩存解析器DNS_RESOLVER = 'scrapy.resolver.CachingThreadedResolver'DNS_TIMEOUT = 60下載器相關(guān)。這部分的配置比較多,也是主要影響性能的地方。我們對一些關(guān)鍵的配置進(jìn)行說明,具體如下:DOWNLOAD_DELAY:下載器在從同一網(wǎng)站下載連續(xù)頁面之前應(yīng)等待的時間,通過該配置可以限制爬蟲的爬取速度。此外,該設(shè)置也受RANDOMIZE_DOWNLOAD_DELAY 設(shè)置(默認(rèn)情況下啟用)的影響。DOWNLOAD_TIMEOUT:下載超時時間;DOWNLOAD_MAXSIZE:下載器將下載的最大響應(yīng)大??;DOWNLOAD_HANDLERS_BASE:處理不同類型下載的下載器;DOWNLOAD_FAIL_ON_DATALOSS:數(shù)據(jù)丟失后是否繼續(xù)下載;DOWNLOADER_MIDDLEWARES 和DOWNLOADER_MIDDLEWARES_BASE:分別表示自定義的下載中間件類和默認(rèn)的下載中間件類;DOWNLOADER_STATS:是否啟用下載器統(tǒng)計信息收集。來看看 default_settings.py 中的默認(rèn)配置,具體如下:DOWNLOAD_DELAY = 0DOWNLOAD_HANDLERS = {}DOWNLOAD_HANDLERS_BASE = { 'data': 'scrapy.core.downloader.handlers.datauri.DataURIDownloadHandler', 'file': 'scrapy.core.downloader.handlers.file.FileDownloadHandler', 'http': 'scrapy.core.downloader.handlers.http.HTTPDownloadHandler', 'https': 'scrapy.core.downloader.handlers.http.HTTPDownloadHandler', 's3': 'scrapy.core.downloader.handlers.s3.S3DownloadHandler', 'ftp': 'scrapy.core.downloader.handlers.ftp.FTPDownloadHandler',}DOWNLOAD_TIMEOUT = 180 # 3minsDOWNLOAD_MAXSIZE = 1024 * 1024 * 1024 # 1024mDOWNLOAD_WARNSIZE = 32 * 1024 * 1024 # 32mDOWNLOAD_FAIL_ON_DATALOSS = TrueDOWNLOADER = 'scrapy.core.downloader.Downloader'DOWNLOADER_HTTPCLIENTFACTORY = 'scrapy.core.downloader.webclient.ScrapyHTTPClientFactory'DOWNLOADER_CLIENTCONTEXTFACTORY = 'scrapy.core.downloader.contextfactory.ScrapyClientContextFactory'DOWNLOADER_CLIENT_TLS_CIPHERS = 'DEFAULT'# Use highest TLS/SSL protocol version supported by the platform, also allowing negotiation:DOWNLOADER_CLIENT_TLS_METHOD = 'TLS'DOWNLOADER_CLIENT_TLS_VERBOSE_LOGGING = FalseDOWNLOADER_MIDDLEWARES = {}DOWNLOADER_MIDDLEWARES_BASE = { # Engine side 'scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware': 100, 'scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware': 300, 'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware': 350, 'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware': 400, 'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': 500, 'scrapy.downloadermiddlewares.retry.RetryMiddleware': 550, 'scrapy.downloadermiddlewares.ajaxcrawl.AjaxCrawlMiddleware': 560, 'scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware': 580, 'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 590, 'scrapy.downloadermiddlewares.redirect.RedirectMiddleware': 600, 'scrapy.downloadermiddlewares.cookies.CookiesMiddleware': 700, 'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': 750, 'scrapy.downloadermiddlewares.stats.DownloaderStats': 850, 'scrapy.downloadermiddlewares.httpcache.HttpCacheMiddleware': 900, # Downloader side}DOWNLOADER_STATS = TrueDUPEFILTER_CLASS:指定去重類;DUPEFILTER_CLASS = 'scrapy.dupefilters.RFPDupeFilter'自定義擴展和內(nèi)置擴展配置:EXTENSIONS = {}EXTENSIONS_BASE = { 'scrapy.extensions.corestats.CoreStats': 0, 'scrapy.extensions.telnet.TelnetConsole': 0, 'scrapy.extensions.memusage.MemoryUsage': 0, 'scrapy.extensions.memdebug.MemoryDebugger': 0, 'scrapy.extensions.closespider.CloseSpider': 0, 'scrapy.extensions.feedexport.FeedExporter': 0, 'scrapy.extensions.logstats.LogStats': 0, 'scrapy.extensions.spiderstate.SpiderState': 0, 'scrapy.extensions.throttle.AutoThrottle': 0,}文件存儲相關(guān):FILES_STORE_S3_ACL = 'private'FILES_STORE_GCS_ACL = ''FTP 服務(wù)配置, Scrapy 框架內(nèi)置 FTP 下載程序。我們可以指定 FTP 的相關(guān)參數(shù):FTP_USER = 'anonymous'FTP_PASSWORD = 'guest'FTP_PASSIVE_MODE = TrueHTTP 緩存相關(guān)配置。Scrapy 的 HttpCacheMiddleware 組件(默認(rèn)情況下沒有啟用)提供了一個底層的對HTTP請求和響應(yīng)的緩存。如果啟用的話(把HTTPCACHE_ENABLED設(shè)置為True),它會緩存每個請求和對應(yīng)的響應(yīng)。來看看和其相關(guān)的配置和含義:# 是否啟用http緩存HTTPCACHE_ENABLED = False# 緩存數(shù)據(jù)目錄HTTPCACHE_DIR = 'httpcache'HTTPCACHE_IGNORE_MISSING = False# 緩存存儲的插件HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'# 緩存過期時間HTTPCACHE_EXPIRATION_SECS = 0HTTPCACHE_ALWAYS_STORE = False# 緩存忽略的Http狀態(tài)碼HTTPCACHE_IGNORE_HTTP_CODES = []HTTPCACHE_IGNORE_SCHEMES = ['file']HTTPCACHE_IGNORE_RESPONSE_CACHE_CONTROLS = []HTTPCACHE_DBM_MODULE = 'dbm'# 設(shè)置緩存策略,DummyPolicy是所有請求均緩存,下次在請求直接訪問原來的緩存即可HTTPCACHE_POLICY = 'scrapy.extensions.httpcache.DummyPolicy'# 是否啟用緩存數(shù)據(jù)壓縮HTTPCACHE_GZIP = FalseItem 和 Item pipelines相關(guān)配置:# ITEM處理器ITEM_PROCESSOR = 'scrapy.pipelines.ItemPipelineManager'# 自定義的 item pipelinesITEM_PIPELINES = {}ITEM_PIPELINES_BASE = {}日志相關(guān)的配置:# 啟動日志功能LOG_ENABLED = True# 日志編碼LOG_ENCODING = 'utf-8'# 日志格式器LOG_FORMATTER = 'scrapy.logformatter.LogFormatter'# 日志格式LOG_FORMAT = '%(asctime)s [%(name)s] %(levelname)s: %(message)s'# 日志時間格式LOG_DATEFORMAT = '%Y-%m-%d %H:%M:%S'LOG_STDOUT = False# 日志級別LOG_LEVEL = 'DEBUG'# 指定日志輸出文件LOG_FILE = NoneLOG_SHORT_NAMES = False郵件配置:在 Scrapy 中提供了郵件功能,該功能使用十分簡便且采用了 Twisted 非阻塞模式,避免了對爬蟲的影響。我們只需要在 Scrapy 中進(jìn)行簡單的設(shè)置,就能通過 API 發(fā)送郵件。郵件的默認(rèn)配置項如下:MAIL_HOST = 'localhost'MAIL_PORT = 25MAIL_FROM = 'scrapy@localhost'MAIL_PASS = NoneMAIL_USER = None我們現(xiàn)在可以簡單的使用下 Scrapy 給我們提供的郵件類,來利用它給我們自己發(fā)送一封郵件。首先需要找下自己的 qq 郵箱或者其他郵箱,開啟 POP3/SMTP服務(wù),然后我們可以得到一個授權(quán)碼。這個就是我們登陸這個郵箱服務(wù)的密碼。然后我們配置 settings.py 中的相應(yīng)項:MAIL_HOST = 'smtp.qq.com'MAIL_PORT = 25MAIL_FROM = '2894577759@qq.com'MAIL_PASS = '你的授權(quán)碼'MAIL_USER = '2894577759@qq.com'接下來我們在 scrapy shell 中來調(diào)用相應(yīng)的郵件接口,發(fā)送郵件:(scrapy-test) [root@server china_pub]# scrapy shell --nolog[s] Available Scrapy objects:[s] scrapy scrapy module (contains scrapy.Request, scrapy.Selector, etc)[s] crawler <scrapy.crawler.Crawler object at 0x7f1c3d4e9100>[s] item {}[s] settings <scrapy.settings.Settings object at 0x7f1c3d4e6dc0>[s] Useful shortcuts:[s] fetch(url[, redirect=True]) Fetch URL and update local objects (by default, redirects are followed)[s] fetch(req) Fetch a scrapy.Request and update local objects [s] shelp() Shell help (print this help)[s] view(response) View response in a browser>>> from scrapy.mail import MailSender>>> mailer = MailSender().from_settings(settings)>>> mailer.send(to=["2894577759@qq.com"], subject="這是一個測試", body="來自百度云主機發(fā)送的一封郵件", cc=["2894577759@qq.com"])<Deferred at 0x7f1c3c4d1c40>調(diào)用 Scrapy 的郵件接口發(fā)送郵件內(nèi)存相關(guān)參數(shù):MEMDEBUG_ENABLED = False # enable memory debuggingMEMDEBUG_NOTIFY = [] # send memory debugging report by mail at engine shutdownMEMUSAGE_CHECK_INTERVAL_SECONDS = 60.0# 是否啟用內(nèi)存使用擴展MEMUSAGE_ENABLED = True# 在關(guān)閉Scrapy之前允許的最大內(nèi)存量,為0則不檢查MEMUSAGE_LIMIT_MB = 0# 要達(dá)到內(nèi)存限制時通知的電子郵件列表MEMUSAGE_NOTIFY_MAIL = []# 在發(fā)送警告電子郵件通知之前,要允許的最大內(nèi)存量(以兆字節(jié)為單位)。如果為零,則不會產(chǎn)生警告MEMUSAGE_WARNING_MB = 0調(diào)度器相關(guān)配置:# 調(diào)度器類SCHEDULER = 'scrapy.core.scheduler.Scheduler'# 指定調(diào)度器的三種隊列類SCHEDULER_DISK_QUEUE = 'scrapy.squeues.PickleLifoDiskQueue'SCHEDULER_MEMORY_QUEUE = 'scrapy.squeues.LifoMemoryQueue'SCHEDULER_PRIORITY_QUEUE = 'scrapy.pqueues.ScrapyPriorityQueue'# 正在處理響應(yīng)數(shù)據(jù)的軟限制(以字節(jié)為單位),如果所有正在處理的響應(yīng)的大小總和高于此值,Scrapy不會處理新的請求SCRAPER_SLOT_MAX_ACTIVE_SIZE = 5000000spider 中間件相關(guān)配置,有我們熟悉的 SPIDER_MIDDLEWARES 和 SPIDER_MIDDLEWARES_BASE,表示自定義的 Spider 中間件和 Scrapy 內(nèi)置的 Spider 中間件;SPIDER_MIDDLEWARES = {}SPIDER_MIDDLEWARES_BASE = { # Engine side 'scrapy.spidermiddlewares.httperror.HttpErrorMiddleware': 50, 'scrapy.spidermiddlewares.offsite.OffsiteMiddleware': 500, 'scrapy.spidermiddlewares.referer.RefererMiddleware': 700, 'scrapy.spidermiddlewares.urllength.UrlLengthMiddleware': 800, 'scrapy.spidermiddlewares.depth.DepthMiddleware': 900, # Spider side}指定模板文件目錄,這個在使用 scrapy startproject 項目名 命令創(chuàng)建項目時,對應(yīng)的模板文件所在的目錄:TEMPLATES_DIR = abspath(join(dirname(__file__), '..', 'templates'))USER_AGENT:設(shè)置請求頭的 User-Agent 參數(shù),用來模擬瀏覽器。我們通常都會添加一個瀏覽器的 User-Agent 值,防止爬蟲直接被屏蔽;Scrapy 的大體配置就是這些,還有一些沒有介紹到的參數(shù),課后可以仔細(xì)查看官方文檔進(jìn)行了解。

2. django-guardian 框架實戰(zhàn)

我們前面介紹過 django-guardian 框架時,知道它的主要特點是實現(xiàn)了基于對象的權(quán)限控制。首先我們準(zhǔn)備相應(yīng)的開發(fā)環(huán)境,包括安裝 django-guardian 模塊、注冊 guardian 應(yīng)用以及遷移 guardian 應(yīng)用的數(shù)據(jù)表。我們會在一個全新的項目中來完成 django-guardian 框架的實驗:(django-manual) [root@server django-manual]# django-admin startproject test_guardian(django-manual) [root@server django-manual]# cd test_guardian(django-manual) [root@server test_guardian]# django-admin startapp articles安裝 django-guardian 模塊并修改 settings.py 文件,操作如下:(django-manual) [root@server django-manual]# pip install django-guardianLooking in indexes: https://pypi.tuna.tsinghua.edu.cn/simpleCollecting django-guardian Downloading https://pypi.tuna.tsinghua.edu.cn/packages/56/ed/b0e24c766a498da38e2d10942be313613e12943cfdfe147dcdcdf1516c1d/django_guardian-2.2.0-py3-none-any.whl (104kB) |████████████████████████████████| 112kB 37.8MB/s Requirement already satisfied: Django>=2.1 in /root/.pyenv/versions/3.8.1/envs/django-manual/lib/python3.8/site-packages (from django-guardian) (2.2.12)Requirement already satisfied: pytz in /root/.pyenv/versions/3.8.1/envs/django-manual/lib/python3.8/site-packages (from Django>=2.1->django-guardian) (2019.3)Requirement already satisfied: sqlparse in /root/.pyenv/versions/3.8.1/envs/django-manual/lib/python3.8/site-packages (from Django>=2.1->django-guardian) (0.3.1)Installing collected packages: django-guardianSuccessfully installed django-guardian-2.2.0# 添加guardian應(yīng)用到INSTALLED_APPS中(django-manual) [root@server test_guardian]# vim test_guardian/settings.py...# 調(diào)試階段打開DEBUG = True# 主機白名單ALLOWED_HOSTS = ['*', ]INSTALLED_APPS = [ ... 'guardian', 'articles']AUTHENTICATION_BACKENDS = ( 'django.contrib.auth.backends.ModelBackend', # 這是Django默認(rèn)的 'guardian.backends.ObjectPermissionBackend', # 這是guardian的)TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'templates')], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, { 'BACKEND': 'django.template.backends.jinja2.Jinja2', 'DIRS': ['/root/test/html/jinja2'], }]# 必須先新建立好數(shù)據(jù)庫DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'test_guardian', 'USER': 'store', 'PASSWORD': 'xxxxx', 'HOST': '180.76.152.113', 'PORT': '9002', }}...首先我們建立一個文章表(article),每個記錄關(guān)聯(lián)著一個作者。這里的文章類會有2個權(quán)限:一個是編輯權(quán)限(edit),另一個為刪除權(quán)限(delete)。# 代碼位置:test_guardian/models.pyfrom django.db import modelsfrom django.contrib.auth.models import User# Create your models here.class Article(models.Model): title = models.CharField('文章標(biāo)題', max_length=30) synopsis = models.TextField('文章簡介', max_length=300, default='暫無簡介') content = models.TextField('文章內(nèi)容', default='暫無內(nèi)容') author = models.ForeignKey(User, on_delete=models.CASCADE) created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return self.title class Meta: db_table = 'article' permissions = ( ('edit', 'edit_article'), ('delete', 'delete_article'), )有了模型之后,我們就可以使用 makemigrations 和 migrate 指令來生成我們的數(shù)據(jù)表了。(django-manual) [root@server test_guardian]# python manage.py makemigrations(django-manual) [root@server test_guardian]# python manage.py migrate首先新建 super user,然后我們進(jìn)入 django 的 shell 模式,新建兩個普通用戶以及三篇文章記錄:(django-manual) [root@server test_guardian]# python manage.py createsuperuserUsername (leave blank to use 'root'): adminEmail address: admin@163.comPassword: Password (again): Superuser created successfully.(django-manual) [root@server test_guardian]# python manage.py shellPython 3.8.1 (default, Dec 24 2019, 17:04:00) [GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linuxType "help", "copyright", "credits" or "license" for more information.(InteractiveConsole)>>> from articles.models import Article>>> from django.contrib.auth.models import User>>> author1=User(username="author1", email="11@qq.com")>>> author1.set_password('author1.123456')>>> author1.save()>>> author2=User(username="author2", email="22@qq.com")>>> author2.set_password('author2.123456')>>> author2.save()>>> a1 = Article(title='測試崗位', synopsis='介紹測試崗位職責(zé)', content='這是關(guān)于測試崗位的介紹...')>>> a2 = Article(title='開發(fā)崗位', synopsis='介紹開發(fā)崗位職責(zé)', content='這是關(guān)于開發(fā)崗位的介紹...')>>> a3 = Article(title='瞎寫', synopsis='簡單介紹', content='胡言亂語,一派胡言...')>>> a1.author=author1>>> a2.author=author1>>> a3.author=author2>>> a1.save()>>> a2.save()>>> a3.save()接下來,我們要使用 django-guardian 框架中給我們提供的 assign_perm() 來將對象的權(quán)限賦給相應(yīng)的 user。通過 assign_perm() 可以給用戶賦權(quán),assign_perm() 接收三個參數(shù),分別為perm、user_or_group以及object。這里我們將文章1、2的編輯和刪除權(quán)限賦給 author1,文章3的編輯和刪除權(quán)限賦給 author2:# 用戶是否擁有對象a1的‘edit’權(quán)限>>> author1.has_perm('edit', a1)False>>> author1.has_perm('edit', a2)False>>> author1.has_perm('delete', a1)False>>> author1.has_perm('delete', a2)False>>> from guardian.shortcuts import assign_perm>>> assign_perm('edit', author1, a1)<UserObjectPermission: 測試崗位 | author1 | edit>>>> assign_perm('delete', author1, a1)<UserObjectPermission: 測試崗位 | author1 | delete>>>> assign_perm('edit', author1, a2)<UserObjectPermission: 開發(fā)崗位 | author1 | edit>>>> assign_perm('delete', author1, a2)<UserObjectPermission: 開發(fā)崗位 | author1 | delete>>>> assign_perm('edit', author2, a3)<UserObjectPermission: 瞎寫 | author2 | edit>>>> assign_perm('delete', author2, a3)<UserObjectPermission: 瞎寫 | author2 | delete>django-guardian 中的 get_perms() 方法可以獲取用戶對 obj 的權(quán)限:# 獲取用戶對obj的權(quán)限 >>> from guardian.shortcuts import get_perms >>> get_perms(author1, a1)['edit', 'delete']>>> get_perms(author2, a1)[]>>> get_perms(author2, a2)[]>>> get_perms(author2, a3)['edit', 'delete']get_objects_for_user() 方法用來顯示用戶具有某個權(quán)限的所有對象:# 顯示用戶所具有某個權(quán)限的所有對象>>> from guardian.shortcuts import get_objects_for_user>>> get_objects_for_user(author1,'articles.edit')<QuerySet [<Article: 測試崗位>, <Article: 開發(fā)崗位>]>>>> get_objects_for_user(author2,'articles.edit')<QuerySet [<Article: 瞎寫>]>>>> get_objects_for_user(author2,'articles.delete')<QuerySet [<Article: 瞎寫>]>ObjectPermissionChecker() 方法判斷用戶對某個對象是否具有相應(yīng)的權(quán)限:>>> from guardian.core import ObjectPermissionChecker>>> checker = ObjectPermissionChecker(author1)>>> checker.has_perm('edit', a1)True>>> checker.has_perm('edit', a2)True>>> checker.has_perm('edit', a3)False當(dāng)我們需要去除權(quán)限時,可以使用 remove_perm() 方法,remove_perm()方法與 assign_perm()方法類似,同樣接收三個參數(shù),參數(shù)類型也類似,唯一不同的是 assign_perm() 的第二個參數(shù)可以是 QuerySet,而 remove_perm() 的第二個參數(shù)必須是 instance。以上是在操作對象是可以判斷某個對象是否擁有某個權(quán)限并對其進(jìn)行處理。我們上面已經(jīng)為用戶和文章分配好了相應(yīng)的權(quán)限,接下來我們使用 django-guardian 來完成一個簡單的頁面:不同的用戶只對有權(quán)限的文章進(jìn)行操作。為了簡單方便,我只用操作按鈕代表相應(yīng)的操作。我們按照如下的步驟完成代碼,來實現(xiàn)這樣一個具有權(quán)限控制的頁面。準(zhǔn)備模板文件,包括登錄的頁面 login.html 以及文章列表頁面 articles.html:{% load staticfiles %}<link rel="stylesheet" type="text/css" href="{% static 'css/main.css' %}" /><form action="/articles/login/" method="POST">{% csrf_token %}<div><span>{{ form.name.label }}:</span>{{ form.name }}<div><span>{{ form.password.label }}:</span>{{ form.password }}<div><input class="input-text input-red" type="submit" value="登錄" style="width: 214px"/></div>{% if err_msg %}<div><label class="color-red">{{ err_msg }}</label</div>{% endif %}</form>{% load staticfiles %}{% load guardian_tags %}<p>登錄用戶:{{ username }}<a href="/articles/logout/" style="margin-left:30px">登出</a></p><div><table border="1" class="member-table"> <thead> <tr> <th></th> <th>文章編號</th> <th>文章標(biāo)題</th> <th>文章簡介</th> <th>文章內(nèi)容</th> <th>發(fā)布時間</th> <th>操作</th> </tr> </thead> <tbody> {% for article in articles %} <tr> <td></td> <td>{{ article.id }}</td> <td>{{ article.title }}</td> <td>{{ article.synopsis }}</td> <td>{{ article.content }}</td> <td>{{ article.created_at | date:"Y-m-d H:m:s"}}</td> {% get_obj_perms request.user for article as "article_perms" %} <td> {% if "edit" in article_perms %} <a href="/articles/edit/{{ article.id }}/">編輯</a> {% endif %} {% if "delete" in article_perms %} <a href="/articles/delete/{{ article.id }}/">刪除</a> {% endif %} </td> </tr> {% endfor %} </tbody></table><div ><div class="page"></div></div></div>注意:首先要加載 guardian_tags 標(biāo)簽庫,然后就可以用 get_obj_perms 標(biāo)簽獲取當(dāng)前用戶對 obj 的權(quán)限。編寫視圖,只寫登錄相關(guān)的邏輯,利用了前面的 session 保存用戶信息:# 代碼位置:articles/views.pyfrom django.shortcuts import render, redirectfrom django.views.generic import Viewfrom django import formsfrom django.contrib.auth.models import Userfrom django.contrib.auth import authenticatefrom articles.models import Articleclass LoginForm(forms.Form): name = forms.CharField( label="賬號", min_length=4, required=True, error_messages={'required': '賬號不能為空', "min_length": "賬號名最短4位"}, widget=forms.TextInput(attrs={'class': "input-text", 'placeholder': '請輸入登錄賬號'}) ) password = forms.CharField( label="密碼", min_length=6, max_length=20, required=True, error_messages={'required': '密碼不能為空', "min_length": "密碼最短8位", "max_length": "密碼最長20位"}, widget=forms.TextInput(attrs={'class': "input-text",'placeholder': '請輸入密碼', 'type': 'password'}), )class LoginView(View): def get(self, request, *args, **kwargs): success = False form = LoginForm() if request.session.get('has_login', False): logined_user = User.objects.all().get(id=int(request.session['user_id'])) request.user = logined_user articles = Article.objects.all() return render(request, "articles.html", {"username":logined_user.username, "articles": articles}) return render(request, "login.html", {'form': form}) def post(self, request, *args, **kwargs): form = LoginForm(request.POST) err_msg = "" if form.is_valid(): login_data = form.clean() name = login_data['name'] password = login_data['password'] user = authenticate(username=name, password=password) if not user: success = False err_msg = "用戶名密碼不正確" else: request.user = user request.session['has_login'] = True request.session['user_id'] = user.id # 設(shè)置1000s后過期 request.session.set_expiry(100) articles = Article.objects.all() return render(request, "articles.html", {"username": user.username, "articles": articles}) else: err_msg = "提交表單不正確" return render(request, 'login.html', {'err_msg': err_msg, 'form': form}) def logout(request, *args, **kwargs): if 'has_login' in request.session: del request.session["has_login"] if 'user_id' in request.session: del request.session["user_id"] request.session.flush() return redirect('/articles/login/')這里用到了在上一個項目中用來做登錄的表單,也使用了相關(guān)代碼(session 操作)完成登錄操作。不過登錄的認(rèn)證使用 Django 內(nèi)置的 authenticate() 方法來實現(xiàn)的。另外,我們還實現(xiàn)了一個登出的相關(guān)操作。有了這些視圖出來,就可以編寫 URLConf 配置了。首針對 article 應(yīng)用,添加登錄和登出的 URLConf 配置:# 代碼位置:articles/urls.pyfrom django.urls import pathfrom articles import viewsurlpatterns = [ # 登錄 path('login/', views.LoginView.as_view(), name='login'), path('logout/', views.logout, name='logout'),]最后要在 URLConf 配置的總?cè)肟谔砑釉搼?yīng)用的 URL 前綴,代碼如下:# test_guardian/urls.pyfrom django.contrib import adminfrom django.urls import pathfrom django.conf.urls import include, urlurlpatterns = [ path('admin/', admin.site.urls), url('articles/', include('articles.urls')),]準(zhǔn)備好上面這些之后,我就可以啟動看頁面效果了,具體的演示如下視頻所示。可以看到,針對超級用戶是有所有文章的修改和刪除權(quán)限的,而 author1 則能控制文章1和文章2的修改和刪除,author2只能對文章3進(jìn)行編輯和刪除,這樣就通過 django-guardian 框架實現(xiàn)了在 django 中無法做到的對象權(quán)限控制。更多的使用操作以及相關(guān)的 API 說明,請參考官方文檔 。

首頁上一頁678910下一頁尾頁
直播
查看課程詳情
微信客服

購課補貼
聯(lián)系客服咨詢優(yōu)惠詳情

幫助反饋 APP下載

慕課網(wǎng)APP
您的移動學(xué)習(xí)伙伴

公眾號

掃描二維碼
關(guān)注慕課網(wǎng)微信公眾號