第七色在线视频,2021少妇久久久久久久久久,亚洲欧洲精品成人久久av18,亚洲国产精品特色大片观看完整版,孙宇晨将参加特朗普的晚宴

首頁 慕課教程 Scrapy 入門教程 Scrapy 入門教程 深入分析 Scrapy 下載器原理

深入分析 Scrapy 下載器原理

今天我們來完整分析下 Scrapy 中下載器模塊的代碼,深入理解下載器的實(shí)現(xiàn)原理以及用到的 Twisted 相關(guān)模塊。本節(jié)的內(nèi)容會(huì)有些枯燥,請(qǐng)耐心閱讀下去。

1. Twisted 中的 Web Client 模塊

本小節(jié)內(nèi)容主要參考官方文檔對(duì)于 Web Client 模塊的介紹,也就是文獻(xiàn)1。這部分內(nèi)容正是 Scrapy 下載器的核心,為了能更好的理解下載器,我們需要先學(xué)習(xí)下 Twisted 中的這塊內(nèi)容。

1.1 發(fā)出請(qǐng)求

注意 twisted.web.client.Agent 這個(gè)類,它是客戶端 API 的入口點(diǎn),請(qǐng)求是使用 request() 方法發(fā)出的,該方法以請(qǐng)求方法、請(qǐng)求URI、請(qǐng)求頭和可以生成請(qǐng)求體的對(duì)象作為參數(shù)。代理負(fù)責(zé)連接設(shè)置。因此,它需要一個(gè) reactor 作為初始值設(shè)定項(xiàng)的參數(shù)。來看官方給的第一個(gè)簡單例子:

from __future__ import print_function

from twisted.internet import reactor
from twisted.web.client import Agent
from twisted.web.http_headers import Headers

agent = Agent(reactor)

d = agent.request(
    b'GET',
    b'http://idcbgp.cn/wiki/',
    Headers({'User-Agent': ['Twisted Web Client Example']}),
    None)

def cbResponse(ignored):
    print('Response received')
d.addCallback(cbResponse)

# 關(guān)閉reactor()
def cbShutdown(ignored):
    reactor.stop()
d.addBoth(cbShutdown)

reactor.run()

上述代碼簡單實(shí)例化一個(gè) agent,然后調(diào)用 request() 方法請(qǐng)求 http://idcbgp.cn/wiki/ 這個(gè)地址,這個(gè)動(dòng)作也是一個(gè)延遲加載的方式;接下來的回調(diào)鏈中還會(huì)有請(qǐng)求完成后打印收到響應(yīng)的方法以及最后關(guān)閉 reactor 的方法;執(zhí)行的結(jié)果如下:

[root@server2 scrapy-test]# python3 request.py 
Response received

如果想要給請(qǐng)求帶上參數(shù),就需要傳遞一個(gè) twisted.web.iweb.IBodyProducer 類型的對(duì)象到 Agent.request。我們繼續(xù)來學(xué)習(xí)官方給出的第二個(gè)例子:

下面的代碼給出了一個(gè)簡單的 IBodyProducer 實(shí)現(xiàn),它向使用者寫入內(nèi)存中的字符串

# 代碼文件命名為:bytesprod.py 

from zope.interface import implementer

from twisted.internet.defer import succeed
from twisted.web.iweb import IBodyProducer

@implementer(IBodyProducer)
class BytesProducer(object):
    def __init__(self, body):
        self.body = body
        self.length = len(body)

    def startProducing(self, consumer):
        consumer.write(self.body)
        return succeed(None)

    def pauseProducing(self):
        pass

    def stopProducing(self):
        pass

下面的代碼則在請(qǐng)求中帶上了 body 體:

# 代碼文件:sendbody.py

from twisted.internet import reactor
from twisted.web.client import Agent
from twisted.web.http_headers import Headers

from bytesprod import BytesProducer

