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

Scrapy 中的 Request 和 Response

今天我們來介紹 Scrapy 框架給我們提供的 Request 和 Response 類,通過深入分析源碼找出它的常用屬性和方法以及一些使用技巧。這一小節(jié)內(nèi)容主要是 Scrapy 框架中的基礎(chǔ)知識,后面我們會經(jīng)常用到這兩個類。熟悉和掌握它們的源碼實現(xiàn),對我們在后續(xù)使用它們時會有巨大的幫助。

1. Request 類

首先 Scrapy 中關(guān)于 Request 相關(guān)的源碼位置如下:

圖片描述

scrapy 中 Request 相關(guān)的代碼

可以看到 Request 定義相關(guān)的代碼并不多,這也方便我們?nèi)W(xué)習(xí)和探索。先來看 Request 類的定義:

# 源碼位置:scrapy/http/request/__init__.py
from w3lib.url import safe_url_string

# ...
class Request(object_ref):

    def __init__(self, url, callback=None, method='GET', headers=None, body=None,
                 cookies=None, meta=None, encoding='utf-8', priority=0,
                 dont_filter=False, errback=None, flags=None, cb_kwargs=None):

        self._encoding = encoding  # this one has to be set first
        self.method = str(method).upper()
        self._set_url(url)
        self._set_body(body)
        if not isinstance(priority, int):
            raise TypeError("Request priority not an integer: %r" % priority)
        self.priority = priority

        if callback is not None and not callable(callback):
            raise TypeError('callback must be a callable, got %s' % type(callback).__name__)
        if errback is not None and not callable(errback):
            raise TypeError('errback must be a callable, got %s' % type(errback).__name__)
        self.callback = callback
        self.errback = errback

        self.cookies = cookies or {}
        self.headers = Headers(headers or {}, encoding=encoding)
        self.dont_filter = dont_filter

        self._meta = dict(meta) if meta else None
        self._cb_kwargs = dict(cb_kwargs) if cb_kwargs else None
        self.flags = [] if flags is None else list(flags)
        
    
    # ...

從上面的源碼中可以看到 Scrapy 框架使用了 w3lib 模塊來完成一些 Web 相關(guān)的功能,這里用到了 url 模塊的相關(guān)功能。safe_url_string() 方法是將 url 轉(zhuǎn)成合法的形式,也就是將一些特殊字符比如中文、空格等進(jìn)行想要的編碼。來看下面的例子:

>>> from w3lib.url import safe_url_strin
>>> url = "http://www.baidu.com/?xxx= zyz"
>>> safe_url_string(url)
'http://www.baidu.com/?xxx=%20zyz'

最后得到的 URL 形式和我們在瀏覽器按下 Enter 鍵時一致。此外,對于 Request 類實例化時可以傳入多種初始屬性,常用的屬性含義如下:

  • url:請求地址;
  • method:請求類型,GET|POST|PUT|DELETE 等;
  • callback: HTTP 請求的回調(diào)方法,用于指定該 HTTP 請求的解析響應(yīng)數(shù)據(jù)的方法;
  • headers: 設(shè)置請求頭。一般而言時設(shè)置請求頭的 User-Agent 字段,模擬瀏覽器請求;
  • body: 用于設(shè)置請求參數(shù),比如登錄請求需要帶上用戶名/密碼等參數(shù);
  • cookies: 請求 cookies 信息,一般和登錄認(rèn)證相關(guān),帶上 cookies 用于表明身份信息。

熟悉了這個 Request 類后,我們來看一些在 Request 基礎(chǔ)上進(jìn)一步擴(kuò)展的請求類。其中一個是 FormRequest

# 源碼位置:scrapy/http/request/form.py
# ...

