0%

小白进阶之Scrapy第一篇

这博文写得我懒癌犯了,最后的那个章节内容排序,我没有实验是否是正确的,不过这只是个教大家用 Scrapy 的教程,正确与否并不重要··· 如果不正确,记得留言;等我懒癌过了,我再改改······ 还有其它的问题也是一样··· ,把问题留言下; 等我懒癌过了·· 我改回来!嗯!是等我懒癌结束了,再改。 前面几篇博文,给大家从头到尾做了一个比较高效的爬虫,从这篇起来说说 Python 的爬虫框架 Scrapy; 至于为什么要说框架呢?因为啊,框架可以帮我们处理一部分事情,比如下载模块不用我们自己写了,我们只需专注于提取数据就好了; 最重要的一点啊!框架使用了异步的模式;可以加快我们的下载速度,而不用自己去实现异步框架;毕竟实现异步爬虫是一件比较麻烦的事情。 不过啊!反爬虫这个坎还是要我们自己迈过去啊!这是后话,以后再说。我们先来让 Scrapy 能跑起来,并提取出我们需要的数据,再解决其它问题。 官方文档在这儿:点我 9555112 环境搭建: 关于这一点,对在 Windows 环境下使用的小伙伴来说,请务必使用我之前提到的 Anaconda 这个 Python 的发行版本,不然光环境的各种报错就能消磨掉你所有的学习兴趣! 下载地址在这儿:http://pan.baidu.com/s/1pLgySav 安装完成之后,在 cmd 中执行:conda install Scrapy (如果需要使用特定版本,请在 Scrapy 后面加上 ==XXXX XXXX 代表你需要的版本号) 下面是安装示意图: 安装Scrapy So Easy@@!环境搭建完成!是不是超简单?全程无痛啊! 下面开始踏上新的征程!Go Go Go!! 使用 Scrapy 第一步:创建项目;CMD 进入你需要放置项目的目录 输入:

1
scrapy startproject XXXXX             XXXXX代表你项目的名字

创建项目 OK 项目创建完成。现在可以开始我们的爬取之旅了! 下面是目录中各个文件的作用 各个文件的作用 好了,目录我们认识完了,在开始之前给大家一个小技巧,Scrapy 默认是不能在 IDE 中调试的,我们在根目录中新建一个 py 文件叫:entrypoint.py;在里面写入以下内容:

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