agent = Agent(reactor)
# 構(gòu)造請(qǐng)求體
body = BytesProducer(b"hello, world")
d = agent.request(
    b'POST',
    b'http://httpbin.org/post',
    Headers({'User-Agent': ['Twisted Web Client Example'],
             'Content-Type': ['text/x-greeting']}),
    # 帶上body
    body)

# 回調(diào)鏈,收到上個(gè)request的請(qǐng)求響應(yīng)
def cbResponse(ignored):
    print('Response received')
d.addCallback(cbResponse)

# 關(guān)閉reactor
def cbShutdown(ignored):
    reactor.stop()
d.addBoth(cbShutdown)

reactor.run()

1.2 接收響應(yīng)

接下來一個(gè)內(nèi)容就是關(guān)于數(shù)據(jù)的接收。前面的代碼都只有請(qǐng)求,沒有接收響應(yīng)數(shù)據(jù)。如果 Agent.request 請(qǐng)求成功,則 Deferred 將觸發(fā)一個(gè)響應(yīng)。一旦收到所有響應(yīng)頭,就會(huì)發(fā)生這種情況。它發(fā)生在處理任何響應(yīng)體 (如果有)之前。Response 對(duì)象有一個(gè)使響應(yīng)體可用的方法:deliverBody,接下來我們給出一個(gè)使用實(shí)例:

from __future__ import print_function

from pprint import pformat

from twisted.internet import reactor
from twisted.internet.defer import Deferred
from twisted.internet.protocol import Protocol
from twisted.web.client import Agent
from twisted.web.http_headers import Headers

# 繼承Protocol
class BeginningPrinter(Protocol):
    def __init__(self, finished):
        self.finished = finished
        self.remaining = 1024 * 10

    def dataReceived(self, bytes):
        """
        響應(yīng)的數(shù)據(jù)從該方法獲取,最終獲取的數(shù)據(jù)大小不超過self.remaining 
        """
        if self.remaining:
            display = bytes[:self.remaining]
            print('Some data received:')
            print(str(display, encoding='utf8'))
            self.remaining -= len(display)

    def connectionLost(self, reason):
        print('Finished receiving body:', reason.getErrorMessage())
        self.finished.callback(None)

# 獲取agent實(shí)例,傳入reactor        
agent = Agent(reactor)
# 請(qǐng)求慕課網(wǎng)wiki
d = agent.request(
    b'GET',
    b'http://idcbgp.cn/wiki/',
    Headers({'User-Agent': ['Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36']}),
    None)

def cbRequest(response):
    finished = Deferred()
    # 獲取響應(yīng)數(shù)據(jù)
    response.deliverBody(BeginningPrinter(finished))
    return finished
# 加入回調(diào)鏈
d.addCallback(cbRequest)

def cbShutdown(ignored):
    reactor.stop()
d.addBoth(cbShutdown)

reactor.run()

我們可以直接來看看這個(gè):

其實(shí)仔細(xì)分析 Scrapy 下載器源碼我們可以在 scrapy/core/downloader/handlers/http11.py 的代碼中看到如下非常類似的代碼:

圖片描述

http11.py中的部分代碼

2. Scrapy 中下載器模塊源碼分析

2.1 下載器的__init__() 方法分析

我們直接看源碼目錄,關(guān)于下載器模塊的代碼全在 scrapy/core/downloader 目錄下:

圖片描述

下載器模塊全部代碼

其中主要的核心代碼就是這個(gè) __init__.py 文件,其中定義了 Downloader 這個(gè)類,它就是我們的下載器。首先來看這個(gè)類的初始化過程:

# ...

