登錄的需求
有些數(shù)據(jù),必須在登錄之后才能查看障陶,所以我們?cè)谂廊∵^(guò)程中就會(huì)產(chǎn)生模擬登錄的需求,它有兩個(gè)點(diǎn):
1桐款、未登錄的情況下無(wú)法查看數(shù)據(jù)咸这,或者直接彈出登錄框提示你先登錄
2、登錄后登錄狀態(tài)的保持(通衬д#可以理解為cookie的處理)
登錄的邏輯
- 訪問(wèn)登錄頁(yè)面(部分網(wǎng)站會(huì)在登錄頁(yè)面設(shè)定token或標(biāo)識(shí)來(lái)反爬蟲(chóng)媳维,根據(jù)Network查看post數(shù)據(jù)來(lái)確認(rèn))
- 構(gòu)造登錄所需數(shù)據(jù)酿雪,并攜帶偽造的數(shù)據(jù)發(fā)送登錄請(qǐng)求(如token或標(biāo)識(shí)、User-Agent/HOST/Referer等數(shù)據(jù)侄刽,向登錄地址POST數(shù)據(jù)指黎。)
- 根據(jù)某個(gè)狀態(tài)碼或者登錄后跳轉(zhuǎn)url判斷是否登錄成功
- 登錄成功后獲取start_urls,并且調(diào)用parse進(jìn)行數(shù)據(jù)爬取
模擬登錄注意的地方
登錄爬取有幾個(gè)特點(diǎn)州丹,比如瀏覽器不能換醋安,可能UserAgent也不能換、要用到cookie墓毒、頁(yè)面可能會(huì)有重定向吓揪,通常表現(xiàn)為登錄后跳轉(zhuǎn)、頁(yè)面需要發(fā)送token或其他標(biāo)識(shí)所计,所以正則是個(gè)關(guān)鍵柠辞。
- 最好不要用自動(dòng)切換UserAgent的功能(未測(cè)試)
- 必須在配置開(kāi)啟cookie,COOKIES_ENABLED = True
- 關(guān)閉重定向禁止開(kāi)關(guān) #REDIRECT_ENABLED = False # 禁止重定向
- robots協(xié)議也關(guān)掉 ROBOTSTXT_OBEY = False
代碼實(shí)現(xiàn)
這里以東盟貸為例子主胧,
這是最簡(jiǎn)單的一種例子(登錄時(shí)沒(méi)有驗(yàn)證碼叭首、不用攜帶token、不用攜帶其他標(biāo)識(shí))踪栋,僅僅需要把用戶名和密碼發(fā)送過(guò)去即可焙格。
當(dāng)你需要爬取投資列表的數(shù)據(jù)時(shí),要到【我要投資】頁(yè)面去爬夷都,你想要打開(kāi)那個(gè)頁(yè)面他就會(huì)判斷你是否登錄眷唉,如果沒(méi)登錄就會(huì)給你直接跳轉(zhuǎn)到登錄界面
那我們面對(duì)這種情況损肛,就必須先登錄后請(qǐng)求頁(yè)面再爬取數(shù)據(jù)(我這里僅演示到登錄完成)厢破,示例代碼:
# -*- coding: utf-8 -*-
import scrapy
class DongmengSpider(scrapy.Spider):
name = 'dongmeng'
allowed_domains = ['www.dongmengdai.com']
start_urls = ['https://www.dongmengdai.com/view/Investment_list_che.php?page=1']
# 根據(jù)瀏覽器Network返回值來(lái)構(gòu)造header荣瑟,這是比較簡(jiǎn)單的header治拿,復(fù)雜的還會(huì)有很多信息
header = {
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:57.0) Gecko/20100101 Firefox/57.0",
"HOST": "www.dongmengdai.com",
"Referer": "https://www.dongmengdai.com/index.php?user&q=action/login",
}
def parse(self, response):
""" 正式進(jìn)入爬取區(qū)域 """
pass
def start_requests(self):
"""
重載start_requests方法 待登錄成功后,再進(jìn)入parse進(jìn)行數(shù)據(jù)爬取
訪問(wèn)登錄頁(yè)面 并調(diào)用do_login方法進(jìn)行登錄
"""
return [scrapy.Request('https://www.dongmengdai.com/index.php?user&q=action/login', headers=self.header, callback=self.do_login)]
def do_login(self, response):
"""
根據(jù)Network的信息 向登錄地址發(fā)送請(qǐng)求
攜帶用戶名和密碼 如果需要token或者其他標(biāo)識(shí)則需要用正則進(jìn)行匹配笆焰,然后放到login_data中
調(diào)用is_login方法判斷是否登錄成功
"""
login_url = "https://www.dongmengdai.com/index.php?user&q=action/login"
login_data = {
"keywords": "18077365555",
"password": "123456789"
}
return [scrapy.FormRequest(url=login_url,formdata=login_data,headers=self.header,callback=self.is_login)]
def is_login(self, response):
"""
這個(gè)網(wǎng)站登陸后會(huì)自動(dòng)跳轉(zhuǎn)到用戶中心 可根據(jù)返回的url判斷是否登錄成功
其他網(wǎng)站可以依靠狀態(tài)碼進(jìn)行判斷
如果登錄成功則從 start_urls中抽取url進(jìn)行爬取
這里不用設(shè)置callback回調(diào)parse 因?yàn)樗J(rèn)調(diào)用parse
如果是在crawl模板的爬蟲(chóng)劫谅,可能需要設(shè)置callback調(diào)用
"""
if "index.php?user" in response.url:
for url in self.start_urls:
yield scrapy.Request(url, dont_filter=True, headers=self.header)
else:
print("登錄失敗")
具體邏輯整理
具體的邏輯我已經(jīng)寫在代碼中了,這里再整理一下:
1嚷掠、先確定起始url和設(shè)置domains
2捏检、根據(jù)觀察瀏覽器Network返回值來(lái)構(gòu)造header(因?yàn)樗沧R(shí)別請(qǐng)求頭信息)
3、重載start_requests方法并帶上請(qǐng)求頭信息來(lái)發(fā)起請(qǐng)求
4不皆、do_login方法中執(zhí)行具體的登錄操作贯城,(keywords和password是登錄框的input name,通過(guò)右鍵審查元素可以看到html結(jié)構(gòu)霹娄,通過(guò)input name來(lái)定位輸入框)以及發(fā)起請(qǐng)求
5能犯、is_login方法來(lái)判斷是否登錄成功鲫骗,并且指定了下一步操作的方法(可以開(kāi)始爬數(shù)據(jù)了)
Cookie的問(wèn)題
可以看到,上面的代碼里面只是發(fā)送了用戶名和密碼踩晶,但是常規(guī)的登錄請(qǐng)求是需要保存和發(fā)送cookie的执泰,我們?cè)诖a中并沒(méi)有保存cookie和二次請(qǐng)求攜帶cookie的操作,那Scrapy是如何完成這個(gè)行為的呢渡蜻?
在源碼目錄site-packages/scrapy/downloadermiddlewares/cookies.py文件中术吝,可以看到具體的源碼:
CookiesMiddleware的第前面兩個(gè)個(gè)方法是重載from_crawler:
def __init__(self, debug=False):
self.jars = defaultdict(CookieJar)
self.debug = debug
@classmethod
def from_crawler(cls, crawler):
if not crawler.settings.getbool('COOKIES_ENABLED'):
raise NotConfigured
return cls(crawler.settings.getbool('COOKIES_DEBUG'))
init在加載的時(shí)候初始化CookieJar,from_crawler則是檢查settings里面的cookie配置情況茸苇。
接著到process_request方法:
def process_request(self, request, spider):
if request.meta.get('dont_merge_cookies', False):
return
cookiejarkey = request.meta.get("cookiejar")
jar = self.jars[cookiejarkey]
cookies = self._get_request_cookies(jar, request)
for cookie in cookies:
jar.set_cookie_if_ok(cookie, request)
# set Cookie header
request.headers.pop('Cookie', None)
jar.add_cookie_header(request)
self._debug_cookie(request, spider)
它完成的任務(wù)大致就是設(shè)置cookie排苍,請(qǐng)求的時(shí)候就帶上。
而process_response方法又完成了什么任務(wù)呢:
def process_response(self, request, response, spider):
if request.meta.get('dont_merge_cookies', False):
return response
# extract cookies from Set-Cookie and drop invalid/expired cookies
cookiejarkey = request.meta.get("cookiejar")
jar = self.jars[cookiejarkey]
jar.extract_cookies(response, request)
self._debug_set_cookie(response, spider)
它是完成cookie的篩選学密,提取cookie和刪除廢棄的cookie
下面還有幾個(gè)方法_debug_cookie纪岁、_debug_set_cookie、_format_cookie则果、_get_request_cookies他們幾個(gè)完成了cookie的獲取幔翰、生成和格式化等任務(wù)。
cookie小結(jié)
可以得出結(jié)論西壮,Scrapy框架會(huì)自動(dòng)幫我們處理cookie的問(wèn)題遗增,在常規(guī)的使用當(dāng)中我們不需要關(guān)心它的切換和更新問(wèn)題。只有在一些邏輯處理的時(shí)候款青,有可能涉及到登錄邏輯的改動(dòng)做修,才需要了解底層原理并對(duì)某個(gè)方法進(jìn)行重載,以實(shí)現(xiàn)邏輯的變化抡草。