class FormRequest(Request):
    valid_form_methods = ['GET', 'POST']

    def __init__(self, *args, **kwargs):
        formdata = kwargs.pop('formdata', None)
        if formdata and kwargs.get('method') is None:
            kwargs['method'] = 'POST'

        super(FormRequest, self).__init__(*args, **kwargs)

        if formdata:
            items = formdata.items() if isinstance(formdata, dict) else formdata
            querystr = _urlencode(items, self.encoding)
            if self.method == 'POST':
                self.headers.setdefault(b'Content-Type', b'application/x-www-form-urlencoded')
                self._set_body(querystr)
            else:
                self._set_url(self.url + ('&' if '?' in self.url else '?') + querystr)
        
# ...

FormRequest 類主要用于提交表單請求,比如登錄認(rèn)證、比如提交訂單等。它只支持 GET 和 POST 請求,且相比 Request 類,F(xiàn)ormRequest 類多了一個表單參數(shù)屬性,這個是檢查提交表單請求的數(shù)據(jù)。來分析實例化時對表單參數(shù)的處理,代碼如下:

if formdata:
    items = formdata.items() if isinstance(formdata, dict) else formdata
    querystr = _urlencode(items, self.encoding)
    if self.method == 'POST':
        self.headers.setdefault(b'Content-Type', b'application/x-www-form-urlencoded')
        self._set_body(querystr)
    else:
        self._set_url(self.url + ('&' if '?' in self.url else '?') + querystr)
        
# ...

def _urlencode(seq, enc):
    values = [(to_bytes(k, enc), to_bytes(v, enc))
              for k, vs in seq
              for v in (vs if is_listlike(vs) else [vs])]
    return urlencode(values, doseq=1)

這個代碼的邏輯是非常清晰的,如果有表單數(shù)據(jù),會分成 GET 和 POST 請求處理:

  • GET 請求:將請求參數(shù)添加到 url 后面,用 “?” 連接,參數(shù)之間用 “&” 連接;
  • POST 請求:一方面設(shè)置請求的 header,另一方面將數(shù)據(jù)放到 body 體中;

還有兩個 JsonRequest 和 XmlRpcRequest 類,都是使用不同的形式來發(fā)送 HTTP 請求,我們來看兩個類中非常關(guān)鍵的幾行語句:

# 源碼位置:scrapy/http/request/json_request.py
# ...
class JsonRequest(Request):
    def __init__(self, *args, **kwargs):
        # ...
        
        if body_passed and data_passed:
            # ...
        elif not body_passed and data_passed:
            kwargs['body'] = self._dumps(data)

            if 'method' not in kwargs:
                kwargs['method'] = 'POST'

        super(JsonRequest, self).__init__(*args, **kwargs)
        self.headers.setdefault('Content-Type', 'application/json')
        self.headers.setdefault('Accept', 'application/json, text/javascript, */*; q=0.01')
    
    # ...

這里 JsonRequest 中主要講 data 數(shù)據(jù)轉(zhuǎn)成 json 格式,然后保存到 body 屬性中,然后設(shè)置了請求頭的 Content-Type 屬性為 “application/json”。

# 源碼位置:scrapy/http/request/rpc.py

import xmlrpc.client as xmlrpclib
# ...

class XmlRpcRequest(Request):

    def __init__(self, *args, **kwargs):
        # ...
        if 'body' not in kwargs and 'params' in kwargs:
            kw = dict((k, kwargs.pop(k)) for k in DUMPS_ARGS if k in kwargs)
            # 關(guān)鍵地方
            kwargs['body'] = xmlrpclib.dumps(**kw)

        # ...

XmlRpcRequest 用來發(fā)送 XML-RPC 請求,關(guān)鍵的地方在于請求數(shù)據(jù)設(shè)置,使用了 xmlrpc 模塊。

2. Respone 類

Response 類主要是封裝了前面請求的響應(yīng)結(jié)果,爬蟲的一個很重要的部分就是解析這些 Response,得到我們想要的結(jié)果。這一部分內(nèi)容我們就來深入分析 Response 類以及擴(kuò)展類。

圖片描述

Response類相關(guān)代碼