class Downloader:

    DOWNLOAD_SLOT = 'download_slot'

    def __init__(self, crawler):
        # 獲取配置
        self.settings = crawler.settings
        self.signals = crawler.signals
        self.slots = {}
        self.active = set()
        # 獲取handlers
        self.handlers = DownloadHandlers(crawler)
        # 獲取配置中的請(qǐng)求并發(fā)數(shù)
        self.total_concurrency = self.settings.getint('CONCURRENT_REQUESTS')
        # 每個(gè)域的請(qǐng)求并發(fā)數(shù)
        self.domain_concurrency = self.settings.getint('CONCURRENT_REQUESTS_PER_DOMAIN')
        # ip并發(fā)請(qǐng)求數(shù)
        self.ip_concurrency = self.settings.getint('CONCURRENT_REQUESTS_PER_IP')
        # 是否設(shè)置隨機(jī)延遲
        self.randomize_delay = self.settings.getbool('RANDOMIZE_DOWNLOAD_DELAY')
        # 中間件管理器
        self.middleware = DownloaderMiddlewareManager.from_crawler(crawler)
        self._slot_gc_loop = task.LoopingCall(self._slot_gc)
        self._slot_gc_loop.start(60)

從這個(gè)初始化的過程中,我們要重點(diǎn)關(guān)注幾個(gè)屬性值:

  • settings:對(duì)應(yīng)的 scrapy 項(xiàng)目配置;

  • handlers:實(shí)例化 DownloadHandlers, 而這個(gè)類則是網(wǎng)頁下載的最終處理類;

  • 各種配置屬性:從 settings.py 中獲取控制并發(fā)的參數(shù);

  • middleware:下載中間件的管理器,在下一節(jié)中我們會(huì)詳細(xì)分析這個(gè)屬性;

2.2 下載器實(shí)例的 handlers 屬性值分析

重點(diǎn)先看看這個(gè) handlers 屬性值,它是 DownloadHandlers 類的一個(gè)實(shí)例,對(duì)應(yīng)的代碼位置為:scrapy/core/downloader/handlers/__init__.py 。其類定義如下:

# ...

class DownloadHandlers:

    def __init__(self, crawler):
        self._crawler = crawler
        self._schemes = {}  # stores acceptable schemes on instancing
        self._handlers = {}  # stores instanced handlers for schemes
        self._notconfigured = {}  # remembers failed handlers
        handlers = without_none_values(
            crawler.settings.getwithbase('DOWNLOAD_HANDLERS'))
        for scheme, clspath in handlers.items():
            self._schemes[scheme] = clspath
            self._load_handler(scheme, skip_lazy=True)

        crawler.signals.connect(self._close, signals.engine_stopped)

    def _get_handler(self, scheme):
        """Lazy-load the downloadhandler for a scheme
        only on the first request for that scheme.
        """
        if scheme in self._handlers:
            return self._handlers[scheme]
        if scheme in self._notconfigured:
            return None
        if scheme not in self._schemes:
            self._notconfigured[scheme] = 'no handler available for that scheme'
            return None

        return self._load_handler(scheme)

    def _load_handler(self, scheme, skip_lazy=False):
        path = self._schemes[scheme]
        try:
            dhcls = load_object(path)
            if skip_lazy and getattr(dhcls, 'lazy', True):
                return None
            dh = create_instance(
                objcls=dhcls,
                settings=self._crawler.settings,
                crawler=self._crawler,
            )
        except NotConfigured as ex:
            self._notconfigured[scheme] = str(ex)
            return None
        except Exception as ex:
            # ...
        else:
            self._handlers[scheme] = dh
            return dh

    def download_request(self, request, spider):
        scheme = urlparse_cached(request).scheme
        handler = self._get_handler(scheme)
        if not handler:
            raise NotSupported("Unsupported URL scheme '%s': %s" %
                               (scheme, self._notconfigured[scheme]))
        return handler.download_request(request, spider)

    @defer.inlineCallbacks
    def _close(self, *_a, **_kw):
        # ...

我們分別來解析這個(gè)類中定義的方法,都是非常重要的。此外,每個(gè)方法含義明確,而且代碼精煉,我們對(duì)此一一進(jìn)行說明。

首先是 __init__() 方法,我們可以看到一個(gè)核心的語句:handlers = without_none_values(crawler.settings.getwithbase('DOWNLOAD_HANDLERS'))。其中 getwithbase() 方法的定義位于 scapy/settings/__init__.py 文件中,其內(nèi)容如下:

# 源碼位置:scrapy/settings/__init__.py
# ...

