Scrapy模拟登陆豆瓣抓取数据

上一篇文章中,我们使用requests.Session()来对豆瓣中的电影评论数据进行了抓取,虽然比较简单,但是现在各大公司在招聘员工时都需要熟悉Scrapy框架,因此,今天就来谈一谈如何用Scrapy来模拟登陆并对数据进行抓取

在这里插入图片描述

创建项目

Scrapy中可直接用Scrapy命令来生产,命令如下:

scrapy startproject Alita

这里Alita是我们所创建的文件夹名称。

创建Spider

Spider是我们自己定义的类,Scrapy用它来对网页进行抓取并进行解析。同样,Spider的创建页可以通过命令行来实现,在这里,我们要在刚刚创建的alita文件夹中执行命令

Tip:可直接进入到该文件夹下,按住Shift,在按下鼠标左键,可快速进入该文件夹下的命令窗口
在这里插入图片描述
命令如下:

scrapy genspider alita douban.com
或者
scrapy genspider -t crawl alita douban.com  # 这个在创建时使用的是模板crawl

这里需要注意的是Spider的名称不能和项目的名称重复。

创建后的alita.py的内容为:

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

class AlitaSpider(scrapy.Spider):
    name = 'alita'
    allowed_domains = ['douban.com']
    start_urls = ['http://douban.com/']

    def parse(self, response):
        pass

创建容器Item

创建容器,顾名思义就是要创建一个来存放爬取的数据东西,也就是创建Item,它的使用方法和字典相似。在这里,我们要抓取的是’用户昵称’,‘评分’,‘评论’,‘觉得有用的人数’。

由于在创建项目时,创建了 items.py, 因此我们只需要对其内容进行修改,改为我们所需要的字段,代码如下:

import scrapy

class AlitaItem(scrapy.Item):
    user_nick = scrapy.Field()
    score = scrapy.Field()
    content = scrapy.Field()                       
    userful_num = scrapy.Field()

解析Response,使用Item

本来这里应该是先讲模拟登陆,生成Request请求的,但考虑到内容过多,就放到后面来讲。在Scrapy将网页下载下来后,对response变量的内容解析,并将我们所需要的内容提取出来赋值给Item字段,alita.py中部分代码如下:

    def parse_item(self, response):
        results = response.css('div.comment-item')
        for result in results:
            item = AlitaItem()
            item['user_nick'] = result.css('span.comment-info > a::text').extract_first()
            item['score'] = result.css('span.rating::attr(title)').extract_first()
            item['content'] = result.css('span.short::text').extract_first()
            item['userful_num'] = result.css('span.votes::text').extract_first()
            yield item

我们使用了CSS选择器来对数据进行提取,具体的表达式再此就不进行阐述了。大家可在w3cschool中找到详细的讲解。

模拟登陆

Scrapy来进行模拟登陆的方法主要有三种:

自己直接登陆网站,将登陆成功的cookies保存下来,供Scrapy直接携带

对于该方法不进行展开,毕竟有些网站的cookies会发生变化,短时间内保存下来的cookies再进行模拟登陆时会成功,但是过段时间就不行,因此,对于这种方法并不推荐。当然,没有其他办法的时候,还是可以使用该类方法的(如登陆时需要验证码,验证码较为复杂,或者由一些加密的难以破解的数据)

使用scrapy.Formrequest.from_response()进行登陆, 自动解析当前登陆url,找到表单,发送post请求

该方法对于大对数网站来说应该都可以实现,因为它能够实现自动解析得到表单,并发送post请求,这能给我们的编程带来极大的便利。 该方法的关键就是对表单进行填写,然后通过scrapy.Formrequest.from_response()进行提交便可以了,在这过程中from_response()用来模拟表单上的提交单击。

你可能会问,表单中的内容如何填写呢?不用担心,直接在parse函数中写入即可。注意的是在这里我们要对start_urls进行设置,将其设置为登陆请求的url,parse将对start_urls中返回的response进行解析,进行进一步处理

代码如下:

class AlitaSpider(scrapy.Spider):
    name = 'alita'
    allowed_domains = ['douban.com']
    start_urls = ['https://accounts.douban.com/j/mobile/login/basic']    # 需要登陆的url
    base_url = 'https://movie.douban.com/subject/1652592/comments?start={}&limit=20&sort=new_score&status=P'
 
    def parse(self, response):
        postData = {
            'ck': '',
            'name': '****',      # 用户名
            'password': '****',  # 密码
            'remember': 'false',
            'ticket': ''
        }
        return [FormRequest.from_response(response, formdata = postData, callback = self.after_login, dont_filter = True)]

这里callback回调函数设置的是需要登陆成功后才能进行的一些网页请求。

很不幸的是,在使用该方法进行模拟登陆时,Scrapy的某条Debug显示为403

2019-02-25 22:52:55 [scrapy.core.engine] DEBUG: Crawled (403) <GET https://accounts.douban.com/j/mobile/login/basic> (referer: None)

并且报错信息(截取部分)为:

 raise ValueError("No <form> element found in %s" % response)
ValueError: No <form> element found in <403 https://accounts.douban.com/j/mobile/login/basic>

通过报错信息我们知道时,在解析的response中并没有表单信息存在,这是因为from_response()会对得到的response进行form表单的提取,因此在使用该方法时,一定要确保response中有form表单,否则,都会出现上述的错误,比如我们这里的豆瓣登陆界面就时不存在form表单的,用浏览器打开请求链接,我们只能看到如下的界面(一般都是空白的界面)
在这里插入图片描述
使用该方法,确保response中有form表单!!!
使用该方法,确保response中有form表单!!!
使用该方法,确保response中有form表单!!!

因此,在这里项让该方法可行,只需要将start_urls内容修改为https://accounts.douban.com/passport/login即可。

使用scrapy.FormRequest()进行登陆,对设置的url发送post请求,对得到的cookies进行存储

既然让函数自己去找表单行不通,那我们就自己来呗。不过在这里同样需要用到FormRequest(),只是不再使用from_response()

直接上代码:

而是用了FormRequest实例,手动指定post地址,meta参数同样是要带上的,将response.meta[‘cookiejar’]赋值给cookiejar,供后面的Request使用

    def start_requests(self):
        return [Request(url = 'https://movie.douban.com', meta = {'cookiejar':1}, callback = self.post_login)]
       
    def post_login(self, response):
        return FormRequest(
            url = 'https://accounts.douban.com/j/mobile/login/basic', 
            method = 'POST',    # 指定访问方式
            formdata = {
            'ck': '',
            'name': '***',
            'password': '***',
            'remember': 'false',
            'ticket': ''
            },
            meta = {'cookiejar':response.meta['cookiejar']}, 
            dont_filter = True,    # 不进行去重处理
            callback = self.after_login
            )

    def after_login(self, response):
        for i in range(22, 24):
            url = self.base_url.format(i * 20)
            yield Request(url = url, meta = {'cookiejar':1}, callback = self.parse_item, dont_filter = True)

start_requests中,会默认使用start_urls里面的url来构造Request,在这里我们直接在Request中指定了url,让spider先去访问豆瓣首页(以获取一些隐藏的表单项,在豆瓣登陆里其实并没有什么隐藏的表单项)。start_requests必须返回应该可迭代的对象,因此我们在return后面加了‘[ ]’。之后通过回调函数来发送post请求。

post_login中,我们指定访问方式为post,通过meta参数将response.meta[‘cookiejar’]赋值给cookiejar,供后续的Request使用,并不进行过滤处理。在登陆成功之后,通过回调函数用得到的cookies来生成我们要抓取的页面的Request,同样不进行过滤处理。

alita.py中的完整代码为:

# -*- coding: utf-8 -*-
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule, Request
from alita.items import AlitaItem
from scrapy.http import FormRequest


