Scrapy模擬登錄

想爬取網站數據?先登陸網站!對於大多數大型網站來講,想要爬取他們的數據,第一道門檻就是登陸網站。下面請跟隨個人步伐來學習如何模擬登錄網站。css

爲何進行模擬登錄?

互聯網上的網站分兩種:須要登陸和不須要登陸。(這是一句廢話!)python

那麼,對於不須要登陸的網站,咱們直接獲取數據便可,簡單省事。而對於須要登陸才能夠查看數據或者不登陸只能查看一部分數據的網站來講,咱們只好乖乖地登陸網站了。(除非你直接黑進人家數據庫,黑客操做請慎用!)git

因此,對於須要登陸的網站,咱們須要模擬一下登陸,一方面爲了獲取登錄以後頁面的信息和數據,另外一方面爲了拿到登陸以後的 cookie ,以便下次請求時使用。github

模擬登錄的思路

一提到模擬登錄,你們的第一反應確定是:切!那還不簡單?打開瀏覽器,輸入網址,找到用戶名密碼框,輸入用戶名和密碼,而後點擊登錄就完事!web

這種方式沒毛病,咱們的 selenium 模擬登錄就是這麼操做的。chrome

除此以外呢,咱們的 Requests 還能夠直接攜帶已經登錄過的 cookies 進行請求,至關於繞過了登錄。數據庫

咱們也能夠利用 Requests 發送 post 請求,將網站登陸須要的信息附帶到 post 請求中進行登陸。瀏覽器

以上就是咱們常見的三種模擬登錄網站的思路,那麼咱們的 Scrapy 也使用了後兩種方式,畢竟第一種只是 selenium 特有的方式。bash

Scrapy 模擬登錄的思路:cookie

一、直接攜帶已經登錄過的 cookies 進行請求
二、將網站登陸須要的信息附帶到 post 請求中進行登陸

模擬登錄實例

攜帶 cookies 模擬登錄

每種登錄方式都有它的優缺點以及使用場景,咱們來看看攜帶 cookies 登錄的應用場景:

一、cookie 過時時間很長,咱們能夠登陸一次以後不用擔憂登陸過時問題,常見於一些不規範的網站。
二、咱們能在 cookie 過時以前把咱們須要的全部數據拿到。
三、咱們能夠配合其餘程序使用,好比使用 selenium 把登陸以後的 cookie 獲取保存到本地,而後在 Scrapy 發送請求以前先讀取本地 cookie 。

下面咱們經過模擬登陸被咱們遺忘已久的人人網來說述這種模擬登錄方式。

咱們首先建立一個 Scrapy 項目:

> scrapy startproject login
複製代碼

爲了爬取順利,請先將 settings 裏面的 robots 協議設置爲 False :

ROBOTSTXT_OBEY = False
複製代碼

接着,咱們建立一個爬蟲:

> scrapy genspider renren renren.com
複製代碼

咱們打開 spiders 目錄下的 renren.py ,代碼以下:

# -*- coding: utf-8 -*-
import scrapy


class RenrenSpider(scrapy.Spider):
    name = 'renren'
    allowed_domains = ['renren.com']
    start_urls = ['http://renren.com/']

    def parse(self, response):
        pass

複製代碼

咱們知道,start_urls 存的是咱們須要爬取的第一個網頁地址,這是咱們爬數據的初始網頁,假設我須要爬取人人網的我的中心頁的數據,那麼我登陸人人網後,進入到我的中心頁,網址是:http://www.renren.com/972990680/profile ,若是我直接將這個網址放到 start_urls 裏面,而後咱們直接請求,你們想一下,可不能夠成功?

不能夠,對吧!由於咱們尚未登陸,根本看不到我的中心頁。

那麼咱們的登陸代碼加到哪裏呢?

咱們能肯定的是咱們必須在框架請求 start_urls 中的網頁以前登陸。

咱們進入 Spider 類的源碼,找到下面這一段代碼:

def start_requests(self):
        cls = self.__class__
        if method_is_overridden(cls, Spider, 'make_requests_from_url'):
            warnings.warn(
                "Spider.make_requests_from_url method is deprecated; it "
                "won't be called in future Scrapy releases. Please "
                "override Spider.start_requests method instead (see %s.%s)." % (
                    cls.__module__, cls.__name__
                ),
            )
            for url in self.start_urls:
                yield self.make_requests_from_url(url)
        else:
            for url in self.start_urls:
                yield Request(url, dont_filter=True)

    def make_requests_from_url(self, url):
        """ This method is deprecated. """
        return Request(url, dont_filter=True)

複製代碼

咱們從這段源碼中能夠看到,這個方法從 start_urls 中獲取 URL ,而後構造一個 Request 對象來請求。既然這樣,咱們就能夠重寫 start_requests 方法來作一些事情,也就是在構造 Request 對象的時候把 cookies 信息加進去。

重寫以後的 start_requests 方法以下:

# -*- coding: utf-8 -*-
import scrapy
import re

class RenrenSpider(scrapy.Spider):
    name = 'renren'
    allowed_domains = ['renren.com']
    # 我的中心頁網址
    start_urls = ['http://www.renren.com/972990680/profile']

    def start_requests(self):
        # 登陸以後用 chrome 的 debug 工具從請求中獲取的 cookies
        cookiesstr = "anonymid=k3miegqc-hho317; depovince=ZGQT; _r01_=1; JSESSIONID=abcDdtGp7yEtG91r_U-6w; ick_login=d2631ff6-7b2d-4638-a2f5-c3a3f46b1595; ick=5499cd3f-c7a3-44ac-9146-60ac04440cb7; t=d1b681e8b5568a8f6140890d4f05c30f0; societyguester=d1b681e8b5568a8f6140890d4f05c30f0; id=972990680; xnsid=404266eb; XNESSESSIONID=62de8f52d318; jebecookies=4205498d-d0f7-4757-acd3-416f7aa0ae98|||||; ver=7.0; loginfrom=null; jebe_key=8800dc4d-e013-472b-a6aa-552ebfc11486%7Cb1a400326a5d6b2877f8c884e4fe9832%7C1575175011619%7C1%7C1575175011639; jebe_key=8800dc4d-e013-472b-a6aa-552ebfc11486%7Cb1a400326a5d6b2877f8c884e4fe9832%7C1575175011619%7C1%7C1575175011641; wp_fold=0"
        cookies = {i.split("=")[0]:i.split("=")[1] for i in cookiesstr.split("; ")}

        # 攜帶 cookies 的 Request 請求
        yield scrapy.Request(
            self.start_urls[0],
            callback=self.parse,
            cookies=cookies
        )

    def parse(self, response):
        # 從我的中心頁查找關鍵詞"閒歡"並打印
        print(re.findall("閒歡", response.body.decode()))
複製代碼

我先用帳號正確登陸人人網,登陸以後用 chrome 的 debug 工具從請求中獲取一個請求的 cookies ,而後在 Request 對象中加入這個 cookies 。接着我在 parse 方法中查找網頁中的「閒歡」關鍵詞並打印輸出。

咱們運行一下這個爬蟲:

>scrapy crawl renren
複製代碼

在運行日誌中咱們能夠看到下面這幾行:

2019-12-01 13:06:55 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://www.renren.com/972990680/profile?v=info_timeline> (referer: http://www.renren.com/972990680/profile)
['閒歡', '閒歡', '閒歡', '閒歡', '閒歡', '閒歡', '閒歡']
2019-12-01 13:06:55 [scrapy.core.engine] INFO: Closing spider (finished)
複製代碼

咱們能夠看到已經打印了咱們須要的信息了。

咱們能夠在 settings 配置中加 COOKIES_DEBUG = True 來查看 cookies 傳遞的過程。

加了這個配置以後,咱們能夠看到日誌中會出現下面的信息:

2019-12-01 13:06:55 [scrapy.downloadermiddlewares.cookies] DEBUG: Sending cookies to: <GET http://www.renren.com/972990680/profile?v=info_timeline>
Cookie: anonymid=k3miegqc-hho317; depovince=ZGQT; _r01_=1; JSESSIONID=abcDdtGp7yEtG91r_U-6w; ick_login=d2631ff6-7b2d-4638-a2f5-c3a3f46b1595; ick=5499cd3f-c7a3-44ac-9146-60ac04440cb7; t=d1b681e8b5568a8f6140890d4f05c30f0; societyguester=d1b681e8b5568a8f6140890d4f05c30f0; id=972990680; xnsid=404266eb; XNESSESSIONID=62de8f52d318; jebecookies=4205498d-d0f7-4757-acd3-416f7aa0ae98|||||; ver=7.0; loginfrom=null; jebe_key=8800dc4d-e013-472b-a6aa-552ebfc11486%7Cb1a400326a5d6b2877f8c884e4fe9832%7C1575175011619%7C1%7C1575175011641; wp_fold=0; JSESSIONID=abc84VF0a7DUL7JcS2-6w
複製代碼

發送 post 請求模擬登錄

咱們經過模擬登錄 GitHub 網站爲例,來說述這種模擬登錄方式。

咱們首先建立一個爬蟲 github :

> scrapy genspider github github.com
複製代碼

咱們要用 post 請求模擬登錄,首先須要知道登錄的 URL 地址,以及登錄所須要的參數信息。咱們經過 debug 工具,能夠看到登錄的請求信息以下:

github_login_request.png

從請求信息中咱們能夠找出登錄的 URL 爲:https://github.com/session ,登錄所須要的參數爲:

commit: Sign in
utf8: ✓
authenticity_token: bbpX85KY36B7N6qJadpROzoEdiiMI6qQ5L7hYFdPS+zuNNFSKwbW8kAGW5ICyvNVuuY5FImLdArG47358RwhWQ==
ga_id: 101235085.1574734122
login: xxx@qq.com
password: xxx
webauthn-support: supported
webauthn-iuvpaa-support: unsupported
required_field_f0e5: 
timestamp: 1575184710948
timestamp_secret: 574aa2760765c42c07d9f0ad0bbfd9221135c3273172323d846016f43ba761db
複製代碼

這個請求的參數真是夠多的,汗!

除了咱們的用戶名和密碼,其餘的都須要從登錄頁面中獲取,這其中還有一個 required_field_f0e5 參數須要注意一下,每次頁面加載這個名詞都不同,可見是動態生成的,可是這個值始終傳的都是空,這就爲咱們省去了一個參數,咱們能夠不穿這個參數。

其餘的參數在頁面的位置以下圖:

github_login_params.png

咱們用 xpath 來獲取各個參數,代碼以下(我把用戶名和密碼分別用 xxx 來代替了,你們運行的時候請把本身真實的用戶名和密碼寫上去):

# -*- coding: utf-8 -*-
import scrapy
import re

class GithubSpider(scrapy.Spider):
    name = 'github'
    allowed_domains = ['github.com']
    # 登陸頁面 URL
    start_urls = ['https://github.com/login']

    def parse(self, response):
        # 獲取請求參數
        commit = response.xpath("//input[@name='commit']/@value").extract_first()
        utf8 = response.xpath("//input[@name='utf8']/@value").extract_first()
        authenticity_token = response.xpath("//input[@name='authenticity_token']/@value").extract_first()
        ga_id = response.xpath("//input[@name='ga_id']/@value").extract_first()
        webauthn_support = response.xpath("//input[@name='webauthn-support']/@value").extract_first()
        webauthn_iuvpaa_support = response.xpath("//input[@name='webauthn-iuvpaa-support']/@value").extract_first()
        # required_field_157f = response.xpath("//input[@name='required_field_4ed5']/@value").extract_first()
        timestamp = response.xpath("//input[@name='timestamp']/@value").extract_first()
        timestamp_secret = response.xpath("//input[@name='timestamp_secret']/@value").extract_first()

        # 構造 post 參數
        post_data = {
            "commit": commit,
            "utf8": utf8,
            "authenticity_token": authenticity_token,
            "ga_id": ga_id,
            "login": "xxx@qq.com",
            "password": "xxx",
            "webauthn-support": webauthn_support,
            "webauthn-iuvpaa-support": webauthn_iuvpaa_support,
            # "required_field_4ed5": required_field_4ed5,
            "timestamp": timestamp,
            "timestamp_secret": timestamp_secret
        }

        # 打印參數
        print(post_data)

        # 發送 post 請求
        yield scrapy.FormRequest(
            "https://github.com/session", # 登陸請求方法
            formdata=post_data,
            callback=self.after_login
        )

    # 登陸成功以後操做
    def after_login(self, response):
        # 找到頁面上的 Issues 字段並打印
        print(re.findall("Issues", response.body.decode()))
