Scrapy模拟登陆豆瓣抓取数据

通常情况下,我们都会自己去编写完整的代码来进行数据抓取,但是对于爬取量较大的时候,我们所编写的程序在时间上说不定就没有什么优势了,虽然可以通过多进程、异步等操作来减少爬取时间,但是由于异步编写起来过于复杂(多进程还是挺简单的),并且需要大量的重构代码。那有没有什么简单的方法来实现大量数据的快速抓取呢?当然有,那就是使用Scrapy框架,其是一个快速、高层次的屏幕抓取和web抓取框架,能够实现快速的数据抓取。

之前我们讲了使用requests.Session()来实现,虽然比较简单,但是现在各大公司在招聘员工时都需要熟悉Scrapy框架,因此,今天就来谈一谈如何用Scrapy来模拟登陆并对数据进行抓取。

在这里插入图片描述

创建项目

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

1
scrapy startproject Alita

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

创建Spider

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

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

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

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

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

1
2
3
4
5
6
7
8
9
10

import scrapy

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

def parse(self, response):
pass

创建容器Item

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

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

1
2
3
4
5
6
7
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中部分代码如下:

1
2
3
4
5
6
7
8
9
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来进行模拟登陆的方法主要有三种:

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

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

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

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

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

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class (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

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

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

1
2
 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即可。

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

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

直接上代码:

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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 = {
大专栏  Scrapy模拟登陆豆瓣抓取数据 '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中的完整代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

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 (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代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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,
}

运行程序

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

1
scrapy crawl alita

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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模拟登陆豆瓣并对数据进行抓取。后续还可以将数据保存到本地,数据分析,可视化等操作。

好好学习,天天向上。

× 小奶糖
打赏二维码

猜你喜欢

转载自www.cnblogs.com/lijianming180/p/12389711.html