翻看源碼,我們可以得到如下信息:

  • __init__.py 中定義了 Response 基類;

  • text.pym 中定義的 TextResponse 類直接繼承 Response 類并進(jìn)行了一系列擴(kuò)展和重載部分方法;

  • html.pyxml.py 中分別定義的 HtmlResponse 和 XmlResponse 都只繼承了 TextResponse ,并未做過多的修改,只是分別取了個別名:

    # 源碼位置:scrapy/http/response/html.py
    from scrapy.http.response.text import TextResponse
    
    
    class HtmlResponse(TextResponse):
        pass
    
    
    # 源碼位置:scrapy/http/response/xml.py
    from scrapy.http.response.text import TextResponse
    
    
    class XmlResponse(TextResponse):
        pass
    

接下來我們的重點就是學(xué)習(xí) Response 類和 TextResponse 類。

Response 類有如下幾個常用屬性值:

  • headers:頭部信息;
  • status:返回狀態(tài)碼;
  • body:響應(yīng)內(nèi)容;
  • url:請求的 url;
  • request:對應(yīng)的 request 請求;
  • ip_address:請求的 ip 地址。

我們還是通過 Scrapy Shell 請求廣州鏈家二手房的地址來看看真實的 Response 并打印上述值:

(scrapy-test) [root@server ~]# scrapy shell https://gz.lianjia.com/ershoufang/ --nolog
[s] Available Scrapy objects:
[s]   scrapy     scrapy module (contains scrapy.Request, scrapy.Selector, etc)
[s]   crawler    <scrapy.crawler.Crawler object at 0x7f9890d5a100>
[s]   item       {}
[s]   request    <GET https://gz.lianjia.com/ershoufang/>
[s]   response   <200 https://gz.lianjia.com/ershoufang/>
[s]   settings   <scrapy.settings.Settings object at 0x7f9890d57bb0>
[s]   spider     <DefaultSpider 'default' at 0x7f989034dd90>
[s] Useful shortcuts:
[s]   fetch(url[, redirect=True]) Fetch URL and update local objects (by default, redirects are followed)
[s]   fetch(req)                  Fetch a scrapy.Request and update local objects 
[s]   shelp()           Shell help (print this help)
[s]   view(response)    View response in a browser
>>> response.headers
{b'Server': [b'Lianjia'], b'Date': [b'Sun, 12 Jul 2020 07:37:16 GMT'], b'Content-Type': [b'text/html; charset=UTF-8'], b'Vary': [b'Accept-Encoding'], b'Set-Cookie': [b'select_city=440100; expires=Mon, 13-Jul-2020 07:37:16 GMT; Max-Age=86400; path=/; domain=.lianjia.com', b'lianjia_ssid=a0980b19-93f6-4942-a898-96ea722d524d; expires=Sun, 12-Jul-20 08:07:16 GMT; Max-Age=1800; domain=.lianjia.com; path=/', b'lianjia_uuid=12165c9c-6c66-4996-9e2c-623a838efd4a; expires=Wed, 10-Jul-30 07:37:16 GMT; Max-Age=315360000; domain=.lianjia.com; path=/'], b'Via': [b'web05-online.zeus.ljnode.com']}
>>> response.status
200
>>> response.url
'https://gz.lianjia.com/ershoufang/'
>>> response.ip_address
IPv4Address('211.159.232.241')
>>> 
>>> response.request
<GET https://gz.lianjia.com/ershoufang/>
>>> 

注意:關(guān)于這個 response,我們前面在分析 scrapy shell [url] 命令的執(zhí)行過程中說過,如果命令后面帶上要爬取的 URL 地址,那么在交互式的 shell 生成前,會將一些得到的基本的環(huán)境變量包括請求 URL 的響應(yīng)結(jié)果 (response) 放到該環(huán)境變量中,這就是為什么我們能在該交互模式下直接使用 response 獲取請求結(jié)果的原因。