複製代碼

咱們使用 FormRequest 方法發送 post 請求,運行爬蟲以後,報錯了,咱們來看下報錯信息:

2019-12-01 15:14:47 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://github.com/login> (referer: None)
{'commit': 'Sign in', 'utf8': '✓', 'authenticity_token': '3P4EVfXq3WvBM8fvWge7FfmRd0ORFlS6xGcz5mR5A00XnMe7GhFaMKQ8y024Hyy5r/RFS9ZErUDr1YwhDpBxlQ==', 'ga_id': None, 'login': '965639190@qq.com', 'password': '54ithero', 'webauthn-support': 'unknown', 'webauthn-iuvpaa-support': 'unknown', 'timestamp': '1575184487447', 'timestamp_secret': '6a8b589266e21888a4635ab0560304d53e7e8667d5da37933844acd7bee3cd19'}
2019-12-01 15:14:47 [scrapy.core.scraper] ERROR: Spider error processing <GET https://github.com/login> (referer: None)
Traceback (most recent call last):
  File "/Applications/anaconda3/lib/python3.7/site-packages/scrapy/utils/defer.py", line 102, in iter_errback
    yield next(it)
  File "/Applications/anaconda3/lib/python3.7/site-packages/scrapy/core/spidermw.py", line 84, in evaluate_iterable
    for r in iterable:
  File "/Applications/anaconda3/lib/python3.7/site-packages/scrapy/spidermiddlewares/offsite.py", line 29, in process_spider_output
    for x in result:
  File "/Applications/anaconda3/lib/python3.7/site-packages/scrapy/core/spidermw.py", line 84, in evaluate_iterable
    for r in iterable:
  File "/Applications/anaconda3/lib/python3.7/site-packages/scrapy/spidermiddlewares/referer.py", line 339, in <genexpr>
    return (_set_referer(r) for r in result or ())
  File "/Applications/anaconda3/lib/python3.7/site-packages/scrapy/core/spidermw.py", line 84, in evaluate_iterable
    for r in iterable:
  File "/Applications/anaconda3/lib/python3.7/site-packages/scrapy/spidermiddlewares/urllength.py", line 37, in <genexpr>
    return (r for r in result or () if _filter(r))
  File "/Applications/anaconda3/lib/python3.7/site-packages/scrapy/core/spidermw.py", line 84, in evaluate_iterable
    for r in iterable:
  File "/Applications/anaconda3/lib/python3.7/site-packages/scrapy/spidermiddlewares/depth.py", line 58, in <genexpr>
    return (r for r in result or () if _filter(r))
  File "/Users/cxhuan/Documents/python_workspace/scrapy_projects/login/login/spiders/github.py", line 40, in parse
    callback=self.after_login
  File "/Applications/anaconda3/lib/python3.7/site-packages/scrapy/http/request/form.py", line 32, in __init__
    querystr = _urlencode(items, self.encoding)
  File "/Applications/anaconda3/lib/python3.7/site-packages/scrapy/http/request/form.py", line 73, in _urlencode
    for k, vs in seq
  File "/Applications/anaconda3/lib/python3.7/site-packages/scrapy/http/request/form.py", line 74, in <listcomp>
    for v in (vs if is_listlike(vs) else [vs])]
  File "/Applications/anaconda3/lib/python3.7/site-packages/scrapy/utils/python.py", line 107, in to_bytes
    'object, got %s' % type(text).__name__)
