简介:Scrapy是一个基于Twisted的异步处理框架,是纯Python实现的爬虫框架。
优势:架构清晰、模块之间耦合度程度低,可扩展性强,可以灵活完成各种需求。只需要定制开发几个模块就能轻松实现一个爬虫。
架构介绍:
Engine(引擎),处理整个系统的数据流处理、触发事务,是整个框架的核心。
Item(项目),定义爬取结果的数据结构,爬取的数据会被赋值成该Item的对象。
Scheduler(调度器),接受引擎发过来的请求并将其加入到队列中,在引擎再次请求的时候,将请求提供给引擎。
Downloader(下载器),下载网页内容,并将网页内容返回给蜘蛛。
Spiders(爬虫),定义爬取的逻辑和网页的解析规则,负责解析响应并生成提取结果和新的请求。
Item Pipeline(项目管道),负责处理由蜘蛛从网页中抽取的项目(数据的清洗、验证和存储)。
Downloader Middlewares(下载器中间件),位于引擎和下载器之间的钩子框架,负责处理引擎和下载器之间的请求和响应。
Spider Middlewares(爬虫中间件),位于引擎和蜘蛛之间的钩子框架,负责处理向蜘蛛输入的响应和输出的结果及新的请求。
数据流:
- 【引擎】打开网站,找到处理该网站的【爬虫】并向该【爬虫】请求第一个要爬取的URL。
- 【引擎】拿到第一个要爬取的URL,并通过【调度器】以Request的形式调度(【调度器】会将【引擎】发过来的请求加入到队列中)。
- 【引擎】向【调度器】请求下一个要爬取的URL。
- 【引擎】将下一个要爬取的URL,通过【下载器中间件】转发给【下载器】下载。
- 【下载器】下载好页面后,会生成该页面的Response,然后通过【下载器中间件】发送给【引擎】。
- 【引擎】拿到Response后,会通过【爬虫中间件】转发给【爬虫】处理。
- 【爬虫】处理完Response后,会将提取到的Item和新的Request发送给【引擎】。
- 【引擎】会将拿到的Item转发给【项目管道】,将新的Request转发给【调度器】。
- 重复执行步骤2~8,直至【调度器】中没有更多的Request,【引擎】将关闭该网站,结束爬取作业。
示例:
目标:爬取网站(Quotes to Scrape),试用命令行和MongoDB数据库两种形式保存数据。
环境需求:Scrapy框架、PyMongo库和MongoDB服务。
在命令行中创建项目框架:
scrapy startproject quotes_2022
ps:quotes_2022是自定义的项目名
在命令行中创建Spider:
cd quotes_2022
scrapy genspider quotes quotes.toscrape.com
ps:quotes是自定义的爬虫名;quotes.toscrape.com是目标网站的域名
在PyCharm中查看项目的文件结构
在PyCharm中查看自定义的Spider文件(quotes.py)
import scrapy
class QuotesSpider(scrapy.Spider):
name = 'quotes'
allowed_domains = ['quotes.toscrape.com']
start_urls = ['http://quotes.toscrape.com/']
def parse(self, response):
pass
其中包含了一个刚刚自定义的类(QuotesSpider),这个类继承自Scrapy提供的Spider类(scrapy.Spider)。Scrapy用它来从网页抓取内容,并解析抓取的结果。
在类下面包含了三个属性(name、allowed_domains、start_urls)和一个方法(parse)。
name:用来区分不同的Spider,在同一个项目里面不能重复
allowed_domains:设定允许爬取的域名,如果初始或者后续的请求链接不是这个域名下的,则请求链接会被过滤掉
start_urls:用来定义初始请求,包含Spider在启动时爬取的url列表
parse:它是Spider的一个方法,负责解析返回的响应、提取数据或者进一步生成要处理的请求。(默认情况下被调用时,start_urls里面的链接构成的请求在完成下载执行后,返回的响应会作为唯一的参数传递给parse函数。)
创建Item
在PyCharm中查看items.py文件
import scrapy
class Quotes2022Item(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
pass
其中包含了一个自定义的Item(Quotes2022Item),这个Item继承自scrapy.Item类。Item是保存爬取数据的容器,使用方法和字典类似。相比字典,Item多了额外的保护机制,可以避免拼写错误或者定义字段错误。
此时观察网站,确定需要获取的目标为text、author、tags。
此时将item.py修改一下,添加上需要的字段(类名太挫了,顺手修改下)
import scrapy
class QuotesItem(scrapy.Item):
text = scrapy.Field()
author = scrapy.Field()
tags = scrapy.Field()
解析response
在PyCharm中查看自定义的Spider文件
在Spider中可以看到,parse方法的参数response时start_urls里面的链接爬取后的结果。所以在parse方法中,可以直接对response变量包含的内容进行解析。
此时查看网页结构,可以发现每一页都有多个class为quote的区块,同时每个区块内都包含text、author、tags。那么就简单了,找出所有的quote,然后再提取每一个quote里面的内容。
提取的方式可以是CSS选择器或者XPath选择器(或者两者其一再配合上正则表达式)。
使用CSS选择器的话,parse()改写如下:
def parse(self, response):
quotes = response.css('.quote')
for quote in quotes:
text = quote.css('.text::text').get()
author = quote.css('.author::text').get()
tags = quote.css('.tags .tag::text').getall()
如果想要获取的是author的主页链接,可以用:
author_url = quote.css('span a::attr(href)').get()
print(author_url)
输出结果:'/author/Albert-Einstein'
使用XPath选择器的话,parse改写如下:
def parse(self, response):
quotes = response.xpath("//div[@class='quote']")
for quote in quotes:
text = quote.xpath("./span[@class='text']/text()").get()
author = quote.xpath("./span/small[@class='author']/text()").get()
tags = quote.xpath("./div/a[@class='tag']/text()").getall()
如果想要获取的是author的主页链接,可以用:
author_url = quotes.xpath("./span/a/@href").get()
print(author_url)
输出结果:'/author/Albert-Einstein'
ps:
get()可以用extract_first()替换,选取首个元素;
getall()可以用extract()替换,选取全部元素。
使用Item
在上上一步中,已经定义好了Item,现在就要使用了。依次用刚才解析到的结果赋值Item中的每一个字段,最后将Item返回即可。
ps:需要先导入QuoteItem函数并实例化
import scrapy
from quotes_2022.items import QuotesItem
class QuotesSpider(scrapy.Spider):
name = 'quotes'
allowed_domains = ['quotes.toscrape.com']
start_urls = ['http://quotes.toscrape.com/']
def parse(self, response):
quotes = response.xpath("//div[@class='quote']")
for quote in quotes:
item = QuotesItem()
item['text'] = quote.xpath("./span[@class='text']/text()").get()
item['author'] = quote.xpath("./span/small[@class='author']/text()").get()
item['tags'] = quote.xpath("./div/a[@class='tag']/text()").getall()
yield item
后续Request
这一步需要从当前页面找到信息来生成下一个请求,然后在下一个请求的页面里找到信息再构造下一个请求,如此循环往复,实现整个网站的爬取。
拉网网站底部,找到【下一页】按钮。检查源代码,链接为/page/2,完整链接就是http://quotes.toscrape.com/page/2/,通过这个链接我们就可以构造下一个请求。
构造请求时需要用到scrapy.Request,并且这里需要传递两个参数:url和callback。
url:请求链接
callback:回调函数。(当指定了该回调函数的请求完成之后,获取到响应,引擎会将该响应作为参数传递给这个回调函数。回调函数进行解析或生成下一个请求。)
由于新旧页面的结果是一样的,所以可以继续用parse方法来做页面解析。
在parse方法后追加上如下代码:
next = response.css('.next a::attr(href)').get()
url = response.urljoin(next)
yield scrapy.Request(url=url,callback=self.parse)
在命令行中试运行&保存到文件
scrapy crawl quotes
ps:quotes是自定义的爬虫名
截至目前,数据都只能在控制台查看。但其实只要Scrapy提供的Feed Exports可以轻松将抓取结果输出到本地文件中。
例如,如果想将上面的结果保存成JSON文件,可以执行以下命令:
scrapy crawl quotes -o quotes.json
# 保存到一行
scrapy crawl quotes -o quotes.jsonlines / scrapy crawl quotes -o quotes.jl
# 每一个Item输出一行JSON
输出格式还支持很多种:
scrapy crawl quotes -o quotes.csv
scrapy crawl quotes -o quotes.xml
scrapy crawl quotes -o quotes.pickle
scrapy crawl quotes -o quotes.marshal
scrapy crawl quotes -o ftp://user:pass@ftp.example.com/path/to/quotes.csv
对于小型项目来讲,这可能就已经足够了,但是想要更复杂的输出,比如输出到数据库,那就需要通过Item Pipeline来完成了。
使用Item Pipeline导出到MogonDB数据库
Item Pipeline(项目管道),当Item生成后,会被自动送到Item Pipeline进行处理。Item Pipeline的基本功能:
- 清理HTML数据
- 验证爬取数据,检查爬取字段
- 查重并丢弃重复内容
- 将爬取结果保存到数据库
要想使用Item Pipeline需要定义好一个类及其下的process_item方法(process_item方法必须返回包含数据的字典或Item对象,或者抛出DropItem异常)。
在PyCharm中查看pipelines.py文件
class Quotes2022Pipeline:
def process_item(self, item, spider):
return item
可以看到,process_item方法有两个参数,一个是item(每次Spider生成的Item都会作为参数传递过来),另一个是spider(就是Spider实例)。
修改一下代码,实现:将item中的text字段的长度限制在50个字符以内,超长的部分用“...”代替。
from scrapy.exceptions import DropItem
class TextPipeline:
def __init__(self):
self.limit = 50
def process_item(self, item, spider):
if item['text']:
if len(item['text']) > self.limit:
item['text'] = item['text'][0:self.limit].rstrip()+'...'
return item
else:
return DropItem('Missing Text')
- 导入DropItem方法
- 创建构造方法,定义好长度限制为50
- 先判断item的text属性是否存在,如果不存在,抛出DropTrem异常
- 再判断长度是否大于50,如果是,那就保留50以内的字符然后加上“...”
- 最后将item返回
定义一个新类,实现:将处理后的item存入MongoDB。
import pymongo
class MongoPipeline:
def __init__(self,mongo_uri,mongo_db):
self.mongo_uri = mongo_uri
self.mongo_db = mongo_db
@ classmethod
def from_crawler(cls,crawler):
return cls(
mongo_uri=crawler.settings.get('MONGO_URI'),
mongo_db=crawler.settings.get('MONGO_DB')
)
def open_spider(self,spider):
self.client = pymongo.MongoClient(self.mongo_uri)
self.db = self.client[self.mongo_db]
def process_item(self,item,spider):
name = item.__class__.__name__
self.db[name].insert_one(dict(item))
return item
def close_spider(self,spider):
self.client.close()
__init__:定义构造函数,完成连接MongoDB时需要用到的地址链接和数据库名称两个对象的初始化。
from_crawler:通过crawler拿到全局配置(settings.py)中的各项配置信息。(需要在全局配置中定义好【MONGO_URI】和【MONGO_DB】)
open_spider:当Spider开启时,调用这个方法,完成一些初始化操作
process_item:执行数据插入操作
close_spider:当Spider关闭时,调用这个方法,关闭数据库的连接
在PyCharm中查看settings.py文件
最后还需要在全局配置文件里面补充上MongoDB的链接信息以及激活前面定义的两个Pipeline类。
ITEM_PIPELINES = {
'quotes_2022.pipelines.TextPipeline': 300,
'quotes_2022.pipelines.MongoPipeline': 400,
}
MONGO_URI = 'localhost'
MONGO_DB = 'quotes_2022'
在ITEM_PIPELINES字典中,键名就是Pipeline类的名字,键值代表的事调用优先级(数字越小越早调用)。
在命令行中正式运行
scrapy crawl quotes
等爬取结束后,在MongoDB Compass中刷新一下,可以查看到爬取到的数据了。
目录
在PyCharm中查看自定义的Spider文件(quotes.py)
<完>