Scrapy快速入门

本文最后更新于:1 年前

该教程适用于Scrapy这一Python爬虫框架的入门学习

第一部分 走进Scrapy

0.简介及安装

简介

scrapy设计目的:用于爬取网络数据,提取结构性数据的框架,其中,scrapy使用了Twisted异步网络框架,大大加快了下载速度

Tips:关于开发工具的选取,可以使用Pycharm这款强大的Python IDE,但这里推荐使用VSCode这一象征全世界的代码编辑器!

这里首先介绍如何使用VSCode配置Scrapy框架的开发环境

首先笔者假设您已经完整阅读过VSCode快速入门、Conda快速入门

首先,您需要创建一个文件夹用以存放Scrapy项目文件夹,建议以Scrapy命名

在VSCode中打开文件夹Scrapy

打开文件夹

打开目标文件夹Scrapy后,需要配置Python解释器,笔者这里假设您已经安装好python,或者使用conda安装了Python虚拟环境,VSCode会在下图圈中位置提醒您配置解释器,笔者这里已经配置好了Python解释器

配置解释器

配置完成后,开始进行Scrapy框架的安装 该框架的安装非常简单 需要用到终端输入命令行,如果您没有出现终端窗口,可以进行下图操作

打开控制台

安装

接下来,您只需要在终端中输入以下命令即可进行安装,当安装成功后,进入下一步操作

1
pip install scrapy

如果您希望尽早开始实际操作,可以先完成第二部分后再来看下面内容

如果您希望保证教程的连续性,浏览下面的理论内容也是可行的,但建议您实操完后对下面内容进行复习巩固 相信您会对理论有更深入的认识

1.Scrapy项目开发流程

1.创建项目

1
scrapy startproject <项目名称>

2.生成一个爬虫

1
scrapy genspider <爬虫名称> <允许爬取的域名>

3.提取数据:根据网站结构在spider中实现数据采集相关内容

4.保存数据:使用pipline进行数据后续处理和保存

2.Scrapy框架运行流程

流程图

各模块作用

原理描述

1.爬虫中起始url构造的url对象 -> 爬虫中间件 -> 引擎 -> 调度器
2.调度器把request发送 -> 引擎 -> 下载中间件 -> 下载器
3.下载器发送请求,获取request响应 -> 下载中间件 -> 引擎 -> 爬虫中间件 -> 爬虫
4.爬虫提取url地址,组装成request对象 -> 爬虫中间件 -> 引擎 -> 调度器,重复步骤2
5.爬虫提取数据 -> 引擎 -> 管道处理和保存数据

Tips:原理描述中的下载中间件和爬虫中间件只是运行时的位置不同,作用是重复的!

==下面到本次教程的重点!!! 您的第一个Scrapy从现在开始!!!==

第二部分 创建&&运行您的第一个Scrapy项目!

1.创建项目

相信如果您仔细阅读本教程 在第一部分的Scrapy项目开发流程中,有两个终端指令,其中第一个指令即为创建新项目的指令

1
2
创建scrapy项目的命令:scrapy startproject <项目名称>
示例:
1
scrapy startproject SivanWuLa #后续的教程均以该项目为示例

在终端运行该条指令后,您会发现在Scrapy目录下生成了一个SivanWuLa文件夹,该文件夹的文件树如下图:

项目文件树

2.爬虫文件的创建

熟悉其他爬虫框架的读者应当发觉,该文件树中缺少重要的核心爬虫文件,但不要担心,Scrapy设置了终端命令,可以自动生成爬虫文件

但生成之前,您应当保证终端中进入了刚才生成的SivanWuLa根路径下,若没有,请在终端输入:

1
cd SivanWuLa

保证在项目根路径下后,执行一下终端命令

1
scrapy genspider <爬虫名字> <允许爬取的域名>

示例:

1
scrapy genspider getSivanInfo sivanwula.tech

我这里给出的域名,是我的个人技术博客网站,欢迎大家多多访问,我的博客支持友链入驻,欢迎各位读者!

3.运行Scrapy爬虫

命令:在项目目录下执行

1
scrapy crawl <爬虫名字>

