上一篇文章中,我们使用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
模拟登陆豆瓣并对数据进行抓取。后续还可以将数据保存到本地,数据分析,可视化等操作。
好好学习,天天向上。