初识Scrapy爬虫框架

Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架,非常出名,非常强悍。所谓的框架就是一个已经被集成了各种功能(高性能异步下载,队列,分布式,解析,持久化等)的具有很强通用性的项目模板。对于框架的学习,重点是要学习其框架的特性、各个功能的用法即可。

介绍

Scrapy主要包括了以下组件:

  • 引擎(Scrapy)
    用来处理整个系统的数据流处理, 触发事务(框架核心)
  • 调度器(Scheduler)
    用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL(抓取网页的网址或者说是链接)的优先队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址
  • 下载器(Downloader)
    用于下载网页内容, 并将网页内容返回给蜘蛛(Scrapy下载器是建立在twisted这个高效的异步模型上的)
  • 爬虫(Spiders)
    爬虫是主要干活的, 用于从特定的网页中提取自己需要的信息, 即所谓的实体(Item)。用户也可以从中提取出链接,让Scrapy继续抓取下一个页面
  • 项目管道(Pipeline)
    负责处理爬虫从网页中抽取的实体,主要的功能是持久化实体、验证实体的有效性、清除不需要的信息。当页面被爬虫解析后,将被发送到项目管道,并经过几个特定的次序处理数据。
  • 下载器中间件(Downloader Middlewares)
    位于Scrapy引擎和下载器之间的框架,主要是处理Scrapy引擎与下载器之间的请求及响应。
  • 爬虫中间件(Spider Middlewares)
    介于Scrapy引擎和爬虫之间的框架,主要工作是处理蜘蛛的响应输入和请求输出。
  • 调度中间件(Scheduler Middewares)
    介于Scrapy引擎和调度之间的中间件,从Scrapy引擎发送到调度的请求和响应。

Scrapy运行流程大概如下:

  1. 引擎从调度器中取出一个链接(URL)用于接下来的抓取
  2. 引擎把URL封装成一个请求(Request)传给下载器
  3. 下载器把资源下载下来,并封装成应答包(Response)
  4. 爬虫解析Response
  5. 解析出实体(Item),则交给实体管道进行进一步的处理
  6. 解析出的是链接(URL),则把URL交给调度器等待抓取

安装

linux

1
pip3 install scrapy

windows

安装wheel

1
pip3 install wheel

