這段時間在研究美團爬蟲,用的是scrapy-redis分布式爬蟲框架,奈何scrapy-redis與scrapy框架不同,預設只傳送get請求,換句話說,不能直接傳送post請求,而美團的資料請求方式是post,網上找了一圈,發現關於scrapy-redis傳送post的資料寥寥無幾,只能自己剛原始碼了。
先來說一說需求,也就是說美團post請求形式。我們以獲取某個地理座標下,所有店鋪類別列表請求為例。獲取所有店鋪類別列表時,我們需要構造乙個包含位置座標經緯度等資訊的表單資料,以及為了向下一層parse方法傳遞的一些必要資料,即meta,然後發起乙個post請求。
url:
url = ''
url最後面的13位數字是時間戳,實際應用時用time模組生成一下就好了。
表單資料:
'''
'''form_data =
meta資料:
meta資料不是必須的,但是,如果你在傳送請求時,有一些資料需要向下一層parse方法(解析爬蟲返回的response的方法)中傳遞的話,就可以構造這一資料,然後作為引數傳遞進request中。
meta =
採集店鋪類別列表時需要傳送怎樣乙個post請求在上面已經說明了,那麼,在scrapy-redis框架中,這個post該如何來傳送呢?我相信,開啟我這篇博文的讀者都是用過scrapy的,用scrapy傳送post肯定沒問題(重寫start_requests方法即可),但scrapy-redis不同,scrapy-redis框架只會從配置好的redis資料庫中讀取起始url,所以,在scrapy-redis中,就算重寫start_requests方法也沒用。怎麼辦呢?我們看看原始碼。
我們知道,scrapy-redis與scrapy的乙個很大區別就是,scrapy-redis不再繼承spider類,而是繼承redisspider類的,所以,redisspider類原始碼將是我們分析的重點,我們開啟redisspider類,看看有沒有類似於scrapy框架中的start_requests、make_requests_from_url這樣的方法。redisspider原始碼如下:
'''
'''class redisspider(redismixin, spider):
@classmethod
def from_crawler(self, crawler, *args, **kwargs):
obj = super(redisspider, self).from_crawler(crawler, *args, **kwargs)
obj.setup_redis(crawler)
return obj
def start_requests(self):
return self.next_requests()
呵,真簡潔,直接把所有任務丟給next_requests方法,繼續:
'''
'''def next_requests(self):
"""returns a request to be scheduled or none."""
use_set = self.settings.getbool('redis_start_urls_as_set', defaults.start_urls_as_set)
fetch_one = self.server.spop if use_set else self.server.lpop
# ***: do we need to use a timeout here?
found = 0
# todo: use redis pipeline execution.
while found < self.redis_batch_size: # 每次讀取的量
data = fetch_one(self.redis_key) # 從redis中讀取一條記錄
if not data:
# queue empty.
break
req = self.make_request_from_data(data) # 根據從redis中讀取的記錄,例項化乙個request
if req:
yield req
found += 1
else:
self.logger.debug("request not made from data: %r", data)
if found:
self.logger.debug("read %s requests from '%s'", found, self.redis_key)
上面next_requests方法中,關鍵的就是那個while迴圈,每一次迴圈都呼叫了乙個make_request_from_data方法,從函式名可以函式,這個方法就是根據從redis中讀取從來的資料,例項化乙個request,那不就是我們要找的方法嗎?進入make_request_from_data方法一**竟:
def make_request_from_data(self, data):
url = bytes_to_str(data, self.redis_encoding)
return self.make_requests_from_url(url) # 這是重點,圈起來,要考
因為scrapy-redis預設值傳送get請求,所以,在這個make_request_from_data方法中認為data只包含乙個url,但如果我們要傳送post請求,這個data包含的東西可就多了,我們上面美團post請求說明中就說到,至少要包含url、form_data。所以,如果我們要傳送post請求,這裡必須改,make_request_from_data方法最後呼叫的make_requests_from_url是scrapy中的spider中的方法,不過,我們也不需要繼續往下看下去了,我想諸位都也清楚了,要傳送post請求,重寫這個make_request_from_data方法,根據傳入的data,例項化乙個request返回就好了。
明白上面這些東西後,就可以開始寫**了。修改原始碼嗎?不,不存在的,改原始碼可不是好習慣。我們直接在我們自己的spider類中重寫make_request_from_data方法就好了:
'''
'''from scrapy import formrequest
from scrapy_redis.spiders import redisspider
class meituanspider(redisspider):
"""此處省略若干行
"""def make_request_from_data(self, data):
"""重寫make_request_from_data方法,data是scrapy-redis讀取redis中的[url,form_data,meta],然後傳送post請求
:param data: redis中都去的請求資料,是乙個list
:return: 乙個formrequest物件
"""data = json.loads(data)
url = data.get('url')
form_data = data.get('form_data')
meta = data.get('meta')
return formrequest(url=url, formdata=form_data, meta=meta, callback=self.parse)
def parse(self, response):
pass
搞清楚原理之後,就是這麼簡單。萬事俱備,只欠東風——將url,form_data,meta儲存到redis中。另外新建乙個模組實現這一部分功能:
def push_start_url_data(request_data):
"""將乙個完整的request_data推送到redis的start_url列表中
:param request_data:
:return:
"""r.lpush('meituan:start_urls', request_data)
if __name__ == '__main__':
url = ''
form_data =
meta =
request_data =
push_start_url_data(json.dumps(request_data))
在啟動scrapy-redis之前,執行一下這一模組即可。如果有很多poi(地理位置興趣點),迴圈遍歷每乙個poi,生成request_data,push到redis中。這一迴圈功能就你自己寫吧。
沒有什麼是擼一遍原始碼解決不了的,如果有,就再擼一遍!
scrapy redis原始碼困惑記錄
原始碼可從github上獲取 可以把原始碼轉殖下來,執行如下命令 git clone r 字串 採用repr 的顯示 s 字串 採用str 的顯示 區別 str和repr str即以人類理解的方式轉化為字串,repr是以機器理解的方式直接為資料加上單引號,其他不變。pipe self.server....
azkaban web server原始碼解析
azkaban主要用於hadoop相關job任務的排程,但也可以應用任何需要排程管理的任務,可以完全代替crontab。azkaban主要分為web server 任務上傳,管理,排程 executor server 接受web server的排程指令,進行任務執行 1.資料表 projects 工...
JDK LinkedHashMap原始碼解析
今天來分析一下jdk linkedhashmap的源 public class linkedhashmapextends hashmapimplements map可以看到,linkedhashmap繼承自hashmap,並且也實現了map介面,所以linkedhashmap沿用了hashmap的大...