TypeError: to_bytes must receive a unicode, str or bytes object, got NoneType
2019-12-01 15:14:47 [scrapy.core.engine] INFO: Closing spider (finished)
複製代碼

看這個報錯信息,好像是參數值中有一個參數取到 None 致使的,咱們看下打印的參數信息中,發現 ga_idNone ,咱們再修改一下,當 ga_idNone 時,咱們傳空字符串試試。

修改代碼以下:

ga_id = response.xpath("//input[@name='ga_id']/@value").extract_first()
if ga_id is None:
    ga_id = ""
複製代碼

再次運行爬蟲,此次咱們來看看結果:

Set-Cookie: _gh_sess=QmtQRjB4UDNUeHdkcnE4TUxGbVRDcG9xMXFxclA1SDM3WVhqbFF5U0wwVFp0aGV1UWxYRWFSaXVrZEl0RnVjTzFhM1RrdUVabDhqQldTK3k3TEd3KzNXSzgvRXlVZncvdnpURVVNYmtON0IrcGw1SXF6Nnl0VTVDM2dVVGlsN01pWXNUeU5XQi9MbTdZU0lTREpEMllVcTBmVmV2b210Sm5Sbnc0N2d5aVErbjVDU2JCQnA5SkRsbDZtSzVlamxBbjdvWDBYaWlpcVR4Q2NvY3hwVUIyZz09LS1lMUlBcTlvU0F0K25UQ3loNHFOZExnPT0%3D--8764e6d2279a0e6960577a66864e6018ef213b56; path=/; secure; HttpOnly

2019-12-01 15:25:18 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://github.com/> (referer: https://github.com/login)
['Issues', 'Issues']
2019-12-01 15:25:18 [scrapy.core.engine] INFO: Closing spider (finished)
複製代碼

咱們能夠看到已經打印了咱們須要的信息,登陸成功。

Scrapy 對於表單請求,FormRequest 還提供了另一個方法 from_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)
        if method is not None:
            method = method.upper()
            if method not in cls.valid_form_methods:
                method = 'GET'

        return cls(url=url, method=method, formdata=formdata, **kwargs)
複製代碼

咱們能夠看到這個方法的參數有好多,都是有關 form 定位的信息。若是登陸網頁中只有一個表單, Scrapy 能夠很容易定位,可是若是網頁中含有多個表單呢?這個時候咱們就須要經過這些參數來告訴 Scrapy 哪一個纔是登陸的表單。

固然,這個方法的前提是須要咱們網頁的 form 表單的 action 裏面包含了提交請求的 url 地址。

在 github 這個例子中,咱們的登陸頁面只有一個登陸的表單,所以咱們只須要傳入用戶名和密碼就能夠了。代碼以下:

# -*- coding: utf-8 -*-
import scrapy
import re

class Github2Spider(scrapy.Spider):
    name = 'github2'
    allowed_domains = ['github.com']
    start_urls = ['http://github.com/login']

    def parse(self, response):
        yield scrapy.FormRequest.from_response(
            response, # 自動從response中尋找form表單
            formdata={"login": "xxx@qq.com", "password": "xxx"},
            callback=self.after_login
        )
    # 登陸成功以後操做
    def after_login(self, response):
        # 找到頁面上的 Issues 字段並打印
        print(re.findall("Issues", response.body.decode()))
複製代碼

運行爬蟲後,咱們能夠看到和以前同樣的結果。

這種請求方式是否是簡單了許多?不須要咱們費力去找各類請求參數,有沒有以爲 Amazing ?

總結

本文向你們介紹了 Scrapy 模擬登錄網站的幾種方法,你們能夠本身運用文中的方法去實踐一下。固然,這裏沒有涉及到有驗證碼的狀況,驗證碼是一個複雜而且難度很高的專題,之後有時間再給你們介紹。

相關文章
相關標籤/搜索