- Python 3反爬蟲原理與繞過實戰(zhàn)
- 韋世東
- 4721字
- 2021-02-07 09:17:45
2.1 nginx服務(wù)器
Web網(wǎng)站的功能由編程語言實現(xiàn),例如Java、Python和PHP等。編程語言專注于網(wǎng)站功能的實現(xiàn),資源映射與連接處理則由服務(wù)器軟件完成。常見的服務(wù)器軟件有Apache、nginx和Tomcat等,接下來我們將通過nginx來增進對服務(wù)器的了解。
nginx是一個HTTP和反向代理服務(wù)器,同時也是郵件代理服務(wù)器和通用的TCP / UDP代理服務(wù)器。它具有模塊化設(shè)計、可擴展、低內(nèi)存消耗、支持熱部署等優(yōu)秀特性,所以非常多的Web應(yīng)用將其作為服務(wù)器軟件。本書也將使用它實現(xiàn)一些反爬蟲的功能。
nginx有一個主進程和若干工作進程,其中主進程用于讀取和評估配置并維護工作進程,工作進程會對請求進行實際處理。nginx采用基于事件的模型和依賴于操作系統(tǒng)的機制,有效地在工作進程之間分發(fā)請求。工作進程數(shù)在配置文件中進行定義,可以設(shè)定具體數(shù)值或使用默認選項。
2.1.1 nginx的信號
信號(signal)是控制nginx工作狀態(tài)的模塊,我們可以在終端使用信號來控制nginx的啟動、停止和配置重載等。信號的語法格式如下:
nginx -s signal
其中signal是信號名稱。常用的nginx信號有以下幾種。
? stop:快速關(guān)機。
? quit:正常關(guān)機。
? reload:重新加載配置文件。
? reopen:重新打開日志文件。
假如我們需要停止nginx服務(wù),但又希望它處理完當前請求后再停止工作進程,可以在終端向nginx發(fā)送正常關(guān)機的信號,命令為:
$ nginx -s quit
當nginx的配置被更改或者添加新的輔助配置文件時,它們不會立即生效。如果想讓新的配置生效,就必須重新啟動nginx或者進行配置重載。假如我們希望nginx在不影響當前任務(wù)處理的情況下重載配置,可以通過終端向nginx發(fā)送重新加載配置文件的信號,命令為:
$ nginx -s reload
一旦主進程收到配置重載信號,它將檢查新配置文件的語法有效性,并嘗試應(yīng)用其中的配置。如果成功,主進程將啟動新的工作進程并向舊工作進程發(fā)送關(guān)閉請求,否則主進程將回滾更改,并繼續(xù)使用舊配置。舊工作進程在接收關(guān)閉請求后會停止接受新連接,并且繼續(xù)為當前請求提供服務(wù),直到當前請求處理完畢才關(guān)閉。
更多nginx信號的知識可前往nginx官方文檔查看,詳見http://nginx.org/en/docs/control.html。
2.1.2 nginx配置文件
nginx由模塊組成,而這些模塊由配置文件中特定的指令控制,也就是說nginx的配置文件決定了nginx及其模塊的工作方式。nginx的配置文件分為主配置文件和輔助配置文件:主配置文件名為nginx.conf,默認存放在 /etc/nginx目錄中;輔助配置文件要求以 .conf作為文件后綴,并且默認存放在/etc/nginx/conf.d目錄中。要注意的是,nginx允許同時存在多個輔助配置文件。
nginx的指令分為簡單指令和塊指令。一個簡單的指令由指令名稱和參數(shù)組成,它們以空格作為分隔符,并以分號結(jié)尾,如:
error_page 404 /404.html;
其中error_page是指令名稱,404和 /404.html共同組成參數(shù),作用是指定404錯誤顯示的HTML文件。
塊指令與簡單指令具有相同的結(jié)構(gòu),但它不是以分號結(jié)尾,而是以花括號包圍的一組附加指令結(jié)束,如:
location /404.html { root /home/async/www/error_page; }
如果塊指令內(nèi)包含其他指令,則該塊指令稱為上下文。常見的上下文有events、http、server和location。要注意的是,這里還有一個隱藏的main上下文,它并非實際存在,類似于層級的根目錄,即所有的指令的最外層都是main。main上下文作為其他上下文的參考對象,例如events和http,必須寫在main上下文中,server必須寫在http中,而location則必須寫在server中。對于它們的關(guān)系,我們可以通過一段簡單的配置來理解:
http { server { location / { root /www/index index.html; } location /images/ { # ... } } }
配置文件的注釋符為#。以上配置默認監(jiān)聽80端口,當我們在本地訪問http://localhost/時,服務(wù)器將根據(jù)配置文件設(shè)定的資源路徑尋找資源,并將符合條件的資源發(fā)送給客戶端,如果資源不存在,則發(fā)送404錯誤。我們并沒有在配置中添加任何有關(guān)main的文字,但http上下文確實包含在main中。
nginx提供了一個默認的輔助配置文件default.conf,存放在 /etc/nginx/conf.d目錄中,里面包含了若干server塊指令示例。我們可以在終端使用如下命令查看default.conf文件的內(nèi)容:
$sudo cat /etc/nginx/conf.d/default.conf
命令執(zhí)行后,終端會輸出如下內(nèi)容(以 ... 代替部分被注釋的內(nèi)容):
server { listen 80; server_name localhost; #charset koi8-r; #access_log /var/log/nginx/host.access.log main; location / { root /usr/share/nginx/html; index index.html index.htm; } #error_page 404 /404.html; # redirect server error pages to the static page /50x.html # error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } # proxy the PHP scripts to Apache listening on 127.0.0.1:80 # ... #} }
2.1.3 簡單的代理服務(wù)
提供資源是服務(wù)器的功能之一,接下來我們就來學(xué)習(xí)如何通過nginx實現(xiàn)簡單的代理服務(wù)。本次任務(wù)要求服務(wù)器根據(jù)用戶的請求URL,響應(yīng)服務(wù)器本地目錄中的資源(如 /data/www目錄中包含的HTML文件和 /data/images目錄中包含的圖片文件)。我們可以通過編輯nginx的配置文件,將URL和本地目錄中的資源進行映射,從而實現(xiàn)這個需求。
首先,我們需要準備本地目錄的資源。在用戶目錄(如 /home/async)下創(chuàng)建www文件夾,將包含任意內(nèi)容的index.html文件放到www目錄中。在www文件夾中創(chuàng)建images文件夾,并在里面放置一張名為example.png的圖片。
然后使用編輯器打開nginx默認的輔助配置文件default.conf,將其中的所有代碼注釋后,編寫新的配置:
http { server { } }
在通常情況下,server需要確定監(jiān)聽的端口和服務(wù)器名稱, nginx一旦決定處理請求,就會根據(jù)塊指令中定義的指令參數(shù)測試請求頭中指定的URI。我們將下方的location添加到server中:
location / { root /home/async/www; }
配置中的location指定“/”與請求中的URI進行比較。對于匹配的請求,URI將指向root指令中指定的路徑,即 /home/async/www,以便將本地資源與請求對應(yīng)。如果存在多個匹配的location塊指令,那么nginx會選擇具有最長前綴的塊指令。當無法匹配到其他前綴時,就會匹配“/”。
接下來添加第二個location塊指令:
location /images/ { root /home/async/www; }
它將匹配以“/images/”開頭的URI(“/”也匹配此類請求,但由于“/”的前綴長度比“/images/”短,所以優(yōu)先匹配“/images/”)。完整的輔助配置文件內(nèi)容如下:
server { location / { root /home/async/www; } location /images/ { root /home/async/www; } }
如果想讓剛才的配置生效,我們需要給nginx發(fā)送重載配置的信號:
$ nginx -s reload
配置生效后,nginx就會監(jiān)聽80端口(80是默認值),我們可以在瀏覽器中訪問http://localhost/。服務(wù)器為響應(yīng)這次請求,會根據(jù)配置文件中指定的文件路徑搜索HTML文件,并優(yōu)先選擇名為index.html的文件作為響應(yīng)內(nèi)容。如果在瀏覽器中訪問http://localhost/images/example.png,那么nginx會根據(jù)配置文件,從指定的路徑中搜索example.png文件,如果存在,則返回文件資源,否則觸發(fā)404錯誤。
在真正訪問http://localhost/之前,還有兩件事要做。第一件事是編輯nginx主配置文件,將配置中User指定的用戶從nginx改為你的操作系統(tǒng)的用戶名(比如我的操作系統(tǒng)的用戶名為async)。打開主配置文件的命令為:
$sudo nano /etc/nginx/nginx.conf
主配置文件的第一行即是User設(shè)置。
第二件事是關(guān)閉SELinux。SELinux是美國國家安全局(NSA)對于強制訪問控制的實現(xiàn),默認安裝在版本較新的Linux系統(tǒng)中。如果當前操作系統(tǒng)中沒有SELinux,則無須關(guān)閉。打開SELinux配置文件的命令為:
$sudo nano /etc/selinux/config
注意將配置文件中的SELINUX=enforcing改為SELINUX=disabled,保存改動后重新啟動操作系統(tǒng)。
接著我們用瀏覽器訪問http://localhost/,此時顯示的內(nèi)容如圖2-2所示。

圖2-2 瀏覽器顯示的內(nèi)容
再試一試訪問http://localhost/images/example.png,瀏覽器顯示的內(nèi)容如圖2-3所示。

圖2-3 瀏覽器中顯示的圖片
當我們使用瀏覽器訪問指定的URL后,如果瀏覽器中能夠正確顯示我們準備的HTML內(nèi)容和圖片,就說明nginx輔助配置已生效。
2.1.4 nginx模塊與指令
nginx內(nèi)置了很多模塊,可以從nginx文檔中的Modules reference部分查看。其中比較常用的模塊是ngx_http_rewrite_module(詳見http://nginx.org/en/docs/http/ngx_http_rewrite_module.html),我們可以通過學(xué)習(xí)該模塊來熟悉nginx模塊的語法。
ngx_http_rewrite_module模塊的主要作用是重定向,它通過正則表達式或判斷語句來更改請求的URI。該模塊有一些主要的指令,這些指令分別是if、set、break、return和rewrite。if指令的語法和語境如表2-1所示。
表2-1 if指令的語法和語境

我們可以看到if指令的語法是:
if 條件 { ... }
這與其他編程語言的語法非常相似,很容易理解。if指令沒有默認值,使用范圍限制在server塊和location塊內(nèi)。它的條件有以下幾種情況。
? 變量名稱:如果變量的值是空字符串或0,則條件的布爾值為false;在nginx 1.0.1版本之前,任何以0開頭的字符串都被認為是錯誤的值。
? =或!=:比較變量和字符串。
? ~或~*:將變量與正則表達式匹配,區(qū)分大小寫。如果正則表達式包含}或;字符,則整個表達式應(yīng)該用單引號或雙引號括起來。
? -f或!-f:檢查文件是否存在。
? -d或!-d:檢查目錄是否存在。
? -e或!-e:檢查文件、目錄或符號鏈接是否存在。
? -x或!-x:檢查可執(zhí)行文件。
我們可以通過一些例子來理解這些條件判斷:
# 當請求頭中User-Agent 頭域的值包含MSIE 字符串,則重定向到指定URI if ($http_user_agent ~ MSIE) { rewrite ^(.*)$ /msie/$1 break; } # 當請求頭中Cookie 頭域的值滿足條件,則設(shè)定$id 變量值為正則部分 if ($http_cookie ~* "id=([^;]+)(?:;|$)") { set $id $1; } # 如果請求方式是 POST,則返回 405 if ($request_method = POST) { return 405; } # 限制下載速度為10k,$slow 可以通過set 指令設(shè)置 if ($slow) { limit_rate 10k; } #當請求頭中Referer 頭域的值為空或www.example.com 時,允許訪問,否則返回403 valid_referers none www.example.com; if ($invalid_referer) { return 403; }
我們可以從其中選出一個作為驗證對象。打開nginx輔助配置文件default.conf,并在文件中添加對Referer的判斷語句:
server { location / { # 對請求的Referer 進行驗證,如果沒有Referer 頭域 # 或者頭域值為www.example.com,則允許訪問 valid_referers none www.example.com; if ($invalid_referer) { return 403; } root /home/async/www; } location /images/ { root /home/async/www; } }
為了讓nginx啟用新配置,我們需要給它發(fā)送reload信號:
$ nginx -s reload
為了驗證請求的過濾效果,我們可以使用Postman進行測試。首先測試沒有Referer頭域的請求,得到的結(jié)果如圖2-4所示。

圖2-4 Postman請求結(jié)果1
本次請求的響應(yīng)狀態(tài)碼為200,說明服務(wù)器可以正常響應(yīng)客戶端的請求。然后測試Referer頭域的值不滿足條件的情況,結(jié)果如圖2-5所示。

圖2-5 Postman請求結(jié)果2
本次請求的響應(yīng)狀態(tài)碼為403,說明服務(wù)器對Referer頭域的值進行判斷后,發(fā)現(xiàn)它并不符合要求。除了以上給出的$invalid_referer和$request_method變量之外,還有哪些變量呢?在介紹ngx_http_core_module時,我們給出了nginx支持的嵌入式變量,其中常用的變量及含義如表2-2所示。
表2-2 nginx中常用的變量及其含義

nginx所支持的嵌入變量詳見http://nginx.org/en/docs/http/ngx_http_core_module.html#variables。
nginx指令列表詳見http://nginx.org/en/docs/http/ngx_http_core_module.html#Directives。以limit_rate指令為例,其語法和語境如表2-3所示。
表2-3 limit_rate指令的語法和語境

limit_rate的作用是限制客戶端的傳輸速度,單位為字節(jié)/秒,默認值為0,即不限速。limit_rate是對單個請求設(shè)置限制的,意味著如果客戶端同時打開兩個連接,則總速率將是指定限制的兩倍。除了全局限速以外,還可以根據(jù)條件設(shè)置限速:
server { if (condition) { set $limit_rate 100k; } }
ngx_http_rewrite_module模塊的語法和語境如表2-4所示。
表2-4 ngx_http_rewrite_module模塊的語法和語境

如果指定的正則表達式與請求URI匹配,則URI將根據(jù)replacement字符串的指定進行更改。該rewrite指令按照其在配置文件中出現(xiàn)的順序依次執(zhí)行,可以使用標志終止對指令的進一步處理。如果替換字符串以http://、https://或$scheme開頭,則處理停止并將重定向返回給客戶端。
flag參數(shù)可以是以下值之一。
? last:停止處理當前的ngx_http_rewrite_module指令集,并開始搜索與更改后的URI匹配的新位置。
? break:ngx_http_rewrite_module與break指令一樣,可以停止處理當前的指令集。
? redirect:返回帶有302代碼的臨時重定向。如果替換字符串不以http://、https://或$scheme開頭,則使用它。
? permanent:返回301代碼的永久重定向。
官方給出的重定向示例代碼如下:
server { ... rewrite ^(/download/.*)/media/(.*)\..*$ $1/mp3/$2.mp3 last; rewrite ^(/download/.*)/audio/(.*)\..*$ $1/mp3/$2.ra last; return 403; ... }
但是如果將這些指令放在/download/位置內(nèi),則該last標志應(yīng)替換為break,否則nginx將進行10次循環(huán)并返回500錯誤:
location /download/ { rewrite ^(/download/.*)/media/(.*)\..*$ $1/mp3/$2.mp3 break; rewrite ^(/download/.*)/audio/(.*)\..*$ $1/mp3/$2.ra break; return 403; }
2.1.5 nginx日志
日志是nginx的重要組成部分,記錄著每一次請求的相關(guān)信息,是開發(fā)者了解客戶端請求和服務(wù)器端響應(yīng)狀態(tài)的好幫手。nginx的日志分為訪問日志和錯誤日志,存儲路徑可以在nginx主配置文件中查看,設(shè)置訪問日志存放路徑的指令名為access_log,而設(shè)置錯誤日志存放路徑的指令名為error_log,示例命令如下:
access_log /var/log/nginx/access.log main; error_log /var/log/nginx/error.log;
1. 訪問日志
訪問日志主要記錄客戶端訪問nginx的請求信息,如客戶端的IP地址、請求的URI、響應(yīng)狀態(tài)、Referer頭域的值等。以下記錄為本機nginx訪問記錄中的一條:
127.0.0.1 - - [11/Mar/2019:08:42:43 +0800] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0" "-"
nginx中與訪問日志相關(guān)的指令是log_format和access_log。log_format用來設(shè)置訪問日志的格式,也就是日志文件中每條日志記錄的格式,它在主配置文件中的設(shè)置如圖2-6所示。

圖2-6 訪問日志格式
訪問日志默認使用main格式,并且里面記錄了很多的信息,這些信息的含義如表2-5所示。
表2-5 log_format支持的變量及其釋義

2. 錯誤日志
錯誤日志主要記錄客戶端訪問nginx錯誤時的請求信息,不支持自定義格式。錯誤日志有多種等級,如debug、info、notice、warn、error和crit,從左到右日志級別逐步遞增。nginx的主配置文件中將錯誤日志的級別設(shè)為warn。以下記錄為本機nginx錯誤記錄中的一條:
2019/03/10 20:30:44 [error] 11160#11160: *1 "/home/async/www/index.html" is forbidden (13: Permission denied), client: 127.0.0.1, server: , request: "GET / HTTP/1.1", host: "localhost"
我們可以在錯誤記錄中看到錯誤發(fā)生的具體時間、原因、客戶端的IP地址、請求方式和協(xié)議版本等信息,這對我們排查錯誤和測試有很大的幫助。
2.1.6 小結(jié)
nginx是一款輕量、高性能的服務(wù)器應(yīng)用,配置靈活、功能強大,深受開發(fā)者喜愛。nginx具有條件判斷、連接限制和客戶端信息獲取等功能,這些功能為開發(fā)者限制爬蟲程序提供了條件。
- TypeScript Essentials
- .NET之美:.NET關(guān)鍵技術(shù)深入解析
- Building a RESTful Web Service with Spring
- 少年輕松趣編程:用Scratch創(chuàng)作自己的小游戲
- Mastering C# Concurrency
- Apache Karaf Cookbook
- Hands-On Swift 5 Microservices Development
- PhoneGap:Beginner's Guide(Third Edition)
- Visual C#.NET程序設(shè)計
- NetBeans IDE 8 Cookbook
- 一本書講透Java線程:原理與實踐
- Java Web從入門到精通(第3版)
- Sails.js Essentials
- Clojure Web Development Essentials
- SCRATCH編程課:我的游戲我做主