Scrapy筆記(11)- 模擬登入
有時候爬取網站的時候需要登入,在Scrapy中可以通過模擬登入儲存cookie後再去爬取相應的頁面。這裡我通過登入github然後爬取自己的issue列表來演示下整個原理。
要想實現登入就需要表單提交,先通過瀏覽器訪問github的登入頁面https://github.com/login,然後使用瀏覽器除錯工具來得到登入時需要提交什麼東西。
我這裡使用chrome瀏覽器的除錯工具,F12開啟後選擇Network,並將Preserve log勾上。我故意輸入錯誤的使用者名稱和密碼,得到它提交的form表單引數還有POST提交的UR
去檢視html原始碼會發現表單裡面有個隱藏的authenticity_token
重寫start_requests方法
要使用cookie,第一步得開啟它呀,預設scrapy使用CookiesMiddleware
中介軟體,並且打開了。如果你之前禁止過,請設定如下
COOKIES_ENABLES = True
我們先要開啟登入頁面,獲取authenticity_token
值,這裡我重寫了start_requests方法
# 重寫了爬蟲類的方法, 實現了自定義請求, 執行成功後會呼叫callback回撥函式 def start_requests(self): return [Request("https://github.com/login", meta={'cookiejar': 1}, callback=self.post_login)] # FormRequeset def post_login(self, response): # 先去拿隱藏的表單引數authenticity_token authenticity_token = response.xpath( '//input[@name="authenticity_token"]/@value').extract_first() logging.info('authenticity_token=' + authenticity_token) pass
start_requests
方法指定了回撥函式,用來獲取隱藏表單值authenticity_token
,同時我們還給Request指定了cookiejar
的元資料,用來往回調函式傳遞cookie標識。
使用FormRequest
Scrapy為我們準備了FormRequest
類專門用來進行Form表單提交的
# 為了模擬瀏覽器,我們定義httpheader post_headers = { "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "Accept-Encoding": "gzip, deflate", "Accept-Language": "zh-CN,zh;q=0.8,en;q=0.6", "Cache-Control": "no-cache", "Connection": "keep-alive", "Content-Type": "application/x-www-form-urlencoded", "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.75 Safari/537.36", "Referer": "https://github.com/", } # 使用FormRequeset模擬表單提交 def post_login(self, response): # 先去拿隱藏的表單引數authenticity_token authenticity_token = response.xpath( '//input[@name="authenticity_token"]/@value').extract_first() logging.info('authenticity_token=' + authenticity_token) # FormRequeset.from_response是Scrapy提供的一個函式, 用於post表單 # 登陸成功後, 會呼叫after_login回撥函式,如果url跟Request頁面的一樣就省略掉 return [FormRequest.from_response(response, url='https://github.com/session', meta={'cookiejar': response.meta['cookiejar']}, headers=self.post_headers, # 注意此處的headers formdata={ 'utf8': '✓', 'login': 'yidao620c', 'password': '******', 'authenticity_token': authenticity_token }, callback=self.after_login, dont_filter=True )] def after_login(self, response): pass
FormRequest.from_response()
方法讓你指定提交的url,請求頭還有form表單值,注意我們還通過meta
傳遞了cookie標識。它同樣有個回撥函式,登入成功後呼叫。下面我們來實現它
def after_login(self, response):
# 登入之後,開始進入我要爬取的私信頁面
for url in self.start_urls:
# 因為我們上面定義了Rule,所以只需要簡單的生成初始爬取Request即可
yield Request(url, meta={'cookiejar': response.meta['cookiejar']})
這裡我通過start_urls
定義了開始頁面,然後生成Request,具體爬取的規則和下一頁
規則在前面的Rule裡定義了。注意這裡我繼續傳遞cookiejar
,訪問初始頁面時帶上cookie資訊。
重寫_requests_to_follow
有個問題剛開始困擾我很久就是這裡我定義的spider繼承自CrawlSpider,它內部自動去下載匹配的連結,而每次去訪問連結的時候並沒有自動帶上cookie,後來我重寫了它的_requests_to_follow()
方法解決了這個問題
def _requests_to_follow(self, response):
"""重寫加入cookiejar的更新"""
if not isinstance(response, HtmlResponse):
return
seen = set()
for n, rule in enumerate(self._rules):
links = [l for l in rule.link_extractor.extract_links(response) if l not in seen]
if links and rule.process_links:
links = rule.process_links(links)
for link in links:
seen.add(link)
r = Request(url=link.url, callback=self._response_downloaded)
# 下面這句是我重寫的
r.meta.update(rule=n, link_text=link.text, cookiejar=response.meta['cookiejar'])
yield rule.process_request(r)
頁面處理方法
在規則Rule裡面我定義了每個連結的回撥函式parse_page
,就是最終我們處理每個issue頁面提取資訊的邏輯
def parse_page(self, response):
"""這個是使用LinkExtractor自動處理連結以及`下一頁`"""
logging.info(u'--------------訊息分割線-----------------')
logging.info(response.url)
issue_title = response.xpath(
'//span[@class="js-issue-title"]/text()').extract_first()
logging.info(u'issue_title:' + issue_title.encode('utf-8'))
完整原始碼
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
Topic: 登入爬蟲
Desc : 模擬登入https://github.com後將自己的issue全部爬出來
tips:使用chrome除錯post表單的時候勾選Preserve log和Disable cache
"""
import logging
import re
import sys
import scrapy
from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor
from scrapy.http import Request, FormRequest, HtmlResponse
logging.basicConfig(level=logging.INFO,
format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
handlers=[logging.StreamHandler(sys.stdout)])
class GithubSpider(CrawlSpider):
name = "github"
allowed_domains = ["github.com"]
start_urls = [
'https://github.com/issues',
]
rules = (
# 訊息列表
Rule(LinkExtractor(allow=('/issues/\d+',),
restrict_xpaths='//ul[starts-with(@class, "table-list")]/li/div[2]/a[2]'),
callback='parse_page'),
# 下一頁, If callback is None follow defaults to True, otherwise it defaults to False
Rule(LinkExtractor(restrict_xpaths='//a[@class="next_page"]')),
)
post_headers = {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Accept-Encoding": "gzip, deflate",
"Accept-Language": "zh-CN,zh;q=0.8,en;q=0.6",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
"Content-Type": "application/x-www-form-urlencoded",
"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.75 Safari/537.36",
"Referer": "https://github.com/",
}
# 重寫了爬蟲類的方法, 實現了自定義請求, 執行成功後會呼叫callback回撥函式
def start_requests(self):
return [Request("https://github.com/login",
meta={'cookiejar': 1}, callback=self.post_login)]
# FormRequeset
def post_login(self, response):
# 先去拿隱藏的表單引數authenticity_token
authenticity_token = response.xpath(
'//input[@name="authenticity_token"]/@value').extract_first()
logging.info('authenticity_token=' + authenticity_token)
# FormRequeset.from_response是Scrapy提供的一個函式, 用於post表單
# 登陸成功後, 會呼叫after_login回撥函式,如果url跟Request頁面的一樣就省略掉
return [FormRequest.from_response(response,
url='https://github.com/session',
meta={'cookiejar': response.meta['cookiejar']},
headers=self.post_headers, # 注意此處的headers
formdata={
'utf8': '✓',
'login': 'yidao620c',
'password': '******',
'authenticity_token': authenticity_token
},
callback=self.after_login,
dont_filter=True
)]
def after_login(self, response):
for url in self.start_urls:
# 因為我們上面定義了Rule,所以只需要簡單的生成初始爬取Request即可
yield Request(url, meta={'cookiejar': response.meta['cookiejar']})
def parse_page(self, response):
"""這個是使用LinkExtractor自動處理連結以及`下一頁`"""
logging.info(u'--------------訊息分割線-----------------')
logging.info(response.url)
issue_title = response.xpath(
'//span[@class="js-issue-title"]/text()').extract_first()
logging.info(u'issue_title:' + issue_title.encode('utf-8'))
def _requests_to_follow(self, response):
"""重寫加入cookiejar的更新"""
if not isinstance(response, HtmlResponse):
return
seen = set()
for n, rule in enumerate(self._rules):
links = [l for l in rule.link_extractor.extract_links(response) if l not in seen]
if links and rule.process_links:
links = rule.process_links(links)
for link in links:
seen.add(link)
r = Request(url=link.url, callback=self._response_downloaded)
# 下面這句是我重寫的
r.meta.update(rule=n, link_text=link.text, cookiejar=response.meta['cookiejar'])
yield rule.process_request(r)
你可以在GitHub上看到本文的完整專案原始碼,還有另外一個自動登陸iteye網站的例子。https://github.com/yidao620c/core-scrapy