class BaseSettings(MutableMapping):
    # ...
    
    def getwithbase(self, name):
        """Get a composition of a dictionary-like setting and its `_BASE`
        counterpart.

        :param name: name of the dictionary-like setting
        :type name: string
        """
        compbs = BaseSettings()
        compbs.update(self[name + '_BASE'])
        compbs.update(self[name])
        return compbs
    
    # ...

從上面的代碼中,可以知道 crawler.settings.getwithbase('DOWNLOAD_HANDLERS') 語句其實(shí)是獲取了 settings.py 配置中的 DOWNLOAD_HANDLERSDOWNLOAD_HANDLERS_BASE 值,并返回包含這兩個(gè)屬性值的 BaseSettings 實(shí)例。我們從默認(rèn)的 scrapy/settings/default_settings.py 中可以看到這兩個(gè)屬性值如下:

# 源碼位置:scrapy/settings/default_settings.py

DOWNLOAD_HANDLERS = {}
DOWNLOAD_HANDLERS_BASE = {
    'data': 'scrapy.core.downloader.handlers.datauri.DataURIDownloadHandler',
    'file': 'scrapy.core.downloader.handlers.file.FileDownloadHandler',
    'http': 'scrapy.core.downloader.handlers.http.HTTPDownloadHandler',
    'https': 'scrapy.core.downloader.handlers.http.HTTPDownloadHandler',
    's3': 'scrapy.core.downloader.handlers.s3.S3DownloadHandler',
    'ftp': 'scrapy.core.downloader.handlers.ftp.FTPDownloadHandler',
}

通常我們項(xiàng)目中的下載請(qǐng)求一般是 http 或者 https,對(duì)應(yīng)的都是 scrapy.core.downloader.handlers.http.HTTPDownloadHandler 這個(gè)位置的 handler,從這里也可以看到 scrapy 框架其實(shí)是支持非常多協(xié)議下載的,比如 s3 下載、文件下載以及 ftp下載等。緊接著的兩句就是在設(shè)置 self._schemesself._handlers 的值。其中 self._handlers 會(huì)加載對(duì)應(yīng) Handler 類:

    def _load_handler(self, scheme, skip_lazy=False):
        # 獲取協(xié)議對(duì)應(yīng)的handler類路徑,比如 ftp協(xié)議對(duì)應(yīng)著的handler類為
        # scrapy.core.downloader.handlers.ftp.FTPDownloadHandler
        path = self._schemes[scheme]
        try:
            # 獲取相應(yīng)的handler類,非字符串形式
            dhcls = load_object(path)
            # 默認(rèn)__init__()方法調(diào)用時(shí)參數(shù)skip_lazy為True,我們需要檢查對(duì)應(yīng)handler類中的lazy屬性值,沒有設(shè)置時(shí)默認(rèn)為True;如果handler類中l(wèi)azy屬性設(shè)置為True或者不設(shè)置,則該handler不會(huì)加入到self._handlers中
            if skip_lazy and getattr(dhcls, 'lazy', True):
                return None
            # 創(chuàng)建相應(yīng)的handler類實(shí)例,需要的參數(shù)為:
            dh = create_instance(
                objcls=dhcls,
                settings=self._crawler.settings,
                crawler=self._crawler,
            )
        except NotConfigured as ex:
            # ...
        except Exception as ex:
            # ...
        else:
            # 設(shè)置self._handlers
            self._handlers[scheme] = dh
            return dh

上面的注釋已經(jīng)非常清楚,在眾多 handlers 中,只有 S3DownloadHandler 類中沒有設(shè)置 lazy。所以在 __init__() 方法執(zhí)行完后,self._handlers 中不會(huì)有鍵 “s3” 及其對(duì)應(yīng)的實(shí)例。

_get_handler() 就非常明顯了,由于我們?cè)诔跏蓟椒?__init__() 中已經(jīng)得到了 self._handlers,此時(shí)該方法就是根據(jù)傳入的協(xié)議獲取相應(yīng) Handler 類的實(shí)例。 例如傳入的 scheme="http",則返回就是 HTTPDownloadHandler 類的一個(gè)實(shí)例。

