Scrapy框架原理
scrapy爬虫执行示意图:
Scrapy主要包括了以下组件:
- 引擎(Scrapy)
用来处理整个系统的数据流处理, 触发事务(框架核心)。 - 调度器(Scheduler)
用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL(抓取网页的网址或者说是链接)的优先队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址。 - 下载器(Downloader)
用于下载网页内容, 并将网页内容返回给蜘蛛(Scrapy下载器是建立在twisted这个高效的异步模型上的)。 - 爬虫(Spiders)
爬虫是主要干活的, 用于从特定的网页中提取自己需要的信息, 即所谓的实体(Item)。用户也可以从中提取出链接,让Scrapy继续抓取下一个页面。 - 项目管道(Pipeline)
负责处理爬虫从网页中抽取的实体,主要的功能是持久化实体、验证实体的有效性、清除不需要的信息。当页面被爬虫解析后,将被发送到项目管道,并经过几个特定的次序处理数据。 - 下载器中间件(Downloader Middlewares)
位于Scrapy引擎和下载器之间的框架,主要是处理Scrapy引擎与下载器之间的请求及响应。 - 爬虫中间件(Spider Middlewares)
介于Scrapy引擎和爬虫之间的框架,主要工作是处理蜘蛛的响应输入和请求输出。 - 调度中间件(Scheduler Middewares)
介于Scrapy引擎和调度之间的中间件,从Scrapy引擎发送到调度的请求和响应。
Scrapy运行流程大概如下:
- 引擎从调度器中取出一个链接(URL)用于接下来的抓取
- 引擎把URL封装成一个请求(Request)传给下载器
- 下载器把资源下载下来,并封装成应答包(Response)
- 爬虫解析Response
- 解析出实体(Item),则交给实体管道进行进一步的处理
- 解析出的是链接(URL),则把URL交给调度器等待抓取
个人认为,爬虫的流程就是,当开始执行爬虫时,引擎从被执行的爬虫中获取第一个页面,交给调度器,调度器将网址入栈,同时出栈一个网址(其实在最开始,就是出栈刚刚放进去的网址)交给下载器去下载,下载完成后,交给爬虫进行清洗,最后将需要爬取的数据封装在item中,交给管道进行持久化。
所以爬虫(特指爬虫模块)的任务就是清晰数据,将数据封装进item中,同时实现翻页等功能;下载器是下载调度器给定的网址,作为response传给爬虫;管道的任务是将数据持久化;item是被爬取数据的实体。当然实际上在爬虫和管道中,可以干任何你相干的事,比如在爬虫中将数据持久化为一个txt文本,但这会影响爬虫的效率。
通过这些分析可知,在编写一个简单的爬虫时,只需要编写item,spiders,pip这三个文件。
Scrapy框架使用步骤
- 安装scrapy框架
使用cmd安装,命令如下:
pip install scrapy
- 创建爬虫项目
同样使用cmd进行创建,在想要防止爬虫的文件夹打开爬虫,执行下面的命令:
scrapy startproject HuyaSpider # 创建一个爬虫项目,HuyaSpider是爬虫项目的名字。
cd HuyaSpider # 切换到刚创建的爬虫项目下。
scrapy genspider huya "huya.com" # 创建一个爬虫,
# 第一参数是爬虫的名字,自己随便定义,注意不要与爬虫项目相同,
# 第二个参数是爬取的域名,超过这个域名的链接不会被下载器下载
# 一个爬虫项目可以有多个爬虫。
- 项目结构
创建后的项目就是一个文件夹,结构如下:
HuyaSpider/
scrapy.cfg
HuyaSpider/
__init__.py
items.py
middlewares.py
pipelines.py
settings.py
spiders/
__init__.py
huya.py
*其他爬虫.py
在settings.py中进行爬虫的设置,如headsers配置,管道的开启和关闭;在item.py中抽象要爬取的内容;在管道中进行数据持久化。
示例
settings.py:
# 设置默认的headers
DEFAULT_REQUEST_HEADERS = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36'
}
# 关闭或开启管道,注释状态下就是关闭。
# Configure item pipelines
# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
#ITEM_PIPELINES = {
# 'HuyaSpider.pipelines.HuyaspiderPipeline': 300,
#}
item.py:
# Define here the models for your scraped items
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.html
import scrapy
class HuyaspiderItem(scrapy.Item):
live_name = scrapy.Field() # 虎牙直播间名称
live_url = scrapy.Field() # 直播间网址
live_img_url = scrapy.Field() # 直播间封面网址
live_appearance_level = scrapy.Field() # 颜值打分
huya.py:
# encoding:UTF-8 #
import scrapy
from HuyaSpider.items import HuyaspiderItem
from HuyaSpider.AppearanceLevel import BaiduAI
# 创建自scrapy.Spider 最基础类
class HuyaSpider(scrapy.Spider):
name = 'huya' # 爬虫名字,必须唯一
allowed_domains = ['huya.com'] # 允许采集的域名
start_urls = ['https://www.huya.com/g/2168'] # 开始采集的网站
count = 0 # 计数器,记录爬取了几个直播间
# 解析响应数据,提取网址或数据,response就是响应数据
def parse(self, response): # 提取数据 selector选择器 正则、BeautyfulSoup、xpath、css
live_rooms = response.xpath('//div[@class="box-bd box-live-card-list"]/ul/li') # 注意没有.extract()方法,这里提取了的话,下面的for循环中就无法使用xpath语法提取了。
for liveRoom in live_rooms:
huyaspiderItem = HuyaspiderItem() # 创建一个要爬取的item的对象
huyaspiderItem["live_name"] = liveRoom.xpath("./a/img[@class='pic']/@title").extract()[0] # 直播间名称
huyaspiderItem["live_url"] = liveRoom.xpath("./a/@href").extract()[0] # 直播间地址
huyaspiderItem["live_img_url"] = liveRoom.xpath("./a/img[@class='pic']/@data-original").extract()[0] # 直播间封面网址
# 实例化颜值测试类
live_appearance_level = BaiduAI(huyaspiderItem["live_img_url"])
# 测试颜值
huyaspiderItem["live_appearance_level"] = live_appearance_level.face_identification() # 封面颜值
HuyaSpider.count += 1 # 打印测试数量
print(HuyaSpider.count)
yield huyaspiderItem # 将item交给pip持久化
piplines.py:
这个项目最后没有选择用管道持久化数据,因为目标是将采集到的数据保存为一个.csv文件,而scrapy框架提供的简便的命令实现。
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html
# useful for handling different item types with a single interface
from itemadapter import ItemAdapter
from HuyaSpider.AppearanceLevel import BaiduAI
class HuyaspiderPipeline:
def process_item(self, item, spider):
print('直播间名称: ' + item["live_name"])
print('直播间网址: ' + item["live_url"])
print('直播间封面照网址: ' + item["live_img_url"])
print('颜值: ' + item["live_appearance_level"])
return item # 管道是有优先级的,可以在settings中设置,如果不return,下面的管道就接收不到item。
颜值测试类AppearanceLevel.py:
这个类和爬虫没关系,只是为了实现功能,放在了项目中,可以是为一个库。
# encoding:utf-8
import base64
import json
import requests
'''
通过百度人脸识别api测试颜值
'''
class BaiduAI:
def __init__(self, img):
self.AK = "v16G2mjnKu3ypaKcAShKkLhK" # 应用API Key
self.SK = "1clCBt8Mo08aUxIRs8z0rRi1VPz6uWxb" # 应用Secret Key
self.img_src = img
self.headers = {
"Content-Type": "application/json; charset=UTF-8"
}
def get_AccessToken(self):
# 获取Access Token
host = 'https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=' + self.AK + '&client_secret=' + self.SK
response = requests.get(host, headers=self.headers)
json_result = json.loads(response.text)
if response:
return json_result['access_token']
else:
print(json_result)
return 0
def face_identification(self):
# 人脸检测与属性分析
# img = self.img_to_base64(self.img_src)
request_url = "https://aip.baidubce.com/rest/2.0/face/v3/detect"
post_data = {
"image": self.img_src,
"image_type": "URL",
"face_field": "gender,age,beauty,gender,race,emotion,face_shape,landmark",# 包括age,beauty,expression,face_shape,gender,glasses,landmark,emotion,face_type,mask,spoofing信息
"face_type": "LIVE"# 人脸的类型。LIVE表示生活照,IDCARD表示身份证芯片照,WATERMARK表示带水印证件照,CERT表示证件照片,默认LIVE。
}
access_token = self.get_AccessToken()
request_url = request_url + "?access_token=" + access_token
response = requests.post(url=request_url, data=post_data, headers=self.headers)
json_result = json.loads(response.text)
# print(json_result)
if json_result['error_code'] == 0:
# print("人脸表情:", json_result['result']['face_list'][0]['emotion']['type'])
# print("人物年龄:", json_result['result']['face_list'][0]['age'])
return json_result['result']['face_list'][0]['beauty']
# print("人物性别:", json_result['result']['face_list'][0]['gender']['type'])
# print("人物种族:", json_result['result']['face_list'][0]['race']['type'])
# print("人物特征点位置:", json_result['result']['face_list'][0]['landmark72'])
else:
print(json_result['error_code'])
print(json_result['error_msg'])
爬虫的流程是,引擎首先将huya爬虫中的类变量https://www.huya.com/g/2168作为开始地址放入调度器;调度器再将这个网址交给下载器;下载器下载网页源码后,作为response参数交给huya爬虫,随后爬虫使用xpath清晰数据,获取这个页面的全部直播间封面网址、直播间名称、直播间网址。随后进入列表遍历,在yield执行时,huyaSpiderItem被保存,并作为item参数交给管道;管道中的代码执行完后,huya爬虫返回现场,继续执行for循环,将列表中的下一项装入item中,直到列表最后一项被装入。
项目使用方法
在爬虫项目的文件夹下打开cmd,执行以下代码:
scrapy crawl huya -o huya.csv
# 自动将爬取的结果保存为名为huya.csv的csv文件,位置在爬虫项目文件夹下。
# huya是爬虫的名字,不是整个项目的,一个项目可以有多个爬虫。
# huya.csv的名字可以随便定义。
注意事项
yield
yield的作用和return很像,使用了yield的函数称为generator(生成器)。但与return不同的是,yield的在返回变量后,会保存现场,下次执行函数时,函数中的局部变量的值不会归零。示例代码如下:
def test():
a = 0
yield a = a + 1
执行:
test()
test()
结果如下:
1
2
导入类
在可以看到,在爬虫文件huya.py中引入了item类,但是在书写 from 文件夹.文件名 import 类名 代码时,却提示无法找到模块。解决办法是右键选择items所在的文件夹,在Mark Diretory as菜单项中选则将项目设置为Source Root。如果还不行,就将再上一级的文件夹设为Source Root。
如果依旧无法解决,依次打开File->Settings->Build, Execution, Deployment->Console->Python Console->Configure Interpreters勾选Add source roots to PYTHONPATH。
xpath语法
表达式 | 描述 |
---|---|
nodename | 选取此节点的所有子节点。 |
/ | 从根节点选取。 |
// | 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。 |
. | 选取当前节点。 |
.. | 选取当前节点的父节点。 |
@ | 选取属性。 |
html示例:
<?xml version="1.0" encoding="ISO-8859-1"?>
<bookstore>
<book id = "1">
<title lang="eng">Harry Potter</title>
<price>29.99</price>
</book>
<book class = "2">
<title lang="eng">Learning XML</title>
<price>39.95</price>
</book>
</bookstore>
xpath示例:
路径表达式 | 结果 |
---|---|
bookstore | 选取 bookstore 元素的所有子节点。 |
/bookstore | 选取根元素 注释:假如路径起始于正斜杠( / ),则此路径始终代表到某元素的绝对路径! |
bookstore/book | 选取属于 bookstore 的子元素的所有 book 元素。 |
//book | 选取所有名为book的子元素,而不管它们在文档中的位置。 |
bookstore//book | 选择属于 bookstore 元素的后代的所有 book 元素,而不管它们位于bookstore之下的什么位置。 |
//@lang | 选取名为 lang 的所有属性。 |
//book[@id=“1”] | 选取所有名为book,id为1的元素 |
//book[@class=“1”] | 选取所有名为book,class为1的元素 |
//book[@id=“1”]/price/@text | 选取所有名为book,id为1的元素下的price元素中的文本。 |