注意!第二行中代码中的前两个参数是不变的,第三个参数请使用自己的 spider 的名字。稍后我会讲到!! 现在整个目录看起来是这样: 快捷启动 基础工作准备完毕!我们来说说基本思路。 上面的准备工作完成之后,我们先不要着急开始工作,毕竟作为一个框架,还是很复杂的;贸然上手 开整,很容易陷入懵逼状态啊!一团浆糊,理不清思路,后面的事情做起来很很麻烦啦! 我们来看看下面这张图: scrapy_architecture 这就是整个 Scrapy 的架构图了; 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 会重新下载。) 以上就是 Scrapy 整个流程了。 QQ图片20161022193315 大家将就着看看。 建立一个项目之后: 第一件事情是在 items.py 文件中定义一些字段,这些字段用来临时存储你需要保存的数据。方便后面保存数据到其他地方,比如数据库 或者 本地文本之类的。 第二件事情在 spiders 文件夹中编写自己的爬虫 第三件事情在 pipelines.py 中存储自己的数据 还有一件事情,不是非做不可的,就 settings.py 文件 并不是一定要编辑的,只有有需要的时候才会编辑。 建议一点:在大家调试的时候建议大家在 settings.py 中取消下面几行的注释: 设置setting01 这几行注释的作用是,Scrapy 会缓存你有的 Requests!当你再次请求时,如果存在缓存文档则返回缓存文档,而不是去网站请求,这样既加快了本地调试速度,也减轻了 网站的压力。一举多得 第一步定义字段: 好了,我们来做 第一步 定义一些字段;那具体我们要定义那些字段呢? 这个根据自己需要的提取的内容来定义。 比如:我们爬取小说站点都需要提取些什么数据啊? 小说名字、作者、小说地址、连载状态、连载字数、文章类别 就像下面这样: Scrapy01 这样我们第一步就完成啦!是不是 So Easy?ヾ(´▽‘)ノ ; 下面开始重点了哦!编写 spider(就是我们用来提取数据的爬虫了) 第二步编写 Spider: 在 spiders 文件中新建一个 dingdian.py 文件 并导入我们需用的模块 Scrapy02 PS:Scrapy 中 Response 可以直接使用 Xpath 来解析数据;不过大家也可以使用自己习惯的包,比如我导入的 BS4 、re ;当然也可以使其他比如 pyquery 之类的。这个并没有什么限制 另外或许个别小伙伴会遇到 from dingdian.items import DingdianItem 这个导入失败的情况;可以试试把项目文件移动到根目录。 Request 这个模块可以用来重写单独请求一个 URL,用于我们后面跟进 URL。 好了开整;首先我们需要什么? 我们需要从一个地址入手开始爬取,我在顶点小说上没有发现有全站小说地址,但是我找到每个分类地址全部小说: 玄幻魔幻:http://www.23wx.com/class/1_1.html 武侠修真:http://www.23wx.com/class/2_1.html 都市言情:http://www.23wx.com/class/3_1.html 历史军事:http://www.23wx.com/class/4_1.html 侦探推理:http://www.23wx.com/class/5_1.html 网游动漫:http://www.23wx.com/class/6_1.html 科幻小说:http://www.23wx.com/class/7_1.html 恐怖灵异:http://www.23wx.com/class/8_1.html 散文诗词:http://www.23wx.com/class/9_1.html 其他:http://www.23wx.com/class/10_1.html 全本:http://www.23wx.com/quanben/1 好啦!入口地址我们找到了,现在开始写第一部分代码: 当然对于上面的地址,我们是可以直接全使用 Start_urls 这种列表全部请求,不过并不太美观,我需要把其中,有规律的部分,单独其他方式实现,比如字典之类的: Scrapy22 第十行:首先我们创建一个类 Myspider;这个类继承自 scrapy.Spider(当然还有一些其他父类,继承各个父类后能实现的功能不一样); 第十二行:我们定义 name:dingdian (请注意,这 name 就是我们在 entrypoint.py 文件中的第三个参数!)!!!!请务必注意:此 Name 的!名字!在整个项目中有且只能有一个、名字不可重复!!!! 第十一行:我们定义了一个 allowed_domains;这个不是必须的;但是在某写情况下需要用得到,比如使用爬取规则的时候就需要了;它的作用是只会跟进存在于 allowed_domains 中的 URL。不存在的 URL 会被忽略。 第十七行到第十九行:我们使用字符串拼接的方式实现了我们上面发现的全部 URL。 第二十行和二十一行:我们使用了导入的 Request 包,来跟进我们的 URL(并将返回的 response 作为参数传递给 self.parse, 嗯!这个叫回调函数!) 第二十三行:使用 parse 函数接受上面 request 获取到的 response。(请务必注意:不要轻易改写 parse 函数(意思就是不要把 parse 函数用作它用);因为这样 request 的回调函数被你用了,就没谁接受 request 返回的 response 啦!如果你非要用作它用,则需要自己给 request 一个回调函数哦!)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import re
import scrapy #导入scrapy包
from bs4 import BeautifulSoup
from scrapy.http import Request ##一个单独的request的模块,需要跟进URL的时候,需要用它
from dingdian.items import DingdianItem ##这是我定义的需要保存的字段,(导入dingdian项目中,items文件中的DingdianItem类)

class Myspider(scrapy.Spider):

name = 'dingdian'
allowed_domains = ['23wx.com']
bash_url = 'http://www.23wx.com/class/'
bashurl = '.html'

def start_requests(self):
for i in range(1, 11):
url = self.bash_url + str(i) + '_1' + self.bashurl
yield Request(url, self.parse)
yield Request('http://www.23wx.com/quanben/1', self.parse)

def parse(self, response):
print(response.text)

我们测试一下是否正常工作:在 IDE 中运行我们之前创建的 entrypoint.py 文件(如果没有这个文件是不能在 IDE 中运行的哦!ヽ(=^・ω・^=)丿) 然后会像这样: Spider编写03 你会发现在红色状态报告之后,所有页面几乎是一瞬间出现的;那是因为 Scrapy 使用了异步啦!ヽ(°◇° )ノ 另外因为 Scrapy 遵循了 robots 规则,如果你想要获取的页面在 robots 中被禁止了,Scrapy 是会忽略掉的哦!!ヾ(。 ̄ □  ̄)ツ゜゜゜ 请求就这么轻而易举的实现了啊!简直 So Easy! 继续 继续! 我们需要历遍所有页面才能取得所有的小说页面连接: 分析网页2 分析网页01 每个页面的这个位置都是最后一个页面,我们提取出它,历遍就可以拼接出一个完整的 URL 了ヾ§  ̄ ▽)ゞ 2333333 Go Go Scrapy20 第二十三行:def parse(self, response)这个函数接受来在二十一行返回的 response,并处理。 第二十四行:我们使用 BS4 从 response 中获取到了最大页码。 第二十五行至二十七行:我们照例拼接了一个完整的 URL(response.url:就是这个 response 的 URL 地址) 第二十八行:功能和第二十行一样,callback= 是指定回调函数,不过不写 callback=也没有什么影响! 注意我只是说的 callback=这个几个;不是后面的 self.get_name. 看清楚了 response 是怎么用的没?ヾ§  ̄ ▽)ゞ 2333333 是不是 So Easy? 如果不清楚那个拼接 URL 的小伙伴可以打印出来,看看哦··· 再去观察一下网页,就很明白啦 上面两个函数就彻底的把整个网站的所有小说的页面 URL 的提取出来了,并将每个页面的 response 交给了 get_name 函数处理哦! 现在我们的爬虫就开始处理具体的小说了哦: Scrapy07 瞅见没 我们需要的东西,快用 F12 工具看一下吧,在什么位置有什么标签,可以方便我们提取数据。还不知道怎么看的小伙伴,去看看妹子图那个教程,有教哦;实在不行百度一下也行! 过程忽略了,直接上代码(主要是懒癌来了): Scrapy09 前面三行不说了, 第三十七和三十八行:是我们的小说名字和 URL 第三十九行和第四十行;大伙儿可能会发现,多了个一个 meta 这么一个字典,这是 Scrapy 中传递额外数据的方法。因我们还有一些其他内容需要在下一个页面中才能获取到。 下面我的爬虫进入了这个页面: Scrapy10 这个页面就有很多我们需要的信息了:废话不说了代码上来: Scrapy11 第四十行:将我们导入的 item 文件进行实例化,用来存储我们的数据。 后面全部:将需要的数据,复制给 item[key] (注意这儿的 Key 就是我们前面在 item 文件中定义的那些字段。) 注意!response.meta[key]:这个是提取从上一个函数传递下来的值。 return item 就是返回我们的字典了,然后 Pipelines 就可以开始对这些数据进行处理了。比如 存储之类的。 好啦,Spiders 我们先编写到这个地方。(是不是有小伙伴发现我还有几个字段没有取值?当然留着你们自己试试了,哈哈哈ヽ(=^・ω・^=)丿)后面再继续。 我现在教教大家怎么处理这些数据:对头就是说说 Pipeline 了: 对于基本的 Pipeline 存储方式,网上有很多教程了,今天我们做一个自定义的 MySQL 的 Pipeline: 首先为了能好区分框架自带的 Pipeline,我们把 MySQL 的 Pipeline 单独放到一个目录里面。 Scrapy12 我们在项目中新建了一个 mysqlpipelines 的文件夹,我们所有的 MySQL 文件都放在这个目录。 init.py 这个文件不需要我说了吧,不知道做啥的小哥儿自己百度。 pipelines.py 这个是我们写存放数据的文件 sql.py 看名字就知道,需要的 sql 语句。 首先是需要的 MySQL 表,(MySQL 都没有的小哥儿 自己百度装一个啊,我就不教了)

1
2
3
4
5
6
7
8
9
DROP 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 的配置文件(当然你也可以直接定义在 sql.py 文件中): MySQL setting PS :注意 MySQL 的默认端口是 3306;我自己的 MySQL 改成了 3389。这儿各位酌情自己更改。 在开始写 sql.py 之前,我们需要安装一个 Python 操作 MySQL 的包,来自 MySQL 官方的一个包:点我下载 下载完成后解压出来,从 CMD 进入该目录的绝对路径,然后 Python setup.py install ;即可完成安装 下面是我们的 sql.py 文件: sql01 第一行至第二行分别导入了:我的 MySQL 操作包,settings 配置文件 第四行至第八行 : 从 settings 配置文件中获取到了,我们的 MySQL 配置文件 第十行至第十一行: 初始化了一个 MySQL 的操作游标 第十三行: 定义了一个 Sql 的类 第十六行至第二十五行:定义了一个函数,将函数中的四个变量写入数据库(这四个变量就是我们后面传进来的需要存储的数据。) 关于@classmethod 这个是一个修饰符;作用是我们不需要初始化类就可以直接调用类中的函数使用(具体说起来麻烦,知道作用就好啦) 好了第一部分写完了,我们还需要一个能够去重的: sql01 这一段代码会查找 name_id 这个字段,如果存在则会返回 1 不存在则会返回 0 Nice!sqi.py 这一部分我们完成,来开始写 pipeline 吧: pipeline02 第一行至第二行:我们导入了之前编写的 sql.py 中的 Sql 类,和我们建立的 item 第六行:建立了一个 DingdianPipeline 的类(别忘了一定要继承 object 这个类啊,这是做啥的不用多了解,说多了你们头晕,我也懒) 第八行:我们定义了一个 process_item 函数并有,item 和 spider 这两个参数(请注意啊!这两玩意儿 务必!!!要加上!!千万不能少!!!!务必!!!务必!!!) 第十行:你这样理解如果在 item 中存在 DingdianItem;就执行下面的。 第十一行:从 item 中取出 name_id 的值。 第十二行:调用 Sql 中的 select_name 函数获得返回值 第十三行:判断 ret 是否等于 1 ,是的话证明已经存了 第二十行:调用 Sql 中的 insert_dd_name 函数,存储上面几个值。 搞完!下面我们启用这个 Pipeline 在 settings 中作如下设置: setting02 PS: dingdian(项目目录).mysqlpipelines(自己建立的 MySQL 目录).pipelines(自己建立的 pipelines 文件).DingdianPipeline(其中定义的类) 后面的 1 是优先级程度(1-1000 随意设置,数值越低,组件的优先级越高) 好!我们来运行一下试试!!Go Go Go! scrapy15 Nice!!完美!!我之前运行过了 所以提示已经存在。 scrapy17 下面我们开始还剩下的一些内容获取:小说章节 和章节内容 首先我们在 item 中新定义一些需要获取内容的字段: scrapy16 代码不解释了哦!(懒癌来了,写不下去了) 继续编写 Spider 文件: scrapy18 请注意我图中画红框的的地方,这个地方返回 item 是不能用 return 的哦!用了就结束了,程序就不会继续下去了,得用 yield(你知道就行,这玩意儿说起来麻烦。) 第五十八行: num 这个变量的作用是 因为 Scrapy 是异步的方式运作,你采集到的章节顺序都是混乱的,需要给它有序的序列,我们按照这个排序就能得到正确的章节顺序啦 请注意在顶部导入定义的第二个 item 类! 下面我们来写存储这部分 spider 的 Pipeline: 数据表:

1
2
3
4
5
6
7
8
9
10
11
DROP 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;

Sql.py: Scrapy13 Scrapy14 不解释了哦! 下面是 Pipeline: scrapy21 有小伙伴注意,这儿比上面一个 Pipeline 少一个判断,因为我把判断移动到 Spider 中去了,这样就可以减少一次 Request,减轻服务器压力。 改变后的 Spider 长这样: Scrapy16 别忘了在 spider 中导入 Sql 哦!ヾ(。 ̄ □  ̄)ツ゜゜゜ 到此收工!!!! 至于小说图片,因为 Scrapy 的图片下载管道,是自动以 md5 命名,而且感觉不爽··· 后面单独写一个异步下载的脚本··· https://github.com/thsheep/dingdian