<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          為什么爬蟲工程師應(yīng)該有一些基本的后端常識?

          共 7237字,需瀏覽 15分鐘

           ·

          2021-06-25 02:23


          今天在交流群里面,有個同學(xué)說他發(fā)現(xiàn)了Requests的一個 bug,并修復(fù)了它:

          聊天記錄中對應(yīng)的圖片為:

          看到這個同學(xué)的截圖,我大概知道他遇到了什么問題,以及為什么會誤認(rèn)為這是 Requests 的 bug。

          要解釋這個問題,我們需要首先明白一個問題,那就是 JSON 字符串的兩種顯示形式和json.dumpsensure_ascii參數(shù)。

          假設(shè)我們在 Python 里面有一個字典:

          info = {'name''青南''age'20}

          當(dāng)我們想把它轉(zhuǎn)成 JSON 字符串的時候,我們可能會這樣寫代碼:

          import json
          info = {'name''青南''age'20}
          info_str = json.dumps(info)
          print(info_str)

          運(yùn)行效果如下圖所示,中文變成了 Unicode 碼:

          我們也可以增加一個參數(shù)ensure_ascii=False,讓中文正常顯示出來:

          info_str = json.dumps(info, ensure_ascii=False)

          運(yùn)行效果如下圖所示:

          這位同學(xué)認(rèn)為,由于{"name": "\u9752\u5357", "age": 20}{"name": "青南", "age": 20}從字符串角度看,顯然不相等。而 Requests 在 POST 發(fā)送數(shù)據(jù)的時候,默認(rèn)是沒有這個參數(shù),而對json.dumps來說,省略這個參數(shù)等價于ensure_ascii=True

          所以實際上Requests在 POST 含有中文的數(shù)據(jù)時,會把中文轉(zhuǎn)成 Unicode 碼發(fā)給服務(wù)器,于是服務(wù)器根本就拿不到原始的中文信息了。所以就會導(dǎo)致報錯。

          但實際上,并不是這樣的。我常常跟群里的同學(xué)說,做爬蟲的同學(xué),應(yīng)該要有一些基本的后端常識,才不至于被這種現(xiàn)象誤導(dǎo)。為了說明為什么上面這個同學(xué)的理解是錯誤的,為什么這不是 Requests 的 bug,我們自己來寫一個含有 POST 的服務(wù),來看看我們POST 兩種情況的數(shù)據(jù)有沒有區(qū)別。為了證明這個特性與網(wǎng)絡(luò)框架無關(guān),我這里分別使用Flask、Fastapi 、Gin 來進(jìn)行演示。

          首先,我們來看看Requests 測試代碼。這里用3種方式發(fā)送了 JSON 格式的數(shù)據(jù):

          import requests 
          import json 

          body = {
              'name''青南',
              'age'20
          }
          url = 'http://127.0.0.1:5000/test_json'

          # 直接使用 json=的方式發(fā)送
          resp = requests.post(url, json=body).json() 
          print(resp)

          headers = {
              'Content-Type''application/json'
          }

          # 提前把字典序列化成 JSON 字符串,中文轉(zhuǎn)成 Unicode,跟第一種方式等價
          resp = requests.post(url,
                               headers=headers,
                               data=json.dumps(body)).json()
          print(resp)

          # 提前把字典序列化成 JSON 字符串,中文保留
          resp = requests.post(url,
                               headers=headers,
                               data=json.dumps(body, ensure_ascii=False).encode()).json()
          print(resp)

          這段測試代碼使用3種方式發(fā)送 POST 請求,其中,第一種方法就是 Requests 自帶的json=參數(shù),參數(shù)值是一個字典。Requests 會自動把它轉(zhuǎn)成 JSON 字符串。后兩種方式,是我們手動提前把字典轉(zhuǎn)成 JSON 字符串,然后使用data=參數(shù)發(fā)送給服務(wù)器。這兩種方式需要在 Headers 里面指明'Content-Type': 'application/json',服務(wù)器才知道發(fā)上來的是 JSON 字符串。

          我們再來看看 Flask 寫的后端代碼:

          from flask import Flask, request
          app = Flask(__name__)


          @app.route('/')
          def index():
              return {'success'True}


          @app.route('/test_json', methods=["POST"])
          def test_json():
              body = request.json 
              msg = f'收到 POST 數(shù)據(jù),{body["name"]=}{body["age"]=}'
              print(msg)
              return {'success'True'msg': msg}

          運(yùn)行效果如下圖所示:

          可以看到,無論使用哪種 POST 方式,后端都能接收到正確的信息。

          我們再來看 Fastapi 版本:

          from fastapi import FastAPI
          from pydantic import BaseModel 


          class Body(BaseModel):
              name: str
              age: int 

          app = FastAPI()



          @app.get('/')
          def index():
              return {'success'True}


          @app.post('/test_json')
          def test_json(body: Body):
              msg = f'收到 POST 數(shù)據(jù),{body.name=}{body.age=}'
              print(msg)
              return {'success'True'msg': msg}

          運(yùn)行效果如下圖所示,三種 POST 發(fā)送的數(shù)據(jù),都能被后端正確識別:

          我們再來看看 Gin 版本的后端:

          package main

          import (
              "fmt"
              "net/http"

              "github.com/gin-gonic/gin"
          )

          type Body struct {
              Name string `json:"name"`
              Age  int16  `json:"age"`
          }

          func main() {
              r := gin.Default()
              r.GET("/"func(c *gin.Context) {
                  c.JSON(http.StatusOK, gin.H{
                      "message""running",
                  })
              })
              r.POST("/test_json"func(c *gin.Context) {
                  json := Body{}
                  c.BindJSON(&json)
                  msg := fmt.Sprintf("收到 POST 數(shù)據(jù),name=%s, age=%d", json.Name, json.Age)
                  fmt.Println(">>>", msg)
                  c.JSON(http.StatusOK, gin.H{
                      "msg": fmt.Sprintf("收到 POST 數(shù)據(jù),name=%s, age=%d", json.Name, json.Age),
                  })
              })
              r.Run()
          }

          運(yùn)行效果如下,三種請求方式的數(shù)據(jù)完全相同:

          從這里可以知道,無論我們 POST 提交的 JSON 字符串中,中文是以 Unicode 碼的形式存在還是直接以漢字的形式存在,后端服務(wù)都可以正確解析。

          為什么我說中文在 JSON 字符串里面以哪種形式顯示并不重要呢?這是因為,對 JSON 字符串來說,編程語言把它重新轉(zhuǎn)換為對象的過程(叫做反序列化),本身就可以正確處理他們。我們來看下圖:

          ensure_ascii參數(shù)的作用,僅僅控制的是 JSON 的顯示樣式,當(dāng)ensure_asciiTrue的時候,確保 JSON 字符串里面只有 ASCII 字符,所以不在 ASCII 128個字符內(nèi)的字符,都會被轉(zhuǎn)換。而當(dāng)ensure_asciiFalse的時候,這些非 ASCII 字符依然以原樣顯示。這就像是一個人化妝和不化妝一樣,本質(zhì)并不會改變?,F(xiàn)代化的編程語言在對他們進(jìn)行反序列化的時候,兩種形式都能正確識別。

          所以,如果你是用現(xiàn)代化的 Web 框架來寫后端,那么這兩種 JSON 形式應(yīng)該是沒有任何區(qū)別的。Request 默認(rèn)的json=參數(shù),相當(dāng)于ensure_ascii=True,任何現(xiàn)代化的 Web 框架都能正確識別 POST 提交上來的內(nèi)容。

          當(dāng)然,如果你使用的是 C 語言、匯編或者其他語言來裸寫后端接口,那確實可能有所差別??芍巧陶5娜耍l會這樣做?

          綜上所述,這位同學(xué)遇到的問題,并不是 Requests 的 bug,而是他的后端接口本身有問題??赡苣莻€后端使用了某種弱智 Web 框架,它接收到的被 POST 發(fā)上來的信息,沒有經(jīng)過反序列化,就是一段 JSON 字符串,而那個后端程序員使用正則表達(dá)式從 JSON 字符串里面提取數(shù)據(jù),所以當(dāng)發(fā)現(xiàn) JSON 字符串里面沒有中文的時候,就報錯了。

          除了這個 POST 發(fā)送 JSON 的問題,以前我有個下屬,在使用 Scrapy 發(fā)送 POST 信息的時候,由于不會寫POST 的代碼,突發(fā)奇想,把 POST 發(fā)送的字段拼接到 URL 上,然后用 GET 方式請求,發(fā)現(xiàn)也能獲取數(shù)據(jù),類似于:

          body = {'name''青南''age'20}
          url = 'http://www.xxx.com/api/yyy'
          requests.post(url, json=body).text

          requests.get('http://www.xxx.com/api/yyy?name=青南&age=20').text

          于是,這個同學(xué)得出一個結(jié)論,他認(rèn)為這是一個普遍的規(guī)律,所有 POST 的請求都可以這樣轉(zhuǎn)到 GET 請求。

          但顯然,這個結(jié)論也是不正確的。這只能說明,這個網(wǎng)站的后端程序員,讓這個接口能同時兼容兩種提交數(shù)據(jù)的方式,這是需要后端程序員額外寫代碼來實現(xiàn)的。在默認(rèn)情況下,GET 和 POST 是兩種完全不同的請求方式,也不能這樣轉(zhuǎn)換。

          如果這位同學(xué)會一些簡單的后端,那么他立刻就可以寫一個后端程序來驗證自己的猜想。

          再來一個例子,有一些網(wǎng)站,他們在 URL 中可能會包含另外一個 URL,例如:

          https://kingname.info/get_info?url=https://abc.com/def/xyz?id=123&db=admin

          如果你沒有基本的后端知識,那么你可能看不出上面的網(wǎng)址有什么問題。但是如果你有一些基本的后端常識,那么你可能會問一個問題:網(wǎng)址中的&db=admin,是屬于https://kingname.info/get_info的一個參數(shù),跟url=平級;還是屬于https://abc.com/def/xyz?id=123&db=admin的參數(shù)?你會疑惑,后端也會疑惑,所以這就是為什么我們這個時候需要 urlencode 的原因,畢竟下面兩種寫法,是完全不一樣的:

          https://kingname.info/get_info?url=https%3A%2F%2Fabc.com%2Fdef%2Fxyz%3Fid%3D123%26db%3Dadmin

          https://kingname.info/get_info?url=https%3A%2F%2Fabc.com%2Fdef%2Fxyz%3Fid%3D123&db=admin

          最后,以我的爬蟲書序言中的一句話來作為總結(jié):

          爬蟲是一門雜學(xué),如果你只會爬蟲,那么你是學(xué)不好爬蟲的。

          快進(jìn)來看王冰冰!Python+Flask寫了一個學(xué)習(xí)提醒系統(tǒng)


          使用FastAPI重寫Django官網(wǎng)Polls教程



          瀏覽 53
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  成人黄色电影在线视频 | 在线播放视频一区 | 成人禁区 | 欧美性猛交XXXX乱大交HD | 中文字幕无码综合 |