最后一個(gè) download_request() 方法可以說是下載網(wǎng)頁的核心調(diào)用方法。我們來逐步分析該方法中的語句:

scheme = urlparse_cached(request).scheme
handler = self._get_handler(scheme)

上面兩句是獲取對(duì)應(yīng)的下載請(qǐng)求的 Handler 實(shí)例,比較容易理解。接下來的 return 就是調(diào)用 handler 實(shí)例中的 download_request() 方法按照對(duì)應(yīng)協(xié)議方式下載請(qǐng)求數(shù)據(jù):

return handler.download_request(request, spider)

每個(gè) handler 類都會(huì)有對(duì)應(yīng)的 download_request() 方法。我們重點(diǎn)看看 httphttps 協(xié)議對(duì)應(yīng)的 handler 類:

# 源碼位置:scrapy/core/downloader/handlers/http.py
from scrapy.core.downloader.handlers.http10 import HTTP10DownloadHandler
from scrapy.core.downloader.handlers.http11 import (
    HTTP11DownloadHandler as HTTPDownloadHandler,
)

可以看到,這里的 HTTPDownloadHandler 類實(shí)際上是 http11.py 中的 HTTP11DownloadHandler 類。這里面內(nèi)容有點(diǎn)多,我們簡要地分析下:

# 源碼位置:scrapy/core/downloader/handlers/http11.py
# ...

class HTTP11DownloadHandler:
    # ...
    
    def download_request(self, request, spider):
        """Return a deferred for the HTTP download"""
        agent = ScrapyAgent(
            contextFactory=self._contextFactory,
            pool=self._pool,
            maxsize=getattr(spider, 'download_maxsize', self._default_maxsize),
            warnsize=getattr(spider, 'download_warnsize', self._default_warnsize),
            fail_on_dataloss=self._fail_on_dataloss,
            crawler=self._crawler,
        )
        return agent.download_request(request)
    
    # ...

是不是挺簡單的兩條語句:得到 agent,然后調(diào)用 agent.download_request() 方法得到請(qǐng)求的結(jié)果?我們繼續(xù)追蹤這個(gè) ScrapyAgent 類:

# 源碼位置:scrapy/core/downloader/handlers/http11.py
# ...
from twisted.web.client import Agent, HTTPConnectionPool, ResponseDone, ResponseFailed, URI
# ...

class ScrapyAgent:

    _Agent = Agent
    # ...
    
    def _get_agent(self, request, timeout):
        from twisted.internet import reactor
        bindaddress = request.meta.get('bindaddress') or self._bindAddress
        # 從請(qǐng)求的meta參數(shù)中獲取proxy
        proxy = request.meta.get('proxy')
        if proxy:
            # 有代理的情況
            # ...
        
        # 沒有代理返回Agent的一個(gè)實(shí)例
        return self._Agent(
            reactor=reactor,
            contextFactory=self._contextFactory,
            connectTimeout=timeout,
            bindAddress=bindaddress,
            pool=self._pool,
        )
    
    def download_request(self, request):
        from twisted.internet import reactor
        # 從meta中獲取下載超時(shí)時(shí)間
        timeout = request.meta.get('download_timeout') or self._connectTimeout
        # 獲取agent
        agent = self._get_agent(request, timeout)

        # request details
        url = urldefrag(request.url)[0]
        method = to_bytes(request.method)
        headers = TxHeaders(request.headers)
        if isinstance(agent, self._TunnelingAgent):
            headers.removeHeader(b'Proxy-Authorization')
        if request.body:
            bodyproducer = _RequestBodyProducer(request.body)
        else:
            bodyproducer = None
        start_time = time()
        # 使用agent發(fā)起請(qǐng)求
        d = agent.request(method, to_bytes(url, encoding='ascii'), headers, bodyproducer)
        # set download latency
        d.addCallback(self._cb_latency, request, start_time)
        # response body is ready to be consumed
        d.addCallback(self._cb_bodyready, request)
        d.addCallback(self._cb_bodydone, request, url)
        # check download timeout
        self._timeout_cl = reactor.callLater(timeout, d.cancel)
        d.addBoth(self._cb_timeout, request, url, timeout)
        return d

