有些网站需要用户登录后才能获取数据,爬虫要爬取这些受限的数据,就需要先登录网站,然后才能爬取数据。
1、 分析登录表单
使用Scrapy模拟登陆网站前,先了解一下网站登录原理,观察浏览器与网站是如何交互的。
登录表单及调试窗口
www.milihua/sample/login.aspx是一个登录页面,登录页面非常简单,要求用户输入登录账号和登录密码,因是一个参考案例,登录密码采用了明文。用户输入正确的登录账号和登录密码,网页会跳转到资源页,在资源页面包含2个图片的下载链接。
在Google Chrome浏览器打开登录页面,按下F12键,浏览器会打开网页代码调试窗口,在代码调试窗口,单击Sources选项卡,会看到网页的源代码,如下图所示:

实际的网页代码和Google Chrome浏览器显示的网页代码稍有不同,login.aspx网页代码如下:
<html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title></title> </head> <body> <h3>请输入用户名和密码登录系统</h3> <hr> <form id="form1" method="post" action="login.aspx" runat="server"> <div> <p> 登录账号:<input type="text" id="username" value="" runat=server/></p> <p> 登录密码:<input type="password" id="psw" value="" runat=server/></p> <p><input type="submit" value="提交"/></p> </div> </form> </body> </html>
<form>是表单标签,标签之间的内容是表单数据,表单数据会提交到由标签action属性指定的URL页面,该页面会获取从客户端提交的表单数据,在本案例中,获取id值为username和id值为psw的表单数据,然后判断username和id的值是否是正确的登录用户和登录密码,若数据正确,跳转到resource.aspx网页,该网页包含两个图片的下载链接。
查看表单提交情况。
在Google Chrome浏览器调试窗口,单击Network选项卡,在选项卡下方的内容过滤工具条中选择ALL选项。
在网页窗口输入登录用户和登录密码,登录用户为admin,登录密码为pswtest,并单击【提交】按钮,若没有输入错误,网页会跳转到resources.aspx页面,在该页面会看到两个图片的下载链接。
在Google Chrome浏览器调试窗口,单击Name子窗口下的login.aspx条目,Name子窗口的右侧子窗口会显示客户端请求和服务器响应数据。如下图所示:

Request Headers是客户端向服务端发送的请求数据,Scrapy的Request对象会创建Request Headers请求数据。
Form Date是客户端提交的表单数据,表单数据紧跟在Request Headers数据后面。表单数据由多个键值对构成,每个键值对对应一个表单元素。

案例中的表单数据有5个键值对,其中前缀“_”符号的键值对是ASPX页面自动添加的隐藏域。username键值对和psw键值对是用户输入的登录用户和登录密码。
Response Headers是服务器返回的响应数据,响应数据也是由多个键值对构成,爬虫程序可以从响应数据获取登录成功后跳转页面的URL。

爬虫程序可以从Location键值对提取登录成功后跳转页面的URL。
2、 在shell环境下模拟表单登录
FormRequest类用于发送包含表单数据的Request请求,FormRequest类继承Request类,该类额外包含一个formdata参数,接收字典形式的表单数据。
下面在scrapy shell环境下模拟表单登录。
步骤1:
在Windows命令行窗口,输入下面的scrapy shell命令,爬虫login.aspx网页:
scrapy shell http://www.milihua.com/sample/login.aspx
步骤2:
使用response的Xpath方法提取隐藏域,隐私域type属性的值为hidden。在scrapy shell环境下输入下面的命令:
hidden = response.xpath("//input[@type='hidden']")在scrapy shell环境下输入变量名称hidden,查看hidden内容,若XPath表达式没有错误,hidden内容存储了三个隐藏域的数据,隐藏域的数据是input类型。
步骤3:
构造表单数据,将三个隐藏域的name和value添加到表单,在scrapy shell环境下输入下面的命令:
form = dict(zip(hidden.xpath('@name').extract(),hidden.xpath('@value').extract()))在scrapy shell环境下输入变量名称form,查看form内容,若输入没有错误,生成的表单数据如下:

步骤4:
添加登录用户和登录密码输入域到表单,登录用户输入域的name属性值为username,value属性的值为admin,登录密码输入域的name属性值为psw,value属性的值为pswtest。
在scrapy shell环境下分别输入下面的命令:
form["username"] = "admin" form["psw"] = "pswtest"
输入变量名称form,查看form内容,生成的表单数据如下:

至此,已经构建了完整的表单数据。
步骤5:
使用FormRequest类模拟表单登录,在scrapy shell环境下,分别输入下面的命令:
from scrapy.http import FormRequest
request = FormRequest("http://www.milihua.com/sample/login.aspx",formdata=form)上面的命令创建了FormRequest实例对象request,request请求包含了URL和表单数据。下面使用shell环境下的fetch()函数来提交request,在shell环境下输入下面的命令:
fetch(request)
fetch()函数等同于scrapy fetch <url>命令,在shell环境下将fetch命令封装为fetch()函数。
执行fetch()函数后,shell环境会输出下面的内容:
2020-10-29 09:59:40 [scrapy.downloadermiddlewares.redirect] DEBUG: Redirecting (302) to <GET http://www.milihua.com/sample/resources.aspx> from <POST http://www.milihua.com/sample/login.aspx> 2020-10-29 09:59:40 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://www.milihua.com/sample/resources.aspx> (referer: None)
从输出内容可以看出,已经登录成功,服务器返回了资源页URL,后续可以继续爬取资源页来下载图片。
使用上面的表单构造方法比较麻烦,需要识别出隐藏域。Scrapy还提供了一种更简单的表单构造方法,FormRequest提供了from_response()方法,该方法从传入的response 对象中提取表单隐藏域自动添加到传入的表单中,from_response()方法声明如下:
from_response( response[, formname=None, formid=None, formnumber=0, formdata=None, formxpath=None, formcss=None, clickdata=None, dont_click=False, ...] )
方法参数比较多,除了response参数外,其它都是可选参数。重要且常用的参数就是两个:一个是response必选参数,包含HTML表单的响应,该表单将预填充表单字段;另外一个参数是formdata,类型是一个字典对象,用于设置预填充表单中重写的字段,例如用户输入的登录用户和登录密码等表单字段。
打开Windows命令行窗口,分别执行下面的命令:
scrapy shell http://www.milihua.com/sample/login.aspx
formdata = {"username":"admin","psw":"pswtest"}
from scrapy.http import FormRequest
formdata = {"username":"admin","psw":"pswtest"}
request = FormRequest.from_response(response,formdata=formdata)
fetch(request)命令执行完成后,从返回的结果可以看出登录成功,服务器返回了资源页URL。
3、 建立模拟登录爬虫项目
启动PyCharm建立项目,项目名称为login。在项目终端窗口使用Scrapy创建项目命令创建爬虫项目,在终端窗口输入下面的Scrapy命令并回车执行命令:
scrapy startproject simulatedlogin
在终端窗口将当前工作目录切换到simulatedlogin爬虫项目目录,输入创建爬虫命令:
scrapy genspider spider_simulatedlogin www.milihua.com
命令执行成功后,Scrapy会在项目的spiders目录创建爬虫spider_ simulatedlogin.py模块文件
(1)编辑items.py文件,定义下载图片文件必要的数据项。
import scrapy class SimulatedloginItem(scrapy.Item): # 定义image_urls数据项,存储图片文件URL路径 image_urls = scrapy.Field() # 定义image_paths数据项,存储图片文件下载路径 image_paths = scrapy.Field()
(2)编辑pipelines.py文件,继承ImagesPipeline类,重写ImagesPipeline类的三个方法。
# 导入os模块
import os
# 导入scrapy
import scrapy
# 导入适配器
from itemadapter import ItemAdapter
# 导入urlparse模块
from urllib.parse import urlparse
# 导入ImagesPipeline模块
from scrapy.pipelines.images import ImagesPipeline
# 导入Scrapy异常处理模块
from scrapy.exceptions import DropItem
class SimulatedloginPipeline(ImagesPipeline):
# 图片文件重命名(包含后缀名),若不重写该函数,图片名默认为图片内容的哈希值
def file_path(self, request, response=None, info=None, *, item=None):
# 图片文件名称取URL中的图片名称
return os.path.basename(urlparse(request.url).path)
# 设置需要下载的图片URL,并返回图片request
def get_media_requests(self, item, info):
# 遍历image_urls数据项,为每个图片URL生成request请求
for image_url in item['image_urls']:
yield scrapy.Request(image_url)
# 存储图片下载路径
def item_completed(self, results, item, info):
# result是一个包含元组的列表
# 元组包含两个值,第一个代表状态True/False,
# 第二个值是一个dict对象,若元素中状态为True则取dict中的path值
image_paths = [x['path'] for ok, x in results if ok]
# 若image_paths为空,说明下载失败
# 抛出DropItem异常
if not image_paths:
raise DropItem("图片文件下载失败")
# 获取Item的适配器
adapter = ItemAdapter(item)
# 存储image_paths
adapter['image_paths'] = image_paths
return item(3)编辑spider_simulatedlogin.py爬虫文件,使用FormRequest模拟表单登录。
import scrapy
# 导入Request和FormRequest
from scrapy.http import Request, FormRequest
from simulatedlogin.items import SimulatedloginItem
class SpiderSimulatedloginSpider(scrapy.Spider):
name = 'spider_simulatedlogin'
allowed_domains = ['www.milihua.com']
start_urls = ['http://www.milihua.com']
# 登录页面URL
login_url = "http://www.milihua.com/sample/login.aspx"
def parse(self, response):
pass
# 重写start_requests方法
def start_requests(self):
yield Request(self.login_url, callback=self.login)
# 定义登录页面解析函数
def login(self, response):
# 构造FormRequest对象提交表单
fd = {"username":"admin","psw":"pswtest"}
# 发送FormRequest请求
yield FormRequest.from_response(response, formdata=fd,
callback=self.parse_login)
# 登录成功后返回资源页
# 解析资源页面,下载图片
def parse_login(self, response):
item = SimulatedloginItem()
# 提取图片下载链接
down_link = response.xpath("//li/a/@href").extract()
# 相对URL转换为绝对URL
item["image_urls"] = [response.urljoin(link) for link in down_link]
# 使用yield语句返回item给parse的调用者
yield item在爬虫代码中,重写了start_requests()方法,该方法在爬取开始时调用,且仅调用一次,若要更改开始爬取的URL,可以在该方法中实现。该方法将首次爬取的URL定向为login_url,并发送Request请求,该请求绑定的处理方法为login()方法。
在login()方法内,使用response填充表单,并填入登录用户和登录密码表单字段,发送FormRequest请求,该请求绑定的处理方法为parse_login()。
在parse_login()方法内,若登录成功,response指向登录成功后服务器返回的资源页面,从资源页面中提取图片链接填充到item对象,并返回item对象。
(4)编辑settings.py配置文件,将管道类配置项的注释去掉,在配置文件尾部添加图片存储路径配置项。
去掉管道类配置项的注释
ITEM_PIPELINES = {
'simulatedlogin.pipelines.SimulatedloginPipeline': 300,
}添加图片存储路径配置项
# 下载图片存储路径配置项
IMAGES_STORE = os.path.join(os.path.dirname(os.path.dirname(__file__)),"images")
该配置项使用了os模块,需要在配置文件头部导入os模块。
(5)运行爬虫
在PyCharm环境中,打开终端窗口,在终端窗口将当前工作目录切换到爬虫项目目录,输入Scrapy运行爬虫命令:
scrapy crawl spider_simulatedlogin -o items.json
爬虫运行完成后,会将下载的图片文件存储到爬虫项目目录下的images目录。
项目全部代码见课程资源(unit4\ login)