scrapy 快速上手
Scrapy快速上手
scrapy简介
scrapy
是一款爬虫框架,相比于一般的基于requests
自行编写的爬虫,其特点主要包括:
- 系统化&结构化:这意味着你编写的代码能够有很高的扩展性,维护更加容易,对于不擅长设计项目架构的童鞋帮助很大
- 高效率:
scrapy
让你在编写程序的同时不需要考虑许多性能上的东西(例如多线程),这些scrapy
都已为你考虑好了,你真正需要关注的是爬虫的解析部分 - 一步到位:这里指的是一个完整的爬虫工作,包括页面下载->解析->过滤->存储几个基本步骤,
scrapy
都存在相应的模块进行处理,并且这些步骤之间的衔接也由scrapy
完成,让你摆脱数据在不同阶段传输的焦虑
scrapy
官方文档对于scrapy有着非常鲜明的解释了,包括一个基础的教程,各个模块具体的功能,以及一些高级操作,本文档的目的旨在:快速上手,略过一些不必要的细节,实现让一个小白也能根据文档快速实现利用scrapy
实现一个完整的爬虫。
一个简单的例子
快速上手一个项目的方式就是跑通一个小例子,一方面简洁的程序能更容易理解,另一方面跑通程序能够提高我们对项目的研究兴趣,下面的例子实现的功能是访问请求:http://quotes.toscrape.com/page/1/,将该页面每个方框内的**文字、作者、标签**信息提取出来,并存储成`json lines`的形式。
- 创建一个
scrapy
项目:scrapy startproject tutorial
,你将得到如下结构的项目,目前不必纠结每个文件目录是干什么的
tutorial/
scrapy.cfg # deploy configuration file
tutorial/ # project's Python module, you'll import your code from here
__init__.py
items.py # project items definition file
middlewares.py # project middlewares file
pipelines.py # project pipelines file
settings.py # project settings file
spiders/ # a directory where you'll later put your spiders
__init__.py
- 在根目录下创建一个
spider
(针对每一个不同的网站都要创建一个spider
) :scrapy genspider quotes quotes.toscrape.com
,这会在spiders
下创建一个新的文件,包含以下内容:
import scrapy
class QuotesSpider(scrapy.Spider):
name = "quotes"
start_urls = [
'http://quotes.toscrape.com/page/1/',
]
def parse(self, response):
pass
这里有一些需要解释的,
start_urls
是一个列表,包含了我们需要爬取的网址,同时也是起始网址,可以理解为爬虫入口,很多爬虫会从起始网址开始,一步步提取出更多的网址并进一步爬取,但是在这个例子中,我们只考虑单个网址。parse
是一个解析方法,它有一个参数response
,就是爬取start_urls
列表中的网址后得到的结果,你可以理解为response=requests.get(url=start_requests[i]), i=0,1,2...
,注意,爬虫的核心代码就是从网页中解析出我们需要的数据,因此在你的爬虫文件中编写的其他函数(例如parse_authro(self, response)
),应该也遵照这个思想:接收response,解析数据,yield
新请求。有点扯远了,继续下一步
- 我们需要从
response
中解析出方框的内容,这来源于我们对于网页源代码的观察,如下图:
容易发现每个方框都是一个class="quote"
的div
标签,因此我们可以用如下的代码去解析其中的数据(代码运用了css
解析,后面会详解,这里了解一下就好)
import scrapy
class QuotesSpider(scrapy.Spider):
name = "quotes"
start_urls = [
'http://quotes.toscrape.com/page/1/',
]
def parse(self, response):
for quote in response.css('div.quote'):
yield {
'text': quote.css('span.text::text').get(),
'author': quote.css('small.author::text').get(),
'tags': quote.css('div.tags a.tag::text').getall(),
}
上面的代码让我们遍历所有
div
标签,并从中提取出三个字段的内容,至此,我们的爬虫解析部分就写好了
- 运行我们的爬虫很容易,直接执行代码:
scrapy crawl quotes -o res.jl
就可以看到我们的数据了
scrapy运作机制
pass
scrapy选择器:css与xpath使用
css&xpath是scrapy中
response
解析提取数据的两种主要方式,在此之前,你一定用过例如Beautifulsoup
进行网页文本数据的解析,相比之下,scrapy
内置的解析器效率要更高,因此强烈建议将这两种解析方法都掌握。下面的列表不会像官网一样一步步教你解析,更多的是一种功能式的查询,即回答我该怎么样获取我想要的数据的问题。
查找指定标签文本
response.css('a::text')
response.xpath('//a/text()') # 有多个标签返回多个
查找指定标签的属性
response.css('a::attr(href)')
response.xpath('//a/@href')
查找指定属性为指定值的标签
response.css('a[href="xxx"]') # 获取属性href=xxx的a标签
response.xpath('//a[@href="xxx"]')
查找指定属性包含指定值的标签
response.css('a[href*="xxx"]')
response.xpath('a[contains(@href, "xxx")]')
查找文本为指定值的标签
response.xpath('//a[text()="xxx"]')
查找文本包含指定值的标签
response.xpath('a[contains(text(), "xxx")]')
当获取到标签之后,可以通过 get()
, getall()
等方法提取需要的信息,或者在此基础上再进行标签的获取
文件&图片下载
有些时候我们希望爬虫不仅爬取文字信息,也要下载图片&文件内容到本地,这个时候就需要用到scrapy.pipelines.FilesPipeline
和scrapy.pipelines.ImagePipeline
了,它们分别定义了文件管道和图片管道。传统的下载方式是在parse
的时候加入下载链接到item
中,然后调用下载函数(如cptools.process.download_file
)指定下载并存储,但这种下载方式是阻塞式的,用pipeline
的好处是可以异步下载,提高效率,主要的步骤如下:
- 定义一个
Item
(可以是原本已有的),添加两个字段files
,file_urls
,后者是下载链接的列表(即一次可传入多个下载链接),前者是文件下载完成后存储的下载相关信息(如下载路径、url、校验码等) - 在
settings.py
中设置变量FILES_STORE
即存储路径,之后下载的内容都会存储在这个路径下,最终结果是FILES_STORE/full/3afec3b4765f8f0a07b78f98c07b83f013567a0a
(最后一串是文件的sha1值命名的文件) - 在
settings.py
中启动pipeline
:即scrapy.pipelines.files.FilesPipeline:1
下面是一个例子:
# items.py
class SFItem(scrapy.Item):
name = scrapy.Field() # 固件名称
# 下载信息存储,下面字段必要
file_urls = scrapy.Field()
files = scrapy.Field()
# spiders
def parse(self, response):
item = SFItem()
item['file_urls'] = ["https://download_url.zip"] # 注意要列表传入
yield item
# settings.py
ITEM_PIPELINES = {
'IndustryInfoCrawler.pipelines.SFFilesPipeline': 1,
'IndustryInfoCrawler.pipelines.MongoPipeline': 300,
}
FILES_STORE = 'root'
上面的方式基本满足了对文件的下载操作,但有两点问题:
下载路径我们只定义了根目录,如果我们相对文件分类,那怎么办
文件名sha1值是为了不重复,但可读性很差,我们希望能够自定义文件名
解决办法通过重写Pipeline
实现,样例如下:
from scrapy.pipelines.images import FilesPipeline
class SFFilesPipeline(FilesPipeline):
"""
自定义下载管道
"""
def get_media_requests(self, item, info):
for file_url in item['file_urls']:
yield scrapy.Request(image_url) # 请求下载
def file_path(self, request, response=None, info=None, *, item=None):
# parent = super().file_path(request, response, info) # 获取父类目录
SF_path = 'SF' # 在这里自定义存储的目录,也可以根据不同文件类型,自己设置分类目录
filename = 'test.zip'
return os.path.join(SF_path, filename) # 返回结果为文件存储路径,最终结果是在根目录下存储`FILES_STORE/SF_PATH/filename`
- 文件到期:很多时候我们不希望重复对本地已有的文件进行下载操作,可以自定义下载管道中请求的时候检查本地文件是否存在,也可以通过设置文件到期时间(未到期的文件不会重复下载)
# settings.py
# 120 days of delay for files expiration
FILES_EXPIRES = 120
# 30 days of delay for images expiration
IMAGES_EXPIRES = 30
# 如果存在自定义的文件管道,如上面的SFFilesPipeline,可以对单个子类管道设置到期时间,以子类名称大写开头
SFFILESPIPELINE_FILES_EXPIRES = 120 # 120天内不重复下载通过SFFilesPipeline的文件
还有一个注意点是,当下载文件过大,超出scrapy预期的话,会报警告,可以通过在
settings.py
中设置DOWNLOAD_WARNSIZE = 0
去除
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!