本系列文档用于对Python爬虫技术的学习进行简单的教程讲解,巩固自己技术知识的同时,万一一不小心又正好对你有用那就更好了。
Python 版本是3.7.4
前面我们学习都是爬虫相关的一些基本知识,学会掌握了前面的技术知识我们可以解决90%爬虫相关的问题。但是我们如何更快更高效的解决这些问题呢(不光是开发快还有请求处理爬取快),这就用到了框架。下面我们开始一步一步学习Scrapy框架。
写一个爬虫,需要做很多的事情。比如:发送网络请求、数据解析、数据存储、反反爬虫机制(更换ip代理、设置请求头等)、异步请求 等。这些工作如果每次都要自己从零开始写的话,比较浪费时间。因此Scrapy
把一些基础的东西都封装好了,在它上面开发爬虫可以变得更加的高效(爬取效率和开发效率)。因此真正在公司里,一些上了量的爬虫,都是使用Scrapy
框架来解决(关于框架的概念在这里就不再做说明)。
Scrapy Engine(引擎)
: Scrapy框架的核心,负责在Spider
和Item Pipeline
、Downloader
、Scheduler
中间通信、传输数据等。Spider(爬虫)
: 发送需要爬取的链接给引擎,最后引擎把其他模块请求回来的数据再发给爬虫,爬虫就去解析想要的数据。这部分是我们开发者自己写的,因为要爬取哪些链接,页面中的哪些数据是我们需要的,都是由程序员自己决定。Scheduler(调度器)
: 复制接收引擎发送过来的请求,并按照一定的方式进行排列和整理,负责调度请求的顺序等。Downloader(下载器)
: 负责接收引擎传过来的下载请求,然后去网络上下载对应的数据在交还给引擎。Item Pipeline(管道)
: 负责将Spider(爬虫)
传递过来的数据进行保存,具体保存在哪里,因该看开发者自己的需求。Downloader Middlewares(下载中间件)
: 可以扩展下载器和引擎之间通信功能的中间件。Spider Middlewares(Spider中间件)
: 可以扩展引擎和爬虫之间通信功能的中间件。Scrapy中的数据流由执行引擎控制,其过程如下:
pip install scrapy
命令安装即可。注意:
1. 在Ubuntu
上安装Scrapy
之前需要先安装以下依赖:
sudo apt-get install python-dev python-pip libxml2-dev libxslt1-dev zliblg-dev libffi-dev libssl-dev
然后再通过pip install scrapy
安装。
2. 如果在Windows
系统下,提示这个错误ModuleNotFoundError:No module named 'win32api'
,那么使用以下命令 可以解决:pip install pypiwin32
。
要使用Scrapy
框架创建目录,需要通过命令来创建,首先进入到你想把这个项目存放的目录。然后使用以下命令创建:
scrapy startproject [项目名称]
不一定非要使用命令来创建,也可以按照其项目目录结构进行手动创建,但是手动创建不是很方便并且创建起来很麻烦。
进入到项目所在的路径,执行命令:
scrapy genspider [爬虫名称] [爬虫的域名]
(注意:爬虫的名称不能和项目名称一样)。
下面为项目主要的文件目录及文件作用:
project_folder -- 项目文件夹名称
|
├──project_name -- 该项目的python模块,一般和项目文件夹名称相同
| |
| ├──spider -- 放置spider代码的包,以后所有的爬虫,都存放在这个里面
| |
| ├──items.py -- 用来存放爬虫怕写来的数据的模型
| |
| ├──middlewares.py -- 用来存放各种中间件的文件
| |
| ├──pipelines.py -- 用来对items里面提取的数据做进一步处理,如保存到本地磁盘等
| |
| ├──settings.py -- 本爬虫的一些配置信息(如请求头、多久发送一次请求、ip代理池等)
|
├──scrapy.cfg -- 项目的配置文件
创建一个名字叫做qsbk
的爬虫,并且能爬取的网页只会限制在qiushibaike.com
这个域名下(注意:爬虫的名称不能和项目名称一样)。
scrapy genspider qsbk_spider 'qiushibaike.com'
import scrapy
class QsbkItem(scrapy.Item):
# define the fields for your item here like:
# 定义item数据字段
author = scrapy.Field()
content = scrapy.Field()
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/page/1/']
def parse(self, response):
# SelectorList
# 解析页面
content_left = response.xpath('//div[@id="content-left"]/div')
# 提取数据
for dz_div in content_left:
# Selector
author = dz_div.xpath(".//h2/text()").get().strip()
content_tmp = dz_div.xpath(".//div[@class='content']//text()").getall()
content = ''.join(content_tmp).strip()
item = QsbkItem(author=author, content=content)
# 使用yield返回给pipeline
yield item
import json
class QsbkPipeline(object):
def __init__(self):
"""
打开文件,也可放在open_spider中
"""
self.fp = open('duanzi.json', 'w', encoding='utf-8')
def open_spider(self, spider):
"""
爬虫被打开的时候执行
:param spider:
:return:
"""
print("爬虫开始....")
def process_item(self, item, spider):
"""
爬虫有item传过来的时候会被调用
:param item:
:param spider:
:return:
"""
item_json = json.dumps(dict(item), ensure_ascii=False)
# 数据写入文件
self.fp.write(item_json + '\n')
return item
def close_spider(self, spider):
"""
爬虫关闭的时候被调用
:param spider:
:return:
"""
# 关闭文件
self.fp.close()
print("爬虫结束....")
运行爬虫只要我们运行命令即可:
scrapy crawl qsbk_spider
每次运行爬虫我们都需要执行命令,所以我们可以在项目根目录下创建一个文件start.py
l里面放置需要执行的命令,文件代码如下:
from scrapy import cmdline
cmdline.execute("scrapy crawl qsbk_spider".split())
# execute里面需要传递列表数据,等价于
# cmdline.execute(["scrapy", "crawl", "qsbk_spider"])
运行爬虫会打印很多log信息,我们可以在setting.py
文件中进行设置仅打印WARNING
等级以上的错误。在文件中增加LOG_LEVEL = 'WARNING'
配置即可。
scrapy.http.response.html.HtmlResponse
对象,可以执行xpath
和css
语法来提取数据。Selector
或者SelectorList
对象,如果想要获取其中的字符串,那么应该执行getall()
或者get()
方法。getall()
方法:获取Selector
中所有文本,返回的是一个列表。get()
方法:获取的是Selector
中的第一个文本,返回德是一个string类型。pipeline
处理,那个可以使用yield
来返回。或者是收集所有的item,最后统一使用return
返回。items.py
中定义好模型,以后就不要使用字典。open_spider(self,spider)
:当爬虫被打开的时候执行;process_item(self,item,spider)
:当爬虫有item传过来的时候会被调用;close_spider(self,spider)
:当爬虫关闭的时候被调用。setting.py
中,设置ITEM_PIPELINES
。示例如下:ITEM_PIPELINES = {
'qsbk.pipelines.QsbkPipeline': 300,
}
上面例子我们在进行存储数据的时候需要先将字典转换成json,然后再做一些其他的存储处理操作,这样有点麻烦。我们可以使用Scrapy框架自带的Exporter导出器scrapy.exporters
,其中就有一个JSON的导出模块JsonItemExporter
。
修改pipelines.py文件如下:
# 引入JsonItemExporter类库
from scrapy.exporters import JsonItemExporter
class QsbkPipeline(object):
def __init__(self):
"""
打开文件,也可放在open_spider中
"""
self.fp = open('duanzi1.json', 'wb')
# 初始化导出器
# 导出文件必须以二进制打开
self.exporter = JsonItemExporter(self.fp, ensure_ascii=False, encoding='utf-8')
def open_spider(self, spider):
"""
爬虫被打开的时候执行
:param spider:
:return:
"""
print("爬虫开始....")
def process_item(self, item, spider):
"""
爬虫有item传过来的时候会被调用
:param item:
:param spider:
:return:
"""
# 进行导出
self.exporter.export_item(item)
return item
def close_spider(self, spider):
"""
爬虫关闭的时候被调用
:param spider:
:return:
"""
# 完成导出
self.exporter.finish_exporting()
# 关闭文件
self.fp.close()
print("爬虫结束....")
我们在使用JsonItemExporter
导出器的时候,它是把所有的数据都当成列表中的一项进行导出。这种方式有一个缺陷,它的导出过程是先把你传回来的所有字典保存在一个列表当中,在最后执行finish_exporting()
的时候再统一写到json文件中。那么这样的话如果传入的json数据比较大的话就不是很好了,它会把所有要导出的json先放到内存中,如果内存不够大的话就对内存的使用不是很友好。
所以我们可以使用另外一种导出器的使用JsonLinesItemExporter
,我们使用这种就像第一次数据处理时的效果,它会将json每行写入到文件中。
修改pipelines.py文件如下:
# 引入JsonLinesItemExporter类库
from scrapy.exporters import JsonLinesItemExporter
class QsbkPipeline(object):
def __init__(self):
"""
打开文件,也可放在open_spider中
"""
self.fp = open('duanzi2.json', 'wb')
# 初始化导出器
# 导出文件必须以二进制打开
self.exporter = JsonLinesItemExporter(self.fp, ensure_ascii=False, encoding='utf-8')
def open_spider(self, spider):
"""
爬虫被打开的时候执行
:param spider:
:return:
"""
print("爬虫开始....")
def process_item(self, item, spider):
"""
爬虫有item传过来的时候会被调用
:param item:
:param spider:
:return:
"""
# 进行导出
self.exporter.export_item(item)
return item
def close_spider(self, spider):
"""
爬虫关闭的时候被调用
:param spider:
:return:
"""
# 关闭文件
self.fp.close()
print("爬虫结束....")
Scrapy导出器不光有JSON格式导出,还有XML、CSV、PICK等方式格式化数据导出。
保存Json数据的时候,可以使用这两个类,让操作变得更简单:
JsonItemExporter
: 这个时每次把数据添加到内存中,最后统一写入到磁盘中,好处时存储的数据是一个满足json规则的数据,坏处是如果数据量比较大,那么比较耗内存。JsonLinesItemExporter
: 这个每次调用export_item
的时候就把这个item存储到硬盘中。患处是每一个字典是一行,整个文件不是一个满足json格式的文件,好处是每次处理数据的时候就直接存储到了硬盘中,这样就不会耗内存。数据也比较安全。修改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/page/1/']
base_url = 'https://www.qiushibaike.com'
def parse(self, response):
# SelectorList
# 解析页面
content_left = response.xpath('//div[@id="content-left"]/div')
# 提取数据
for dz_div in content_left:
# Selector
author = dz_div.xpath(".//h2/text()").get().strip()
content_tmp = dz_div.xpath(".//div[@class='content']//text()").getall()
content = ''.join(content_tmp).strip()
item = QsbkItem(author=author, content=content)
# 使用yield返回给pipeline
yield item
# 获取下一页地址
next_url = response.xpath('//ul[@class="pagination"]/li[last()]/a/@href').get()
if not next_url:
# 没有下一页地址结束爬虫
return
else:
# 将下一页请求返回给调度器
yield scrapy.Request(self.base_url + next_url, callback=self.parse)
需另写文章进行记录日志学习