class AlitaSpider(scrapy.Spider):
    name = 'alita'
    allowed_domains = ['douban.com']
    base_url = 'https://movie.douban.com/subject/1652592/comments?start={}&limit=20&sort=new_score&status=P'

    def start_requests(self):
        return [Request(url = 'https://movie.douban.com', meta = {'cookiejar':1}, callback = self.post_login)]
        
  
    def post_login(self, response):
        return FormRequest(
            url = 'https://accounts.douban.com/j/mobile/login/basic', 
            method = 'POST',
            formdata = {
                'ck': '',
                'name': '***',
                'password': '***',
                'remember': 'false',
                'ticket': ''
            },
            meta = {'cookiejar':response.meta['cookiejar']},
            dont_filter = True,
            callback = self.after_login
            )

    def after_login(self, response):
        for i in range(22, 24):    # 20页之后的需要登陆之后才能访问
            url = self.base_url.format(i * 20)
            yield Request(url = url, meta = {'cookiejar':1}, callback = self.parse_item, dont_filter = True)   


    def parse_item(self, response):
        results = response.css('div.comment-item')
        for result in results:
            item = AlitaItem()
            item['user_nick'] = result.css('span.comment-info > a::text').extract_first()
            item['score'] = result.css('span.rating::attr(title)').extract_first()
            item['content'] = result.css('span.short::text').extract_first()
            item['userful_num'] = result.css('span.votes::text').extract_first()
            yield item

到目前为止,我们的工作已经完成99%了,就差最后一步了。为了能够让Request加入直接登陆后的cookies信息,我们需要在settings.py中的DOWNLOADER_MIDDLEWARES开启中间件scrapy.downloadermiddlewares.cookies.CookiesMiddleware
关于中间件的更多信息可参阅
http://scrapy-chs.readthedocs.io/zh_CN/latest/topics/settings.html#std:setting-DOWNLOADER_MIDDLEWARES_BASE

由于豆瓣中存在着反爬虫机制,所以我们还需要增加User-Agent来伪装成浏览器,在这里为了省事儿,我们直接在settings.py中进行了修改

最后得到的settings.py代码为:

BOT_NAME = 'alita'

SPIDER_MODULES = ['alita.spiders']
NEWSPIDER_MODULE = 'alita.spiders'

# Crawl responsibly by identifying yourself (and your website) on the user-agent
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36'

# Obey robots.txt rules
ROBOTSTXT_OBEY = False

DOWNLOADER_MIDDLEWARES = {
   'scrapy.downloadermiddlewares.cookies.CookiesMiddleware': 700,
}

运行程序

进入目录,运行如下命令:

scrapy crawl alita

由于Scrapy的运行结果过长,我们仅截取了部分关键信息放在这里:

2019-02-26 09:11:00 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://movie.douban.com> (referer: None)
2019-02-26 09:11:00 [scrapy.core.engine] DEBUG: Crawled (200) <POST https://accounts.douban.com/j/mobile/login/basic> (referer: https://movie.douban.com)
2019-02-26 09:11:00 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://movie.douban.com/subject/1652592/comments?start=460&limit=20&sort=new_score&status=P> (referer: https://accounts.douban.com/j/mobile/login/basic)
2019-02-26 09:11:01 [scrapy.core.scraper] DEBUG: Scraped from <200 https://movie.douban.com/subject/1652592/comments?start=460&limit=20&sort=new_score&status=P>
2019-02-26 09:11:01 [scrapy.core.scraper] DEBUG: Scraped from <200 https://movie.douban.com/subject/1652592/comments?start=460&limit=20&sort=new_score&status=P>
{'content': '**********',
 'score': '还行',
 'user_nick': '*****',
 'userful_num': '***'}
2019-02-26 09:11:01 [scrapy.core.scraper] DEBUG: Scraped from <200 https://movie.douban.com/subject/1652592/comments?start=460&limit=20&sort=new_score&status=P>
{'content': '*********',
 'score': '力荐',
 'user_nick': '*****',
 'userful_num': '***'}
 ....

这里为保护用户信息,我们将评论等内容替换为了‘***’

至此,我们便完整的实现了使用Scrapy模拟登陆豆瓣并对数据进行抓取。后续还可以将数据保存到本地,数据分析,可视化等操作。

好好学习,天天向上。

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值