如何更好地結(jié)構(gòu)化表示一個(gè) URL?
“
閱讀本文大概需要 3 分鐘。
相信各位 Python 開(kāi)發(fā)者都用過(guò) Requests 庫(kù),有些朋友還用過(guò) WebSockets 庫(kù)。這里回顧一下它們的基本用法,例如使用 Requests 庫(kù)向目標(biāo)網(wǎng)站發(fā)出 GET 請(qǐng)求:
import requestsurl ="https://www.baidu.com"resp = requests.get(url)print(resp.status_code)# output -> 200
使用起來(lái)非常簡(jiǎn)單,我們很輕松地向目標(biāo)網(wǎng)站發(fā)出了請(qǐng)求并打印輸出響應(yīng)狀態(tài)碼。當(dāng)然,你還可以把它縮短:
import requestsprint(requests.get("https://www.baidu.com").status_code)# output -> 200
怎么寫出更短的代碼并不是這次要討論的話題。今天我們來(lái)研究一下:運(yùn)行代碼的計(jì)算機(jī)是如何找到目標(biāo)服務(wù)器的?
顯然,你的第一映象是 IP 地址和端口號(hào)。
沒(méi)錯(cuò),就是 IP 地址和端口號(hào)。
但你明明輸入的是 URL 地址,怎么就 IP + 端口號(hào)呢?
URL 解析的原因一下子你也回答不上來(lái)吧?
我們可以將上方代碼的邏輯,即計(jì)算機(jī)向目標(biāo)服務(wù)器發(fā)出請(qǐng)求并拿到響應(yīng)信息的過(guò)程抽象成下圖:

程序輸入的是?https://www.baidu.com,但最終要解析出具體的 IP 地址和端口號(hào)才能訪問(wèn),例如?183.232.231.172:443。
網(wǎng)絡(luò)交互實(shí)際上屬于 Socket 編程的范疇,無(wú)論是 Requests 還是 WebSockets 庫(kù),最終都會(huì)通過(guò) Socket 與目標(biāo)網(wǎng)站的服務(wù)器進(jìn)行交互。
而 Socket 編程中并不能直接使用域名,而是采用 IP + 端口號(hào)這種形式進(jìn)行尋址的。
假設(shè)你現(xiàn)在需要編寫一個(gè)網(wǎng)絡(luò)請(qǐng)求庫(kù),有可能是 HTTP 協(xié)議的,也有可能是 WebSocket 協(xié)議的。
你要解決的第一個(gè)問(wèn)題就是解析 URL,將網(wǎng)址轉(zhuǎn)換成 IP + 端口號(hào),甚至還需要分割出協(xié)議類型、資源路徑以及是否采用更安全的傳輸方式等。
URL 解析格式以 WebSocket 協(xié)議方面的客戶端庫(kù)為例,在雙端確認(rèn)連接之前有一個(gè)「握手」的過(guò)程,這個(gè)過(guò)程之前已經(jīng)需要雙端的 IP 和端口號(hào)等信息了。下面的代碼描述了 WebSocket 發(fā)出「握手」請(qǐng)求之前,雙端建立連接時(shí)需要用到的基本信息:
# aiowebsocketreader, writer = await asyncio.open_connection(host=host, port=port, ssl=ssl)
也就是?host、port?和?ssl。
大部分的 WebSocket 服務(wù)給出的都是域名,例如?wss://echo.websocket.org。「握手」時(shí)還會(huì)用到資源路徑。
接下來(lái),我們來(lái)嘗試一下,如何將域名轉(zhuǎn)換為 IP + 端口號(hào)和 is ssl 這樣的格式。
代碼實(shí)現(xiàn) URL 解析開(kāi)始之前,我們先規(guī)劃一下基本步驟:

然后確定要使用的標(biāo)準(zhǔn)庫(kù):解析 URL 當(dāng)然要用到 urllib 庫(kù)中的 url parse;解析 address 則需要用到 socket 庫(kù);為了方面取數(shù)據(jù),可以嘗試使用 collections 庫(kù)中的 namedtuple。
首先引入這幾個(gè)庫(kù):
import socketfrom collections import namedtuplefrom urllib.parse import urlparse
然后定義輸出結(jié)構(gòu),對(duì)應(yīng)代碼如下:
REMOTE = namedtuple('REMOTE',['scheme','hostname','address','port','resource','ssl'])然后定義一個(gè)方法,我們傳入 URL,獲得解析好的 REMOTE 對(duì)象。方法定義如下:
def parses(url: str)-> REMOTE:pass
待會(huì)我們?cè)?pass?處編寫屬于該方法的代碼。
最開(kāi)始要解析 URL,獲得?scheme?和?hostname,對(duì)應(yīng)代碼如下:
url = urlparse(url)urlparse?方法會(huì)返回一個(gè)?ParseResult?對(duì)象,對(duì)象大體格式如下:
ParseResult(scheme='wss', netloc='echo.websocket.org', path='',params='', query='', fragment='')有了?scheme?和?hostname?后,就可以得到?port、is ssl?和?address。對(duì)應(yīng)代碼如下:
scheme = url.schemeaddress = url.hostnameport = url.port or(443if scheme =='wss'else80)ssl =Trueif scheme =='wss'elseFalse
WebSocket 協(xié)議中只有兩種協(xié)議頭:ws?和?wss。它們對(duì)應(yīng)的端口分別是?80、443,這里借助?scheme?的值進(jìn)行判斷即可得到答案。同理,也直接得到了?is ssl?答案。
拿到?hostname?后,調(diào)用 socket 庫(kù)的?getbyhostname?方法就能夠得到目標(biāo)服務(wù)器的 IP 地址了。對(duì)應(yīng)代碼如下:
address = socket.gethostbyname(hostname)至于資源路徑,它早已存在于?ParseResult?對(duì)象中,直接取出即可:
resource = url.path要注意的是,有些 URL 中還會(huì)攜帶請(qǐng)求正文(即參數(shù)和值)。所以這里需要取?query,并將其拼接到?resource?中:
if url.query:resource +='?'+ url.query
至此,我們已經(jīng)拿到了所需的所有數(shù)據(jù)。
現(xiàn)在將它們裝在到?REMOTE?結(jié)構(gòu)中,返回給調(diào)用方:
return REMOTE(scheme, hostname, address, port, resource, ssl)此時(shí),調(diào)用?parses?方法后就會(huì)拿到?REMOTE?結(jié)構(gòu),它的取值方式很舒服,用?.?符號(hào)取值即可。例如:
res = parses("ws://echo.websocket.org?sign=i9878")print(res.address, res.port, res.resource)
代碼運(yùn)行結(jié)果如下:
174.129.224.7380?sign=i9878
這樣,我們就完成了 URL 解析的代碼編寫。
小結(jié)代碼雖然不多,邏輯也并不復(fù)雜。但我們完整實(shí)現(xiàn)了網(wǎng)絡(luò)請(qǐng)求庫(kù)中的 URL 解析模塊,這代表著完成了編寫庫(kù)的基石之一。
在這個(gè)過(guò)程當(dāng)中,我們了解到雙端通信的基本過(guò)程和要用到的信息。在編碼中學(xué)會(huì)了如何將?urlparse、socket?和?namedtuple?結(jié)合到一起。
而且,你今天學(xué)到了?namedtuple?這個(gè)新姿勢(shì)!
「你好騷啊.gif」
完整代碼可在我的 Github 倉(cāng)庫(kù)查看:https://github.com/asyncins/CFA/tree/master/FightingCoder
推薦閱讀
1
跟繁瑣的命令行說(shuō)拜拜!Gerapy分布式爬蟲(chóng)管理框架來(lái)襲!
2
跟繁瑣的模型說(shuō)拜拜!深度學(xué)習(xí)腳手架 ModelZoo 來(lái)襲!
3
只會(huì)用Selenium爬網(wǎng)頁(yè)?Appium爬App了解一下
4??
媽媽再也不用擔(dān)心爬蟲(chóng)被封號(hào)了!手把手教你搭建Cookies池
崔慶才
靜覓博客博主,《Python3網(wǎng)絡(luò)爬蟲(chóng)開(kāi)發(fā)實(shí)戰(zhàn)》作者
隱形字
個(gè)人公眾號(hào):進(jìn)擊的Coder


長(zhǎng)按識(shí)別二維碼關(guān)注
