Scrapy框架概念

Scrapy是一个Python编写的开源网络爬虫框架。它是一个被设计用于爬取网络数据、提取结构性数据的框架。

Scrapy文档地址:http://scrapy-chs.readthedocs.io/zh_CN/1.0/intro/overview.html

Scrapy框架作用

少量的代码,就能够快速的抓取。一般用于爬取大量数据。

Scrapy框架工作流程

  1. 回顾request的爬虫流程

    原始的爬虫流程

    我们可以在此基础上改写流程:

    改写的爬虫流程

    而上面改写的流程图也更加便于大家去理解scrapy的流程

  2. Scapy的流程

    scapy的流程

    其流程详细如下:

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

​ 注意:

  • 图中绿色线条的表示数据的传递
  • 注意图中中间件的位置,决定了其作用
  • 注意其中引擎的位置,所有的模块之前相互独立,只和引擎进行交互

各模块的具体作用

各模块作用

​ 各模块功能:

  • 引擎 —— 数据和信号的传递
  • 调度器 —— 任务url队列
  • 下载器 —— 发送请求、获取响应
  • 爬虫 —— 起始的url、解析数据
  • 管道 —— 保存数据
  • 中间件 —— 定制化操作

三个内置对象

  • request请求对象:由url、method、post_data、headers等构成
  • response响应对象:由url、body、status、headers等构成
  • item数据对象:本质是个字典

安装

有时pip版本过于老旧不能使用,需要升级pip版本,输入pip install --upgrade pip回车,升级成功

安装scrapy命令:

1
pip/pip3 install Scrapy

scrapy项目开发流程

  1. 创建项目
  2. 生成一个爬虫
  3. 提取数据
  4. 保存数据

创建项目

创建scrpy项目的命令:

1
scrapy startproject <项目名字>

示例:

1
scrapy startproject myspider

创建爬虫

通过命令创建出爬虫文件,爬虫文件为主要的代码作业文件,通常一个网站的爬取动作都会在爬虫文件中进行编写。

命令:在项目路径下执行

1
scrapy genspider <爬虫名字> <允许爬取的域名>
  • 爬虫名字:作为爬虫运行时的参数
  • 允许爬取的域名:为对于爬虫设置的爬取范围,设置之后用于过滤要爬取的url,如果爬取的url与允许的域不通则被过滤掉。如不确定时,可以设置xx.com,后期再进行修改。

这里我们以豆瓣电影Top250作为示例:

1
2
cd myspider
scrapy genspider douban movie.douban.com

生成的目录和文件结果如下:

目录文件总览

完善爬虫

在上一步生成出来的爬虫文件中编写指定网站的数据采集操作,实现数据提取

一、在item.py中定义要提取的字段

1
2
3
4
5
6
7
import scrapy
class MyspiderItem(scrapy.Item):
# define the fields for your item here like:
num = scrapy.Field() # 电影序号
name = scrapy.Field() # 电影名字
score = scrapy.Field() # 电影评分
con = scrapy.Field() # 电影简介

二、在/myspider/myspider/spiders/douban.py 中修改内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import scrapy
from ..items import MyspiderItem

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

def parse(self, response):
# 电影名字
name = response.xpath('.//div[@id="content"]/div/div/ol/li[*]/div/div/div/a/span[1]/text()').getall()

# 电影评分
score = response.xpath('.//*[@id="content"]/div/div[1]s/ol/li[*]/div/div/div/div/span[3]/@content').getall()

# 美化格式
for i in range(25):
#在爬虫中导入并且实例化对象, 使用方法跟使用字典类似
item = MyspiderItem()
item['name'] = name[i] # 这里的键名要跟item.py中字段名一致
item['score'] = score[i]
print(item)
yield item # yield 会把数据传给管道

注意:

  • scrapy.Spider爬虫类中必须有名为parse的解析
  • 如果网站结构层次比较复杂,也可以自定义其他解析函数
  • 在解析函数中提取的url地址如果要发送请求,则必须属于allowed_domains范围内,但是start_urls中的url地 址不受这个限制,我们会在后续的课程中学习如何在解析函数中构造发送请求
  • 启动爬虫的时候注意启动的位置,是在项目路径下启动
  • parse()函数中使用yield返回数据,注意:解析函数中的yield能够传递的对象只能是:BaseItem, Request, dict, None

三、定位元素以及提取数据、属性值的方法

解析并获取scrapy爬虫中的数据: 利用xpath规则字符串进行定位和提取

  1. response.xpath方法的返回结果是一个类似list的类型,其中包含的是selector对象,操作和列表一样,但是有 一些额外的方法

  2. 额外方法extract():返回一个包含有字符串的列表(相当于getall() )

  3. 额外方法extract_first():返回列表中的第一个字符串,列表为空没有返回None(相当于get() )

