整合營(yíng)銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          用python3教你任意Html主內(nèi)容提取

          x1 工具準(zhǔn)備

          工欲善其事必先利其器,爬取語(yǔ)料的根基便是基于python。

          我們基于python3進(jìn)行開(kāi)發(fā),主要使用以下幾個(gè)模塊:requests、lxml、json。

          簡(jiǎn)單介紹一個(gè)各模塊的功能

          01|requests

          requests是一個(gè)Python第三方庫(kù),處理URL資源特別方便。它的官方文檔上寫(xiě)著大大口號(hào):HTTP for Humans(為人類使用HTTP而生)。相比python自帶的urllib使用體驗(yàn),筆者認(rèn)為requests的使用體驗(yàn)比urllib高了一個(gè)數(shù)量級(jí)。

          我們簡(jiǎn)單的比較一下:

          urllib:

           1import urllib2
           2import urllib
           3
           4URL_GET = "https://api.douban.com/v2/event/list"
           5#構(gòu)建請(qǐng)求參數(shù)
           6params = urllib.urlencode({'loc':'108288','day_type':'weekend','type':'exhibition'})
           7
           8#發(fā)送請(qǐng)求
           9response = urllib2.urlopen('?'.join([URL_GET,'%s'])%params)
          10#Response Headers
          11print(response.info())
          12#Response Code
          13print(response.getcode())
          14#Response Body
          15print(response.read())
          復(fù)制代碼
          

          requests:

           1import requests
           2
           3URL_GET = "https://api.douban.com/v2/event/list"
           4#構(gòu)建請(qǐng)求參數(shù)
           5params = {'loc':'108288','day_type':'weekend','type':'exhibition'}
           6
           7#發(fā)送請(qǐng)求
           8response = requests.get(URL_GET,params=params)
           9#Response Headers
          10print(response.headers)
          11#Response Code
          12print(response.status_code)
          13#Response Body
          14print(response.text)復(fù)制代碼
          

          我們可以發(fā)現(xiàn),這兩種庫(kù)還是有一些區(qū)別的:

          1. 參數(shù)的構(gòu)建:urllib需要對(duì)參數(shù)進(jìn)行urlencode編碼處理,比較麻煩;requests無(wú)需額外編碼處理,十分簡(jiǎn)潔。

          2. 請(qǐng)求發(fā)送:urllib需要額外對(duì)url參數(shù)進(jìn)行構(gòu)造,變?yōu)榉弦蟮男问剑籸equests則簡(jiǎn)明很多,直接get對(duì)應(yīng)鏈接與參數(shù)。

          3. 連接方式:看一下返回?cái)?shù)據(jù)的頭信息的“connection”,使用urllib庫(kù)時(shí),"connection":"close",說(shuō)明每次請(qǐng)求結(jié)束關(guān)掉socket通道,而使用requests庫(kù)使用了urllib3,多次請(qǐng)求重復(fù)使用一個(gè)socket,"connection":"keep-alive",說(shuō)明多次請(qǐng)求使用一個(gè)連接,消耗更少的資源

          4. 編碼方式:requests庫(kù)的編碼方式Accept-Encoding更全,在此不做舉例

          綜上所訴,使用requests更為簡(jiǎn)明、易懂,極大的方便我們開(kāi)發(fā)。

          02|lxml

          BeautifulSoup是一個(gè)庫(kù),而XPath是一種技術(shù),python中最常用的XPath庫(kù)是lxml。

          當(dāng)我們拿到requests返回的頁(yè)面后,我們?cè)趺茨玫较胍臄?shù)據(jù)呢?這個(gè)時(shí)候祭出lxml這強(qiáng)大的HTML/XML解析工具。python從不缺解析庫(kù),那么我們?yōu)槭裁匆诒姸鄮?kù)里選擇lxml呢?我們選擇另一款出名的HTML解析庫(kù)BeautifulSoup來(lái)進(jìn)行對(duì)比。

          我們簡(jiǎn)單的比較一下:

          BeautifulSoup:

          1from bs4 import BeautifulSoup #導(dǎo)入庫(kù)
          2# 假設(shè)html是需要被解析的html
          3
          4#將html傳入BeautifulSoup 的構(gòu)造方法,得到一個(gè)文檔的對(duì)象
          5soup = BeautifulSoup(html,'html.parser',from_encoding='utf-8')
          6#查找所有的h4標(biāo)簽 
          7links = soup.find_all("h4")
          復(fù)制代碼
          

          lxml:

          1from lxml import etree
          2# 假設(shè)html是需要被解析的html
          3
          4#將html傳入etree 的構(gòu)造方法,得到一個(gè)文檔的對(duì)象
          5root = etree.HTML(html)
          6#查找所有的h4標(biāo)簽 
          7links = root.xpath("http://h4")
          復(fù)制代碼
          

          我們可以發(fā)現(xiàn),這兩種庫(kù)還是有一些區(qū)別的:

          1. 解析html: BeautifulSoup的解析方式和JQ的寫(xiě)法類似,API非常人性化,支持css選擇器;lxml的語(yǔ)法有一定的學(xué)習(xí)成本

          2. 性能:BeautifulSoup是基于DOM的,會(huì)載入整個(gè)文檔,解析整個(gè)DOM樹(shù),因此時(shí)間和內(nèi)存開(kāi)銷都會(huì)大很多;而lxml只會(huì)局部遍歷,另外lxml是用c寫(xiě)的,而B(niǎo)eautifulSoup是用python寫(xiě)的,明顯的性能上lxml>>BeautifulSoup。

          綜上所訴,使用BeautifulSoup更為簡(jiǎn)明、易用,lxml雖然有一定學(xué)習(xí)成本,但總體也很簡(jiǎn)明易懂,最重要的是它基于C編寫(xiě),速度快很多,對(duì)于筆者這種強(qiáng)迫癥,自然而然就選lxml啦。

          03|json

          python自帶json庫(kù),對(duì)于基礎(chǔ)的json的處理,自帶庫(kù)完全足夠。但是如果你想更偷懶,可以使用第三方j(luò)son庫(kù),常見(jiàn)的有demjson、simplejson。

          這兩種庫(kù),無(wú)論是import模塊速度,還是編碼、解碼速度,都是simplejson更勝一籌,再加上兼容性 simplejson 更好。所以大家如果想使用方庫(kù),可以使用simplejson。

          0x2 確定語(yǔ)料源

          將武器準(zhǔn)備好之后,接下來(lái)就需要確定爬取方向。

          以電競(jìng)類語(yǔ)料為例,現(xiàn)在我們要爬電競(jìng)類相關(guān)語(yǔ)料。大家熟悉的電競(jìng)平臺(tái)有企鵝電競(jìng)、企鵝電競(jìng)和企鵝電競(jìng)(斜眼),所以我們以企鵝電競(jìng)上直播的游戲作為數(shù)據(jù)源進(jìn)行爬取。

          我們登陸企鵝電競(jìng)官網(wǎng),進(jìn)入游戲列表頁(yè),可以發(fā)現(xiàn)頁(yè)面上有很多游戲,通過(guò)人工去寫(xiě)這些游戲名收益明顯不高,于是我們就開(kāi)始我們爬蟲(chóng)的第一步:游戲列表爬取。


           1import requests
           2from lxml import etree
           3
           4# 更新游戲列表
           5def _updateGameList():
           6 # 發(fā)送HTTP請(qǐng)求時(shí)的HEAD信息,用于偽裝為瀏覽器
           7 heads = { 
           8 'Connection': 'Keep-Alive',
           9 'Accept': 'text/html, application/xhtml+xml, */*',
          10 'Accept-Language': 'en-US,en;q=0.8,zh-Hans-CN;q=0.5,zh-Hans;q=0.3',
          11 'Accept-Encoding': 'gzip, deflate',
          12 'User-Agent': 'Mozilla/6.1 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko'
          13 }
          14 # 需要爬取的游戲列表頁(yè)
          15 url = 'https://egame.qq.com/gamelist'
          16
          17 # 不壓縮html,最大鏈接時(shí)間為10妙
          18 res = requests.get(url, headers=heads, verify=False, timeout=10)
          19 # 為防止出錯(cuò),編碼utf-8
          20 res.encoding = 'utf-8'
          21 # 將html構(gòu)建為Xpath模式
          22 root = etree.HTML(res.content)
          23 # 使用Xpath語(yǔ)法,獲取游戲名
          24 gameList = root.xpath("http://ul[@class='livelist-mod']//li//p//text()")
          25 # 輸出爬到的游戲名
          26 print(gameList)
          復(fù)制代碼
          

          當(dāng)我們拿到這幾十個(gè)游戲名后,下一步就是對(duì)這幾十款游戲進(jìn)行語(yǔ)料爬取,這時(shí)候問(wèn)題就來(lái)了,我們要從哪個(gè)網(wǎng)站來(lái)爬這幾十個(gè)游戲的攻略呢,taptap?多玩?17173?在對(duì)這幾個(gè)網(wǎng)站進(jìn)行分析后,發(fā)現(xiàn)這些網(wǎng)站僅有一些熱門(mén)游戲的文章語(yǔ)料,一些冷門(mén)或者低熱度的游戲,例如“靈魂籌碼”、“奇跡:覺(jué)醒”、“死神來(lái)了”等,很難在這些網(wǎng)站上找到大量文章語(yǔ)料,如圖所示:

          我們可以發(fā)現(xiàn),“ 奇跡:覺(jué)醒”、“靈魂籌碼”的文章語(yǔ)料特別少,數(shù)量上不符合我們的要求。 那么有沒(méi)有一個(gè)比較通用的資源站,它擁有著無(wú)比豐富的文章語(yǔ)料,可以滿足我們的需求。

          其實(shí)靜下心來(lái)想想,這個(gè)資源站我們天天都有用到,那就是百度。我們?cè)诎俣刃侣勊阉飨嚓P(guān)游戲,拿到搜索結(jié)果列表,這些列表的鏈接的網(wǎng)頁(yè)內(nèi)容幾乎都與搜索結(jié)果強(qiáng)相關(guān),這樣我們數(shù)據(jù)源不夠豐富的問(wèn)題便輕松解決了。但是此時(shí)出現(xiàn)了一個(gè)新的問(wèn)題,并且是一個(gè)比較難解決的問(wèn)題——如何抓取到任意網(wǎng)頁(yè)的文章內(nèi)容?

          因?yàn)椴煌木W(wǎng)站都有不同的頁(yè)面結(jié)構(gòu),我們無(wú)法與預(yù)知將會(huì)爬到哪個(gè)網(wǎng)站的數(shù)據(jù),并且我們也不可能針對(duì)每一個(gè)網(wǎng)站都去寫(xiě)一套爬蟲(chóng),那樣的工作量簡(jiǎn)直難以想象!但是我們也不能簡(jiǎn)單粗暴的將頁(yè)面中的所有文字都爬下來(lái),用那樣的語(yǔ)料來(lái)進(jìn)行訓(xùn)練無(wú)疑是噩夢(mèng)!

          經(jīng)過(guò)與各個(gè)網(wǎng)站斗智斗勇、查詢資料與思索之后,終于找到一條比較通用的方案,下面為大家講一講筆者的思路。

          0x3 任意網(wǎng)站的文章語(yǔ)料爬取

          01|提取方法

          1)基于Dom樹(shù)正文提取

          2)基于網(wǎng)頁(yè)分割找正文塊

          3)基于標(biāo)記窗的正文提取

          4)基于數(shù)據(jù)挖掘或機(jī)器學(xué)習(xí)

          5)基于行塊分布函數(shù)正文提取

          02|提取原理

          大家看到這幾種是不是都有點(diǎn)疑惑了,它們到底是怎么提取的呢?讓筆者慢慢道來(lái)。

          1)基于Dom樹(shù)的正文提取:

          這一種方法主要是通過(guò)比較規(guī)范的HTML建立Dom樹(shù),然后地柜遍歷Dom,比較并識(shí)別各種非正文信息,包括廣告、鏈接和非重要節(jié)點(diǎn)信息,將非正文信息抽離之后,余下來(lái)的自然就是正文信息。

          但是這種方法有兩個(gè)問(wèn)題

          ① 特別依賴于HTML的良好結(jié)構(gòu),如果我們爬取到一個(gè)不按W3c規(guī)范的編寫(xiě)的網(wǎng)頁(yè)時(shí),這種方法便不是很適用。

          ② 樹(shù)的建立和遍歷時(shí)間復(fù)雜度、空間復(fù)雜度都較高,樹(shù)的遍歷方法也因HTML標(biāo)簽會(huì)有不同的差異。

          2) 基于網(wǎng)頁(yè)分割找正文塊 :

          這一種方法是利用HTML標(biāo)簽中的分割線以及一些視覺(jué)信息(如文字顏色、字體大小、文字信息等)。

          這種方法存在一個(gè)問(wèn)題:

          ① 不同的網(wǎng)站HTML風(fēng)格迥異,分割沒(méi)有辦法統(tǒng)一,無(wú)法保證通用性。

          3) 基于標(biāo)記窗的正文提取:

          先科普一個(gè)概念——標(biāo)記窗,我們將兩個(gè)標(biāo)簽以及其內(nèi)部包含的文本合在一起成為一個(gè)標(biāo)記窗(比如 <h1>我是h1</h1> 中的“我是h1”就是標(biāo)記窗內(nèi)容),取出標(biāo)記窗的文字。

          這種方法先取文章標(biāo)題、HTML中所有的標(biāo)記窗,在對(duì)其進(jìn)行分詞。然后計(jì)算標(biāo)題的序列與標(biāo)記窗文本序列的詞語(yǔ)距離L,如果L小于一個(gè)閾值,則認(rèn)為此標(biāo)記窗內(nèi)的文本是正文。

          這種方法雖然看上去挺好,但其實(shí)也是存在問(wèn)題的:

          ① 需要對(duì)頁(yè)面中的所有文本進(jìn)行分詞,效率不高。

          ② 詞語(yǔ)距離的閾值難以確定,不同的文章?lián)碛胁煌拈撝怠?/p>

          4)基于數(shù)據(jù)挖掘或機(jī)器學(xué)習(xí)

          使用大數(shù)據(jù)進(jìn)行訓(xùn)練,讓機(jī)器提取主文本。

          這種方法肯定是極好的,但是它需要先有html與正文數(shù)據(jù),然后進(jìn)行訓(xùn)練。我們?cè)诖瞬贿M(jìn)行探討。

          5)基于行塊分布函數(shù)正文提取

          對(duì)于任意一個(gè)網(wǎng)頁(yè),它的正文和標(biāo)簽總是雜糅在一起。此方法的核心有亮點(diǎn):① 正文區(qū)的密度;② 行塊的長(zhǎng)度;一個(gè)網(wǎng)頁(yè)的正文區(qū)域肯定是文字信息分布最密集的區(qū)域之一,這個(gè)區(qū)域可能最大(評(píng)論信息長(zhǎng)、正文較短),所以同時(shí)引進(jìn)行塊長(zhǎng)度進(jìn)行判斷。

          實(shí)現(xiàn)思路:

          ① 我們先將HTML去標(biāo)簽,只留所有正文,同時(shí)留下標(biāo)簽取出后的所有空白位置信息,我們稱其為Ctext;

          ② 對(duì)每一個(gè)Ctext取周圍k行(k<5),合起來(lái)稱為Cblock;

          ③ 對(duì)Cblock去掉所有空白符,其文字總長(zhǎng)度稱為Clen;

          ④ 以Ctext為橫坐標(biāo)軸,以各行的Clen為縱軸,建立坐標(biāo)系。

          以這個(gè)網(wǎng)頁(yè)為例: http://www.gov.cn/ldhd/2009-11/08/content_1459564.htm 該網(wǎng)頁(yè)的正文區(qū)域?yàn)?45行至182行。


          由上圖可知,正確的文本區(qū)域全都是分布函數(shù)圖上含有最值且連續(xù)的一個(gè)區(qū)域,這個(gè)區(qū)域往往含有一個(gè)驟升點(diǎn)和一個(gè)驟降點(diǎn)。因此,網(wǎng)頁(yè)正文抽取問(wèn)題轉(zhuǎn)化為了求行塊分布函數(shù)上的驟升點(diǎn)和驟降點(diǎn)兩個(gè)邊界點(diǎn),這兩個(gè)邊界點(diǎn)所含的區(qū)域包含了當(dāng)前網(wǎng)頁(yè)的行塊長(zhǎng)度最大值并且是連續(xù)的。

          經(jīng)過(guò)大量實(shí)驗(yàn),證明此方法對(duì)于中文網(wǎng)頁(yè)的正文提取有較高的準(zhǔn)確度,此算法的優(yōu)點(diǎn)在于,行塊函數(shù)不依賴與HTML代碼,與HTML標(biāo)簽無(wú)關(guān),實(shí)現(xiàn)簡(jiǎn)單,準(zhǔn)確率較高。

          主要邏輯代碼如下:

           1# 假設(shè)content為已經(jīng)拿到的html
           2
           3# Ctext取周圍k行(k<5),定為3
           4blocksWidth = 3
           5# 每一個(gè)Cblock的長(zhǎng)度
           6Ctext_len = []
           7# Ctext
           8lines = content.split('n')
           9# 去空格
          10for i in range(len(lines)):
          11 if lines[i] == ' ' or lines[i] == 'n':
          12 lines[i] = ''
          13# 計(jì)算縱坐標(biāo),每一個(gè)Ctext的長(zhǎng)度
          14for i in range(0, len(lines) - blocksWidth):
          15 wordsNum = 0
          16 for j in range(i, i + blocksWidth):
          17 lines[j] = lines[j].replace("\s", "")
          18 wordsNum += len(lines[j])
          19 Ctext_len.append(wordsNum)
          20# 開(kāi)始標(biāo)識(shí)
          21start = -1
          22# 結(jié)束標(biāo)識(shí)
          23end = -1
          24# 是否開(kāi)始標(biāo)識(shí)
          25boolstart = False
          26# 是否結(jié)束標(biāo)識(shí)
          27boolend = False
          28# 行塊的長(zhǎng)度閾值
          29max_text_len = 88
          30# 文章主內(nèi)容
          31main_text = []
          32# 沒(méi)有分割出Ctext
          33if len(Ctext_len) < 3:
          34 return '沒(méi)有正文'
          35for i in range(len(Ctext_len) - 3):
          36 # 如果高于這個(gè)閾值
          37 if(Ctext_len[i] > max_text_len and (not boolstart)):
          38 # Cblock下面3個(gè)都不為0,認(rèn)為是正文
          39 if (Ctext_len[i + 1] != 0 or Ctext_len[i + 2] != 0 or Ctext_len[i + 3] != 0):
          40 boolstart = True
          41 start = i
          42 continue
          43 if (boolstart):
          44 # Cblock下面3個(gè)中有0,則結(jié)束
          45 if (Ctext_len[i] == 0 or Ctext_len[i + 1] == 0):
          46 end = i
          47 boolend = True
          48 tmp = []
          49
          50 # 判斷下面還有沒(méi)有正文
          51 if(boolend):
          52 for ii in range(start, end + 1):
          53 if(len(lines[ii]) < 5):
          54 continue
          55 tmp.append(lines[ii] + "n")
          56 str = "".join(list(tmp))
          57 # 去掉版權(quán)信息
          58 if ("Copyright" in str or "版權(quán)所有" in str):
          59 continue
          60 main_text.append(str)
          61 boolstart = boolend = False
          62# 返回主內(nèi)容
          63result = "".join(list(main_text))
          復(fù)制代碼
          

          0x4 結(jié)語(yǔ)

          至此我們就可以獲取任意內(nèi)容的文章語(yǔ)料了,但這僅僅是開(kāi)始,獲取到了這些語(yǔ)料后我們還需要在一次進(jìn)行清洗、分詞、詞性標(biāo)注等,才能獲得真正可以使用的語(yǔ)料。

          .1 瀏覽器根據(jù)域名解析IP地址

          瀏覽器根據(jù)訪問(wèn)的域名找到其IP地址。DNS查找過(guò)程如下:

          瀏覽器緩存:首先搜索瀏覽器自身的DNS緩存(緩存的時(shí)間比較短,大概只有1分鐘,且只能容納1000條緩存),看自身的緩存中是否是有域名對(duì)應(yīng)的條目,而且沒(méi)有過(guò)期,如果有且沒(méi)有過(guò)期則解析到此結(jié)束。

          系統(tǒng)緩存:如果瀏覽器自身的緩存里面沒(méi)有找到對(duì)應(yīng)的條目,那么瀏覽器會(huì)搜索操作系統(tǒng)自身的DNS緩存,如果找到且沒(méi)有過(guò)期則停止搜索解析到此結(jié)束。

          路由器緩存:如果系統(tǒng)緩存也沒(méi)有找到,則會(huì)向路由器發(fā)送查詢請(qǐng)求。

          ISP(互聯(lián)網(wǎng)服務(wù)提供商) DNS緩存:如果在路由緩存也沒(méi)找到,最后要查的就是ISP緩存DNS的服務(wù)器。

          1.2 瀏覽器與WEB服務(wù)器建立一個(gè)TCP連接

          TCP的3次握手。

          1.3 瀏覽器給WEB服務(wù)器發(fā)送一個(gè)HTTP請(qǐng)求

          一個(gè)HTTP請(qǐng)求報(bào)文由請(qǐng)求行(request line)、請(qǐng)求頭部(headers)、空行(blank line)和請(qǐng)求數(shù)據(jù)(request body)4個(gè)部分組成。

          圖1 HTTP請(qǐng)求格式

          1.3.1 請(qǐng)求行

          請(qǐng)求行分為三個(gè)部分:請(qǐng)求方法、請(qǐng)求地址URL和HTTP協(xié)議版本,它們之間用空格分割。例如,GET /index.html HTTP/1.1。

          1.請(qǐng)求方法

          HTTP/1.1 定義的請(qǐng)求方法有8種:GET(完整請(qǐng)求一個(gè)資源)、POST(提交表單)、PUT(上傳文件)、DELETE(刪除)、PATCH、HEAD(僅請(qǐng)求響應(yīng)首部)、OPTIONS(返回請(qǐng)求的資源所支持的方法)、TRACE(追求一個(gè)資源請(qǐng)求中間所經(jīng)過(guò)的代理)。最常的兩種GET和POST,如果是RESTful接口的話一般會(huì)用到GET、POST、DELETE、PUT。

          (1)GET

          當(dāng)客戶端要從服務(wù)器中讀取文檔時(shí),當(dāng)點(diǎn)擊網(wǎng)頁(yè)上的鏈接或者通過(guò)在瀏覽器的地址欄輸入網(wǎng)址來(lái)瀏覽網(wǎng)頁(yè)的,使用的都是GET方式。GET方法要求服務(wù)器將URL定位的資源放在響應(yīng)報(bào)文的數(shù)據(jù)部分,會(huì)送給客戶端。

          使用GET方法時(shí),請(qǐng)求參數(shù)和對(duì)應(yīng)的值附加在URL后面,利用一個(gè)問(wèn)號(hào)‘?’代表URL的結(jié)尾與請(qǐng)求參數(shù)的開(kāi)始,傳遞參數(shù)長(zhǎng)度受限制。例如,/index.jsp?id=100&op=bind。通過(guò)GET方式傳遞的數(shù)據(jù)直接放在地址中,所以GET方式的請(qǐng)求一般不包含“請(qǐng)求內(nèi)容”部分,請(qǐng)求數(shù)據(jù)以地址的形式表現(xiàn)在請(qǐng)求行。

          地址中‘?’之后的部分就是通過(guò)GET發(fā)送的請(qǐng)求數(shù)據(jù),各個(gè)數(shù)據(jù)之間用‘&’符號(hào)隔開(kāi)。顯然這種方式不適合傳送私密數(shù)據(jù)。另外,由于不同的瀏覽器對(duì)地址的字符限制也有所不同,一般最多只能識(shí)別1024個(gè)字符,所以如果需要傳送大量數(shù)據(jù)的時(shí)候,也不適合使用GET方式。如果數(shù)據(jù)是英文字母/數(shù)字,原樣發(fā)送;如果是空格,轉(zhuǎn)換為+;如果是中文/其他字符,則直接把字符串用BASE64加密,得出:%E4%BD%A0%E5%A5%BD,其中%XX中的XX為該符號(hào)以16進(jìn)制表示的ASCII。

          (2)POST

          允許客戶端給服務(wù)器提供信息較多。POST方法將請(qǐng)求參數(shù)封裝在HTTP請(qǐng)求數(shù)據(jù)中,以名稱/值的形式出現(xiàn),可以傳輸大量數(shù)據(jù),這樣POST方式對(duì)傳送的數(shù)據(jù)大小沒(méi)有限制,而且也不會(huì)顯示在URL中。POST方式請(qǐng)求行中不包含數(shù)據(jù)字符串,這些數(shù)據(jù)保存在“請(qǐng)求內(nèi)容”部分,各數(shù)據(jù)之間也是使用‘&’符號(hào)隔開(kāi)。POST方式大多用于頁(yè)面的表單中。因?yàn)镻OST也能完成GET的功能,因此多數(shù)人在設(shè)計(jì)表單的時(shí)候一律都使用POST方式,其實(shí)這是一個(gè)誤區(qū)。GET方式也有自己的特點(diǎn)和優(yōu)勢(shì),我們應(yīng)該根據(jù)不同的情況來(lái)選擇是使用GET還是使用POST。

          圖2 HTTP請(qǐng)求方法

          2.URL

          URL:統(tǒng)一資源定位符,是一種資源位置的抽象唯一識(shí)別方法。

          組成:<協(xié)議>://<主機(jī)>:<端口>/<路徑>

          端口和路徑有事可以省略(HTTP默認(rèn)端口號(hào)是80)

          3.協(xié)議版本

          協(xié)議版本的格式為:HTTP/主版本號(hào).次版本號(hào),常用的有HTTP/1.0和HTTP/1.1

          1.3.2 請(qǐng)求頭部

          請(qǐng)求頭部為請(qǐng)求報(bào)文添加了一些附加信息,由“名/值”對(duì)組成,每行一對(duì),名和值之間使用冒號(hào)分隔。

          請(qǐng)求頭部的最后會(huì)有一個(gè)空行,表示請(qǐng)求頭部結(jié)束,接下來(lái)為請(qǐng)求數(shù)據(jù)。

          1.3.3 請(qǐng)求數(shù)據(jù)

          請(qǐng)求數(shù)據(jù)不在GET方法中使用,而在POST方法中使用。POST方法適用于需要客戶填寫(xiě)表單的場(chǎng)合。與請(qǐng)求數(shù)據(jù)相關(guān)的最長(zhǎng)使用的請(qǐng)求頭部是Cntent-Type和Content-Length。下面是一個(gè)POST方法的請(qǐng)求報(bào)文:

          POST  /index.php HTTP/1.1    請(qǐng)求行

          Host: localhost

          User-Agent: Mozilla/5.0 (Windows NT 5.1; rv:10.0.2) Gecko/20100101 Firefox/10.0.2請(qǐng)求頭

          Accept: text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8

          Accept-Language: zh-cn,zh;q=0.5

          Accept-Encoding: gzip, deflate

          Connection: keep-alive

          Referer: http://localhost/

          Content-Length:25

          Content-Type:application/x-www-form-urlencoded

            空行

          username=aa&password=1234  請(qǐng)求數(shù)據(jù)

          1.4 服務(wù)器端響應(yīng)HTTP請(qǐng)求,瀏覽器得到HTML代碼

          HTTP響應(yīng)報(bào)文由狀態(tài)行(status line)、相應(yīng)頭部(headers)、空行(blank line)和響應(yīng)數(shù)據(jù)(response body)4個(gè)部分組成。

          1.4.1 狀態(tài)行

          狀態(tài)行由3部分組成,分別為:協(xié)議版本、狀態(tài)碼、狀態(tài)碼掃描。其中協(xié)議版本與請(qǐng)求報(bào)文一致,狀態(tài)碼描述是對(duì)狀態(tài)碼的簡(jiǎn)單描述。

          1.4.2 響應(yīng)頭部

          1.4.3 響應(yīng)數(shù)據(jù)

          用于存放需要返回給客戶端的數(shù)據(jù)信息。

          HTTP/1.1 200 OK  狀態(tài)行

          Date: Sun, 17 Mar 2013 08:12:54 GMT  響應(yīng)頭部

          Server: Apache/2.2.8 (Win32) PHP/5.2.5

          X-Powered-By: PHP/5.2.5

          Set-Cookie: PHPSESSID=c0huq7pdkmm5gg6osoe3mgjmm3; path=/

          Expires: Thu, 19 Nov 1981 08:52:00 GMT

          Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0

          Pragma: no-cache

          Content-Length: 4393

          Keep-Alive: timeout=5, max=100

          Connection: Keep-Alive

          Content-Type: text/html; charset=utf-8

            空行

          <html>  響應(yīng)數(shù)據(jù)

          <head>

          <title>HTTP響應(yīng)示例<title>

          </head>

          <body>

          Hello HTTP!

          </body>

          </html>

          1.5 瀏覽器解析HTML代碼,并請(qǐng)求HTML代碼中的資源

          瀏覽器拿到HTML文件后,開(kāi)始解析HTML代碼,遇到靜態(tài)資源時(shí),就向服務(wù)器端去請(qǐng)求下載。

          1.6 關(guān)閉TCP連接,瀏覽器對(duì)頁(yè)面進(jìn)行渲染呈現(xiàn)給用戶

          瀏覽器利用自己內(nèi)部的工作機(jī)制,把請(qǐng)求到的靜態(tài)資源和HTML代碼進(jìn)行渲染,呈現(xiàn)給用戶。

          來(lái)源:CSDN

          我們這個(gè)Web服務(wù)器有了一個(gè)基本的門(mén)面以后,我們是時(shí)候來(lái)用它做點(diǎn)實(shí)際的事情了。還記得我們最早提到HTTP協(xié)議的用途是什么嗎?它叫超文本傳輸協(xié)議啊,所以我們必須考慮讓我們的服務(wù)器能夠接收到客戶端傳來(lái)的數(shù)據(jù)。因?yàn)槲覀兡壳巴瓿闪舜蟛糠值墓ぷ鳎詫?duì)數(shù)據(jù)傳輸這個(gè)問(wèn)題我們這里選擇以最簡(jiǎn)單的GET和POST為例來(lái)實(shí)現(xiàn),這樣我們今天的重點(diǎn)就落實(shí)在Get和Post的實(shí)現(xiàn)這個(gè)問(wèn)題上來(lái)。而從原理上來(lái)講,無(wú)論Get方式請(qǐng)求還是Post方式請(qǐng)求,我們都可以在請(qǐng)求報(bào)文中獲得其請(qǐng)求參數(shù),不同的是前者出現(xiàn)在請(qǐng)求行中,而后者出現(xiàn)在消息體中。例如我們傳遞的兩個(gè)參數(shù)num1和num2對(duì)應(yīng)的數(shù)值分別是12和24,那么在具體的請(qǐng)求報(bào)文中我們都能找到類似“num1=12&num2=24”這樣的字符結(jié)構(gòu),所以只要針對(duì)這個(gè)字符結(jié)構(gòu)進(jìn)行解析,就可以獲得客戶端傳遞給服務(wù)器的參數(shù)啦。

          實(shí)現(xiàn)Get請(qǐng)求

          首先我們來(lái)實(shí)現(xiàn)Get請(qǐng)求,Get是HTTP協(xié)議中默認(rèn)的請(qǐng)求類型,我們平時(shí)訪問(wèn)網(wǎng)頁(yè)、請(qǐng)求資源實(shí)際上都是通過(guò)Get方式實(shí)現(xiàn)的。Get方式請(qǐng)求需要通過(guò)類似“?id=001&option=10”這樣的形式附加在URL上,因此Get方式對(duì)瀏覽器來(lái)說(shuō)是透明的,即用戶可以通過(guò)瀏覽器地址欄知道,這個(gè)過(guò)程中傳遞了哪些參數(shù)以及這些參數(shù)的值分別是什么。而由于瀏覽器的限制,我們通過(guò)這種方式請(qǐng)求的時(shí)候能夠傳遞的參數(shù)數(shù)目和長(zhǎng)度都是有限的,而且當(dāng)參數(shù)中存在中文數(shù)值的時(shí)候還需要對(duì)其進(jìn)行編碼。Get方式請(qǐng)求相對(duì)簡(jiǎn)單,我們下面來(lái)看看它的請(qǐng)求報(bào)文:

          GET /?num1=23&num2=12 HTTP/1.1
          Accept: text/html, application/xhtml+xml, image/jxr, */*
          Accept-Language: zh-Hans-CN,zh-Hans;q=0.5
          User-Agent: Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Safari/537.36 Edge/13.10586
          Accept-Encoding: gzip, deflate
          Host: localhost:4040
          Connection: Keep-Alive
          Cookie: _ga=GA1.1.1181222800.1463541781
          1
          2
          3
          4
          5
          6
          7
          8
          

          此時(shí)我們可以注意到在請(qǐng)求報(bào)文第一行,即請(qǐng)求行中出現(xiàn)了“/?num1=23&num2=12”這樣的字樣,這就是客戶端傳遞給服務(wù)器的參數(shù),我們很容易想到只需要將這個(gè)字段串中的“鍵”和“值”都解析出來(lái),服務(wù)器就可以對(duì)這些數(shù)據(jù)進(jìn)行處理然后返回給客戶端了。所以下面我們通過(guò)這樣的方式來(lái)實(shí)現(xiàn),我們?yōu)镠tttpRequest類增加了一個(gè)Parms屬性,它是一個(gè)鍵和值均為字符串類型的字典,我們使用這個(gè)字典來(lái)存儲(chǔ)和管理客戶端傳遞來(lái)的參數(shù)。

          //獲取請(qǐng)求參數(shù)
          if(this.Method == "GET" && this.URL.Contains('?'))
           this.Params = GetRequestParams(lines[0].Split(' ')[1].Split('?')[1]);
          1
          2
          3
          

          顯然我們首先需要判斷請(qǐng)求類型是否為GET以及請(qǐng)求中是否帶有參數(shù),其方法是判斷請(qǐng)求地址中是否含有“?”字符。這里的lines是指將報(bào)文信息按行分割以后的數(shù)組,顯然請(qǐng)求地址在第一行,所以我們根據(jù)“?”分割該行數(shù)據(jù)以后就可以得到“num1=23&num2=12”這樣的結(jié)果,這里我們使用一個(gè)方法GetRequestParms來(lái)返回參數(shù)字典,這樣作做是為了復(fù)用方法,因?yàn)樵谔幚鞵ost請(qǐng)求的時(shí)候我們會(huì)繼續(xù)使用這個(gè)方法。該方法定義如下:

           /// <summary>
          /// 從內(nèi)容中解析請(qǐng)求參數(shù)并返回一個(gè)字典
          /// </summary>
          /// <param name="content">使用&連接的參數(shù)字符串</param>
          /// <returns>如果存在參數(shù)則返回參數(shù)否則返回null</returns>
          protected Dictionary<string, string> GetRequestParams(string content)
          {
           //防御編程
           if(string.IsNullOrEmpty(content))
           return null;
           //按照&對(duì)字符進(jìn)行分割
           string[] reval = content.Split('&');
           if(reval.Length <= 0)
           return null;
           //將結(jié)果添加至字典
           Dictionary<string, string> dict = new Dictionary<string, string>();
           foreach(string val in reval)
           {
           string[] kv = val.Split('=');
           if(kv.Length <= 1)
           dict.Add(kv[0], "");
           dict.Add(kv[0],kv[1]);
           }
           //返回字典
           return dict;
          }
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          25
          26
          27
          28
          29
          

          實(shí)現(xiàn)Post請(qǐng)求

          Post請(qǐng)求相對(duì)Get請(qǐng)求比較安全,因?yàn)樗朔薌et請(qǐng)求參數(shù)長(zhǎng)度的限制問(wèn)題,而且由于它的參數(shù)是存放在消息體中的,所以在傳遞參數(shù)的時(shí)候?qū)τ脩舳允遣豢梢?jiàn)的,我們平時(shí)接觸到的網(wǎng)站登錄都是這種類型,而復(fù)雜點(diǎn)的網(wǎng)站會(huì)通過(guò)驗(yàn)證碼、Cookie等形式來(lái)避免爬蟲(chóng)程序模擬登錄,在Web開(kāi)發(fā)中Post請(qǐng)求可以由一個(gè)表單發(fā)起,可以由爬蟲(chóng)程序如HttpWebRequest、WebClient等發(fā)起,下面我們重點(diǎn)來(lái)分析它的請(qǐng)求報(bào)文:

          POST / HTTP/1.1
          Accept: text/html, application/xhtml+xml, image/jxr, */*
          Accept-Language: zh-Hans-CN,zh-Hans;q=0.5
          User-Agent: Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Safari/537.36 Edge/13.10586
          Accept-Encoding: gzip, deflate
          Host: localhost:4040
          Connection: Keep-Alive
          Cookie: _ga=GA1.1.1181222800.1463541781
          num1=23&num2=12
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          

          我們可以注意到此時(shí)請(qǐng)求行的請(qǐng)求方法變成了POST,而在報(bào)文結(jié)尾增加了一行內(nèi)容,我們稱其為“消息體”,這是一個(gè)可選的內(nèi)容,請(qǐng)注意它前面有一個(gè)空行。所以,當(dāng)我們處理一個(gè)Posst請(qǐng)求的時(shí)候,通過(guò)最后一行就可以解析出客戶端傳遞過(guò)來(lái)的參數(shù),和Get請(qǐng)求相同,我們這里繼續(xù)使用GetRequestParams來(lái)完成解析。

          if(this.Method == "POST")
           this.Params = GetRequestParams(lines[lines.Length-1]);
          1
          2
          

          實(shí)例

          現(xiàn)在我們來(lái)完成一個(gè)簡(jiǎn)單地實(shí)例,服務(wù)器自然由我們這里設(shè)計(jì)的這個(gè)服務(wù)器來(lái)完成咯,而客戶端則由Unity來(lái)完成因?yàn)閁nity有簡(jiǎn)單的WWW可以使用。首先來(lái)編寫(xiě)服務(wù)端,這個(gè)繼承HttpServer就好了,我們主要來(lái)寫(xiě)這里的方法:

          using System;
          using System.Collections.Generic;
          using System.Linq;
          using System.Text;
          using System.Threading.Tasks;
          using HttpServerLib;
          using System.IO;
          namespace HttpServer
          {
           public class ExampleServer : HttpServerLib.HttpServer
           {
           /// <summary>
           /// 構(gòu)造函數(shù)
           /// </summary>
           /// <param name="ipAddress">IP地址</param>
           /// <param name="port">端口號(hào)</param>
           public ExampleServer(string ipAddress, int port)
           : base(ipAddress, port)
           {
           }
           public override void OnPost(HttpRequest request)
           {
           //獲取客戶端傳遞的參數(shù)
           int num1 = int.Parse(request.Params["num1"]);
           int num2 = int.Parse(request.Params["num2"]);
           //設(shè)置返回信息
           string content = string.Format("這是通過(guò)Post方式返回的數(shù)據(jù):num1={0},num2={1}",num1,num2);
           //構(gòu)造響應(yīng)報(bào)文
           HttpResponse response = new HttpResponse(content, Encoding.UTF8);
           response.StatusCode = "200";
           response.Content_Type = "text/html; charset=UTF-8";
           response.Server = "ExampleServer";
           //發(fā)送響應(yīng)
           ProcessResponse(request.Handler, response);
           }
           public override void OnGet(HttpRequest request)
           {
           //獲取客戶端傳遞的參數(shù)
           int num1 = int.Parse(request.Params["num1"]);
           int num2 = int.Parse(request.Params["num2"]);
           //設(shè)置返回信息
           string content = string.Format("這是通過(guò)Get方式返回的數(shù)據(jù):num1={0},num2={1}",num1,num2);
           //構(gòu)造響應(yīng)報(bào)文
           HttpResponse response = new HttpResponse(content, Encoding.UTF8);
           response.StatusCode = "200";
           response.Content_Type = "text/html; charset=UTF-8";
           response.Server = "ExampleServer";
           //發(fā)送響應(yīng)
           ProcessResponse(request.Handler, response);
           }
           }
          }
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          25
          26
          27
          28
          29
          30
          31
          32
          33
          34
          35
          36
          37
          38
          39
          40
          41
          42
          43
          44
          45
          46
          47
          48
          49
          50
          51
          52
          53
          54
          55
          56
          57
          58
          59
          60
          61
          62
          

          因?yàn)檫@里需要對(duì)Get和Post進(jìn)行響應(yīng),所以我們這里對(duì)OnGet和OnPost兩個(gè)方法進(jìn)行了重寫(xiě),這里的處理方式非常簡(jiǎn)單,按照一定格式返回?cái)?shù)據(jù)即可。下面我們來(lái)說(shuō)說(shuō)Unity作為客戶端這邊要做的工作。WWW是Unity3D中提供的一個(gè)簡(jiǎn)單的HTTP協(xié)議的封裝類,它和.NET平臺(tái)下的WebClient、HttpWebRequest/HttpWebResponse類似,都可以處理常見(jiàn)的HTTP請(qǐng)求如Get和Post這兩種請(qǐng)求方式。

          WWW的優(yōu)勢(shì)主要是簡(jiǎn)單易用和支持協(xié)程,尤其是Unity3D中的協(xié)程(Coroutine)這個(gè)特性,如果能夠得到良好的使用,常常能夠起到事倍功半的效果。因?yàn)閃WW強(qiáng)調(diào)的是以HTTP短鏈接為主的易用性,所以相應(yīng)地在超時(shí)、Cookie等HTTP頭部字段支持的完整性上無(wú)法和WebClient、HttpWebRequest/HttpWebRespons相提并論,當(dāng)我們需要更復(fù)雜的HTTP協(xié)議支持的時(shí)候,選擇在WebClient、HttpWebRequest/HttpWebResponse上進(jìn)行深度定制將會(huì)是一個(gè)不錯(cuò)的選擇。我們這里需要的是發(fā)起一個(gè)簡(jiǎn)單的HTTP請(qǐng)求,所以使用WWW完全可以滿足我們的要求,首先我們來(lái)看在Unity3D中如何發(fā)起一個(gè)Get請(qǐng)求,這里給出一個(gè)簡(jiǎn)單的代碼示例:

          //采用GET方式請(qǐng)求數(shù)據(jù)
          IEnumerator Get()
          {
           WWW www = new WWW ("http://127.0.0.1:4040/?num1=12&num2=23");
           yield return www;
           Debug.Log(www.text);
          }
          1
          2
          3
          4
          5
          6
          7
          

          現(xiàn)在我們是需要使用StartCoroutine調(diào)用這個(gè)方法就可以啦!同樣地,對(duì)于Post請(qǐng)求,我們這里采用一個(gè)WWWForm來(lái)封裝參數(shù),而在網(wǎng)頁(yè)開(kāi)發(fā)中我們通常都是借助表單來(lái)向服務(wù)器傳遞參數(shù)的,這里給出同樣簡(jiǎn)單的代碼示例:

          //采用POST方式請(qǐng)求數(shù)據(jù)
          IEnumerator Post()
          {
           WWWForm form = new WWWForm ();
           form.AddField ("num1", 12);
           form.AddField ("num2", 23);
           WWW www = new WWW ("http://127.0.0.1:4040/", form);
           yield return www;
           Debug.Log (www.text);
          }
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          

          而運(yùn)行這個(gè)實(shí)例,我們可以得到下面的結(jié)果:


          都是誰(shuí)告訴你做服務(wù)器開(kāi)發(fā)一定要用Java的啊,現(xiàn)在我們可以寫(xiě)出自己的服務(wù)器了,本篇結(jié)束


          主站蜘蛛池模板: 香蕉免费一区二区三区| 国产一区二区在线观看视频| 夜夜精品视频一区二区| 蜜桃视频一区二区| 亚洲高清一区二区三区电影| 曰韩精品无码一区二区三区| 蜜桃视频一区二区| 国产一区二区三区在线观看影院| 午夜福利无码一区二区| 精品国产一区二区三区四区 | 亲子乱AV视频一区二区| 国产一国产一区秋霞在线观看| 国产精品香蕉在线一区| 亚洲码一区二区三区| 精品国产一区二区三区麻豆| 亚洲综合一区二区| 国产对白精品刺激一区二区| 精品一区二区三区四区在线播放| 色欲精品国产一区二区三区AV| 国产美女精品一区二区三区| 精品一区二区久久| 久久精品一区二区国产| 久久国产精品最新一区| 亚洲伦理一区二区| 国产香蕉一区二区在线网站| 久久人妻无码一区二区| 人妻夜夜爽天天爽一区| 国产人妖在线观看一区二区| 四虎一区二区成人免费影院网址| 国模精品一区二区三区视频| 精品综合一区二区三区| 美女视频免费看一区二区| 国产成人久久精品麻豆一区| 中文字幕乱码一区二区免费| 人妻无码一区二区不卡无码av | 精品国产一区二区麻豆| 国产一区在线视频观看| 东京热人妻无码一区二区av| 一区二区三区午夜视频| 无码人妻啪啪一区二区| 亚洲中文字幕丝袜制服一区 |