來看看 Response 類中預(yù)留的一些方法:

# 源碼位置:scrapy/http/response/__init__.py
# ...

class Response(object_ref):
    def __init__(self, url, status=200, headers=None, body=b'', flags=None,
                 request=None, certificate=None, ip_address=None):
        self.headers = Headers(headers or {})
        self.status = int(status)
        self._set_body(body)
        self._set_url(url)
        self.request = request
        self.flags = [] if flags is None else list(flags)
        self.certificate = certificate
        self.ip_address = ip_address
        
    # ...
    
    
    @property
    def text(self):
        """For subclasses of TextResponse, this will return the body
        as str
        """
        raise AttributeError("Response content isn't text")

    def css(self, *a, **kw):
        """Shortcut method implemented only by responses whose content
        is text (subclasses of TextResponse).
        """
        raise NotSupported("Response content isn't text")

    def xpath(self, *a, **kw):
        """Shortcut method implemented only by responses whose content
        is text (subclasses of TextResponse).
        """
        raise NotSupported("Response content isn't text")
        
    # ...

上面這些預(yù)留的 text 屬性、css() 方法以及 xpath() 方法都會在 TextResponse 中有相應(yīng)的實現(xiàn)。接下來我們仔細(xì)分析 TextResponse 的這些屬性和的方法:

# 源碼位置: 

class TextResponse(Response):

    _DEFAULT_ENCODING = 'ascii'
    _cached_decoded_json = _NONE

    def __init__(self, *args, **kwargs):
        self._encoding = kwargs.pop('encoding', None)
        self._cached_benc = None
        self._cached_ubody = None
        self._cached_selector = None
        super(TextResponse, self).__init__(*args, **kwargs)
        
    # ...

__init__() 方法中可以看到,TextResponse 的屬性和父類基本沒變化,只是增加了一些用于緩存的屬性。接下來我們再看幾個重要的屬性和方法:

# ...

class TextResponse(Response):
    ....
    
    
    @property
    def text(self):
        """ Body as unicode """
        # access self.encoding before _cached_ubody to make sure
        # _body_inferred_encoding is called
        benc = self.encoding
        if self._cached_ubody is None:
            charset = 'charset=%s' % benc
            self._cached_ubody = html_to_unicode(charset, self.body)[1]
        return self._cached_ubody
    
    # ...

上面這段代碼的邏輯就是將 body 屬性中的值轉(zhuǎn)成 str,我們可以在 Scrapy Shell 模式下復(fù)現(xiàn)這一操作:

(scrapy-test) [root@server ~]# scrapy shell https://www.baidu.com --nolog
[s] Available Scrapy objects:
[s]   scrapy     scrapy module (contains scrapy.Request, scrapy.Selector, etc)
[s]   crawler    <scrapy.crawler.Crawler object at 0x7faf4f318190>
[s]   item       {}
[s]   request    <GET https://www.baidu.com>
[s]   response   <200 https://www.baidu.com>
[s]   settings   <scrapy.settings.Settings object at 0x7faf4f315b50>
[s]   spider     <DefaultSpider 'default' at 0x7faf4e9122b0>
[s] Useful shortcuts:
[s]   fetch(url[, redirect=True]) Fetch URL and update local objects (by default, redirects are followed)
[s]   fetch(req)                  Fetch a scrapy.Request and update local objects 
[s]   shelp()           Shell help (print this help)
[s]   view(response)    View response in a browser
>>> response.body
b'<!DOCTYPE html>\r\n<!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link rel=stylesheet type=text/css href=https://ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/cache/bdorz/baidu.min.css><title>\xe7\x99\xbe\xe5\xba\xa6\xe4\xb8\x80\xe4\xb8\x8b\xef\xbc\x8c\xe4\xbd\xa0\xe5\xb0\xb1\xe7\x9f\xa5\xe9\x81\x93</title></head> <body link=#0000cc> <div id=wrapper> <div id=head> <div class=head_wrapper> <div class=s_form> <div class=s_form_wrapper> <div id=lg> <img hidefocus=true src=//www.baidu.com/img/bd_logo1.png width=270 height=129> </div> <form id=form name=f action=//www.baidu.com/s class=fm> <input type=hidden name=bdorz_come value=1> <input type=hidden name=ie value=utf-8> <input type=hidden name=f value=8> <input type=hidden name=rsv_bp value=1> <input type=hidden name=rsv_idx value=1> <input type=hidden name=tn value=baidu><span class="bg s_ipt_wr"><input id=kw name=wd class=s_ipt value maxlength=255 autocomplete=off autofocus=autofocus></span><span class="bg s_btn_wr"><input type=submit id=su value=\xe7\x99\xbe\xe5\xba\xa6\xe4\xb8\x80\xe4\xb8\x8b class="bg s_btn" autofocus></span> </form> </div> </div> <div id=u1> <a href=http://news.baidu.com name=tj_trnews class=mnav>\xe6\x96\xb0\xe9\x97\xbb</a> <a href=https://www.hao123.com name=tj_trhao123 class=mnav>hao123</a> <a href=http://map.baidu.com name=tj_trmap class=mnav>\xe5\x9c\xb0\xe5\x9b\xbe</a> <a href=http://v.baidu.com name=tj_trvideo class=mnav>\xe8\xa7\x86\xe9\xa2\x91</a> <a href=http://tieba.baidu.com name=tj_trtieba class=mnav>\xe8\xb4\xb4\xe5\x90\xa7</a> <noscript> <a href=http://www.baidu.com/bdorz/login.gif?login&amp;tpl=mn&amp;u=http%3A%2F%2Fwww.baidu.com%2f%3fbdorz_come%3d1 name=tj_login class=lb>\xe7\x99\xbb\xe5\xbd\x95</a> </noscript> <script>document.write(\'<a + encodeURIComponent(window.location.href+ (window.location.search === "" ? "?" : "&")+ "bdorz_come=1")+ \'" name="tj_login" class="lb">\xe7\x99\xbb\xe5\xbd\x95</a>\');\r\n                </script> <a href=//www.baidu.com/more/ name=tj_briicon class=bri style="display: block;">\xe6\x9b\xb4\xe5\xa4\x9a\xe4\xba\xa7\xe5\x93\x81</a> </div> </div> </div> <div id=ftCon> <div id=ftConw> <p id=lh> <a href=http://home.baidu.com>\xe5\x85\xb3\xe4\xba\x8e\xe7\x99\xbe\xe5\xba\xa6</a> <a href=http://ir.baidu.com>About Baidu</a> </p> <p id=cp>&copy;2017&nbsp;Baidu&nbsp;<a href=http://www.baidu.com/duty/>\xe4\xbd\xbf\xe7\x94\xa8\xe7\x99\xbe\xe5\xba\xa6\xe5\x89\x8d\xe5\xbf\x85\xe8\xaf\xbb</a>&nbsp; <a href=http://jianyi.baidu.com/ class=cp-feedback>\xe6\x84\x8f\xe8\xa7\x81\xe5\x8f\x8d\xe9\xa6\x88</a>&nbsp;\xe4\xba\xacICP\xe8\xaf\x81030173\xe5\x8f\xb7&nbsp; <img src=//www.baidu.com/img/gs.gif> </p> </div> </div> </div> </body> </html>\r\n'
>>> type(response.body)
<class 'bytes'>
>>> from w3lib.encoding import html_to_unicode
>>> html_to_unicode("charset=None", response.body)
('utf-8', '<!DOCTYPE html>\r\n<!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link rel=stylesheet type=text/css href=https://ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/cache/bdorz/baidu.min.css><title>百度一下,你就知道</title></head> <body link=#0000cc> <div id=wrapper> <div id=head> <div class=head_wrapper> <div class=s_form> <div class=s_form_wrapper> <div id=lg> <img hidefocus=true src=//www.baidu.com/img/bd_logo1.png width=270 height=129> </div> <form id=form name=f action=//www.baidu.com/s class=fm> <input type=hidden name=bdorz_come value=1> <input type=hidden name=ie value=utf-8> <input type=hidden name=f value=8> <input type=hidden name=rsv_bp value=1> <input type=hidden name=rsv_idx value=1> <input type=hidden name=tn value=baidu><span class="bg s_ipt_wr"><input id=kw name=wd class=s_ipt value maxlength=255 autocomplete=off autofocus=autofocus></span><span class="bg s_btn_wr"><input type=submit id=su value=百度一下 class="bg s_btn" autofocus></span> </form> </div> </div> <div id=u1> <a href=http://news.baidu.com name=tj_trnews class=mnav>新聞</a> <a href=https://www.hao123.com name=tj_trhao123 class=mnav>hao123</a> <a href=http://map.baidu.com name=tj_trmap class=mnav>地圖</a> <a href=http://v.baidu.com name=tj_trvideo class=mnav>視頻</a> <a href=http://tieba.baidu.com name=tj_trtieba class=mnav>貼吧</a> <noscript> <a href=http://www.baidu.com/bdorz/login.gif?login&amp;tpl=mn&amp;u=http%3A%2F%2Fwww.baidu.com%2f%3fbdorz_come%3d1 name=tj_login class=lb>登錄</a> </noscript> <script>document.write(\'<a + encodeURIComponent(window.location.href+ (window.location.search === "" ? "?" : "&")+ "bdorz_come=1")+ \'" name="tj_login" class="lb">登錄</a>\');\r\n                </script> <a href=//www.baidu.com/more/ name=tj_briicon class=bri style="display: block;">更多產(chǎn)品</a> </div> </div> </div> <div id=ftCon> <div id=ftConw> <p id=lh> <a href=http://home.baidu.com>關(guān)于百度</a> <a href=http://ir.baidu.com>About Baidu</a> </p> <p id=cp>&copy;2017&nbsp;Baidu&nbsp;<a href=http://www.baidu.com/duty/>使用百度前必讀</a>&nbsp; <a href=http://jianyi.baidu.com/ class=cp-feedback>意見反饋</a>&nbsp;京ICP證030173號&nbsp; <img src=//www.baidu.com/img/gs.gif> </p> </div> </div> </div> </body> </html>\r\n')