看完上面的代碼就應(yīng)該比較清楚了,我們先不考慮代理的相關(guān)代碼。直接看 _get_agent() 方法就是獲取 twisted 模塊中 Agent 的一個(gè)實(shí)例,然后通過這個(gè) agent 去請(qǐng)求網(wǎng)頁 (在 download_request() 方法中完成 ),最后返回的仍然是一個(gè) Deferred 對(duì)象。在 download_request() 中的代碼就和我們?cè)诘谝恍」?jié)中介紹的類似,這里便是 Scrapy 最后完成網(wǎng)頁抓取的地方,就是基于 Twisted 的 web client 部分的方法。其中,最核心的一行語句就是:

d = agent.request(method, to_bytes(url, encoding='ascii'), headers, bodyproducer)

這個(gè)分析已經(jīng)走到了 Scrapy 下載器的最后一層,在往下就是研究 Twisted 框架的源碼了。同學(xué)們有興趣的話,可以繼續(xù)追蹤下去,對(duì)本節(jié)課而言,追蹤到此已經(jīng)結(jié)束了。我們跳出 handlers,繼續(xù)研究 Downloader 類。

2.3 下載器的 _download() 分析

我們回到 Downloader 類上繼續(xù)學(xué)習(xí),該下載器類中最核心的有如下三個(gè)方法:

  • _enqueue_request():請(qǐng)求入隊(duì);
  • _process_queue():處理隊(duì)列中的請(qǐng)求;
  • _download():下載網(wǎng)頁;

從代碼中很明顯可以看到,三個(gè)函數(shù)的關(guān)系如下:

圖片描述

Downloader中三個(gè)核心函數(shù)關(guān)系

我們來重點(diǎn)看看這個(gè) _download() 方法:

圖片描述

_download()方法分析

看圖中注釋部分,_download() 方法先創(chuàng)建一個(gè)下載的 deferred,注意這里的方法正是 self.handlersdownload_request() 方法,這是網(wǎng)頁下載的主要語句。 接下來又使用 deferred 的 addCallback() 方法添加一個(gè)回調(diào)函數(shù):_downloaded()。很明顯,_downloaded() 就是下載完成后調(diào)用的方法,其中 response 就是下載的結(jié)果,也就是后續(xù)會(huì)返回給 spider 中的 parse() 方法的那個(gè)。我們可以簡單做個(gè)實(shí)驗(yàn),看看是不是真的會(huì)在這里打印出響應(yīng)的結(jié)果:

創(chuàng)建一個(gè)名為 test_downloader 的 scrapy 的項(xiàng)目:

[root@server2 scrapy-test]# scrapy startproject test_downloader

生成一個(gè)名為 downloader 的 spider:

# 進(jìn)入到spider目錄
[root@server2 scrapy-test]# cd test_downloader/test_downloader/spiders/
# 新建一個(gè)spider文件
[root@server2 spiders]# scrapy genspider downloader idcbgp.cn/wiki/
[root@server2 spiders]# cat downloader.py 
import scrapy


class DownloaderSpider(scrapy.Spider):
    name = 'downloader'
    allowed_domains = ['idcbgp.cn/wiki/']
    start_urls = ['http://idcbgp.cn/wiki/']

    def parse(self, response):
        pass

我們添加幾個(gè)配置,將 scrapy 的日志打到文件中,避免影響我們打印一些結(jié)果:

# test_download/settings.py
# ...