下载`

http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted

该页面下载与你php版本对应的twisted

安装twisted

进入下载的twisted目录,安装twisted文件

1
pip install traits-5.1.2-cp37-cp37m-win_amd64.whl

安装pywin32

1
pip install pywin32

安装scrapy

1
pip install scrapy

使用

创建项目

1
scrapy startproject 你的项目名称

自动创建目录:

1
2
3
4
5
6
7
8
9
┌┈┈ 你的项目名称/
├┈┈ scrapy.cfg
└┈┈ 你的项目名称/
├┈┈ __init__.py
├┈┈ items.py
├┈┈ pipelines.py
├┈┈ settings.py
└┈┈ spiders/
└┈┈ __init__.py

文件说明:

  • scrapy.cfg 项目的配置信息,主要为Scrapy命令行工具提供一个基础的配置信息。(真正爬虫相关的配置信息在settings.py文件中)
  • items.py 设置数据存储模板,用于结构化数据,如:DjangoModel
  • pipelines 数据处理行为,如:一般结构化的数据持久化
  • settings.py 配置文件,如:递归的层数、并发数,延迟下载等
  • spiders 爬虫目录,如:创建文件,编写爬虫规则

注意:一般创建爬虫文件时,以网站域名命名

创建爬虫

进入刚才的项目目录,即cd your_project_name

1
scrapy genspider 应用名称 爬取网页的起始url

如:scrapy genspider diandian www.diandian100.cn

编写爬虫

上述代码执行后,将在你的spiders目录中生成一个diandian.py的爬虫文件

1
2
3
4
5
6
7
8
9
10
11
12
13
import scrapy

class QiubaiSpider(scrapy.Spider):
name = 'diandian' #应用名称
#允许爬取的域名(如果遇到非该域名的url则爬取不到数据)
allowed_domains = ['https://www.diandian100.cn/']
#起始爬取的url
start_urls = ['https://www.diandian100.cn/']

#访问起始URL并获取结果后的回调函数,该函数的response参数就是向起始的url发送请求后,获取的响应对象.该函数返回值必须为可迭代对象或者NUll
def parse(self, response):
print(response.text) #获取字符串类型的响应内容
print(response.body)#获取字节类型的相应内容

执行爬虫

scrapy crawl 爬虫名称 :该种执行形式会显示执行的日志信息
scrapy crawl 爬虫名称 --nolog:该种执行形式不会显示执行的日志信息

1
scrapy crawl  应用名称

递归的访问

以上的爬虫仅仅是爬去初始页,而我们爬虫是需要源源不断的执行下去,直到所有的网页被执行完毕

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
# -*- coding: utf-8 -*-
import scrapy
from scrapy.http import Request

class DiandianSpider(scrapy.Spider):
name = 'diandian'
allowed_domains = ['www.diandian100.cn']
start_urls = ['https://www.diandian100.cn/']

def parse(self, response):
# print(response.text)
# 获取每篇文章
article_list = response.xpath('//div[@class="posts-expand"]/article')

# 循环每篇文章
for item in article_list:
# 取出该条文章第一个link标签下的href属性值
url = item.xpath('./link/@href').extract_first()
# 在该条文章html中搜索h2标签下的第一个a标签的内容
tit = item.xpath('.//h2/a/text()').extract_first()
print(url, '-', tit)

# 获取分页标签
page_nav = response.xpath('//*[@class="pagination"]/a/@href').extract()
# 循环读取每页
for page in page_nav:
page_url = 'http://www.diandian100.cn/' + page
yield Request(url = page_url, callback=self.parse)

持久化

指令持久化

基于终端指令的持久化存储 ,保证爬虫文件的parse方法中有可迭代类型对象(通常为列表or字典)的返回,该返回值可以通过终端指令的形式写入指定格式的文件中进行持久化操作。执行输出指定格式进行存储:将爬取到的数据写入不同格式的文件中进行存储。

1
2
3
scrapy crawl 爬虫名称 -o xxx.json
scrapy crawl 爬虫名称 -o xxx.xml
scrapy crawl 爬虫名称 -o xxx.csv

管道持久化

scrapy框架中已经为我们专门集成好了高效、便捷的持久化操作功能,我们直接使用即可。要想使用·的持久化操作功能,我们首先来认识如下两个文件:

items.py:数据结构模板文件。定义数据属性。
pipelines.py:管道文件。接收数据(items),进行持久化操作。

持久化流程

​ 1.爬虫文件爬取到数据后,需要将数据封装到items对象中。
​ 2.使用yield关键字将items对象提交给pipelines管道进行持久化操作。
​ 3.在管道文件中的process_item方法中接收爬虫文件提交过来的item对象,然后编写持久化存储的代码将item对象中存储的数据进行持久化存储
​ 4.settings.py配置文件中开启管道

持久化示例

文件持久化
爬虫文件diandian.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
# -*- coding: utf-8 -*-
import scrapy
from scrapy.http import Request

from study.items import StudyItem

class DiandianSpider(scrapy.Spider):
name = 'diandian'
allowed_domains = ['www.diandian100.cn']
start_urls = ['http://www.diandian100.cn/']

def parse(self, response):
# print(response.text)
# 获取每篇文章
article_list = response.xpath('//div[@class="posts-expand"]/article')

# 循环每篇文章
for item in article_list:
# 取出该条文章第一个link标签下的href属性值
url = item.xpath('./link/@href').extract_first()
# 在该条文章html中搜索h2标签下的第一个a标签的内容
tit = item.xpath('.//h2/a/text()').extract_first()
# print(url, '-', tit)
# 将解析到的数据封装至items对象中,提交item到管道文件(pipelines.py)
yield StudyItem(tit=tit, url=url)
items.py
1
2
3
4
5
6
import scrapy


class StudyItem(scrapy.Item):
tit = scrapy.Field() # 存储标题
url = scrapy.Field() # 存储url
管道文件:pipelines.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class StudyPipeline(object):
#构造方法
def __init__(self):
self.fp = None #定义一个文件描述符属性
  #下列都是在重写父类的方法:
#开始爬虫时,执行一次
def open_spider(self,spider):
print('爬虫开始')
self.fp = open('./data.txt', 'w')

   #因为该方法会被执行调用多次,所以文件的开启和关闭操作写在了另外两个只会各自执行一次的方法中。
def process_item(self, item, spider):
#将爬虫程序提交的item进行持久化存储
self.fp.write(item['tit'] + ':' + item['url'] + '\n')
return item

#结束爬虫时,执行一次
def close_spider(self,spider):
self.fp.close()
print('爬虫结束')
配置文件:settings.py
1
2
3
4
# 开启管道
ITEM_PIPELINES = {
'study.pipelines.StudyPipeline': 300, #300表示为优先级,值越小优先级越高
}
数据库持久化

上述实例中, 在管道文件里将item对象中的数据值存储到了磁盘中,如果将~数据写入mysql数据库的话,只需要将上述案例中的管道文件修改成如下形式:

管道文件:pipelines.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
26
27
28
29
#导入数据库的类
import pymysql
class StudyByMysql(object):

conn = None #mysql的连接对象声明
cursor = None#mysql游标对象声明
def open_spider(self,spider):
print('开始爬虫')
#链接数据库
self.conn = pymysql.Connect(host='127.0.0.1',port=3306,user='root',password='123456',db='study')
#编写向数据库中存储数据的相关代码
def process_item(self, item, spider):
#1.链接数据库
#2.执行sql语句
sql = 'insert into qiubai values("%s","%s")'%(item['tit'],item['url'])
self.cursor = self.conn.cursor()
#执行事务
try:
self.cursor.execute(sql)
self.conn.commit()
except Exception as e:
print(e)
self.conn.rollback()

return item
def close_spider(self,spider):
print('爬虫结束')
self.cursor.close()
self.conn.close()
配置文件:settings.py
1
2
3
ITEM_PIPELINES = {
'study.pipelines.StudyByMysql': 300,
}
redis持久化

上例中,在管道文件里将item对象中的数据值存储到了磁盘中,如果将item数据写入redis数据库的话,只需要将上述案例中的管道文件修改成如下形式 。

管道文件:pipelines.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import redis

class StudyByRedis(object):
conn = None
def open_spider(self,spider):
print('开始爬虫')
#创建链接对象
self.conn = redis.Redis(host='127.0.0.1',port=6379)
def process_item(self, item, spider):
dict = {
'tit':item['tit'],
'url':item['url']
}
#写入redis中
self.conn.lpush('data', dict)
return item
配置文件:settings.py
1
2
3
ITEM_PIPELINES = {
'study.pipelines.StudyByRedis': 300,
}