可以看到 ,Response 中的 body 屬性值是 bytes 類型,通過 html_to_unicode() 方法可以將其轉(zhuǎn)成 str,然后我們得到的網(wǎng)頁文本就是 str 類型:

>>> text = html_to_unicode("charset=None", response.body)
>>> type(text[1])
<class 'str'>

接下來的這三個方法我們在上一節(jié)介紹過,正是由于有了這些屬性和方法,我們便可以使用 response.xpath() 或者 response.css() 這樣的寫法提取網(wǎng)頁數(shù)據(jù)。

# ...

class TextResponse(Response):
    ....
    
    @property
    def selector(self):
        from scrapy.selector import Selector
        if self._cached_selector is None:
            self._cached_selector = Selector(self)
        return self._cached_selector

    def xpath(self, query, **kwargs):
        return self.selector.xpath(query, **kwargs)

    def css(self, query):
        return self.selector.css(query)

TextResponse 類比較重要的屬性和方法就這些,其他的則需要自行深入去研究相關(guān)的方法及其作用。我們現(xiàn)在來解答上一節(jié)提出的問題:

為什么 Scrapy 的 TextResponse 實例可以使用這樣的表達(dá)式:response.xpath(...).extrat()[0] 或者 response.xpath(...).extrat_first()?

接下來我們帶著這個問題來繼續(xù)追蹤下代碼。我們以上一節(jié)的例子為例,打印 response.xpath() 的返回類型:

(scrapy-test) [root@server ~]# scrapy shell https://gz.lianjia.com/ershoufang/ --nolog
...
>>> data = response.xpath('//ul[@class="sellListContent"]/li/div/div[@class="title"]/a/text()')
>>> type(data)
<class 'scrapy.selector.unified.SelectorList'>
>>> 

可以看到結(jié)果是 SelectorList 實例,我們來看對應(yīng)定義的代碼:

# 源碼位置:scrapy/selector/unified.py

from parsel import Selector as _ParselSelector
# ...

class SelectorList(_ParselSelector.selectorlist_cls, object_ref):
    """
    The :class:`SelectorList` class is a subclass of the builtin ``list``
    class, which provides a few additional methods.
    """

它直接繼承的是 parsel 模塊中的 selectorlist_cls。繼續(xù)看這個值的定義:

# 源碼位置:parsel/selector.py

class Selector(object):
    # ...
    
    selectorlist_cls = SelectorList
    
    # ...
# 源碼位置:parsel/selector.py

class SelectorList(list):
    # ...

    def getall(self):
        """
        Call the ``.get()`` method for each element is this list and return
        their results flattened, as a list of unicode strings.
        """
        return [x.get() for x in self]
    extract = getall

    def get(self, default=None):
        """
        Return the result of ``.get()`` for the first element in this list.
        If the list is empty, return the default value.
        """
        for x in self:
            return x.get()
        return default
    extract_first = get

是不是找到了 extract()extract_first() 方法?注意理解這段代碼:

for x in self:
    return x.get()
return default

self 表示的是 SelectorList 的實例,它其實也是一個列表,列表中的元素是 Selector 的實例。這個 for 循環(huán)相當(dāng)于取的是一個元素,然后直接返回,返回的值是 x.get(),這里又會涉及 Selector 類的 get() 方法 :

# 源碼位置:parsel/selector.py
from lxml import etree, html
# ...

class Selector(object):
    # ...
    def get(self):
        """
        Serialize and return the matched nodes in a single unicode string.
        Percent encoded content is unquoted.
        """
        try:
            return etree.tostring(self.root,
                                  method=self._tostring_method,
                                  encoding='unicode',
                                  with_tail=False)
        except (AttributeError, TypeError):
            if self.root is True:
                return u'1'
            elif self.root is False:
                return u'0'
            else:
                return six.text_type(self.root)
            
    # ...

我們可以同樣在 Scrapy Shell 中來繼續(xù)做個測試:

>>> data_list = response.xpath('//ul[@class="sellListContent"]/li/div/div[@class="title"]/a/text()')
>>> type(data_list)
<class 'scrapy.selector.unified.SelectorList'>
>>> data = data_list[0]
>>> type(data)
<class 'scrapy.selector.unified.Selector'>
>>> data.get()
'地鐵口  總價低  精裝實用小兩房'

Selector 的 get() 方法最后提取出了我們匹配的文本,因此在 SelectorList 中的 extract()[0] 和 ``extract_first()` 方法將得到同樣的結(jié)果:

>>> response.xpath('//ul[@class="sellListContent"]/li/div/div[@class="title"]/a/text()').extract_first()
'地鐵口  總價低  精裝實用小兩房'
>>> response.xpath('//ul[@class="sellListContent"]/li/div/div[@class="title"]/a/text()').extract()[0]
'地鐵口  總價低  精裝實用小兩房'

這樣一步步追蹤和實驗,源碼里面很多的語句就會清晰明了,我們在使用 Request 和 Response 類時便會顯得更加得心應(yīng)手。Request 實例化時需要哪些參數(shù),Response 的實例有哪些方法可用, 這些疑惑在源碼面前都會迎刃而解。

3. 小結(jié)

本節(jié)主要是深入 Scrapy 的源碼去了解框架中定義的 Request 和 Response 類,通過源碼我們就能了解其可用的屬性和方法,只有這樣我們才能掌握和使用好 Scrapy 框架。