四、response响应对象的常用属性

  • response.url:当前响应的url地址
  • response.request.url:当前响应对应的请求的url地址
  • response.headers:响应头
  • response.requests.headers:当前响应的请求头
  • response.body:响应体,也就是html代码,byte类型
  • response.status:响应状态码

五、(改进版)可构造Request对象,并发送请求

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import scrapy
from ..items import MyspiderItem

num = 0 # 全局变量

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

def parse(self, response):
# 电影名字
name = response.xpath('.//div[@id="content"]/div/div/ol/li[*]/div/div/div/a/span[1]/text()').getall()

# 电影评分
score = response.xpath('.//*[@id="content"]/div/div[1]/ol/li[*]/div/div/div/div/span[2]/text()').getall()

# 电影链接
link = response.xpath('.//*[@id="content"]/div/div[1]/ol/li[*]/div/div[2]/div[1]/a/@href').getall()

# 美化格式
for i in range(25):
item = MyspiderItem()
global num
if num == i: # 防止乱序
item['num'] = num+1
item['name'] = name[i]
item['score'] = score[i]
# print(item)
# yield item # yield 会把数据传给管道

# print(link[i])
# 对获取的电影链接去发送请求
yield scrapy.Request(link[i], callback=self.parse_data, meta={'item2': item})
num += 1

# 翻页操作一
next_url = response.xpath('.//*[@id="content"]/div/div[1]/div[2]/span[3]/a/@href').get()
# 拼接
next_url = 'https://movie.douban.com/top250' + next_url

# # 翻页操作二
# # n_url = response.xpath("//a[text()='后页>']/@href")
# # 直接使用response携带残缺的url
# # yield response.follow(n_url, callback=self.parse)

# 手动构造请求对象,指定解析起始url的parse方法
# yield scrapy.Request(next_url, callback=self.parse) # 数据过多,防止被反爬,先注释掉

# 解析详情的函数(自定义)
def parse_data(self, response):
item = response.meta.get('item2') # 或 item = response.meta['item2']
# print(item)

# 提取详情简介
# 有的节点不一样,有些是div/span 有些div/span/span
# // *[ @ id = "link-report"] / span[1] / text()
# // *[ @ id = "link-report"] / span[1] / span / text()

# content = response.xpath('//*[@id="link-report"]/span[1]/span/text()').get()

# string(path) 方法会提取父标签下的文本内容
# path 就是父标签的路径
content = response.xpath('string(.//*[@id="link-report"]/span)').get()
# print('详情:', content)
item['con'] = content.strip() # strip()去除左右两边的空格

yield item

scrapy.Request()中的常见参数解释

参数 解释 是否必填
url 请求的url
callback 回调函数,用于接收请求后的返回信息,若没指定,则默认为parse()函数
meta 方法之间以字典形式传递参数,这个参数一般也可在middlewares中处理
method http请求的方式,默认为GET请求,一般不需要指定。若需要POST请求,建议使用用scrapy.FormRequest()
headers dict类型,请求头信息,一般在settings中设置即可,也可在middlewares中设置
cookies dict或list类型,请求的cookie
dont_filter 是否开启过滤,默认关闭,开启之后爬取过的url,下一次不会再爬取
errback 抛出错误的回调函数并打印出来,错误包括404,超时,DNS错误等

发送post请求

scrapy.FormRequest(url,callback, formdata)

FormRequest 类为Request的子类,用于POST请求,其他参数与Request一样,其中新增的formdata是dict类型,相当于meta。

保存数据

利用管道pipeline来处理(保存)数据

一、在pipelines.py文件中定义对数据的操作

  1. 定义一个管道类
  2. 重写管道类的process_item方法
  3. process_item方法处理完item之后必须返回给引擎
  4. 定义数据的保存逻辑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import json

class MyspiderPipeline:
def open_spider(self, spider):
self.file = open('douban.json', 'w', encoding='utf-8')

def process_item(self, item, spider):
# with open('douban250.txt', 'a', encoding='utf-8') as f:
# f.write(str(item) + '\n')

# 把传递数据的载体item对象转为一个字典
dic = dict(item)
js_data = json.dumps(dic, ensure_ascii=False) + '\n'
self.file.write(js_data)

return item

def close_spider(self, spider):
self.file.close()
  • def open_spider(self, spider) —— 爬虫开启时执行一次,可用来打开文件
  • def process_item(self, item, spider) ——实现数据的写入操作
  • def close_spider(self, spider) —— 爬虫关闭时执行一次,可用来关闭文件

二、在settings.py 配置启用管道

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 启用管道配置
ITEM_PIPELINES = {
'myspider.pipelines.MyspiderPipeline': 300,
}

# 绕过robots规则,直接爬取页面
# Obey robots.txt rules
ROBOTSTXT_OBEY = False

