種子站點的集中特性使得它們很容易被關閉(提供音樂、電影等版權內容的種子文件的網站經常會因法律原因而被關閉,如海盜灣等的關停或者被墻,較近的如17年5月17日Extra Torrent關停)。
而來自土耳其·伊斯坦布爾的19歲程序員Bora想要解決這個問題,致力于真正的去中心化文件分享,他用python制作了一個開源軟件,使得可以輕松地在自己的電腦上開啟一個種子搜索引擎。(進一步,用pyinstaller打包成exe,前端用electron或者其它工具簡單封裝一下,完全不熟悉python 的普通用戶也可以輕松使用)。
實際上再進一步,如果每個人將自己搜索到的種子數據在開放無審查的零網上進行分享互換(這也可以用python自動化),就可以實現完全無審查去中心化的文件分享機制。(零網參見開放的零網與個人站點嘗試)
使用效果圖
- 在騰訊云最低配服務器上跑了兩天的效果,可以看出速度還是很快的,一天上萬種子。
快速安裝
-
linux使用:在python3的虛擬環境下(小白可以參看云服務器簡單配置)
pip3 install magneticod
就安裝好了種子爬蟲,在虛擬環境下命令行執行magneticod
就可以運行了,等待一會兒,可以看到如下的日志輸出,表明爬蟲正在運行,并且收集到種子了。
magneticod -d
運行顯示更詳細的信息,-d
參數表示輸出debug信息。)
網頁顯示與查詢功能安裝,同樣在虛擬環境下pip install magneticow
就好了,然后命令行magneticow --port 8080 --user 用戶名 密碼
就可以運行在8080端口了。使用localhost:8080
訪問,輸入自己更改的用戶名和密碼即可。 -
windows使用:windows下直接使用pip安裝暫時有點問題(可以先嘗試像上面那樣pip安裝),需要到github上下載源碼包 - download zip,如下圖:
然后解壓進入magneticod文件夾,在當前目錄打開命令行,python setup.py install
安裝,再進入magneticow文件夾同樣命令安裝。還需要做一件事,就是找到你的python動態鏈接庫中的sqlite3.dll
(我的在Anaconda3\DLLs
路徑下),在SQLite Download Page找到適合你版本的sqlite-dll替換掉它,我用的是sqlite-dll-win64-x64-3200000.zip。(參考stackoverflow)這樣就可以成功運行了。
windows的使用效果不如linux(暫時認為是windows的bug,udp也會報錯——[WinError 10054] 遠程主機強迫關閉了一個現有的連接
),限制速度之后就會好。
windows不限速
種子與磁力鏈接
- 說實話,作為一個從不開車、偶爾上車的良好公民 :),我對這些東西的了解十分有限。
- 我一直習慣使用的是基于ipv6的非公開BT站點,類似北郵人BT、6維空間這樣需要注冊的BT站點。想要資源時,在站點上下載相應的種子(torrent)文件,使用utorrent客戶端下載即可,至于其中的原理不求甚解,大概知道的就是下載完成該資源且開啟ut客戶端的用戶越多我的下載速度越快,同時下載的人越多速度越快,因為下載的同時會互相上傳文件的不同部分給對方,下載完成后要盡保種的義務,盡量開啟ut客戶端,方便其他用戶下載該資源,增加我的上傳量。分享文件做種時,要生成一個種子文件,并提交到站點服務器。
- 為了有更清楚的認知,打開ut客戶端研究一下,順便下載下權力的游戲最新集。用ut客戶端打開種子進行下載,在下載時觀察下面的狀態欄,第二欄就是Trackers,看來是非常關鍵的一個東西。
基本概念 - Tracker:收集下載者信息的服務器,并將此信息提供給其他下載者,使下載者們相互連接起來,傳輸數據。
-
原來,該BT站點提供了一個Tracker服務器(倒數一二行),記錄了所有下載者的信息或者文件分享者的信息,當我下載文件時,這個服務器會告訴我其他人的下載狀態(誰擁有資源,誰下載完成了,誰正在下載),也告訴其他人我的下載狀態,讓我們互通有無,互相傳遞資源,增加下載速度,而tracker服務器充當了信息交換中心的角色。可以看到,第一行的DHT被禁用了,是為了不走外網流量,DHT非常重要,下文會講到,暫時不管。
在peers一欄我們可以看到其他下載者的詳細信息,大家互相幫助上傳與下載。
-
在pieces一欄可以發現文件被分割成為很多份,每小份為1M大小,而我當前下載的視頻為4個多G。在files欄里可以看到所有區塊的下載狀況。
-
在對資源下載過程有了初步認識后,我們來詳細地看下種子文件到底是什么。原來種子文件本質上是一種B編碼后的文本文件,它包含了資源的詳細信息,要打開它看里面的內容需要先解碼,我們可以使用BEncode Editor這個軟件。隨便打開一個外網站點上下載的種子。
-
在BEncode Editor中顯示如下,這個73KB的種子包含了豐富的信息,在它的tracker服務器列表中可以看到名為海盜黨的希臘域名,這個種子文件也才創建不久。
-
而最關鍵的信息全在
info
字典中,它是種子文件元信息。點開查看詳細內容。可以看到下載文件的分塊機制和我們之前在ut客戶端看到的一致。在種子文件的元信息中包含了所有內容信息,以及所有分塊的哈希驗證碼(數字指紋),來確保文件的真實性。在下載時我們會向種子文件中記錄的tracker服務器發出請求,得到其他有該資源的用戶的地址,一小塊一小塊的下載,每小塊下載完成后都與種子文件中的該小塊的哈希值進行比對,看是否被篡改。
每個種子文件也具有一個唯一標識碼,稱為種子文件的info_hash,是種子中所有info信息B編碼后的SHA-1哈希值:20個字節,即40個16進制碼。根據這串碼就能找到對應的種子文件。
-
除了種子,我們還會遇到磁力鏈接,如下圖,磁力鏈接又是什么呢?
-
下圖是一個磁力鏈接的分解,來自阮一峰老師的BT下載的未來,詳細見wiki百科-磁力鏈接
可以得出磁力鏈接最重要的是紅線勾出來的那40個16進制字符碼,也就是種子文件的info_hash,根據它就能找到對應的種子文件,得到資源的詳細信息,進而下載資源。
-
擁有這串16進制碼,我們可以輕松構造出磁力鏈,通常我們會打開迅雷下載,迅雷會自動搜索相應的種子。
-
或者我們也可以到提供服務的站點,如thetorrent.org,由哈希碼得到相應種子文件再進行下載。
通過磁力鏈接中的info_hash碼獲取種子文件 在沒有服務商或站點情況下,通過info_hash碼可以直接獲取到相應的種子元信息及其資源嗎?可以,基于DHT協議(BEP-5: DHT Protocol - 翻譯),該協議基于Kademila算法,用udp實現。DHT是分布式哈希表的簡寫,用來存儲種子的下載者(peer)的聯系信息。一般每個下載者(peer)擁有一個節點(node),而DHT網絡由節點組成(node)。每個節點(node)擁有一個唯一的ID:20字節標識碼,和種子文件的info_hash一樣長。每個節點都維持一個自己的路由表,存儲了一小部分其他節點的聯系方式,同時也存儲了一些下載者的聯系信息。節點之間互相聯系幫助尋找資源。比如節點A拿著資源X的唯一標識碼(info_hash)去問在它路由表中的節點B有沒有資源X的下載者信息,如果節點B中沒有資源X的記錄信息,節點B會在自己的路由表中選出最可能擁有資源X的k個節點,把他們的聯系方式返回給節點A,節點A再根據節點B的返回信息去聯系這些節點進行詢問,依次迭代。(每一個節點比其他節點對它周邊的節點有更好的感知能力,
周邊與否
由Kademila算法定義)(node用來查找和存儲信息,peer負責下載)(詳細信息見BEP文檔)只要找到一個種子的下載者(peer)就可以使用
BEP-9: Extension for Peers to Send Metadata Files拓展協議從該下載者(peer)處下載種子元信息(info)(同樣可以使用元信息的哈希值(info_hash)來驗證該信息的真實性),當然也可以從它那下載資源。這樣只需要一個磁力鏈接在沒有中心服務器的情況下也可以下載資源了。(注:ut客戶端中會自動保存一份已下載資源的種子文件)
補充1:在前面我們看到的種子文件中,
announce
鍵記錄了tracker服務器的地址信息,在BEP-5的Torrent File Extensions小節中提到無tracker的種子文件中沒有announce
鍵,而有nodes
鍵記錄了良好的節點地址。總之,在一個新節點(自身路由表為空)加入DHT網絡時,需要一個引導過程,要知道一個已經在該網絡中的節點。要么通過tracker服務器獲取節點,要么是直接得到節點,并沒有那么自由。此外,tracker服務器的速度還是比DHT查找要快,一般下載過程是兩者的結合。
補充2:文件分享過程
補充3:Kademlia算法概要 - Kademlia基于兩個節點之間的距離計算,該距離是兩個網絡節點ID號的異或,計算的結果最終作為整型數值返回。資源的info_hash和節點ID有同樣的格式和長度,因此,可以使用同樣的方法計算資源(info_hash)和節點ID之間的距離。節點ID一般是一個大的隨機數,選擇該數的時候所追求的一個目標就是它的唯一性(希望在整個網絡中該節點ID是唯一的)。異或距離跟實際上的地理位置沒有任何關系,只與ID相關。因此很可能來自德國和澳大利亞的節點由于選擇了相似的隨機ID而成為鄰居。選擇異或是因為通過它計算的距離享有幾何距離公式的一些特征
(此外還有用戶交換 (PEX)協議,暫不討論)
源碼分析
注:網絡分享繁多,很多話的正確性都不是那么高,深入探索還得自己去讀官方英文協議和論文。別人的話都只是你通往更高處的一個墊腳石,不能停留在上面。
之前網絡上已經有不少開源的dht種子搜索的python代碼。比較有名的是手撕包菜種子搜索引擎網站的python代碼(開源在github上),但在項目介紹里的相關博文鏈接已經失效,來看源碼,關鍵的種子嗅探爬蟲都在目錄workers下。而最重要的info_hash碼(即種子唯一標識碼)爬蟲為simdht_worker.py文件。主要使用了CreateChen/simDownloader項目中的代碼。而與fanpei91/simDHT基本一致。
simDHT最簡單易讀,單線程,建議先閱讀。思路是偽裝成一個DHT節點。初始化時,給自己隨機生成一個20位的ID,通過大的tracker服務器(如
router.bittorrent.com
)獲取其他節點的地址信息(find_node
操作),進入DHT網絡,利用KRPC協議傳輸B編碼的字典信息,即DHT查詢信息(4種,見bep05),與DHT網絡中的其他節點互相通信。由于在初始化時已經從大的Tracker服務器獲取了一定量的節點信息,接下來向這些節點發送
find_node
請求,參數中:1. 將自己的ID構造成被請求節點的(按異或)相近ID(代碼為get_neighbor
函數,如向ID為A的節點發送find_node
請求,將自己ID構造成A[:end]+X[:20-end]
,也就是構造的ID的前end位與A節點ID相同,而后(20-end)位隨意,end取10-15,這樣偽裝成A的周邊節點,而按規則每個節點對周邊節點的感知能力要好,它很可能將你記錄在它的路由表上,使得它自己或引導其他節點主動向你通信),2. 要查找的節點ID隨機生成即可。大部分隨機生成的節點ID是不可能直接查找到的,那么被請求節點會返回另一批節點信息,在接受到返回的新節點信息后,向新節點繼續發送請求,依此迭代進行,目的就是不斷地和其他節點混臉熟,即auto_send_find_node
函數。這里維護了一個有限長的雙端隊列(deque)存儲節點。節點不斷從隊首取出,向其發送find_node
請求,收到的應答中的新節點不斷被添加到隊尾。如果隊列為空,則重新初始化一下。一旦和越來越多的節點混臉熟了,就會有不斷的查詢請求從其他節點發送過來,作為爬蟲,只要處理
get_peers
和announce_peer
請求就夠了。get_peers
是一個節點向另一個節點發出的查詢與info_hash
相關的下載者信息,包含的info_hash
參數就是我們需要的種子的info_hash,但是,get_peers
中包含的info_hash對應的種子可能已經失效或者難連接上,不采用。這時我們要回應它,關鍵是給他一個token
(自己以一定方式生成,不固定,用來校驗的)與一個空的nodes
參數(我們沒有種子的下載者信息,按協議應當返回最有可能有該信息的K個節點給查詢節點,但是也可以返回空值)。這個向我查詢的節點如果最終(通過其他節點)找到了資源(其他下載者,即peer),而控制該節點的下載者開始下載資源了,該節點很可能向我發送
announce_peer
消息,該消息告訴我們它的下載者信息。消息參數中的info_hash和下載者地址就是我們需要的,同時要驗證參數中的token
是否就是我之前發送給該節點的,保證真實性。返回給它的消息只是自己節點的ID。對于其他節點發送給自己的
ping
和find_node
查詢不用管即可。(可以思考,對于這兩種查詢是否有某種響應方式可以給自己帶來更多收益)以上就是simDHT的內容了。
而進一步,光有種子的info_hash碼還不夠,能直接拿到種子的元信息就好了。這就是手撕包菜里的simMetadata.py實現的通過bep9拓展協議從之前
announce_peer
消息中的下載者那里獲取到種子的元信息。此外,wuzhenda/simDHT和0x0d/dhtfck實現了K桶等,有一定注釋和他個人的理解,但作為爬蟲可能并不需要這個功能,還有NanYoMy的DHT-woodworm 以及DHT-simDHT增加了一點新特性,可以瀏覽下。
以上都是python2的,基于python3的異步IO特性的DHT爬蟲并不多,有whtsky/maga,而zrools基于maga,寫了asyncDHT,并有圖文并茂的博文,DHT爬蟲:18.4GB種子分析小記,值得一看。
另外,B編碼的編解碼庫:python2使用的多為bencode,
pip install bencode
。python可以使用的有bcoding,pip install bcoding
與better-bencode,pip install better-bencode
。實現bttorrent客戶端的python庫libtorrent,貌似只支持python2,libtorrent庫的使用可以看: 從磁力鏈獲取種子文件 - Magnet_To_Torrent2.py的43行到67行(其他都是次要代碼)。還可以參考creating daemon using Python libtorrent for fetching meta data of 100k+ torrents。(注:還發現有個Simple libtorrent,
pip install SimpleTorrentStreaming
)回到文章開頭使用的
magneticod
,它使用了python3提供的asyncio機制。主要嗅探代碼部分magneticod/dht.py中的思路和之前介紹的simDHT基本相似,有點不同的是,它維護了一個自己的節點字典self._routing_table
,每一輪(間隔一秒)向里面所有節點發出find_node
查詢然后清空字典,如果收到自己發出的find_node
請求的響應時,字典中的所有節點數超出self.__n_max_neighbours
數則不再加入新節點。(主要函數為async def tick_periodically(self)
,這個機制還有可以斟酌的地方)在大的異步結構上繼承了官方的
asyncio.DatagramProtocol
,可以先看官方樣例UDP echo client protocol和UDP echo server protocol,很簡明,數據的發送和接收都封裝好了,并且可以通過pause_writing
和resume_writing
控制流量。在處理
announce_peer
消息時,用asyncio.ensure_future
新建異步任務來抓取種子元信息,且對同一個種子元信息的多個抓取進行管理。在數據存儲方面使用了python自帶的sqlite,不用安裝就能使用,很方便。數據庫存儲位置管理使用了appdirs庫。
作者表示近期會有一次大的重構,讓我們拭目以待。
論文
- Kademlia: A Peer-to-Peer Information System Based on the XOR Metric
- A Torrent Recommender based on DHT Crawling
- Real-World Sybil Attacks in BitTorrent Mainline DHT
- Measuring large-scale distributed systems: case of BitTorrent Mainline DHT
- Crawling BitTorrent DHTs for Fun and Profit
可用參考
- 如何通過infohash得到torrent種子文件?
- 種子文件 - Torrent file
- Torrent 文件格式詳解
- B編碼以及BT種子文件分析
- What exactly is the info_Hash in a torrent file
- Hash calculation in torrent clients
- BT服務器/tracker服務器 - BitTorrent tracker - Trackerless torrents
- 分布式散列表
- Kademlia算法
其他
-
http://torrage.com/
- SimpleXMLRPCServer
-
迅雷從磁力鏈接下載種子時的http請求
請求,參數為大寫的infohash
響應