不过,在运行爬虫之前,我们先要编写spiders文件夹下的SivanWula爬虫文件

我先给出一个完整版的示例,然后分块进行详细的讲解

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
# -*- coidng utf-8 -*-
import scrapy

class SivaninfoSpider(scrapy.Spider):
# 爬虫运行时的参数
name = 'SivanInfo'
#检查运行爬的域名
allowed_domains = ['sivanwula.tech']
# 1.修改设置起始的url
start_urls = ['http://sivanwula.tech/']

# 数据提取的方法接收下载中间件传过来的response,定义对于网站相关的操作
def parse(self, response):
#获取网站中文章板中所有文章的节点
a_list = response.xpth("//*[@id='board']/div/div/div/div")
print(a_list)
# 遍历文章节点列表
art_list = {}

for article in a_list:
# xpath方法返回的是选择器对象列表
# extract()方法可以提取到selector对象中data对应的数据
article['title'] = article.xpath('./article/h1/a/text()')[0].extract()
article['abstract'] = article.xpath('./article/a/div/text()')[0].extract()
yield article

此时您直接输入指令,发现已经可以运行了

直接运行

当然,这个只是刚刚开始,我们刚刚明确了爬虫所爬取数据,接下来使用管道进行数据持久化操作

4.管道数据持久化

修改我们的SivanWuLa.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
# -*- coidng utf-8 -*-
import scrapy
from ..items import SivanwulaItem

class SivaninfoSpider(scrapy.Spider):
# 爬虫运行时的参数
name = 'SivanInfo'
#检查运行爬的域名
allowed_domains = ['sivanwula.tech']
# 1.修改设置起始的url
start_urls = ['http://sivanwula.tech/']

# 数据提取的方法接收下载中间件传过来的response,定义对于网站相关的操作
def parse(self, response):
#获取网站中文章板中所有文章的节点
a_list = response.xpth("//*[@id='board']/div/div/div/div")
print(a_list)
# 遍历文章节点列表
item = SivanwulaItem() #创建了一个储存数据的类对象
for article in a_list:
# xpath方法返回的是选择器对象列表
# extract()方法可以提取到selector对象中data对应的数据
item['title'] = article.xpath('./article/h1/a/text()')[0].extract()
item['abstract'] = article.xpath('./article/a/div/text()')[0].extract()
yield item

修改后文件后,是不是发现部分内容发生了变化,我们导入了根目录下items.py文件中的SivanwulaItem,这是一个类,可以用来创建储存数据的具有字典属性的对象,该类在items.py文件中定义,(后续部分的数据建模有详细介绍)如下:

1
2
3
4
5
6
7
8
9
10
# Define here the models for your scraped items
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.html

import scrapy

class SivanwulaItem(scrapy.Item):
title = scrapy.Field()
abstract = scrapy.Field()

我们发现类中的title,abstract都是Field类的对象,我们查看Field源码,如下:

1
2
class Field(dict):
"""Container of field metadata"""

发现该类只是内置字典类(dict)的一个别名,并没有提供额外的方法和属性。

该类创建的对象有下面四个特征:

  • Field对象指明了每个字段的元数据(任何元数据),Field对象接受的值没有任何限制
  • 设置Field对象的主要目就是在一个地方定义好所有的元数据
  • 注意,声明item的Field对象,并没有被赋值成class属性。(可通过item.fields进行访问)
  • 被用来基于类属性的方法来支持item生命语法。

对于我们有用的一点在于,该类创建的对象可以用来存储任何类型的值,所以SivanWuLa.py文件中,我们直接将获取的网站中文章板中关于文章有用数据的xpath存入了iten中的相应的field对象中,关于什么是xpath,该教程不予以赘述,这里是传送门

默认读者已经理解xpath的具体概念,但其实不理解也没关系,因为重点在于我们该如何获取它,获取文章板中所有的文章节点时,我们直接使用了下载中间件传来的response,这意味着下载中间件已经将相应HTML存储到了磁盘中,此时我们只需直接通过xpath搜寻我们已经下载好的感兴趣数据。