# 添加User-agent
#USER_AGENT = 'myspider (+http://www.yourdomain.com)'
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36'

# 添加cookie
COOKIES_ENABLED = False
DEFAULT_REQUEST_HEADERS = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en',
'Cookie': 'xxx'
}

setting.py一般都会将管道配置注释掉,取消注释即可。

配置项中键为使用的管道类,管道类使用.进行分割,第一个为项目目录,第二个为文件,第三个为定义的管道类。 配置项中值为管道的使用顺序,设置的数值越小越优先执行,该值一般设置为1000以内。

运行爬虫

命令:在项目目录下执行

1
scrapy crawl <爬虫名字>	(--nolog)

—nolog:不显示调试信息,不加即默认显示

示例:

1
scrapy crawl douban --nolog

运行结果如下:

运行结果

crawlspider爬虫

回顾之前的代码,有很多一部分时间都寻找下一页的url地址或者内容的url地址上面,而这个过程能更简单吗?

需求思路:

  1. 从response中提取所有的满足规则的url地址
  2. 自动的构造自己requests请求,发送给引擎

crawlspider就可以满足上述需求,能够匹配满足条件的url地址,组装成Reuqest对象后自动发送给引擎, 同时能够指定callback函数,

即:crawlspider爬虫可以按照规则自动获取连接

Scrapy框架中分两类爬虫,Spider类和CrawlSpider类。CrawlSpider继承自spider,只不过是在之前的基础上增加了新的功能。可以定义爬取的url的规则,以后scrapy碰到满足条件的url都进行爬取,而不用手动yield Request。

创建crawlspider爬虫并观察爬虫内的默认内容

一、创建crawlspider爬虫:

1
scrapy genspider -t crawl douban movie.douban.com

二、spider中默认生成的内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule

class DoubnSpider(CrawlSpider):
name = 'douban'
allowed_domains = ['movie.douban.com']
start_urls = ['http://movie.douban.com/']

rules = (
Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True),
)

def parse_item(self, response):
item = {}
#item['domain_id'] = response.xpath('//input[@id="sid"]/@value').get()
#item['name'] = response.xpath('//div[@id="name"]').get()
#item['description'] = response.xpath('//div[@id="description"]').get()
return item

三、观察其与跟普通的scrapy.spider的区别

在crawlspider爬虫中,没有parse函数

重点在rules中:

  1. rules是一个元组或者是列表,包含的是Rule对象
  2. Rule表示规则,其中包含LinkExtractor,callback和follow等参数
  3. LinkExtractor:连接提取器,可以通过正则或者是xpath来进行url地址的匹配
  4. callback :表示经过连接提取器提取出来的url地址响应的回调函数,可以没有,没有表示响应不会进行回调函数 的处理
  5. follow:连接提取器提取的url地址对应的响应是否还会继续被rules中的规则进行提取,True表示会,Flase表示不会

四、crawlspider使用的注意点

  1. 除了用命令 scrapy genspider -t crawl <爬虫名> <allowed_domail>创建一个crawlspider的模板,页可以手动创建
  2. crawlspider中不能再有以parse为名的数据提取方法,该方法被crawlspider用来实现基础url提取等功能
  3. Rule对象中LinkExtractor为固定参数,其他callback、follow为可选参数
  4. 不指定callback且follow为True的情况下,满足rules中规则的url还会被继续提取和请求
  5. 如果一个被提取的url满足多个Rule,那么会从rules中选择一个满足匹配条件的Rule执行

五、crawlspider其他知识点的了解

  1. 链接提取器LinkExtractor的更多常见参数
    • allow:满足括号中的’re’表达式的url会被提取,如果为空,则全部匹配
    • deny:满足括号中的’re’表达式的url不会被提取,优先级高于allow
    • allow_domains:会被提取的链接的domains(url范围),如: [‘hr.tencent.com’, ‘baidu.com’]
    • deny_domains:不会被提取的链接的domains(url范围)
    • restrict_xpaths:使用xpath规则进行匹配,和allow共同过滤url,即xpath满足的范围内的url地址会被 提取,如: restrict_xpaths=‘//div[@class=“pagenav”]’
  2. Rule常见参数
    • LinkExtractor:链接提取器,可以通过正则或者是xpath来进行url地址的匹配
    • callback:表示经过连接提取器提取出来的url地址响应的回调函数,可以没有,没有表示响应不会进行回调 函数的处理
    • follow:连接提取器提取的url地址对应的响应是否还会继续被rules中的规则进行提取,默认True表示会, Flase表示不会
    • process_links:当链接提取器LinkExtractor获取到链接列表的时候调用该参数指定的方法,这个自定义方 法可以用来过滤url,且这个方法执行后才会执行callback指定的方法