python爬蟲之protobuf協議介紹
前言
在你學習爬蟲的知識過程中是否遇到下面的類型。如果有興趣學習一下或者了解相關知識的,且不嫌在下才疏學淺,可以參考一下。歡迎各位網友的指正。
首先敘述一下問題的會出現的式樣。
你可能會在請求參數中看到如下亂碼的行為:
接著你會發現content-type數據類型為x-protobuf類型,那麼可能你可能需要學習一下protobuf協議才能繼續你的爬蟲。
那麼接下來我們敘述一下為什麼會出現這個問題呢?
我不知道這樣說下是否正確,僅供參考吧,可以提供一種思路。先說一個正常數據的content-type數據類型為
情況下。網頁根據utf-8編碼對數據進行解碼。但如果content-type數據類型為x-protobuf時,他不能根據protobuf協議去解析,所以會出現亂碼的行為。
接下來就進行到我們的正題吧。
首先本文會向你介紹protobuf協議的定義方式和解析方法,使你可以更深入的了解protobuf協議,在下節會介紹在爬蟲中遇到protobuf
協議如何解決的實踐操作。
一、什麼是protobuf協議?
protobuf (protocol buffer) 是Google內部的混合語言數據標準。通過將結構化的數據進行序列化(串列化),用於通訊協議、數據存儲等領域的語言無關、平台無關、可擴展的序列化結構數據格式。
- 序列化:將結構數據或者對象轉換成能夠用於存儲和傳輸的格式。
- 反序列化:在其他的計算環境中,將序列化後的數據還原為結構數據和對象。
1.1 序列化與反序列化的關係
如圖所示程式設計師編寫好proto文件的程式,然後編程成適應程式語言的包。這個過程可以通過下載下方鏈接。(後續會敘述這個過程)
//github.com/protocolbuffers/protobuf/releases/
將proto文件編成你所需要的包。你需要做的是寫出proto文件的內容。
然後通過編譯成的包可以將數據和二進位之間進行轉換被稱為序列化和反序列化。
二、編寫proto文件
2.1 為什麼要編寫proto文件
可能有人好奇,我們只是想把一個亂碼的轉換為我們能看懂的數據為什麼要學習編寫這個文件。那麼你可以先看再看一下上方的圖,如果你請求數據中攜帶參數是亂碼的,你要造出這種亂碼的數據那麼就需要去學習如何通過proto編譯出來的包(由於本文敘述是python語言那麼這個是py文件),來將數據轉換為二進位文件。以此來正常請求數據。
2.2 proto文件編寫過程
首先先寫一個簡單的proto文件
查看程式碼
syntax = "proto3";
message Panda {
int32 id = 1;
string name = 2;
}
第一行確定proto使用的協議,現在大部分使用proto3而不是proto2
然後定義一個消息體裡面存放一些你需要的數據欄位,
其中每個數據欄位都有一種類型,一個名字和一個值構成。這個值不是你的數據中的值,而是查找定義這個數據欄位的類型的識別符。我沒有嘗試過使用非數字的情況。具體的書寫格式可根據下方這個圖中proto的格式進行書寫。你可以根據自身的情況定義你需要的數據類型。
寫完成之後將文件名稱保存為xxx.proto文件。至此我們完成了編寫proto的過程,接下來我們需要將寫完的proto文件編譯成我們程式使用的包需要,下載//github.com/protocolbuffers/protobuf/releases/
網址中的文件,下載windows版本
設置環境變數
運行cmd,找到proto文件處
編譯成python文件
至此我們就已經完成proto文件的編譯了。
接下來我們開始將數據序列化為二進位
你需要安裝protobuf==3.20 google以及反序列化庫blackboxprotobuf
至此你已經可以完成proto文件的編寫,下面是一個稍微複雜一丟丟的程式碼,你可以根據上方數據類型的表來看程式碼。下方程式碼可以很好的為你解析一些proto中嵌套數據的關係
程式碼展示
syntax = "proto3";
message Message
{
int32 id_2 = 2;
int32 id_3 = 3;
Info id_5 = 5;
message Info
{
string id_1 = 1;
repeated int32 id_2 = 2 [packed=false];
int32 id_3 = 3;
Number id_5 = 5;
message Number
{
int32 id_2 = 2;
}
repeated int32 id_8 = 8 [packed=false];
int32 id_6 = 6;
int32 id_7 = 7;
int32 id_9 = 9;
int32 id_11 = 11;
}
string id_6 = 6;
}
調用結果展示

好了,上述描述為你解釋了如何編寫proto的過程,下面將會告訴你如何解析二進位proto的文件。先休息一下吧。
三、Protobuf數據格式解析
參考部落格 //www.cxymm.net/article/mine_song/76691817
首先我們要了解Varints編碼,然後通過Varints編碼了解protobuf的解碼過程
3.1 Varints編碼
Varint 是一種緊湊的表示數字的方法。它用一個或多個位元組來表示一個數字,值越小的數字使用越少的位元組數。這能減少用來表示數字的位元組數。
Varint 中的每個位元組(最後一個位元組除外)都設置了最高有效位(msb),這一位表示還會有更多位元組出現。每個位元組的低 7 位用於以 7 位組的形式存儲數字的二進位補碼錶示,最低有效組首位。
如果用不到 1 個位元組,那麼最高有效位設為 0 ,如下面這個例子,1 用一個位元組就可以表示,所以 msb 為 0.
0000 0001
如果需要多個位元組表示,msb 就應該設置為 1 。例如 300,如果用 Varint 表示的話:
1010 1100 0000 0010
如果按照正常的二進位計算的話,這個表示的是 88068 (65536 + 16384 + 4096 + 2048 + 4)。
但是如果按照 Varint 編碼的方式,首先看第一個位元組:10101100,最高位是 1,剩下的是 0101100,msb 為 1,表示還有剩下的位元組要讀取,第二個位元組 00000010,最高位是 0,剩下的是 0000010,msb 為 0,表示後面沒有位元組了。將兩個 7 為二進位數合在一起,就是目標值
0000010 0101100 => 4 + 8 + 32 + 256 = 300
這裡是小端模式,低位在前,先讀出來,高位在後,後讀出來。所以 0000010 要放在後面計算。
3.2 protobuf解析
首先我們先寫一個簡單的protobuf編碼,如圖所示
然後將賦值
所以獲得二進位位
08 01 12 04 74 65 73 74
解析敘述我們解碼過程,在分析解碼的過程中,我們需要了解Wire Type,每一個消息項前面都會有對應的tag,才能解析對應的數據類型,表示tag的數據類型也是Varint。
tag的計算方式: (field_number << 3) | wire_type
每種數據類型都有對應的wire_type:
Wire Type | Meaning Used For |
---|---|
0 | Varint int32, int64, uint32, uint64, sint32, sint64, bool, enum |
1 | 64-bit fixed64, sfixed64, double |
2 | Length-delimited string, bytes, embedded messages, packed repeated fields |
3 | Start group groups (deprecated) |
4 | End group groups (deprecated) |
5 | 32-bit fixed32, sfixed32, float |
所以wire_type最多只能支援8種,目前有6種。
所以08 對應的二進位為 :
補位之後是
0 0001 000
為什麼這樣寫?
首先後三位是wire_type的類型 0 ,就代表是int32與我們上面定義的一致
id 在protobuf中是不顯示的,只顯示後面標識符1
即如下圖所示:
然後由於是int32類型所以我們直接取值為 01,這個是我們賦值的值,即
這樣開始分析字元串
分割0 0010 010
即標識符為2 類型Wire Type為Length-delimited string。
接下來跟著的值為字元串的長度為04那麼接下來的四個數據就是字元串的數據
即74 65 73 74 ASCII轉為為test至此編碼的過程就完成了。
你可以嘗試去學一下難度較高的解碼過程,下節,我們敘述如何在爬蟲中使用。