那么,我们该如何定位到感兴趣数据的xpath呢,这时候就是比较笨拙的方法的了,我们直接来到sivanwula.tech(鉴于网页部署在GitHub上的缘故,加载可能有些卡,各位见谅),进入页面,我们直接在文章板处右键,如下:

右键检查

之后会弹出下面界面(其实对于浏览器,直接按f12也可以弹出该界面,但当网页结构较为复杂时,很难定位到所需数据的周围位置,所以推荐检查,定位更为高效)

检查之后

这时候,我们只需右键该行html语言,然后点击复制,在点击复制XPATH,如下:

复制XPATH

这时候,我们就已经获取到了所需xpath

我们发现复制完的xpath为

1
//*[@id="board"]/div/div/div/div

该xpath是整个文章板,可以用来生成文章节点列表,正如代码中所写:

1
a_list = response.xpth("//*[@id='board']/div/div/div/div")

当然,应当注意,需将board的双引号改为单引号,才能符合python的字符串原则

接下来我们需要将感兴趣的数据的xpath复制下来,首先得明确哪些数据对应于我们item对象中的field成员,如下图:

明确数据

那么,接下来,我们以title为例,来获取它的xpath,同样,首先我们直接检查文章板,然后我们点击代码旁的小三角度,进行多次展开展开,观察左边界面,当一个文章节点被选中时,我们再次展开,然后选中到标题的html,然后执行cv操作,如下图:

标题xpath

得到的xpath如下:

1
//*[@id="board"]/div/div/div/div[1]/article/h1/a

该xpath还需要进一步处理,由下段代码:

1
2
3
4
5
6
for article in a_list:
# xpath方法返回的是选择器对象列表
# extract()方法可以提取到selector对象中data对应的数据
item['title'] = article.xpath('./article/h1/a/text()')[0].extract()
item['abstract'] = article.xpath('./article/a/div/text()')[0].extract()
yield item

可知道,我们通过遍历文章节点列表中的节点,并获取相应节点中的相应数据的xpath,所以我们不需要书写完整的xpath,只需要给出特殊部分,根据获得的文章板的xpath,对比一下,我们只需书写

1
./article/h1/a

但为了获得文本,我们需要加上text()这一函数,如下:

1
./article/a/div/text()

abstract的xpath获取方式类似,不予以赘述。

5.管道保存数据

在piplines.py文件中定义对数据的操作!

具体流程:

  • 定义一个管道类
  • 重写管道类的process_item方法
  • process_item方法处理完item之后必须返回给引擎

这里我们使用一个json文件来保存我们获取的数据

piplines.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
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html


# useful for handling different item types with a single interface
from itemadapter import ItemAdapter
import json

class SivanwulaPipeline:
def __init__(self):
self.file = open('SivanWuLaInfo.json','w',encoding = 'utf-8')
def process_item(self, item, spider):
# 将item对象强制转成字典,该操作只能在scrapy中使用
#该方法为固定名称函数
# 默认使用完管道,需要将数据返回给引擎
# 1.将字典数据序列化
'''ensure_ascii=False 将Unicode类型转换为str类型,默认为True'''
json_data = json.dumps(item,ensure_ascii=False,indent=2) + ',\n'
return item
def __del__(self):
self.file.close()

这里需要注意是的是,我们默认使用utf-8编码方式,读者一定要将相关文件改成utf-8,在vscode中修改方式如下图所示:

修改编码

上面piplines.py文件中,提示我们在settings.py中配置启用管道,接下来我们进行管道的启用。

6.settings.py配置启用管道

进入到settingas.py文件中,我们发现有很多可供启用的配置,这里我们主要是启用管道,所以我们可以ctrl+h,搜索pipelines,如下图:

管道启用

接下来我们只需要解除该段代码的封印就可以启用管道了!说明如下:

1
2
3
4
5
6
7
8
9
'''
SivanWuLa: 项目名称目录
pipelines: 项目目录下的pipelines文件
SivanwulaPipeline: 在pipelines文件中定义的普通类SivanwulaPipeline
300: 执行顺序,该数越小越先执行,一般来说,不超过1000
''''''
ITEM_PIPLINES = {
'SivanWuLa.pipelines.SivanwulaPipeline': 300,
}

7.Scrapy数据建模与请求

通常在做项目的过程中,在items.py中进行数据建模!

(1)为什么要建模?

​ 1.定义item即提前规划好哪些字段需要抓取,防止手误,因为定义好之后,在运行过程中,系统会自动检查,值不相同会报错

​ 2.配合注释可以清晰的知道要抓取哪些字段,没有定义的字段不能抓取,在目标字段少的时候可以使用字典代替;

​ 3.使用Scrapy的一些特定组件需要item做支持,如Scrapy的ImagePipeline管道类

(2)在本项目中实操

在items.py文件中操作

1
2
3
4
5
6
7
8
9
10
11
12
13
# Define here the models for your scraped items
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.html

import scrapy


class SivanwulaItem(scrapy.Item):
#文章标题
title = scrapy.Field()
#文章摘要
abstract = scrapy.Field()

注意:
1.from ..items import SivanWulaItem这一代码中 注意item的正确导入路径,使用pycharm时记得忽略其标记的错误
2.python中导入路径要诀:从哪里开始运行,就从哪里开始导入

8.设置user-agent

User Agent中文名为用户代理,简称 UA,它是一个特殊字符串头,使得服务器能够识别客户使用的操作系统及版本、CPU 类型、浏览器及版本、浏览器渲染引擎、浏览器语言、浏览器插件等。开发爬虫过程中,经常会发现反爬措施非常重用,其中设置随机user-agent就是一项重要的反爬措施,Scrapy中设置UA的方式有很多,在教程最后的进阶操作中,笔者会介绍两种常见方式。

这里设置一个UA为例,如下:

1
2
3
4
5
6
7
# settings.py文件中找到如下代码解封,并加入UA:
# Override the default request headers:
DEFAULT_REQUEST_HEADERS = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.162 Safari/537.36',
}

9.到目前为止,一个入门级别的scrapy爬虫已经OK了

如何运行?

现在cd到项目目录下,在终端中输入

1
scrapy crawl SivanInfo

即可运行scrapy!

项目运行

如上图,笔者网页文章板中,读者所感兴趣的信息已经被写入json文件中

第三部分 进阶操作

1.随机设置User Agent

这里提供两种方式:

(1)settings创建user agent表

导入random,随机用choise函数调用user agent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import random
# user agent 列表
USER_AGENT_LIST = [
'MSIE (MSIE 6.0; X11; Linux; i686) Opera 7.23',
'Opera/9.20 (Macintosh; Intel Mac OS X; U; en)',
'Opera/9.0 (Macintosh; PPC Mac OS X; U; en)',
'iTunes/9.0.3 (Macintosh; U; Intel Mac OS X 10_6_2; en-ca)',
'Mozilla/4.76 [en_jp] (X11; U; SunOS 5.8 sun4u)',
'iTunes/4.2 (Macintosh; U; PPC Mac OS X 10.2)',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:5.0) Gecko/20100101 Firefox/5.0',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:9.0) Gecko/20100101 Firefox/9.0',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20120813 Firefox/16.0',
'Mozilla/4.77 [en] (X11; I; IRIX;64 6.5 IP30)',
'Mozilla/4.8 [en] (X11; U; SunOS; 5.7 sun4u)'
]
# 随机生成user agent
USER_AGENT = random.choice(USER_AGENT_LIST)

我们在spider文件中,编写测试代码,如下:

1
2
def parse(self, response):
print(response.request.headers['User-Agent']

注意是在爬虫类中的parse函数进行每次访问的打印输出,结果如下图所示:

随机user-agent

(2)在middleware中调用user agent

!!!在setting中注释user agent 防止干扰

在middlewares中创建如下类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import random
class UserAgentMiddleware(object):
def __init__(self):
self.user_agent_list = [
'MSIE (MSIE 6.0; X11; Linux; i686) Opera 7.23',
'Opera/9.20 (Macintosh; Intel Mac OS X; U; en)',
'Opera/9.0 (Macintosh; PPC Mac OS X; U; en)',
'iTunes/9.0.3 (Macintosh; U; Intel Mac OS X 10_6_2; en-ca)',
'Mozilla/4.76 [en_jp] (X11; U; SunOS 5.8 sun4u)',
'iTunes/4.2 (Macintosh; U; PPC Mac OS X 10.2)',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:5.0) Gecko/20100101 Firefox/5.0',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:9.0) Gecko/20100101 Firefox/9.0',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20120813 Firefox/16.0',
'Mozilla/4.77 [en] (X11; I; IRIX;64 6.5 IP30)',
'Mozilla/4.8 [en] (X11; U; SunOS; 5.7 sun4u)'
]
def process_request(self,request,spider):
request.headers['USER_AGENT']=random.choice(self.user_agent_list)

在settings.py中启用downloader middleware

解除以下代码注释:

1
2
3
DOWNLOADER_MIDDLEWARES = {
'SivanWuLa.middlewares.SivanwulaDownloaderMiddleware': 543
}

测试结果与上一方法相同,这里不再展示。

2.使用Scrapy shell

关于Scrapy shell的描述,我将官方文档的翻译了一下,大概如下:

1)Scrapy shell是一个交互式shell,您可以在其中非常快速地尝试和调试抓取代码,而不必运行爬行器。它的目的是用于测试数据提取代码,但实际上您可以使用它来测试任何类型的代码,因为它也是一个常规的Python shell。
2)shell用于测试XPath或CSS表达式,并查看它们是如何工作的,以及它们从您试图抓取的网页中提取哪些数据。它允许您在编写爬行器时交互式地测试表达式,而不必运行爬行器来测试每个更改。
3)一旦熟悉了Scrapy shell,就会发现它是开发和调试spider的宝贵工具。

从官方文档可以发现,scrapy shell是我们编写的爬虫的一个神器!

我们可以用它来测试我们抓取的XPath或CSS表达式,并能查看提取的数据,这可以大大减少我们调试代码以获取最准确表达式的时间!

执行以下代码,打开shell:

1
scrapy shell <url> #这里的url就是你想要爬取的网站

打开成功后,如下图:

Scrapy shell

接下来,我们就可以进行一些好玩的操作了!

比如:

1
response.status 	#可以查看当前相应状态 如:200,404等响应状态值

或者我们对响应头进行一个打印

首先加载模块pprint,该模块可以用来打印完整的数据结构,看起来更加美观

1
from pprint import pprint

然后执行打印响应头操作:

1
pprint(response.headers)

结果如下:

响应头输出

那么我们是否可以在爬虫运行时,触发shell来检查响应呢,答案是肯定的,下面给出官方文档的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import scrapy


class MySpider(scrapy.Spider):
name = "myspider"
start_urls = [
"http://example.com",
"http://example.org",
"http://example.net",
]

def parse(self, response):
# We want to inspect one specific response.
if ".org" in response.url:
from scrapy.shell import inspect_response
inspect_response(response, self)

# Rest of parsing code.

这里的爬虫是对多个域名网站进行爬取,并使用shell对带有.org的域名网站进行检查

当你执行爬虫后,终端会显示如下:

1
2
3
4
5
6
7
8
2014-01-23 17:48:31-0400 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://example.com> (referer: None)
2014-01-23 17:48:31-0400 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://example.org> (referer: None)
[s] Available Scrapy objects:
[s] crawler <scrapy.crawler.Crawler object at 0x1e16b50>
...

>>> response.url
'http://example.org'

然后,您可以检查提取代码是否正常工作:

1
2
>>> response.xpath('//h1[@class="fn"]')
[]

不,它没有。所以你可以在浏览器里打开这个回复,看看是不是你想要的回复:

1
2
>>> view(response)
True

最后你按下Ctrl-Z 退出shel l并继续爬行:

1
2
3
>>> ^D
2014-01-23 17:50:03-0400 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://example.net> (referer: None)
...

Scrapy快速入门
https://wlpswmt.github.io/2023/03/06/Scrapy快速入门/
作者
Sivan Zhang
发布于
2023年3月6日
许可协议