scrapy框架简介

​ Scrapy是一个快速、高层次的屏幕抓取和web抓取框架,用于抓取web站点并从页面中提取结构化的数据。Scrapy用途广泛,可以用于数据挖掘、监测和自动化测试,也提供了多种类型爬虫的基类,如BaseSpider、sitemap爬虫等。

​ Scrapy 是基于twisted框架开发而来,twisted是一个流行的事件驱动的python网络框架。因此Scrapy使用了一种非阻塞(又名异步)的代码来实现并发。

**Engine(引擎)**:负责Spider、Item Pipeline、Downloader、Scheduler中间的通讯,信号、数据传递等。负责控制系统所有组件之间的数据流,并在某些动作发生时触发事件。

**Scheduler(调度器)**:它负责接受引擎发送过来的Request请求,并按照一定的方式进行整理排列,入队,当引擎需要时,交还给引擎。可以想象成一个URL的优先级队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址。

Downloader(下载器):负责下载Scrapy Engine(引擎)发送的所有Requests请求,并将其获取到的Responses交还给Scrapy Engine(引擎),由引擎交给Spider来处理。

Spider(爬虫):它负责处理所有Responses,从中分析提取数据,获取Item字段需要的数据,并将需要跟进的URL提交给引擎,再次进入Scheduler(调度器)。

**Item Pipeline(项目管道)**:它负责处理Spider中获取到的Item,并进行进行后期处理(详细分析、过滤、存储等)的地方。主要包括清理、验证、持久化(比如存到数据库)等操作。

Spider Middlewares(爬虫中间件):一个可以自定扩展和操作引擎和Spider中间通信的功能组件

  • 安装
1
pip3 install scrapy

命令行工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 1 查看帮助
scrapy -h
scrapy <command> -h

# 2 有两种命令:其中Project-only必须切到项目文件夹下才能执行,而Global的命令则不需要
Global commands:
startproject #创建项目
genspider #创建爬虫程序
settings #如果是在项目目录下,则得到的是该项目的配置
runspider #运行一个独立的python文件,不必创建项目
shell #scrapy shell url地址 在交互式调试,如选择器规则正确与否
fetch #独立于程单纯地爬取一个页面,可以拿到请求头
view #下载完毕后直接弹出浏览器,以此可以分辨出哪些数据是ajax请求
version #scrapy version 查看scrapy的版本,scrapy version -v查看scrapy依赖库的版本
Project-only commands:
crawl #运行爬虫,必须创建项目才行,确保配置文件中ROBOTSTXT_OBEY = False
check #检测项目中有无语法错误
list #列出项目中所包含的爬虫名
edit #编辑器,一般不用
parse #scrapy parse url地址 --callback 回调函数 #以此可以验证我们的回调函数是否正确
bench #scrapy bentch压力测试

# 3 官网链接
https://docs.scrapy.org/en/latest/topics/commands.html

目录结构

1
2
3
4
5
6
7
8
9
10
11
12
project_name/
scrapy.cfg
project_name/
__init__.py
items.py
pipelines.py
settings.py
spiders/
__init__.py
爬虫1.py
爬虫2.py
爬虫3.py

文件说明

  • scrapy.cfg 项目的主配置信息,用来部署scrapy时使用,爬虫相关的配置信息在settings.py文件中。
  • items.py 设置数据存储模板,用于结构化数据,如:Django的Model
  • pipelines 数据处理行为,如:一般结构化的数据持久化
  • settings.py 配置文件,如:递归的层数、并发数,延迟下载等。强调:配置文件的选项必须大写否则视为无效,正确写法USER_AGENT=’xxxx’
  • spiders 爬虫目录,如:创建文件,编写爬虫规则

注意:

  • 一般创建爬虫文件时,以网站域名命名
  • 默认只能在终端执行命令,为了更便捷操作

在根目录下新建 .py 文件名无规定

在项目根目录下新建:bin.py, 这就相当于帮助我们在终端执行了scrapy crawl doutu

1
2
from scrapy.cmdline import execute
execute(['scrapy', 'crawl', 'doutu'])

spider

  • spiders是网站爬取,解析页面,定义自定义行为的地方。 

1、 定义在start_requests()方法内的URL,默认从start_urls列表中获得url地址来生成Request请求。用生成的Requests来爬取第一个URL,并且标识一个回调函数(默认的回调函数是parse方法)
回调函数在下载完成返回response时自动触发。

2、 在回调函数中,解析response并且返回值,返回值有四种

解析数据的字典
Item对象
新的Request对象(新的Requests也需要指定一个回调函数)
可迭代对象(包含Items或Request)

3、在回调函数中解析页面内容,通常使用Scrapy自带的Selectors,也可以使用Beutifulsoup,lxml等。

4、最后,针对返回的Items对象将会被持久化到数据库

通过Item Pipeline组件存到数据库:https://docs.scrapy.org/en/latest/topics/item-pipeline.html)
或者导出到不同的文件(通过Feed exports:https://docs.scrapy.org/en/latest/topics/feed-exports.html)

Item

  • 抓取的主要目标是从非结构化源(通常是网页)中提取结构化数据。Scrapy爬虫可以像Python一样返回提取的数据。但很容易在字段名称中输入拼写错误或返回不一致的数据,尤其是在具有许多爬虫的较大项目中。

    为了定义通用输出数据格式,Scrapy提供了Item类。 Item对象是用于收集抓取数据的简单容器。它们提供类似字典的 API,并具有用于声明其可用字段的方便语法。

1
2
3
4
5
6
7
import scrapy

class Product(scrapy.Item):
name = scrapy.Field()
price = scrapy.Field()
stock = scrapy.Field()
last_updated = scrapy.Field(serializer=str)

Scrapy Items类似于Django Models,而Scrapy Items更简单,因为没有不同字段类型的概念

Field对象用于指定每个字段的元数据。例如,last_updated上面示例中说明的字段的序列化函数。可以为每个字段指定任何类型的元数据。Field对象接受的值没有限制。出于同样的原因,没有所有可用元数据键的参考列表。

Field对象中定义的每个键可以由不同的组件使用,只有那些组件知道它。您也可以根据Field自己的需要定义和使用项目中的任何其他键。

Field对象的主要目标是提供一种在一个地方定义所有字段元数据的方法。通常,行为取决于每个字段的那些组件使用某些字段键来配置该行为。

Item PipeLine

  • 在一个项目被蜘蛛抓取之后,它被发送到项目管道,该项目管道通过顺序执行的几个组件处理它。每个项目管道组件(有时简称为“项目管道”)是一个实现简单方法的Python类。他们收到一个项目并对其执行操作,同时决定该项目是否应该继续通过管道或被丢弃并且不再处理。

项目管道的典型用途是:

  • 清洗数据
  • 验证数据(检查Item某些字段是否为空)
  • 数据查重
  • 写入数据库

Item PipeLine的方法

  • process_item
1
2
3
process_item(self,项目,蜘蛛)
为每个项目管道组件调用此方法。process_item(self, item, spider)
必须要返回带数据的dict,或者返回一个Item (或任何后代类)对象,或者返回Twisted Deferred或引发 DropItem异常。丢弃的项目不再由其他管道组件处理。
  • open_spider
1
2
open_spider(self,蜘蛛)
打开蜘蛛时会调用此方法。
  • close_spider
1
2
close_spider(self,蜘蛛)
当蜘蛛关闭时调用此方法。
  • from_crawler
1
2
3
from_crawler(cls,crawler )
如果存在,则调用此方法从爬虫程序Crawler生成实例它必须返回管道的新实例。Crawler对象提供对所有Scrapy核心组件的访问,
如设置和信号; 它是管道访问它们并将其功能挂钩到Scrapy的一种方式。

当使用 settings 对象获取全局变量时,可以在 Scrapy 配置文件中设置该变量的值,然后通过 settings.get('MY_SETTING') 方法来访问它。这样做很方便,但也有一些限制:

  • 无法根据特定的 Crawler 实例动态地调整全局变量的值。例如,当您在多个爬虫之间共享同一个 Scrapy 项目时,某些设置可能需要在每个爬虫之间进行微调。
  • 无法轻松地将其他参数传递给中间件。如果您想基于环境变量、命令行参数或其他外部因素配置中间件,那么使用 settings 对象可能很麻烦。

为了解决这些问题,Scrapy 引入了 from_crawler 方法。这个方法可以让您根据具体的 Crawler 实例创建中间件实例,并动态地向中间件传递其他参数。

具体来说,from_crawler 方法接收一个 crawler 参数,这是当前 Crawler 实例的引用。可以使用 crawler.settings 访问 Scrapy 的全局配置变量,还可以使用 crawler.signals 注册信号处理程序,以及使用 crawler.enginecrawler.spider 访问其他 Scrapy 子系统和资源。因此,在 from_crawler 中,您可以使用更多的 Scrapy 资源,并根据需要动态地配置中间件。这使得中间件更加灵活和可扩展,可以适应多种场景和需求。

start_rquests重写

  • scrapy中start_url是通过spider下的start_requests()来进行处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def start_requests(self):
cls = self.__class__
if method_is_overridden(cls, Spider, 'make_requests_from_url'):
warnings.warn(
"Spider.make_requests_from_url method is deprecated; it "
"won't be called in future Scrapy releases. Please "
"override Spider.start_requests method instead (see %s.%s)." % (
cls.__module__, cls.__name__
),
)
for url in self.start_urls:
yield self.make_requests_from_url(url)
else:
for url in self.start_urls:
yield Request(url, dont_filter=True)
  • 有时候,需要重写starte_rquests方法发起首次请求
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
import scrapy

class N17kSpider(scrapy.Spider):
name = "n_17k"
start_urls = ["https://user.17k.com/ck/author/shelf?page=1&appKey=2406394919"]

def start_requests(self):
'''
请求登陆的账号和密码
'''
login_url = 'https://passport.17k.com/ck/user/login'
# 使用request进行请求
# yield scrapy.Request(url=login_url, body='loginName=17346570232&password=xlg17346570232', callback=self.do_login, method='POST')
# 使用Request子类FormRequest进行请求 自动为post请求
yield scrapy.FormRequest(
url=login_url,
formdata={'loginName': '13121758648', 'password': 'yuan0316'},
callback=self.do_start
)

def do_start(self, response):
'''
登陆成功后调用parse进行处理
cookie中间件会帮我们自动处理携带cookie
'''
for url in self.start_urls:
yield scrapy.Request(url=url, callback=self.parse)

def parse(self, response, **kwargs):
with open("data.json", "w") as f:
f.write(response.text)

全站爬虫(CrawlSpider)

  • Scrapy 的基本使用当中,spider 如果要重新发送请求的话,就需要自己解析页面,然后发送请求。而 CrawlSpider 则可以通过设置 url 条件自动发送请求。

LinkExtractors

CrawlSpider 是 Spider 的一个派生类。CrawlSpider 与 spider 不同的是就在于下一次请求的 url 不需要自己手动解析,而这一点则是通过 LinkExtractors 实现的。

  • LinkExtractor类的构造方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class scrapy.linkextractors.LinkExtractor(
allow = (),
deny = (),
allow_domains = (),
deny_domains = (),
restrict_xpaths = (),
tags = ('a','area'),
attrs = ('href'),
canonicalize = False,
unique = True,
process_value = None,
deny_extensions = None,
restrict_css=(),
strip=True
)

参数说明

allow:允许的 url。所有满足这个正则表达式的 url 都会被提取
deny:禁止的 url。所有满足这个正则表达式的 url 都不会被提取
allow_domains:允许的域名。只有在这个里面指定的域名的 url 才会被提取
deny_domains:禁止的域名。所有在这个里面指定的域名的 url 都不会被提取
restrict_xpaths:严格的 xpath。和 allow 共同过滤链接

tags:接收一个标签或标签列表,提取标签内的列表,默认为[‘a’, ‘area’]
attrs:接收一个属性或属性列表,提取指定属性内的链接,默认为[‘href’]

Rule

  • LinkExtractors 需要传递到 Rule 类对象中才能发挥作用

link_extractors:是一个LinkExtractor对象,用于定义需要提取的链接

callback:从link_extractor中没获取链接时,参数所制定的值作为回调函数,该回调函数接受一个response作为起第一个参数
注意:当编写爬虫规则是,避免使用parse作为回调函数。由于CrawlSpider使用parse方法来实现其逻辑,如果覆盖了parse方法,CrawlSpider将会运行失败

follow:是一个布尔值(boolean),制定了根据该规则从response提取的链接是需要跟进。如果callback为None,follow默认设置为True,否则默认为Flase

process_links:指定该Spider中那个的函数将会被调用,从link_extractor中获取到链接列表是将会调用该函数。该方法主要用来过滤。
process_request:指定该Spider中哪个的函数将会被调用,该规则提取到每个request是都会调用该函数。(用来过滤request)

CrawlSpider工作流程

首先由start_requestsstart_urls中的每一个url发起请求(make_requests_from_url),这个请求会被parse接收。在Spider里面的parse需要我们定义,但CrawlSpider定义parse去解析响应(self._parse_response(response, self.parse_start_url, cb_kwargs={}, follow=True)
_parse_response根据有无callback,followself.follow_links执行不同的操作

1
2
3
4
5
6
7
8
9
10
11
def _parse_response(self, response, callback, cb_kwargs, follow=True):
##如果传入了callback,使用这个callback解析页面并获取解析得到的reques或item
if callback:
cb_res = callback(response, **cb_kwargs) or ()
cb_res = self.process_results(response, cb_res)
for requests_or_item in iterate_spider_output(cb_res):
yield requests_or_item
## 其次判断有无follow,用_requests_to_follow解析响应是否有符合要求的link。
if follow and self._follow_links:
for request_or_item in self._requests_to_follow(response):
yield request_or_item

其中_requests_to_follow又会获取link_extractor(这个是我们传入的LinkExtractor)解析页面得到的linklink_extractor.extract_links(response),对url进行加工(process_links,需要自定义),对符合的link发起Request。使用.process_request(需要自定义)处理响应。