文章目录
Scrapy爬虫框架
1. 框架架构
1.1 Scrapy框架介绍
- 写一个爬虫,需要做很多的事情。比如:发送网络请求、数据解析、数据存储、反反爬虫机制(更换
ip
代理、设置请求头等)、异步请求等。这些工作如果每次都要自己从零开始写的话,比较浪费时间。因此Scrapy
把一些基础的东西封装好了,在他上面写爬虫可以变的更加的高效(爬取效率和开发效率)。因此真正在公司里,一些上了量的爬虫,都是使用Scrapy
框架来解决。
1.2 Scrapy架构图
1.3 Scrapy框架模块功能
Scrapy Engine(引擎)
: 负责Spider
、ItemPipeline
、Downloader
、Scheduler
中间的通讯,信号、数据传递等。Scheduler(调度器)
: 它负责接受引擎
发送过来的Request请求,并按照一定的方式进行整理排列,入队,当引擎
需要时,交还给引擎
。Downloader(下载器)
:负责下载Scrapy Engine(引擎)
发送的所有Requests请求,并将其获取到的Responses交还给Scrapy Engine(引擎)
,由引擎
交给Spider
来处理,Spider(爬虫)
:它负责处理所有Responses,从中分析提取数据,获取Item字段需要的数据,并将需要跟进的URL提交给引擎
,再次进入Scheduler(调度器)
,Item Pipeline(管道)
:它负责处理Spider
中获取到的Item,并进行进行后期处理(详细分析、过滤、存储等)的地方.Downloader Middlewares(下载中间件)
:你可以当作是一个可以自定义扩展下载功能的组件。Spider Middlewares(Spider中间件)
:你可以理解为是一个可以自定扩展和操作引擎
和Spider
中间通信
的功能组件(比如进入Spider
的Responses;和从Spider
出去的Requests)
1.4 Scrapy的运作流程(容易理解的介绍)
引擎
:Hi!Spider
, 你要处理哪一个网站?Spider
:老大要我处理xxxx.com
。引擎
:你把第一个需要处理的URL给我吧。Spider
:给你,第一个URL是xxxxxxx.com
。引擎
:Hi!调度器
,我这有request请求你帮我排序入队一下。调度器
:好的,正在处理你等一下。引擎
:Hi!调度器
,把你处理好的request请求给我。调度器
:给你,这是我处理好的request引擎
:Hi!下载器,你按照老大的下载中间件
的设置帮我下载一下这个request请求下载器
:好的!给你,这是下载好的东西。(如果失败:sorry,这个request下载失败了。然后引擎
告诉调度器
,这个request下载失败了,你记录一下,我们待会儿再下载)引擎
:Hi!Spider
,这是下载好的东西,并且已经按照老大的下载中间件
处理过了,你自己处理一下(注意!这儿responses默认是交给def parse()
这个函数处理的)Spider
:(处理完毕数据之后对于需要跟进的URL),Hi!引擎
,我这里有两个结果,这个是我需要跟进的URL,还有这个是我获取到的Item数据。引擎
:Hi !管道
我这儿有个item你帮我处理一下!调度器
!这是需要跟进URL你帮我处理下。然后从第四步开始循环,直到获取完老大需要全部信息。管道``调度器
:好的,现在就做!
2. Scrapy入门
2.1 创建项目
- 要使用
Scrapy
框架创建项目,需要在命令行通过命令来创建。打开cmd
进入到你想把这个项目存放的目录。然后使用以下命令scrapy startproject [项目名称]
创建。
2.2 项目的目录结构
创建完成以后,使用pycharm
打开项目。
主要文件的作用:
items.py
:用来存放爬虫爬取下来数据的模型。middlewares.py
:用来存放各种中间件的文件。pipelines.py
:用来将items
的模型存储到本地磁盘中。settings.py
:本爬虫的一些配置信息(比如请求头、多久发送一次请求、ip
代理池等)。scrapy.cfg
:项目的配置文件。spiders包
:以后所有的爬虫,都是存放到这个里面。
2.3 使用Scrapy框架爬取糗事百科段子
-
创建项目:
scrapy startproject qsbk
-
进入
qsbk
目录中,使用命令创建一个爬虫:scrapy genspider qsbk_spider qiushibaike.com
创建了一个名叫做
qsbk_spider
(爬虫名不能和项目名称一样)的爬虫,并且能爬取的网页只会限制在qiushibaike.com
这个域名下。 -
爬虫代码解析:创建的
qsbk_spider
爬虫会在spiders目录中。
qsbk_spider.py
# -*- coding: utf-8 -*- import scrapy class QsbkSpiderSpider(scrapy.Spider): name = 'qsbk_spider' allowed_domains = ['qiushibaike.com'] start_urls = ['http://qiushibaike.com/'] def parse(self, response): pass
其实这些代码我们完全可以自己手动去写,而不用命令。只不过是不用命令,自己写这些代码比较麻烦。
要创建一个Spider,那么必须自定义一个类,继承自scrapy.Spider
,然后在这个类中定义三个属性和一个方法。
- name:这个爬虫的名字,名字必须是唯一的。
- allow_domains:允许的域名。爬虫只会爬取这个域名下的网页,其他不是这个域名下的网页会被自动忽略。
start_urls
:爬虫从这个变量中的url
开始爬取。- parse:引擎会把下载器下载回来的数据扔给爬虫解析,爬虫再把数据传给这个
parse
方法。这个是个固定的写法。这个方法的作用有两个,第一个是提取想要的数据。第二个是生成下一个请求的url
。
-
修改
settings.py
代码:在做一个爬虫之前,一定要记得修改
setttings.py
中的设置。两个地方是强烈建议设置的。ROBOTSTXT_OBEY
设置为False。默认是True。即遵守robots协议,那么在爬虫的时候,scrapy
首先去找robots.txt
文件,如果没有找到。则直接停止爬取。DEFAULT_REQUEST_HEADERS
添加User-Agent
。
-
完成的爬虫代码:
items.py
import scrapy class QsbkItem(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field() author = scrapy.Field() content = scrapy.Field()
pipelines.py
:将数据保存到json
文件中方式1
import json class QsbkPipeline: def __init__(self): self.fp = open('duanzi.json','w',encoding='utf-8') def open_spider(self,spider): pass def process_item(self, item, spider) #将item对象转换为字典,在转换为json字符串 item_json = json.dumps(dict(item),ensure_ascii=False) self.fp.write(item_json+'\n') #pipeline可能有多个,如果不返回item,其他pipeline将不能获得item。 return item def close_spider(self,spider): self.fp.close()
保存
json
数据时,可以使用这两个类,让操作变得更简单。-
JsonItemExporter
:每次把数据添加到内存中,最后统一写入到磁盘中。好处是,存储的数据是一个满足json
规则的数据。坏处是如果数据量比较大,那么比较耗内存。 -
JsonLinesItemExporter
:每次调用export_item
时就把item存储待硬盘中。坏处是每一个字典是一行,整个文件是一个不满足json
格式的文件。好处是每次处理数据的时候就直接存储到了硬盘中,这样不会耗内存,数据也比较安全。
方式2
from scrapy.exporters import JsonItemExporter """ 这种方式先将每个item转换为字典,放到列表中。最后执行方法self.exporter.finish_exporting()统一将列表写到文件中去。 """ class QsbkPipeline: def __init__(self): #以二进制的方式打开文件 self.fp = open('duanzi.json','wb') self.exporter = JsonItemExporter(self.fp,ensure_ascii=False,encoding='utf-8') self.exporter.start_exporting() def open_spider(self,spider): pass def process_item(self, item, spider): self.exporter.export_item(item) return item def close_spider(self,spider): self.exporter.finish_exporting() self.fp.close()
方式3
from scrapy.exporters import JsonLinesItemExporter class QsbkPipeline: def __init__(self): self.fp = open('duanzi.json','wb') self.exporter = JsonLinesItemExporter(self.fp,ensure_ascii=False,encoding='utf-8') # open_spider:当爬虫被打开的时候执行。 def open_spider(self,spider): pass # process_item:当爬虫有item传过来的时候会被调用。 def process_item(self, item, spider): self.exporter.export_item(item) #执行这个方法将item转换为字典写入到文件中 return item # close_spider:当爬虫关闭的时候会被调用。 def close_spider(self,spider): self.fp.close()
还要激活pipeline,才能使用。在
setting.py
中,设置ITEM_PIPLINES
。ITEM_PIPELINES = { 'qsbk.pipelines.QsbkPipeline': 300, }
qsbk_spider.py
爬取多页的代码import scrapy from qsbk.items import QsbkItem class QsbkSpiderSpider(scrapy.Spider): name = 'qsbk_spider' allowed_domains = ['qiushibaike.com'] start_urls = ['https://www.qiushibaike.com/text/'] """ response是一个scrapy.http.response.html.HtmlResponse对象。可以执行xpath和css语法来提取数据。提取出来的数据是一个Selector或者是一个SelectorList对象,这两个对象有get和getall方法。getall方法:将每一个Selector对象转换为字符串,并放在列表中返回。get方法:将第一个Selector对象转换为字符串,直接返回。 """ def parse(self, response): duanzidivs = response.xpath("//div[@class='col1 old-style-col1']/div") #print("========") #print(duanzidivs.getall())#将SelectorList对象中的每个Selector转换为字符串,并放在列表中返回。 #print("========") for duazidiv in duanzidivs: author = duazidiv.xpath(".//h2/text()").get().strip() content = duazidiv.xpath(".//div[@class='content']//text()").getall() content = "".join(content).strip() item = QsbkItem(author=author,content=content) yield item next_page_url = response.xpath("//ul[@class='pagination']/li[last()]/a/@href").get() #爬到最后一页时,next_page_url提取不到,为None,爬虫结束。 if not next_page_url: return next_page_url = "https://www.qiushibaike.com" + next_page_url print(next_page_url) #请求下一页,当请求回来后执行callback指定的回调函数 yield scrapy.Request(next_page_url, callback=self.parse)
编写完爬取多页的代码后,在
settings.py
中设置下载延迟DOWNLOAD_DELAY = 1
,每隔一秒请求一次 -
-
运行
scrapy
项目:运行
scrapy
项目。需要在终端,进入项目所在的路径,然后scrapy crawl [爬虫名字]
即可运行指定的爬虫。如果不想每次都在命令行中运行,那么可以把这个命令写在一个文件中。以后就在pycharm
中执行运行这个文件就可以了。比如现在在项目的根目录下新创建一个文件叫做start.py
,然后在这个文件中编写代码:from scrapy import cmdline cmdline.execute("scrapy crawl qsbk_spider".split()) #cmdline.execute(['scrapy','crawl','qsbk_spider']) 上面的命令与这个等价
3. CrawlSpider
在糗事百科的爬虫案例中。我们是自己在解析完整个页面后获取下一页的url
,然后重新发送一个请求。有时候我们想要这样做,只要满足某个条件的url
,都给我进行爬取。那么这时候我们就可以通过CrawlSpider
来帮我们完成了。CrawlSpider
继承自Spider
,只不过是在之前的基础之上增加了新的功能,可以定义爬取的url
的规则,以后scrapy
碰到满足条件的url
都进行爬取,而不用手动的yield Request
。
3.1 创建CrawlSpider爬虫
之前创建爬虫的方式是通过scrapy genspider [爬虫名字] [域名]
的方式创建的。如果想要创建CrawlSpider
爬虫,那么应该进入你想要存放爬虫项目的目录,通过命令scrapy genspider -t crawl [爬虫名字] [域名]
创建。
CrawlSpider
需要使用LinkExtractors
类和Rule
类,实现对满足条件的url
进行自动爬取。
3.2 LinkExtractors链接提取器
使用LinkExtractors
可以不用程序员自己提取相应的url
,然后发送请求。这些工作都可以交给LinkExtractors
,它会在所有爬的页面中找到满足我们自己设置规则的url
,实现自动的爬取。
创建该类的对象时需要的主要参数为allow
- allow:允许的
url
。所有满足这个正则表达式的url
都会被提取。
3.3 Rule规则类
定义爬虫的规则类。
创建该类的对象时需要的主要参数
- link_extractor:一个
LinkExtractor
对象,用于定义爬取规则。 - callback:满足这个规则的
url
,应该要执行哪个回调函数。 - follow:指定根据该规则从response中提取的链接是否需要跟进。
3.4 微信小程序社区CrawlSpider案例
主要代码如下:
items.py
import scrapy
class WxappItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
title = scrapy.Field()
author = scrapy.Field()
public_time = scrapy.Field()
content = scrapy.Field()
wxapp_spider.py
# -*- coding: utf-8 -*-
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from wxapp.items import WxappItem
class WxappSpiderSpider(CrawlSpider):
name = 'wxapp_spider'
allowed_domains = ['wxapp-union.com']
start_urls = ['http://www.wxapp-union.com/portal.php?mod=list&catid=2&page=1']
"""
这里定义了两个Rule对象,第一个Rule对象中的爬取规则为 .+mod=list&catid=2&page=\d 即将每一页的url提取出来,进行爬取。这里的 follow 参数传入的是True,即根据定义的规则提取的链接,将它下载下来后,继续从它的response中提取满足该规则的url。我们只需要从每一页中获取每一个教程的url即可,不需要解析该页面的内容,所以不需要callback参数。
第二个Rule对象中的爬取规则为 .+article-.+\.html 即提取每一个教程的url,将相应url下载下来后,执行回调函数 parse_item 提取教程的详细信息。follow参数设置为False,即提取出的每一个教程的url,下载下来后不需要再根据规则 .+article-.+\.html 提取url,因为每一页根据规则 .+article-.+\.html 就能将所有的教程url检索出来。
"""
rules = (
Rule(LinkExtractor(allow=r'.+mod=list&catid=2&page=\d'), follow=True),
Rule(LinkExtractor(allow=r'.+article-.+\.html'),callback="parse_item",follow=False)
)
def parse_item(self, response):
title = response.xpath("//h1[@class='ph']/text()").get()
author = response.xpath("//p[@class='authors']/a/text()").get()
public_time = response.xpath("//span[@class='time']/text()").get()
content = response.xpath("//td[@id='article_content']//text()").getall()
content = "".join(content).strip()
item = WxappItem(title=title,author=author,public_time=public_time,content=content)
yield item
pipelines.py
from scrapy.exporters import JsonLinesItemExporter
class WxappPipeline:
def __init__(self):
self.fp = open('wxjc.json','wb')
self.exporter = JsonLinesItemExporter(self.fp,ensure_ascii=False,encoding='utf-8')
def process_item(self, item, spider):
self.exporter.export_item(item)
return item
def close_spider(self,spider):
self.fp.close()
3.5 总结
定义的各个Rule规则类的检索条件将作用于每一个下载下来的url
的response
。
Rule对象中什么情况下使用follow:如果在爬取页面的时候,需要将满足条件的url
再进行跟进(即将满足条件的url
下载下来后,继续从它的response中提取满足定义规则的url
),那么就设置为True,否则设置为False
什么情况下该指定callback:如果这个url
对应的页面,只是为了获取更多的url
,并不需要提取里面的数据,那么可以不指定callback。如果想要获取url
对应页面中的数据,那么就需要指定一个callback。
视频链接:https://www.bilibili.com/video/BV124411A7Ep?p=1
文章如有错误,请指正。