scrapy 介绍
scrapy 是一个基于 Python 语言的爬虫框架。有了框架的存在,我们可以更方便的爬虫了,框架帮我们封装好了下载等模块,而且更重要的是框架使用了异步的模式,加快了爬虫的速度。
环境搭建
在命令行中执行 conda install Scrapy
这样就安装好了 Scrapy 模块,是不是特别简单。ahahahha
开始
接下来就要开始我们的 scrapy 之路了。
新建项目
使用 Scrapy 第一步:创建项目,命令行进入你需要放置项目的目录,然后执行
scrapy startproject ScrapyDemo #ScrapyDemo是项目名称
目录结构
这时会在目录下多出一个 ScrapyDemo 文件夹,这就是 Scrapy 项目了,项目的结构如下1
2
3
4
5
6
7
8
9
10
11.
|-- ScrapyDemo
| `-- ScrapyDemo
| |-- spiders
| | `-- __init__.py
| |-- __init__.py
| |-- items.py
| |-- middlewares.py
| |-- pipelines.py
| `-- settings.py
`-- scrapy.cfg
目录说明:
- ScrapyDemo(外层) 项目总目录
- ScrapyDemo(内层) 项目目录
- scrapy.cfg 项目的配置文件
- spiders 放置我们的爬虫代码的目录
- items.py 定义我们需要获取的字段
- middlewares.py
- pipelines.py 用来定义存储
- settings.py 项目设置文件
还有一点要注意的是,Scrapy 默认是不能在 IDE 中调试的,所以要在项目总目录下新建一个 entrypoint.py 文件,写下以下内容:1
2
3
4from scrapy.cmdline import execute
# 前两个参数是不变的,第三个参数是自己定义的 spider 的名字
execute(['scrapy', 'crawl', 'dingdian'])
现在整个目录应该是这样(盗图..)
架构
下面我们先来看一下框架的架构图
Scrapy Engine: 这是引擎,负责Spiders、ItemPipeline、Downloader、Scheduler中间的通讯,信号、数据传递等等!(像不像人的身体?)
Scheduler(调度器): 它负责接受引擎发送过来的requests请求,并按照一定的方式进行整理排列,入队、并等待Scrapy Engine(引擎)来请求时,交给引擎。
Downloader(下载器):负责下载Scrapy Engine(引擎)发送的所有Requests请求,并将其获取到的Responses交还给Scrapy Engine(引擎),由引擎交给Spiders来处理,
Spiders:它负责处理所有Responses,从中分析提取数据,获取Item字段需要的数据,并将需要跟进的URL提交给引擎,再次进入Scheduler(调度器),
Item Pipeline:它负责处理Spiders中获取到的Item,并进行处理,比如去重,持久化存储(存数据库,写入文件,总之就是保存数据用的)
Downloader Middlewares(下载中间件):你可以当作是一个可以自定义扩展下载功能的组件
Spider Middlewares(Spider中间件):你可以理解为是一个可以自定扩展和操作引擎和Spiders中间‘通信’的功能组件(比如进入Spiders的Responses;和从Spiders出去的Requests)
数据在整个Scrapy的流向:
程序运行的时候,
引擎:Hi!Spider, 你要处理哪一个网站?
Spiders:我要处理23wx.com
引擎:你把第一个需要的处理的URL给我吧。
Spiders:给你第一个URL是XXXXXXX.com
引擎:Hi!调度器,我这有request你帮我排序入队一下。
调度器:好的,正在处理你等一下。
引擎:Hi!调度器,把你处理好的request给我,
调度器:给你,这是我处理好的request
引擎:Hi!下载器,你按照下载中间件的设置帮我下载一下这个request
下载器:好的!给你,这是下载好的东西。(如果失败:不好意思,这个request下载失败,然后引擎告诉调度器,这个request下载失败了,你记录一下,我们待会儿再下载。)
引擎:Hi!Spiders,这是下载好的东西,并且已经按照Spider中间件处理过了,你处理一下(注意!这儿responses默认是交给def parse这个函数处理的)
Spiders:(处理完毕数据之后对于需要跟进的URL),Hi!引擎,这是我需要跟进的URL,将它的responses交给函数 def xxxx(self, responses)处理。还有这是我获取到的Item。
引擎:Hi !Item Pipeline 我这儿有个item你帮我处理一下!调度器!这是我需要的URL你帮我处理下。然后从第四步开始循环,直到获取到你需要的信息,
注意!只有当调度器中不存在任何request了,整个程序才会停止,(也就是说,对于下载失败的 URL,Scrapy会重新下载。)
写代码
建立一个项目之后:
第一件事就是在 items.py 文件中定义我们需要的字段,用来临时存储要保存的数据,方便以后数据的持久化存储,比如 数据库 文本文件等。
第二件事是在 spiders.py 中编写自己的爬虫代码
第三件事是在 pipelines.py 中存储自己的数据
建议:在大家调试的时候在settings.py中取消下面几行的注释:1
2
3
4
5HTTPCACHE_ENABLED = True
HTTPCACHE_EXPIRATION_SECS = 0
HTTPCACHE_DIR = 'httpcache'
HTTPCACHE_IGNORE_HTTP_CODES = []
HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'
上面代码的作用是 Scrapy 会缓存你有的 Requests! 当你再次请求时,如果存在缓存文档则返回缓存文档,而不是去网站请求,这样既加快了本地调试速度,也减轻了 网站的压力。一举多得
定义字段
要根据自己要爬取的内容来定义字段。比如,在这里我们要爬的是小说站点就需要定义,小说名字,作者,小说地址,连载状态,字数,文章类别 等字段。
就像下面这样:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15class ScrapydemoItem(scrapy.Item):
# 小说名字
name = scrapy.Field()
# 作者
author = scrapy.Field()
# 小说地址
novelurl = scrapy.Field()
# 连载状态
serialstatus = scrapy.Field()
# 连载字数
serialnumber = scrapy.Field()
# 类别
category = scrapy.Field()
# 编号
novel_id = scrapy.Field()
编写 spider
在 spiders 目录下新建一个 ScrapyDemo.py 文件,并导入我们需用的模块
1 | import scrapy |
我们需要从一个地址入手开始爬取,我在顶点小说上没有发现有全站小说地址,但是我找到每个分类地址全部小说:
玄幻魔幻:http://www.x23us.com/class/1_1.html
武侠修真:http://www.x23us.com/class/2_1.html
都市言情:http://www.x23us.com/class/3_1.html
历史军事:http://www.x23us.com/class/4_1.html
侦探推理:http://www.x23us.com/class/5_1.html
网游动漫:http://www.x23us.com/class/6_1.html
科幻小说:http://www.x23us.com/class/7_1.html
恐怖灵异:http://www.x23us.com/class/8_1.html
散文诗词:http://www.x23us.com/class/9_1.html
其他:http://www.x23us.com/class/10_1.html
全本:http://www.x23us.com/quanben/1
好啦!入口地址我们找到了,现在开始写第一部分代码:
1 | import scrapy |
首先我们创建一个类 Myspider;这个类继承自 scrapy.Spider
定义了一个 allowed_domains ;这个不是必须的;但是在某写情况下需要用得到,比如使用爬取规则的时候就需要了;它的作用是只会跟进存在于 allowed_domains 中的 URL。不存在的 URL 会被忽略。
第九行定义的 name 是之前我们在 entrypoint.py 文件中的第三个参数 此 name 在整个项目中有且只能有一个、名字不可重复!!!
之后可以运行 entrypoint.py 文件来检查一下代码时候可以正常工作。
请求就这么轻而易举的实现了啊!简直So Easy!
继续 继续!
我们需要历遍所有页面才能取得所有的小说页面连接:
1 | import scrapy |
自定义 Pipeline
做一个自定义的MySQL的Pipeline。首先为了能好区分框架自带的Pipeline,我们把MySQL的Pipeline单独放到一个目录里面。
pipelines.py 这个是我们写存放数据的文件
sql.py 看名字就知道,需要的sql语句。
首先是需要的 MySQL 表,1
2
3
4
5
6
7
8
9DROP TABLE IF EXISTS `dd_name`;
CREATE TABLE `dd_name` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`xs_name` varchar(255) DEFAULT NULL,
`xs_author` varchar(255) DEFAULT NULL,
`category` varchar(255) DEFAULT NULL,
`name_id` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=38 DEFAULT CHARSET=utf8mb4;
记得在 settings.py 文件中定义好 MySQL 的配置文件1
2
3
4
5MYSQL_HOSTS = '127.0.0.1'
MYSQL_USER = 'root'
MYSQL_PORT = '3306'
MYSQL_PASSWORD = '****'
MYSQL_DB = 'xiaoshuo'
在开始写sql.py之前,我们需要安装一个Python操作MySQL的包,来自MySQL官方的一个包:点我下载
下载完成后解压出来,从 cmd 进入该目录的绝对路径,然后 python setup.py install ;即可完成安装(记得以管理员身份打开命令行)
sql.py1
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
35import mysql.connector
from ScrapyDemo import settings
MYSQL_USER = settings.MYSQL_USER
MYSQL_PASSWORD = settings.MYSQL_PASSWORD
MYSQL_HOST = settings.MYSQL_HOSTS
MYSQL_PORT = settings.MYSQL_PORT
MYSQL_DB = settings.MYSQL_DB
cnx = mysql.connector.connect(user=MYSQL_USER, password=MYSQL_PASSWORD, host=MYSQL_HOST, database=MYSQL_DB)
cur = cnx.cursor(buffered=True)
class Sql:
@classmethod
def insert_dd_name(cls, xs_name, xs_author, category, name_id):
sql = 'INSERT INTO dd_name (`xs_name`,`xs_author`,`category`,``name_id) VALUES (%(xs_name)s,%(xs_author)s,,%(category)s,,%(name_id)s)'
VALUE = {
'xs_name': xs_name,
'xs_author': xs_author,
'category': category,
'name_id': name_id
}
cur.execute(sql, VALUE)
cnx.commit()
@classmethod
def select_name(cls, name_id):
sql = 'SELECT EXISTS(SELECT 1 FROM dd_name WHERE name_id=%(name_id)s)'
value = {
'name_id': name_id
}
cur.execute(sql, value)
cnx.commit()
来开始写pipeline1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18from ScrapyDemo.items import ScrapydemoItem
from .sql import Sql
class ScrapyPipeline(object):
def process_item(self, item, sqider):
if isinstance(item, ScrapydemoItem):
name_id = item['name_id']
ret = Sql.select_name(name_id)
if ret[0] == 1:
print('已经存在了')
pass
else:
xs_name = item['xs_name']
xs_author = item['xs_author']
category = item['category']
Sql.insert_dd_name(xs_name, xs_author, category, name_id)
print('开始存小说标题')
定义了一个process_item函数并有,item和spider这两个参数(请注意啊!这两玩意儿 务必!!!要加上!!千万不能少!!!!务必!!!务必!!!)
搞完!下面我们启用这个Pipeline在settings中作如下设置:
后面的 1 是优先级程度(1-1000随意设置,数值越低,组件的优先级越高)
下面我们开始还剩下的一些内容获取:小说章节 和章节内容
首先我们在 item.py 中新定义一些需要获取内容的字段:
1 | import scrapy |
继续编写Spider文件:
1 | def get_chapterurl(self, response): |
注意 13\14 行,这个地方返回item是不能用return的哦!用了就结束了,程序就不会继续下去了,得用 yield
下面我们来写存储这部分spider的Pipeline:
数据表:1
2
3
4
5
6
7
8
9
10
11DROP TABLE IF EXISTS `dd_chaptername`;
CREATE TABLE `dd_chaptername` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`xs_chaptername` varchar(255) DEFAULT NULL,
`xs_content` text,
`id_name` int(11) DEFAULT NULL,
`num_id` int(11) DEFAULT NULL,
`url` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2726 DEFAULT CHARSET=gb18030;
SET FOREIGN_KEY_CHECKS=1;
下面是Pipeline:
有小伙伴注意,这儿比上面一个Pipeline少一个判断,因为我把判断移动到Spider中去了,这样就可以减少一次Request,减轻服务器压力。
改变后的Spider长这样: