• 欢迎来到本博客,希望可以y一起学习与分享

scrapy框架爬虫简单入门(二)–爬取文章网站

Python benz 3年前 (2018-10-04) 80次浏览 0个评论 扫描二维码
文章目录[隐藏]

准备

这里以爬取一个普通的文章网站来学习爬虫。以爬取网址为http://blog.jobbole.com/all-posts/的网址所有文章的目标为例。按照之前的文章搭建好爬虫环境,这里,因为我要爬取文章,所以我创建的爬虫项目名为ArticleSpider,因为是对这个网站(http://blog.jobbole.com/all-posts/)进行文章爬取,所以创建了针对这个网站爬虫的模板(也就是爬虫逻辑处理编写),创建后的目录结构以及模板的初始状态如下:

创建的步骤

一、创建一个virtualenv虚拟空间
二、(安装scrapy框架)。进入这个虚拟空间,pip install requests安装request,pip install scrapy安装scrapy爬虫框架。

在安装scrapy的期间,会报错误:error: Microsoft Visual C++ 14.0 is required.,这是因为scrapy基于Twisted开发的,到https://www.lfd.uci.edu/~gohlke/pythonlibs/下载对应的Python版本的Twisted,输入pip install 下载好的Twisted文件路径安装Twisted即可,再从新安装scrapy。

三、(使用scrapy创建爬虫项目与生成爬虫模板文件)。安装好scrapy框架后,使用scrapy startproject 项目名称,创建爬虫项目,按照后面提示,进入项目,并运行命令scrapy genspider jobbole blog.jobbole.com生成对应网站的爬虫模板文件。其中,jobbole是爬虫文件名,是唯一的,会在spiders文件夹中生成同名py文件;blog.jobbole.com是爬取的网站域名。
四、(创建一个入口文件)。scrapy创建的jobbole.py,是针对blog.jobbole.com的爬虫逻辑文件,但这个文件需要通过命令scrapy crawl jobbole在scrapy框架里才能运行,直接运行文件是不行的。所以,需要通过scrapy提供的命令行方法,写在一个文件中,去管理运行这些文件。这个文件我命名为main,作为入口文件,其中代码如下:

运行这个main.py文件,会报错:No module ‘win32api’,这需要命令:pip install pypiwin32安装即可解决。
重新运行main.py文件,控制台没报错就成功了。
至此,基本的准备已完成。

编写爬取代码

打开 项目ArticleSpider >> spiders >>jobbole.py文件。可以看见scrapy为我们创建了一个简单爬虫代码模板,其中parse方法scrapy框架会首先调用,response 则接收爬取到网页的数据。以后,没有特别的说明,运行文件指的是运行main.py文件。

使用选择器提取文字

选择器有xpath与css两种,按个人习惯使用。

运行main.py,可以输出获取到的文章标题

爬取所有文章

爬取所有文章的流程:
第一:获取一页列表的文章URL并交给scrapy下载并解释(就是爬取每篇文章的数据)
第二:获取下一页的URL,并交给scrapy下载,下载完后交个parse(就是获取每一页的文章列表,交给第一步,提取文章数据)

Request–根据URL获取网页内容

scrapy的下载,需要借助scrapy提供的Request(url=‘’, callback=function) 类。其中url为要下载的网址,callback为回调函数。引入scrapy的Request:from scrapy.http import Request

URL域名缺失的问题

爬取的URL中,有的是完整的网址(带域名):href=”http://blog.jobbole.com/114420/”,有的不是完整的网址(缺少域名): href=”/114420/”。这可以借助urllib提供的parse里的urljoin方法(from urllib import parse),去把没有域名的网址自动补全。 parse.urljoin(base, url),其中,base是域名,url是需要处理的url。 如果是url缺少域名,就会把base域名+url形成新的url;如果url是完整的就不处理。如何获取域名呢?response.url可以返回域名。

scrapy之item

什么是item?

item在项目的items文件中定义。 item是一个类似字典的数据结构,但比字典好,把有用的字段都在item中声明, 最后实例化这个item,把数据与item中的字段对应绑定即可,这样对字段集中管理,可以避免写错字段。 还有一点重要的是,scrapy会把定义的item数据返回到pipelines中,而pipelines是用于数据的处理与保存, 所以,如果要把爬取到的数据传到pipelines中,就要定义item。

使用item

在jobbole.py中,引入在item.py中自定义的item。然后实例化自定义的item,把数据与对应的item绑定,最后yield 绑定好数据的item,scrapy就会把item传递到pipelines.py中,通过process_item(self, item, spider)接收到item,其中参数中的item就是接收到的item。

定义自己的函数库

这时,我需要把文章的URL地址MD5一下,作为url_id存储起来,这时就需要一个MD5的处理方法。这样,我可以在ArticleSpider项目下建立一个名为utils的文件夹,并在里面创建名为common.py的文件,在里面定义自己的函数库。

使用自定义函数库

Request获取携带额外的参数

提取的目标数据是文章详情,如果想把列表的文章封面图也提取,但是这个封面图不属于文章详情页的,如何提取呢?可不可以在获取文章列表的时候获取封面图,当做参数,传进下一次response。 其实这是可以的,Request(url, meta, callback), Request有个meta参数,meta定义数据是:meta={键:值},这样就可以自己定义数据, 在callback函数中,会存在response的meta中,使用response.meta.get(键名, "")即可取出。

scrapy之pipelines

pipelines用于接收item数据并处理数据的存储,比如把数据存储为json或者存储到MySQL数据库。默认的pipelines的示例如下:

其中,class ArticlespiderPipeline(object):是你定义的pipeline名字,继承自object; def process_item(self, item, spider):process_item是scrapy来到pipelines首先调用的方法,这个方法会接收item数据,其中的item参数,就是接收item数据的。

scrapy之settings

settings.py存放着许多关于scrapy的设定。这里说一下ITEM_PIPELINES={'ArticleSpider.pipelines.ArticlespiderPipeline': 300,}这个设定。ITEM_PIPELINES,每当item数据在传输到pipelines之前,都会经过这个设置。这个设置是用来注册pipelines中定义的类,item只会传输到已注册的pipelines中定义的类,并且按权重来调用pipelines中的类。pipelines是一个管道,item是数据流。所以,在默认的设置中,item会传输到pipelines中的ArticlespiderPipeline类中,它的权重为300,数值越小,越优先。

如何保存图片

图片的URL已经有了,如何把图片保存到本地呢?那就是使用scrapy提供的ImagesPipeline。只需要在setting中设置一下就行。

图片的下载,需要pillow库的支持,使用命令:pip install pillow安装即可。
还有一点注意的,传给scrapy的ImagesPipeline的图片路径参数类型是一个dict字典,之前在jobbole.py传的是一个字符串,所以,要把图片的URL转换成字典:

获取保存到本地的图片路径

这个需要重写scrapy的ImagesPipeline中的 item_completed(self, results, item, info)方法。保存路径存在results的path当中。

最后,把这个注册到settings.py中的ITEM_PIPELINES并把权重调为最优先。

保存数据

保存数据,都是在pipelines.py里进行编写的,然后,在settings里注册一下就好了。在pipelines.py自定义的一般都会继承自object,并且重写def process_item(self, item, spider):这个方法,毕竟这个方法是用来接收item数据的。

自定义保存json

保存为json,需要依赖json类库,使用json.dumps()来,把item数据转换成json数据;还有需要依赖codecs类库,使用codecs.open()来创建文件,用于存储json数据。其实也可以不依赖任何类库,直接使用open()来创建文件,之所以使用codecs.open()是因为,codecs创建的文件可以避免编码的问题。

转换为json数据,需要经过三个阶段:
(一):初始化阶段
在初始化阶段,就需要创建好用于保存json数据的文件。codecs.open('article.json','w',encoding='utf-8'),其中参数“article.json”为创建的文件名,“w”为数据写入文件的方式,“utf-8”为写入的文字编码。
(二):数据处理阶段
数据处理,就需要获取到数据,这就避不开process_item(self, item, spider)方法,通过它,能接收item数据。item数据转换成json使用json.dumps(dict(item),ensure_ascii=False),其中,item数据需要先换成dict字典,ensure_ascii参数非常重要,False表示不使用Unicode编码, 否则中文会被Unicode编码,从而乱码。最后把转换的json数据写入到文件中:self.file.write(lines)。最后的最后,记得要把item,return回去。
(三):结束阶段
spider有个生命周期函数def spider_close(self):,在爬虫结束的时候把文件资源关闭释放:self.file.close()
最后到settings.py注册一下就好了。

使用scrapy保存为json文件

scrapy提供了exporter,来把item保存为各种数据,除了json,还可以保存为xml、cvs等等。使用scrapy保存为json数据,需要依赖exporter的JsonItemExporter类库:from scrapy.exporters import JsonItemExporter

scrapy转换为json数据,需要经过三个阶段:
(一):初始化阶段
在初始化阶段,就需要创建好用于保存json数据的文件。open('article_exporter.json','wb'),其中参数“article_exporter.json”为创建的文件名,“wb”为数据写入文件的方式。实例化JsonItemExporter:JsonItemExporter(self.file, encoding='utf-8', ensure_ascii=False),其中,参数“self.file”:创建的文件句柄;“encoding”:数据的文字编码;“ensure_ascii”:是否使用Unicode存储数据。
(二):数据处理阶段
数据处理,就需要获取到数据,这就避不开process_item(self, item, spider)方法,通过它,能接收item数据。只需要把item通过self.exporter.export_item(item)交给scrapy,scrapy会把item转换成json并存入文件。最后的最后,记得要把item,return回去。
(三):结束阶段
scrapy exporter有个生命周期钩子函数:def close_spider(self):,在这里把文件资源关闭释放:self.file.close();关闭JsonItemExporter:self.exporter.finish_exporting()
最后到settings.py注册一下就好了。

数据保存到MySQL数据库

python操作MySQL数据库,除了已经安装好MySQL数据库外,还需要安装mysqlclient:pip install -i https://pypi.douban.com/simple/ mysqlclient,这里使用了豆瓣源,加快下载速度。如果安装不了到这个网站(https://www.lfd.uci.edu/~gohlke/pythonlibs/)上,查找Mysqlclient,并下载对应python版本的文件,进行安装。

最后到settings.py注册一下就好了。

MySQL异步存储数据

scrapy框架爬虫速度非常快,以至于MySQL的数据插入速度跟不上,这时应该考虑到使用MySQL的异步存储,而Twisted框架为我们提供了一个异步容器,并不提供数据库的链接,我们在这个异步容器中结合MySQL的类库,使MySQL具备异步的能力。

在settings.py设置数据库链接参数:

使用item_loader管理item

之前写的item比较混乱,不好管理与维护,所以,借助scrapy的ItemLoader来管理item。

解决某些item数据需要特殊处理

某些item数据需要经过进一步的处理,才能得到目标数据。这就需要在items.py文件中,通过导入scrapy的MapCompose:from scrapy.loader.processors import MapCompose,在MapCompose里传入方法名,就会从左到右依次调用执行方法。

如何解决item为list,但只取第一个

这个需要在items.py中,借助scrapy的TakeFirst:from scrapy.loader.processors import TakeFirst

如果每个item都需要TakeFirst,那么,可以通过自定义ItemLoader来实现全局的调用。
在items.py文件中,我们需要引入ItemLoader:from scrapy.loader import ItemLoader

然后,jobbole.py 就不能再用ItemLoader,改用自定义的ItemLoader:

在全局TakeFirst下,某些item不需要TakeFirst,保持原有数据

在全局TakeFirst下,某些item不需要TakeFirst。在全局的TakeFirst下,我们可以看见,它定义的是:default_output_processor = TakeFirst(),如果某些item不需要TakeFirst,可以在Field()里指定:output_processor = 自定义方法,覆盖掉default_output_processor =TakeFirst()。其中,自定义方法可以固定这样写:

完整代码如下:

item的list变成带分隔符的字符串

这个借助scrapy的Join即可完成:from scrapy.loader.processors import Join:

总结


文章 scrapy框架爬虫简单入门(二)–爬取文章网站 转载需要注明出处
喜欢 (0)

您必须 登录 才能发表评论!