#是否啟動(dòng)日志記錄,默認(rèn)True
LOG_ENABLED = True 
LOG_ENCODING = 'UTF-8'
#日志輸出文件,如果為NONE,就打印到控制臺(tái)
LOG_FILE = 'downloader.log'
#日志級(jí)別,默認(rèn)DEBUG
LOG_LEVEL = 'INFO'
# 日志日期格式 
LOG_DATEFORMAT = "%Y-%m-%d %H:%M:%S"
#日志標(biāo)準(zhǔn)輸出,默認(rèn)False,如果True所有標(biāo)準(zhǔn)輸出都將寫入日志中,比如代碼中的print輸出也會(huì)被寫入到
LOG_STDOUT = False

最重要的步驟來啦,我們?cè)?scrapy 的源碼 scrapy/core/downloader/__init__.py 的中添加一些代碼,用于查看下載器獲取的結(jié)果:

圖片描述

我們來對(duì)添加的這部分代碼進(jìn)行下說明:

# ...

class Downloader:
    # ...
    
    def _download(self, slot, request, spider):
        print('下載請(qǐng)求:{}, {}'.format(request.url, spider.name))
        # The order is very important for the following deferreds. Do not change!

        # 1. Create the download deferred
        dfd = mustbe_deferred(self.handlers.download_request, request, spider)

        # 2. Notify response_downloaded listeners about the recent download
        # before querying queue for next request
        def _downloaded(response):
            self.signals.send_catch_log(signal=signals.response_downloaded,
                                        response=response,
                                        request=request,
                                        spider=spider)
            ###############################新增代碼########################################
            print('__downloaded()中 response 結(jié)果類型:{}'.format(type(response)))
            import gzip
            from io import BytesIO
            from scrapy.http.response.text import TextResponse
            if isinstance(response, TextResponse):
                text = response.text
            else:
                # 解壓縮文本,這部分會(huì)在后續(xù)的下載中間件中被處理,傳給parse()方法時(shí)會(huì)變成解壓后的數(shù)據(jù)
                f = gzip.GzipFile(fileobj = BytesIO(response.body))
                text = f.read().decode('utf-8')
            print('得到結(jié)果:{}'.format(text[:3000]))
            ############################################################################
            
            return response

但就我們新建的項(xiàng)目而言,只是簡單的爬取慕課網(wǎng)的 wiki 頁面,獲取相應(yīng)的頁面數(shù)據(jù)。由于我們沒有禁止 robot 協(xié)議,所以項(xiàng)目第一次會(huì)爬取 /robots.txt 地址,檢查 wiki 頁面是否允許爬?。唤酉聛聿艜?huì)爬取 wiki 頁面。測(cè)試發(fā)現(xiàn),第一次請(qǐng)求 /robots.txt 地址時(shí),在 _downloaded() 中得到的結(jié)果直接就是 TextResponse 實(shí)例,我們可以用 response.text 方式直接拿到結(jié)果;但是第二次請(qǐng)求 http://idcbgp.cn/wiki/ 時(shí),返回的結(jié)果是經(jīng)過壓縮的,其結(jié)果的前三個(gè)字節(jié)碼為:b'\x1f\x8b\x08' 開頭的 ,說明它是 gzip 壓縮過的數(shù)據(jù)。為了能查看相應(yīng)的數(shù)據(jù),我們可以在這里解碼查看,對(duì)應(yīng)的就是上面的 else 部分代碼。我們現(xiàn)在來進(jìn)行演示:

3. 小結(jié)

本小節(jié)中我們?cè)敱M的剖析了 Scrapy 的下載器模塊,找出了最終請(qǐng)求網(wǎng)頁數(shù)據(jù)的方式。整個(gè)下載器的代碼不算特別復(fù)雜,主要是對(duì) Twisted 的 web client 模塊中的類和方法的進(jìn)一步封裝,我們可以通過參考文獻(xiàn)1來掌握該模塊的學(xué)習(xí)。當(dāng)掌握了 Twisted 的這些基礎(chǔ)后,在來看 Scrapy 的代碼就會(huì)一目了然。下一小節(jié)我們將繼續(xù)剖析 Scrapy 的中間件模塊,進(jìn)一步學(xué)習(xí) Scrapy 框架源碼。

4. 參考文獻(xiàn)