Scrapy FormRequests 源碼解讀
爬蟲的本質就是模擬瀏覽器發送message給伺服器, 那麼我們先看看瀏覽器是如何創建message, 然後我們再使用Scrapy 來模擬.
建議讀者把這個視頻看一下, 可以更直觀的感受一下如何抓包以及分析:
Python 模擬登錄的那些事兒-以知乎為例講解 - 騰訊視頻
我們首先來看一下如何使用form標籤來構造表單:
action提供給我們處理這個message的URL, 也就是伺服器端
method決定了我們發送給伺服器的message中的信息的存放位置, 當我們使用GET時, 信息出現在URL, 當我們使用POST時, 信息出現在message的body部分.
input就是我們輸入信息的區域, 這次我們只關注文本信息, 其他比如單選, 多選, 下拉菜單的選擇我們之後講解. 我們注意到input中存儲信息的方式是name, value鍵值對, 和Python中的字典是一樣的, 所以我們在發送信息的時候也會把信息存儲在字典中.
接下來開始講解源代碼:
class FormRequest(Request): 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(bContent-Type, bapplication/x-www-form-urlencoded) self._set_body(querystr) else: self._set_url(self.url + (& if ? in self.url else ?) + querystr)
- formdata就是我們input中的name, value鍵值對
- 如果不指定method, 那麼默認就是POST
- 而我們的URL屬性則來自繼承的父類Request
- 如果我們的method不是POST, 那麼就需要把信息放在URL中, 也就是self._set_url的作用.
- 信息都需要編碼, 這屬於HTTP協議方面的知識, 這裡不細講.
這樣的話我們的三個要素全都有了, 我們可以使使用FormRequest 進行簡單的post操作了.
但是在有些form中, 有些input是hidden狀態的, 我們無法查看也無法修改, 由服務端進行設置, 每次發送message, 這些hidden的input就會隱藏在我們的message中, 比如知乎的登錄表單:
我們在登錄的時候都需要附加一條隱藏的_xsrf, 而且這個是動態變化的, 所以我們需要首先把這個抓取出來, 然後再附加到我們的message中發送.
這裡就是FormRequest.from_response 大展身手的時候了, 它接受一個response(例如之前的知乎登錄頁面), 並且提取其中的數據:
@classmethod def from_response(cls, response, formname=None, formid=None, formnumber=0, formdata=None, clickdata=None, dont_click=False, formxpath=None, formcss=None, **kwargs): kwargs.setdefault(encoding, response.encoding) if formcss is not None: from parsel.csstranslator import HTMLTranslator formxpath = HTMLTranslator().css_to_xpath(formcss) form = _get_form(response, formname, formid, formnumber, formxpath) formdata = _get_inputs(form, formdata, dont_click, clickdata, response) url = _get_form_url(form, kwargs.pop(url, None)) method = kwargs.pop(method, form.method) return cls(url=url, method=method, formdata=formdata, **kwargs)
首先我們注意到最後返回的對象任然是一個FormRequest:
return cls(url=url, method=method, formdata=formdata, **kwargs)
還是那三個要素: url, method, data.
我們看看這三個數據是怎麼獲取的:
url
url = _get_form_url(form, kwargs.pop(url, None))def _get_form_url(form, url): if url is None: return urljoin(form.base_url, form.action) return urljoin(form.base_url, url)
可以看出如果我們指定url, 那麼就是用指定的url(urljoin對於絕對鏈接是不起作用的), 否則的話我們就是用form中的action的鏈接.
method
method = kwargs.pop(method, form.method)
如果我們指定, 那麼使用指定的method, 否則使用form中提供的method.
data
form = _get_form(response, formname, formid, formnumber, formxpath)formdata = _get_inputs(form, formdata, dont_click, clickdata, response)def _get_inputs(form, formdata, dont_click, clickdata, response): try: formdata = dict(formdata or ()) except (ValueError, TypeError): raise ValueError(formdata should be a dict or iterable of tuples) inputs = form.xpath(descendant::textarea |descendant::select |descendant::input[not(@type) or @type[ not(re:test(., "^(?:submit|image|reset)$", "i")) and (../@checked or not(re:test(., "^(?:checkbox|radio)$", "i")))]], namespaces={ "re": "http://exslt.org/regular-expressions"}) values = [(k, u if v is None else v) for k, v in (_value(e) for e in inputs) if k and k not in formdata] values.extend(formdata.items()) return values
get_form 其實就是對xpath的一個包裝, 用於定位form的位置, 這裡不細講.
_get_inputs 使用的是xpath 來匹配[textarea, select, input] 這三個標籤. 然後把他們的name, value 提取出來, 最後和我們的提供的formdata進行合併.
這樣我們就把數據都提取出來了, 而我們所需要做的工作就是提供一下formdata, 其他的工作由Scrapy完成.
實戰:
使用Scrapy五行代碼登錄知乎 - 知乎專欄
推薦閱讀:
※你懂Scrapy嗎?Scrapy大型爬蟲框架講解【一】
※[轉載]教你分分鐘學會用python爬蟲框架Scrapy爬取心目中的女神
※如何利用python爬取靜態網頁數據?
※第十一章 Scrapy入門程序點評
※小白進階之Scrapy第五篇(Scrapy-Splash配合CrawlSpider;瞎幾把整的)