Scrapy框架介绍
Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。其可以应用在数据挖掘,信息处理或存储历史数据等一系列的程序中。其最初是为了页面抓取 (更确切来说, 网络抓取 )所设计的,也可以应用在获取API所返回的数据(例如 Amazon Associates Web Services)或者通用的网络爬虫。
Scrapy用途广泛,可以用于数据挖掘、监测和自动化测试。
发送网络请求、数据解析、数据存储、反反爬虫机制(更换ip代理、设置请求头等)、异步请求等。这些工作如果每次都要自己从零开始写的话,比较浪费时间。因此Scrapy把一些基础的东西封装好了,在上面写爬虫可以变的更加的高效(爬取效率和开发效率)。因此一些上了量的爬虫,都是使用Scrapy框架来解决。
Scrapy使用了Twisted异步网络库来处理网络通讯。
Scrapy整体架构图
Scrapy框架各个模块功能
Scrapy主要包括了以下组件:
-
引擎(Scrapy)
用来处理整个系统的数据流,触发事务(框架核心)
Scrapy Engine(引擎):Scrapy框架的核心部分。负责在Spider和ItemPipeline、Downloader、Scheduler、中间通信、传递数据等
-
爬虫(Spider)
爬虫是主要干活的,用于从特定的网页中提取自己需要的信息,即所谓的实体(Item)。用户也可以从中提取出链接,让Scrapy继续抓取下一个页面
Spider(爬虫):发送需要爬取的链接给引擎,最后引擎把其他模块请求回来的数据再发送给爬虫,爬虫就去解析想要的数据。这个部分是我们开发者自己写的,因为要爬取哪些链接,页面中的哪些数据是我们需要的,都是由程序员自己决定
-
项目管道(Pipeline)
负责处理爬虫从网页中抽取的实体,主要的功能是持久化实体、验证实体的有效性、清除不需要的信息。当页面被爬虫解析后,将被发送到项目管道,并经过几个特定的次序处理数据
Item Pipeline(管道):负责将Spider(爬虫)传递过来的数据进行保存。具体保存在哪里,取决于开发者自己的需求
-
下载器(Downloader)
用于下载网页内容,并将网页内容返回给蜘蛛(Scrapy下载器是建立在twisted这个高效的异步模型上的)
Downloader(下载器):负责接收引擎传过来的下载请求,然后去网络上下载对应的数据再交还给引擎
-
调度器(Scheduler)
用来接受引擎发过来的请求,压入队列中,并在引擎再次请求的时候返回。可以想像成一个URL(抓取网页的网址或者说是链接)的优先队列,由它来决定下一个要抓取的网址是什么,同时去除重复的网址
Scheduler(调度器):负责接收引擎发送过来的请求,并按照一定的方式进行排列和整理,负责调度请求的顺序等
-
下载器中间件(Downloader Middlewares)
位于Scrapy引擎和下载器之间的框架,主要是处理Scrapy引擎与下载器之间的请求及响应
Downloader Middlewares(下载中间件):可以扩展下载器和引擎之间通信功能的中间件
-
爬虫中间件(Spider Middlewares)
介于Scrapy引擎和爬虫之间的框架,主要工作是处理蜘蛛的响应输入和请求输出
Spider Middlewares(爬虫中间件):可以扩展引擎和爬虫之间通信功能的中间件
-
调度中间件(Scheduler Middewares)
介于Scrapy引擎和调度之间的中间件,从Scrapy引擎发送到调度的请求和响应
Scrapy运行流程大概如下:
- 引擎从调度器中取出一个链接(URL)用于接下来的抓取
- 引擎把URL封装成一个请求(Request)传给下载器
- 下载器把资源下载下来,并封装成应答包(Response)
- 爬虫解析Response
- 解析出实体(Item),则交给实体管道进行进一步的处理
- 解析出的是链接(URL),则把URL交给调度器等待抓取
安装和文档
Scrapy官方文档:http://doc.scrapy.org/en/latest
Scrapy中文文档:http://scrapy-chs.readthedocs.io/zh_CN/latest/index.html
安装:通过pip install scrapy
即可安装
安装过程中经常会遇到错误,因为Scrapy需要很多其他依赖,直接使用pip install scrapy
安装的时候,会同时安装上这些依赖,所以会遇到很多库安装失败的情况
在windows系统下,运行Scrapy项目时,如果提示ModuleNotFoundError: No module named 'win32api'
这个错误,使用命令pip install pypiwin32
,安装pypiwin32这个库即可
pypiwin32和pywin32的关系:
pypiwin32是pywin32的旧版经过重新封装的,安装pywin32通过命令pip install pywin32
相关地址:
https://sourceforge.net/projects/pywin32/
https://blog.csdn.net/chennudt/article/details/76726580
在ubuntu上安装scrapy之前,需要先安装以下依赖:
sudo apt-get install python3-dev build-essential python3-pip libxml2-dev libxslt1-dev zlib1g-dev libffi-dev libssl-dev
然后再通过pip install scrapy
安装
各种错误解决办法:
https://blog.csdn.net/saucyj/article/details/79043443
https://www.cnblogs.com/baxianhua/p/8996715.html
https://blog.csdn.net/sinat_37986079/article/details/80166630
https://blog.csdn.net/weixin_42057852/article/details/80857948
一篇关于Scrapy的文章:
https://www.cnblogs.com/kongzhagen/p/6549053.html
快速入门
创建项目
Scrapy框架创建项目需要通过命令来创建,首先进入到你想把这个项目存放的目录,然后使用命令scrapy startproject [项目名称]
创建
创建爬虫
创建项目后,在命令行中进入到项目所在的路径,使用命令scrapy genspider [爬虫名称] [域名]
创建
注意:爬虫名称和项目名称不能一样
目录结构介绍
在命令行中创建爬虫项目后会生成对应的项目文件
主要文件的作用:
- scrapy.cfg:项目的配置文件,主要为Scrapy命令行工具提供一个基础的配置信息。(真正爬虫相关的配置信息在settings.py文件中)
- items.py:用来存放爬虫爬取下来数据的模型;设置数据存储模板,用于结构化数据,如:Django的Model
- middlewares.py:用来存放各种中间件的文件
- pipelines.py:用来将items的模型存储到本地磁盘中;数据处理行为,如:一般结构化的数据持久化
- settings.py:本爬虫的一些配置信息:递归的层数、并发数,延迟下载(请求头、多久发送一次请求、ip代理池等…)
- spiders包:爬虫目录,以后所有的爬虫,都是存放到这个里面,如:创建文件,编写爬虫规则
小案例
使用Scrapy框架爬取糗事百科段子
使用命令创建一个爬虫:
scrapy startproject qiushibk
cd qiushibk
scrapy gensipder qsbk "qiushibaike.com"
创建了一个名字叫做qsbk的爬虫,并且能爬取的网页只会限制在qiushibaike.com这个域名下
爬虫代码解析:
import scrapy
class QsbkSpider(scrapy.Spider):
name = 'qsbk'
allowed_domains = ['qiushibaike.com']
start_urls = ['https://qiushibaike.com/text/page/1/']
def parse(self, response):
pass
其实这些代码我们完全可以自己手动去写,而不用命令行的方式去创建生成,但是自己写比较麻烦
要创建一个Spider,必须自定义一个类,继承自scrapy.Spider,然后在这个类中定义三个属性和一个方法:
- name:这个爬虫的名字,名字必须是唯一的
- allow_domains:允许的域名。爬虫只会爬取这个域名下的网页,其他不是这个域名下的网页会被自动忽略
- start_urls:爬虫从这个变量中的url开始
- parse:引擎会把下载器下载回来的数据扔给爬虫解析,爬虫再把数据传给这个parse方法。这个是个固定的写法。这个方法的作用有两个:1、提取想要的数据,2、生成下一个请求的url
其他说明:
- response是一个scrapy.http.response.html.HtmlResponse对象,可以执行xpath和css语法来进行数据提取
提取出来的数据是一个Selector或SelectorList对象,如果想要获取其中的字符串,可以通过getall或get方法
getall方法:获取Selector中所有的文本,返回一个列表
get方法:获取Selector中的第一个文本,返回str类型 - 如果要将解析数据出来进行保存,需传给pipelines来处理,使用yield来返回。或者将所有的item数据放入一个列表,再统一用return返回这个列表
- item:建议在items.py中定义好模型,就不需要使用字典的方式返回了
- pipelines.py文件:是专门用来保存数据的,有三个方法是经常使用到的:
open_spider(self, spider) 当爬虫被打开时执行此函数
process_item(self, spider) 当爬虫中有item传过来的时候被调用
close_spider(self, spider) 当爬虫关闭时会被调用
要激活pipelines,应该在settings.py中设置ITEM_PIPELINES
修改settings.py代码:
在做一个爬虫之前,一定要记得修改setttings.py中的设置。以下两个地方是强烈建议设置的:
- ROBOTSTXT_OBEY设置为False。默认是True,即遵守机器协议,那么在爬虫的时候,scrapy首先去找网站中的robots.txt文件,如果没有找到,则直接停止爬取
- DEFAULT_REQUEST_HEADERS添加User-Agent、Referer等一些请求头,这个也是告诉服务器,我这个请求是一个正常的请求,不是一个爬虫
完整的爬虫代码:
完善糗事百科案例代码
爬虫部分代码:
spiders文件下的qsbk.py文件,qsbk是创建爬虫时的爬虫名字
import scrapy
# 导入items.py下的QiushibkItem类,此类是自己定义的爬虫传递数据的模型
# from ..items import QiushibkItem
from pa_chong.Scrapy.qiushibk.qiushibk.items import QiushibkItem
# from scrapy.http.response.html import HtmlResponse # response返回类型
# from scrapy.selector.unified import ScrapyDeprecationWarning # response.xpath返回类型
class QsbkSpider(scrapy.Spider): # 继承自scrapy.Spider类
name = 'qsbk' # 爬虫名字 以后运行爬虫都根据这名字,所以名字必须要唯一性
allowed_domains = ['qiushibaike.com'] # 允许的域名
start_urls = ['https://www.qiushibaike.com/text/page/1/'] # 爬虫开始的url,这里放的是第一页的url
def parse(self, response):
outerbox = response.xpath("//div[@id='content-left']/div")
# outerbox是个SelectorList
items = []
for box in outerbox: # box是个Selector
author = box.xpath(".//div[contains(@class,'author')]//h2/text()").extract_first().strip()
content = box.xpath(".//div[@class='content']/span/text()").extract_first().strip()
'''使用.get()和getall()'''
# author = box.xpath('.//h2/text()').get().strip()
# .get()可以把Selector转换成字符串并提取出来
# content = box.xpath('.//div[@class="content"]//text()').getall()
# 段子内容是在div下的span标签里的多个br标签里,不是直接在当前属性里的一个文本,
# 所以text()用两个斜杠获取所有的,再用.getall()将所有内容提取出来转换成字符串
# content = ''.join(content).strip() .getall()返回的是列表,用join拼接成字符串
# 实例化items.py的QiushibkItem类 推荐这种用方式传入数据给pipelines.py
# item = QiushibkItem(author=author, content=content)
# item叫什么名字无所谓,只是一个对象名,最终都会把解析下来的数据返回给pipelines.py里的item参数
item = QiushibkItem()
item["author"] = author
item["content"] = content
yield item
# items.append(item)
# return items # 每次遍历的结果加入到列表 遍历完成后再把列表返回 不推荐
'''判断是否还有下一页,生成下一页url,请求到下一页,返回给parse函数进行下一页的数据解析'''
next_url = response.xpath('//ul[@class="pagination"]/li[last()]/a/@href').get()
# 找到下一页按钮对应的url:class="pagination"的ur标签下面的最后一个li标签下的a标签的href属性
if not next_url:
return # 如果没有找到下一页按钮说明是最后一页了,直接返回
else:
yield scrapy.Request('https://www.qiushibaike.com' + next_url, callback=self.parse)
# 找到下一页按钮后,用yield返回给self.parse函数,然后会继续解析[类似递归或循环]
# (用scrapy下的Request类传递下一页的url和回调函数,程序在发送请求后 会从此函数继续执行
# 回调函数一般就是用来执行请求后需要做的事情的,如:继续请求另一个url或进行一些数据解析/提取)
定义一个列表每次把数据都append到列表里,最后再统一返回一个包含全部的内容的列表,它会一个一个传给pipelines.py里的item参数,进行解析
但是如果有多个页面数据需要返回的话,遇到return就代表函数执行完毕了,就不会再请求其他页面了,因此这种方式只会返回一页内容,所以推荐使用yield
按照传统的方式 手动创建字典表再返回也行,但是一般都用items.py里的模型,这个是Scrapy模块推荐使用的方式,处理数据的时候比较好
# 传统的方式
duanzi = {'作者': author, '段子内容': content}
yield duanzi
# 推荐的方式
item =QiushibkItem()
item['作者'] = author
item['段子内容'] = content
yield item
items.py部分代码:
import scrapy
class QiushibkItem(scrapy.Item): # 定义爬虫传递数据的模型
author = scrapy.Field() # 固定写法,把需要传入item的数据这样定义一下就行了
content = scrapy.Field()
pipeline部分代码:
pipelines.py文件
使用json模块导出数据
import json
class QiushibkPipeline(object):
def __init__(self): # 构造函数里的属性和方法 也可以放在下面open_spider()方法里,都会最先调用
# self.items = []
self.f = open('duanzi.json', 'w', encoding='utf-8') # 存到json文件里,先打开一个文件
def open_spider(self, spider): # 爬虫打开后最先调用的函数
print("="*40 + '爬虫开始执行.....')
def process_item(self, item, spider): # 存储数据的函数
# self.items.append(dict(item)) # 先存到列表里
item_json = json.dumps(dict(item), ensure_ascii=False)
# 先将item dict成字典表,再dumps成字json符串,ensure_ascii=False是让它不对中文进行Unicode编码
# 因为使用了数据模型yield返回的数据不像手动写的那样是个字典表,会变成其他对象类型
self.f.write(item_json + '\n')
return item # 最后要把item返回给爬虫
def close_spider(self,spider): # 当爬虫关闭时会被调用
# with open('duanzi.json', 'w', encoding='utf-8') as f:
# 把前面列表里的 再存到json文件里
# json.dump(self.items, f, ensure_ascii=False)
self.f.close()
print("="*40 + '爬虫结束......')
JsonItemExporter, JsonLinesItemExporter:
保存json数据的时候,可以使用这两个类,让其操作变得更简单
-
JsonItemExporter:每次把数据添加到内存中,最后统一以列表的方式写在磁盘中
优点:存储的数据是一个满足json规则的数据;外面是列表(数组),里面包裹着一个一个的字典表(对象/键值对)
缺点:如果数据量比较大,会比较耗内存。如果过程中出现存储失败,那么有可能所有数据都会存储失败 -
JsonLinesItemExporter:每次调用export_item的时候直接把这个item存储到磁盘中
优点:每次处理数据的时候直接存储到硬盘中,这样一条一条的存储json数据不会耗内存,数据也比较安全
缺点:每一个字典表作为一行json数据存储,整个文件不是一个满足json格式的文件;一行一个字典表(对象/键值对)
这两种方式其实就相当于 在之前没有用scrapy写爬虫的时候,打印(或存储)每个字典表和打印(或存储)列表里包含各个字典表 这两种方式的关系
字典表是提取到的每组数据,比如:{‘user’:‘123’, name:‘456’},在遍历很多组数据时,如果遍历一次打印(或存储)一次此字典表,就是一条一条的打印(或存储)
列表是所有的数据每次遍历后,追加到列表里,最后再整体打印(或存储),比如:[{‘user’:‘123’, name:‘456’}, {‘user’:‘abc’, name:‘789’}, {‘user’:‘aaa’, name:‘bbb’}, …]
from scrapy.exporters import JsonItemExporter, JsonLinesItemExporter
使用scrapy.exporters下的JsonItemExporter
class QiushibkPipeline(object):
def __init__(self):
self.f = open('duanzi.json', 'wb') # 二进制方式打开
self.exporter = JsonItemExporter(self.f, ensure_ascii=False, encoding='utf-8')
self.exporter.start_exporting() # 定义好文件和编码格式后开始导入数据
def open_spider(self, spider): # 爬虫打开后最先调用的函数
print('爬虫开始执行.....')
def process_item(self, item, spider): # 存储数据的函数
self.exporter.export_item(item) # 先把需要存储的数据(item)都放到一个列表里
return item # 最后要把item返回给爬虫
def close_spider(self, spider): # 当爬虫关闭时会被调用
self.exporter.finish_exporting() # 再统一导入到json文件里,然后结束导入
# 这种方式数据比较大的话会浪费内存,可以使用JsonLinesItemExporter,一个一个json数据进行导入
self.f.close()
print('爬虫结束......')
使用scrapy.exporters下的JsonLinesItemExporter
class QiushibkPipeline(object):
def __init__(self):
self.f = open('duanzi.json', 'wb') # 二进制方式打开
self.exporter = JsonLinesItemExporter(self.f, ensure_ascii=False, encoding='utf-8')
def open_spider(self, spider): # 爬虫打开后最先调用的函数
print('爬虫开始执行.....')
def process_item(self, item, spider): # 存储数据的函数
self.exporter.export_item(item) # 开始传入数据 这种方式每次直接将传进来的json数据传入json文件里保存
return item # 最后要把item返回给爬虫
def close_spider(self, spider): # 当爬虫关闭时会被调用
self.f.close()
print('爬虫结束......')
exporters还有其他很多写入文件的方式,具体可以查看模块
运行Scrapy项目:
运行scrapy项目需要在终端进入项目所在的路径,然后输入scrapy crawl [爬虫名字]
回车即可运行指定的爬虫。
虚拟环境问题:
1、如果你的scrapy是安装在手动创建的虚拟环境下的,需要先进入到此虚拟环境中
python配置虚拟环境:
https://www.jianshu.com/p/ad2d8ee4a679
https://www.cnblogs.com/suke99/p/5355894.html
2、如何进入到pycharm所创建的虚拟环境中
如果不想每次都在命令行中运行,那么可以把这个命令写在一个文件中。以后就在pycharm中执行运行这个文件就可以了。比如现在新创建一个文件叫做start.py,然后在这个文件中填入以下代码:
from scrapy import cmdline # 通过scrapy下的cmdline模块来执行爬虫
# 下面两个效果一样
# cmdline.execute('scrapy crawl qsbk'.split()) # 分割'scrapy crawl qsbk'这个字符串
cmdline.execute(['scrapy', 'crawl', 'qsbk']) # 直接以列表方式传入scrapy crawl qsbk这三儿玩意
# qsbk是这个爬虫的爬虫文件的名字