索引擎和聚合類新聞App之所以有源源不斷的新內容供予用戶瀏覽,原因就在于有網絡爬蟲技術的加持。網絡爬蟲的應用對于用戶來說,是一大福利——我們可以從一個搜索引擎輕松搜索到各個領域的信息。但是,對于原創方來說,就涉及到版權被侵犯的問題了。工具理性,但不意味著操持工具的人就可以假借“工具理性”肆意侵犯他人的合法權益,網絡爬蟲技術的應用還應該要在法理之內。
工作的時候,想要查找“產品設計”,可以直接在搜索引擎上輸入內容,就可以直接找到數以百萬計的資料。
上下班路上,刷新聞類APP的時候,只要愿意,就會有源源不斷的新的信息,足夠刷一路的時間。
搜索引擎和(大多數)新聞類APP都不自己生產內容(雖然有些平臺孵化了自己的內容,但也只占整個平臺內容的很少的一部分,更重要的是,成本非常高)。
那么,他們的大量的內容從哪里來?
“我們不生產內容,只是內容的搬運工”,將互聯網上的內容“搬運”到自己的服務器上,這就是爬蟲。
首先,我們需要了解一下互聯網的結構。
互聯網上的內容數以億計,雖然很復雜,但說白了就是一張大網,網上的每個節點就是一個網頁,連接網頁的超鏈接(Hyperlinks)相當于線,線把所有的節點連接在一起,形成了一個復雜的網。
通過點擊超鏈接的文字或者圖片,就可以跳轉到對應的網頁。爬蟲可以自動訪問到每一個網頁,并把網頁的內容保存下來。
世界上第一個網絡爬蟲由麻省理工學院的學生馬修·格雷(Matthew Gray)在1993年寫成,之后的爬蟲盡管越來越復雜。
比如:可以實現更快的訪問速度、訪問更多的網頁、更好的將網站內容解析出來。但爬蟲的基本原理是一樣的,都主要包括三個部分:訪問網頁鏈接,下載網頁內容,解析網頁內容。
爬蟲的工作過程與我們查找網頁的過程是一樣的。
比如,我們想要查一下豆瓣上最新的電影:首先,在瀏覽器地址欄輸入網址鏈接https://movie.douban.com/,之后,瀏覽器會跳轉到豆瓣電影。最后,我們就可以找到當前熱映的電影。
同樣的,一個最簡單的爬蟲三步就可以爬取一個網頁——首先,訪問這個網頁,之后,把網頁內容下載下來,最后,對下載的內容進行解析。
最簡單的爬蟲三步就可以爬取一個網頁,那么要寫多少行代碼呢?
我們寫一個爬蟲,爬取豆瓣的“一周口碑榜”,只要7行代碼!
這里我們使用Python語言,至于為什么選擇Python語言,會在后面交代清楚,如果不懂Python也沒有關系,了解爬蟲是如何工作的就可以了。
代碼如下:
import requests from lxml
import html url=’https://movie.douban.com/’ # 1、需要爬數據的網址
page=requests.Session.get(url) # 2、訪問網頁
tree=html.fromstring(page.text) # 3、解析網頁的過程
result=tree.xpath(‘//td[@class=”title”]//a/text’) #3、解析網頁的過程
print(result) # 打印出結果
在Python環境中運行這幾行代碼,就可以獲取“一周口碑榜”了,結果如下:
[‘迦百農’, ‘綠皮書’, ‘馴龍高手3’, ‘速成家庭’, ‘阿麗塔:戰斗天使’, ‘膚色’, ‘死亡天使’, ‘黎明墻’, ‘小小巨人’, ‘出·路’]
其中最關鍵的是解析網頁內容,主要是(‘//td[@class=”title”]//a/text’)這行代碼,大多數人可能對比較困惑。
這涉及到HTML網頁的結構,可以把網頁理解成一個文件夾,打開一個文件夾,會發現子文件夾,子文件夾或許還有文件夾。通過打開一個個文件夾,最終找到需要的數據。
至于是怎么寫出來這行代碼的,可以通過在網頁空白處點擊右鍵,查看源代碼,就可以找到對應的td、class=”title”、a等標識符。
大多數程序員寫爬蟲選擇python的理由很簡單.
首先,python有很多的庫,可以直接調用,比如:上面的代碼就引入了requests、lxml庫,分別實現訪問網頁、對網頁結構解析。有開源的庫,就直接調用,避免重復造輪子。
其次,python寫起來很方便,配置也簡單,短短幾行的代碼,就可以直接運行了,如果使用C或者Java,可能配置環境就要老半天。
把上面的每個步驟分別實現(模塊化),就可以構成一個簡答的爬蟲系統。
使用URL(可以理解為網址鏈接)管理器管理所有的網址鏈接,使用HTML(可以理解為網頁內容)下載器下載網頁內容,使用HTML解析器對下載的內容解析,再加上數據存儲模塊、控制整個爬蟲的調度模塊,就構成了一個簡單的爬蟲系統。
更具體的說,URL管理器負責管理所有的網址鏈接,記錄下哪些URL已經爬取了,哪些還沒有爬取。如果爬取過了,就要避免再次下載,如果沒有,就要加入隊列,等HTML下載器下載。
HTML下載器可以從服務器下載整個網頁的內容,從URL管理器中獲取未爬取的網址鏈接,之后,訪問這些網頁鏈接,下載網頁。
HTML解析器負責解析下載好的網頁,主要有兩個任務:一方面,解析出需要的信息,比如上文的“一周口碑榜”;另一方面,解析出新的URL鏈接,交給URL管理器,繼續下載,這個功能在上面的“7行代碼”沒有實現。
數據存儲器實現存儲數據的功能,將HTML解析器解析出來的信息存儲起來,否則每次使用都要下載,會浪費大量的時間。圖片、文檔之類的文件可以直接保存到服務器上,文字類的可以通過數據庫存儲起來。
爬蟲調度器作為系統的大腦,負責統籌其他四個模塊的協調工作。
無論是大型的還是小型的爬蟲雖然在設計細節,性能上有所不同,但都不會脫離這五個模塊。
乍一看,每個模塊實現起來都很簡單,但細想,似乎每個模塊都要考慮很多東西。
7行代碼爬取豆瓣電影,直接訪問網址鏈接(https://movie.douban.com/)就可以爬取“一周口碑榜”。對稍大一些的爬蟲系統或者商用爬蟲,就要有更多的考慮了,在保證獲取充足信息的同時,也要保證下載的質量。
對搜索引擎公司而言,要盡可能包括互聯網所有的信息。對垂直領域,更多的偏向業務類信息,比如:對新聞類的APP,主要包括一些新聞網站、政府網站等,對Github這類的編程網站,他們可能就不感興趣。
巧婦難為無米之炊,初始的網址鏈接基本要靠人工憑經驗獲取,比如:新聞類的APP,他們的初始URL列表里可能就包括新浪、網易、搜狐等門戶網站,也包括各個級別的政府網站,還有人民網、新華社、人民日報等媒體的網站。
當一個頁面下載完成后,從這個網頁中提取出其中的網址鏈接,把它們添加到等待下載的隊列中,就可以獲得更多的網址鏈接。
如果一個網頁已經下載過了,重新下載,會消耗大量的時間,并占用存儲空間。更要命的是,如果一直重復下載,就可能陷入死循環。
那么,如何知道這網址鏈接是不是已經下載過了?
對于小型爬蟲,可以使用列表存儲下載過的網址鏈接,當有新的網址鏈接的時候,先查找這個列表中有沒有該網址鏈接。如果有的話,就不用插入,如果沒有的話,就插入列表,等待訪問下載。
對于大型爬蟲,有成百上千個“小爬蟲”(更加專業的名詞叫做分布式爬蟲),分布在不同的服務器上,同時爬取網址鏈接,就要考慮更多的東西。
比如:不同爬蟲之間的分工和通信,如何共同維護上述的列表。
當數據很大的時候,就要考慮分布式、通信、存儲、帶寬等每個環節的限制,無論哪個環節沒有做好,都有可能成為系統的瓶頸,這就像是木桶效應中的短板。
數據量增加10倍,之前的代碼可能要重寫了,工作量可能就要增加100倍,這也是量變引起質量的一個很好的例子。
在計算機領域,這樣的例子隨處可見,當數據增大到一定量級,原有的算法很可能無法繼續使用,需要重新開發,隨之而來的是加班、DEBUG以及延期上線。
爬取豆瓣電影的“一周口碑榜”,需要研究網頁的源代碼,并編寫對應的解析代碼。但是網頁的結構不同,用這個代碼爬取知乎,解析不到任何內容。
以新聞類的APP為例:一個好的新聞類APP需要爬蟲數以億計的網頁,并把里面的文字、視頻、圖片分別解析出來,難度可想而知。
好消息是一部分網站會遵守RSS規范(遵守RSS規范的網頁結構和代碼都有相似性,以便于訂閱器獲取主要信息),一種類型的爬蟲就可以爬取大量這種類似的網頁。但大部分的網站的結構,都是不同的,這需要算法工程師花費大量的時間和精力做解析工作。
新聞類APP通過爬蟲,獲得大量的優質資源,讀者也樂意在一個平臺上看到所有的內容,但“被爬取”的網站就不太高興了。對于大多數依靠廣告收入的網站,沒有了流量,連生存都成了問題,更別說盈利了。
一些自成體系的平臺,比如:大型電商平臺,他們希望所有的用戶在自己的平臺上查找信息,所有的商家在自己的平臺上吸引賣家(廣告費可不能付給搜索引擎),同樣不希望爬蟲的騷擾。
搜索引擎希望爬取更多的信息,優質的內容提供商又不希望被爬蟲騷擾,利益沖突難以調和,于是產生了Robots協議來解決這個問題。
Robots協議網站服務器的一個聲明,通常是保存在網站根目錄下的一個TXT格式的文件,網站通過Robots協議告訴搜索引擎:哪些頁面可以抓取?哪些頁面不能抓取?
當爬蟲訪問一個站點時,它會首先檢查該站點根目錄下是否存在robots.txt,如果存在,爬蟲就會按照該文件中的內容來確定訪問的范圍;如果該文件不存在,所有的爬蟲將能夠訪問網站上所有沒有被口令保護的頁面。
我們使用搜索引擎,經常會看到“由于該網站的robots.txt文件存在限制指令(限制搜索引擎抓取),系統無法提供該頁面的內容描述”,就是源于這個協議。
值得注意的是:Robots協議是國際互聯網界通行的道德規范,并沒有強制性約束力。
一些“沒有道德”的爬蟲同樣會爬取有robots.txt限制指令的網站,這時候就需要一些技術來實現反爬蟲了。
最常見的有三種方式:
每個電腦都有唯一的IP地址,每個爬蟲也有唯一的IP地址,當電腦或者爬蟲訪問網站的時候,網站會記錄這個IP地址。如果同一個IP短時間多次訪問同一個網站,這個網站可能會傾向于認為這是個爬蟲,會采取一些措施。
當然,這在反爬蟲的同時,也會給用戶帶來一些不好的體驗。
相比之下,一些比較優秀的網站或者APP,會根據用戶點擊頻率、時間間隔等信息,判斷是不是爬蟲或者誤點擊,之后再確定是否需要驗證。
更好的用戶體驗背后,是更大的開發成本,更長的開發周期。
當我們使用瀏覽器訪問網站的時候,瀏覽器會自動在訪問請求上添加一些信息,比如:瀏覽器采用的編碼方式、使用的操作系統、瀏覽器版本等信息放在訪問請求的最開始,作為Headers,但爬蟲一般不會附加這些信息。
網站會根據是否存在Headers信息以及Headers信息的內容,判斷對方是不是爬蟲,有必要的話,就拒絕訪問。
之前將的HTML網頁都是靜態的,隨著HTML代碼生成,頁面的內容和顯示效果就不會發生變化了。而動態網頁則不然,動態網站是腳本語言(比如PHP)生成的,一些內容不是直接可見的,而是要運行一些腳本,才能看到。
網址后綴為htm、html、shtml、xml的網頁是靜態網頁,而動態網頁是以·aspx、.asp、.jsp、.php、.perl、.cgi等形式為后綴,并且在動態網頁網址中有一個標志性的符號“?”,這些不同的后綴基本代表了網頁使用的語言。
訪問靜態網頁,只需要直接訪問鏈接就可以了,訪問動態網站,還需要執行一些特定的操作(比如點擊),才能顯示更多的內容,這就增加了爬取的難度,一些簡單的爬蟲就被拒之門外了。
介紹完三種主流的反爬蟲的方式,最后要說的是:反爬蟲技術也不是一勞永逸的,在反爬蟲的發展過程中,爬蟲也進化出了一系列反“反爬蟲”的方式。
針對反爬蟲驗證IP機制,爬蟲“進化”出了IP代理池,這樣,爬蟲就可以不斷變換自己的IP地址,迷惑反爬蟲。針對Headers驗證,爬蟲也會生成一個Headers信息,甚至針對動態頁面,也會模擬瀏覽器的行為。
雖然如此,反爬蟲在一定程度上提高了爬蟲的成本,降低了爬蟲的效率,就可以將一大部分爬蟲擋在門外。
從爬蟲與反爬蟲的例子也可以看出:大多數時候,沒有絕對的有效方式。提高對方的成本,讓對方覺得代價太大,得不償失,就是很好的解決問題的辦法。
上面講了爬蟲是怎么運行的,常見的反爬蟲機制。最后,我們再講一個爬蟲的應用場景的例子,可以幫助我們更好理解爬蟲。
冷啟動是每一個產品經理、運營人員和創業者面臨的重大問題。沒有優質的內容,就吸引不了用戶,沒有大量的用戶,就無法吸引優質的內容,就陷入了先有雞還是先有蛋的悖論。
爬蟲,低成本、快速地解決了這個問題!
“我們不生產新聞,我們只是新聞的搬運工”,通過爬蟲,低成本、快速地爬取整個互聯網的優質內容,并憑借海量數據,利用算法實現內容分類和個性推薦(個性推薦系統會在后序章節詳細介紹),吸引大量的用戶,最終通過廣告變現。
事實證明,這是個非常成功的商業模式。而媒體平臺和新聞網站雇傭大量編輯人員,花費大量時間、金錢寫成的高質量內容,連說一聲都沒有,就這樣被拿走了,這不是侵犯人家版權嘛!
于是,多家媒體聯合發起侵權訴訟或抗議聲討,最終迫使這家新聞巨頭支付版權費,但無論法律上、道德上有多少問題,都不影響這家公司商業成功的既定事實。
類似的事情同樣發生在其他垂直領域。
一家新成立的技術博客平臺,爬取競爭對手上的文章,迅速實現優質內容的聚合。如果原博客主發現了自己的文章被盜用了,新的平臺就移交賬號并看情況給予少許補償。如果對方不樂意,就注銷賬號,當一切都沒有發生過。憑借這種運營方式,順利實現了冷啟動。
短視頻APP的后來者,也可以通過類似的方式,實現用戶的積累和優質內容的聚合。
勝利即正義?
這似乎是過于武斷的一個評價。
上述的視頻APP做得太過分,引起公憤,最終不得不關閉自己的平臺。
對于通過爬蟲獲取內容的平臺而言,內容的獲取也只是萬里長征的第一步,通過運營手段減小生產內容的成本,通過利益共享激勵優質內容的產生,通過技術減小信息成本吸引用戶,更加任重而道遠。
而版權,也始終是懸于頭頂的達摩克利斯之劍。
本文由@linghu 原創發布于人人都是產品經理,未經許可,禁止轉載
題圖來自Unsplash, 基于CC0協議
今年國慶假期終于可以憋在家里了不用出門了,不用出去看后腦了,真的是一種享受。這么好的光陰怎么浪費,睡覺、吃飯、打豆豆這怎么可能(耍多了也煩),完全不符合我們程序員的作風,趕緊起來把文章寫完。
這篇文章比較基礎,在國慶期間的業余時間寫的,這幾天又完善了下,力求把更多的前端所涉及到的關于文件上傳的各種場景和應用都涵蓋了,若有疏漏和問題還請留言斧正和補充。
以下是本文所涉及到的知識點,break or continue ?
原理很簡單,就是根據 http 協議的規范和定義,完成請求消息體的封裝和消息體的解析,然后將二進制內容保存到文件。
我們都知道如果要上傳一個文件,需要把 form 標簽的enctype設置為multipart/form-data,同時method必須為post方法。
那么multipart/form-data表示什么呢?
multipart互聯網上的混合資源,就是資源由多種元素組成,form-data表示可以使用HTML Forms 和 POST 方法上傳文件,具體的定義可以參考RFC 7578。
multipart/form-data 結構
看下 http 請求的消息體
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryDCntfiXcSkPhS4PN 表示本次請求要上傳文件,其中boundary表示分隔符,如果要上傳多個表單項,就要使用boundary分割,每個表單項由———XXX開始,以———XXX結尾。
每一個表單項又由Content-Type和Content-Disposition組成。
Content-Disposition: form-data 為固定值,表示一個表單元素,name 表示表單元素的 名稱,回車換行后面就是name的值,如果是上傳文件就是文件的二進制內容。
Content-Type:表示當前的內容的 MIME 類型,是圖片還是文本還是二進制數據。
解析
客戶端發送請求到服務器后,服務器會收到請求的消息體,然后對消息體進行解析,解析出哪是普通表單哪些是附件。
可能大家馬上能想到通過正則或者字符串處理分割出內容,不過這樣是行不通的,二進制buffer轉化為string,對字符串進行截取后,其索引和字符串是不一致的,所以結果就不會正確,除非上傳的就是字符串。
不過一般情況下不需要自行解析,目前已經有很成熟的三方庫可以使用。
至于如何解析,這個也會占用很大篇幅,后面的文章在詳細說。
使用 form 表單上傳文件
在 ie時代,如果實現一個無刷新的文件上傳那可是費老勁了,大部分都是用 iframe 來實現局部刷新或者使用 flash 插件來搞定,在那個時代 ie 就是最好用的瀏覽器(別無選擇)。
DEMO
這種方式上傳文件,不需要 js ,而且沒有兼容問題,所有瀏覽器都支持,就是體驗很差,導致頁面刷新,頁面其他數據丟失。
HTML
<form method="post" action="http://localhost:8100" enctype="multipart/form-data">
選擇文件:
<input type="file" name="f1"/> input 必須設置 name 屬性,否則數據無法發送<br/>
<br/>
標題:<input type="text" name="title"/><br/><br/><br/>
<button type="submit" id="btn-0">上 傳</button>
</form>
復制代碼
服務端文件的保存基于現有的庫koa-body結合 koa2實現服務端文件的保存和數據的返回。
在項目開發中,文件上傳本身和業務無關,代碼基本上都可通用。
在這里我們使用koa-body庫來實現解析和文件的保存。
koa-body 會自動保存文件到系統臨時目錄下,也可以指定保存的文件路徑。
然后在后續中間件內得到已保存的文件的信息,再做二次處理。
NODE
/**
* 服務入口
*/
var http = require('http');
var koaStatic = require('koa-static');
var path = require('path');
var koaBody = require('koa-body');//文件保存庫
var fs = require('fs');
var Koa = require('koa2');
var app = new Koa();
var port = process.env.PORT || '8100';
var uploadHost= `http://localhost:${port}/uploads/`;
app.use(koaBody({
formidable: {
//設置文件的默認保存目錄,不設置則保存在系統臨時目錄下 os
uploadDir: path.resolve(__dirname, '../static/uploads')
},
multipart: true // 開啟文件上傳,默認是關閉
}));
//開啟靜態文件訪問
app.use(koaStatic(
path.resolve(__dirname, '../static')
));
//文件二次處理,修改名稱
app.use((ctx) => {
var file = ctx.request.files.f1;//得道文件對象
var path = file.path;
var fname = file.name;//原文件名稱
var nextPath = path+fname;
if(file.size>0 && path){
//得到擴展名
var extArr = fname.split('.');
var ext = extArr[extArr.length-1];
var nextPath = path+'.'+ext;
//重命名文件
fs.renameSync(path, nextPath);
}
//以 json 形式輸出上傳文件地址
ctx.body = `{
"fileUrl":"${uploadHost}${nextPath.slice(nextPath.lastIndexOf('/')+1)}"
}`;
});
/**
* http server
*/
var server = http.createServer(app.callback());
server.listen(port);
console.log('demo1 server start ...... ');
復制代碼
CODE
https://github.com/Bigerfe/fe-learn-code/
404頁面的目的是:告訴瀏覽者其所請求的頁面不存在或鏈接錯誤,同時引導用戶使用網站其他頁面而不是關閉窗口離開。
現在大部分開源系統都會為大家考慮到404頁面的跳轉引導,比如:z-blog/wordpress,都是很不錯的開源系統(注意不要用最原始的開源系統,而是采用帶有模板的系統)。菜鳥后院網站本身也是wordpress的開源程序,然后我們用robin模板。(花299元擁有和菜鳥后院一樣的網站,包括域名和1G阿里巴巴云空間)
搜索引擎使用 http 狀態碼來識別網頁的狀態。當搜索引擎獲得不正確的鏈接時,網站應該返回一個狀態代碼404,告訴搜索引擎放棄索引該鏈接。如果返回一個200或302狀態代碼,搜索引擎會對鏈接進行索引,導致許多不同的鏈接指向相同的頁面內容。結果,搜索引擎對這個網站的信任度大大降低。很多網站存在這個問題,那就是404頁面返回的是200或302狀態碼而不是404狀態碼。
1、做一個簡單的404頁面,命名如:404.html;
2、通過ftp把這個404頁面上傳到網站根目錄;
3、進入虛擬主機管理后臺,找到404頁面提交的入口,添加以上404頁面的地址,如:www.cnbackyard.com/404.html(一般空間服務商都有帶著種功能,也可以直接找他們技術客服完成這步操作)
4、輸入一個錯誤的鏈接進行訪問測試,隨便輸入,比如:www.cnbackyard.com/123.html,如果正確返回到404.html頁面,則算正確;
5、使用站長工具(http://tool.chinaz.com/pagestatus),輸入任意一個錯誤網址,檢查返回值是否為404。如果返回值是200,代表該主機商設置有誤,可以與其技術反饋。
以上操作方法對于一個seo初學者來說,還是有點復雜,同學們可以關注燃燈教育直播課程,參加我們的培訓,理解起來會更透徹一點。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。