06 scrapy框架
Scrapy是纯Python开发的一个高效,结构化的网页抓取框架;
Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。 其最初是为了页面抓取 (更确切来说, 网络抓取 )所设计的,也可以应用在获取API所返回的数据(例如 Amazon Associates Web Services ) 或者通用的网络爬虫。 Scrapy用途广泛,可以用于数据挖掘、监测和自动化测试 Scrapy使用了Twisted 异步网络库来处理网络通讯。
一、简单使用
1、新建项目
语法格式:scrapy startproject
该命令会在project_dir文件加下创建一个名为project_name的Scrapy新项目。如果project_dir没有指定,project_dir与project_name相同。
进入你想要存放项目的目录下运行一下命令:
scrapy startproject tz_spider
该命令会在当前目录创建包含一下文件的名为tz_spider的目录。
scrapy startproject mingyan
2、编写爬虫
创建一个TzcSpider的类,它必须继承scrapy.Spider类,需要定义一下三个属性:
name: spider的名字,必须且唯一
start_urls: 初始的url列表
parse(self, response) 方法:每个初始url完成之后被调用。这个函数要完成一下两个功能:
解析响应,封装成item对象并返回这个对象
提取新的需要下载的url,创建新的request,并返回它
我们也可以通过命令创建爬虫
语法格式:scrapy genspider [-t template]
运行命令:scrapy genspider tzc www.shiguangkey.com
会在spiders文件下生成tzc.py文件,文件内容如下:
# -*- coding: utf-8 -*-
import scrapy
class TzcSpider(scrapy.Spider):
name = 'tzc'
# allowed_domains = ['www.']
start_urls = ['http://www.shiguangkey.com/']
def parse(self, response):
pass
3、文件分析
1)scrapy.cfg: 项目的配置文件,现在可以先忽略。
2)tanzhou_spider.py: 该项目的python模块
3)items.py: 项目中的item文件。
Item 是保存爬取到的数据的容器;其使用方法和python字典类似, 并且提供了额外保护机制来避免拼写错误导致的未定义字段错误。
4)pipelines.py: 项目中的pipelines文件。
Scrapy提供了pipeline模块来执行保存数据的操作。在创建的 Scrapy 项目中自动创建了一个 pipeline.py 文件,同时创建了一个默认的 Pipeline 类。比如我们要把item提取的数据保存到mysql数据库 。
5)settings.py: 项目的设置文件。
settings.py是Scrapy中比较重要的配置文件,里面可以设置的内容非常之多。比如我们在前面提到的在pipelines.py中编写了把数据保存到mysql数据的class。
6)middlewares.py:中间件。
4、提取数据
css选择器、xpath选择器、正则
1)css选择器
# 提取a标签
response.css("a")
# 提取a标签的href属性的值
response.css("a::attr(href)")
# 提取a标签的文档内容
response.css("a::text")
CSS 高级用法:
CSS选择器用于选择你想要的元素的样式的模式。"CSS"列表示在CSS版本的属性定义(CSS1,CSS2,或对CSS3)。
选择器 示例 示例说明 CSS
.class .intro 选择所有class="intro"的元素 1
#id #firstname 选择所有id="firstname"的元素 1
* * 选择所有元素 2
element p 选择所有元素 1
element,element div,p 选择所有
元素和元素 1
element element div p 选择
元素内的所有元素 1
element>element div>p 选择所有父级是
元素的 元素 2
element+element div+p 选择所有紧接着
元素之后的元素 2
[attribute] [target] 选择所有带有target属性元素 2
[attribute=value] [target=-blank] 选择所有使用target="-blank"的元素 2
[attribute~=value] [title~=flower] 选择标题属性包含单词"flower"的所有元素 2
[attribute|=language] [lang|=en] 选择一个lang属性的起始值="EN"的所有元素 2
:link a:link 选择所有未访问链接 1
:visited a:visited 选择所有访问过的链接 1
:active a:active 选择活动链接 1
:hover a:hover 选择鼠标在链接上面时 1
:focus input:focus 选择具有焦点的输入元素 2
:first-letter p:first-letter 选择每一个
元素的第一个字母 1
:first-line p:first-line 选择每一个
元素的第一行 1
:first-child p:first-child 指定只有当
元素是其父级的第一个子级的样式。 2
:before p:before 在每个
元素之前插入内容 2
:after p:after 在每个
元素之后插入内容 2
:lang(language) p:lang(it) 选择一个lang属性的起始值="it"的所有
元素 2
element1~element2 p~ul 选择p元素之后的每一个ul元素 3
[attribute^=value] a[src^="https"] 选择每一个src属性的值以"https"开头的元素 3
[attribute$=value] a[src$=".pdf"] 选择每一个src属性的值以".pdf"结尾的元素 3
[attribute*=value] a[src*="runoob"] 选择每一个src属性的值包含子字符串"runoob"的元素 3
:first-of-type p:first-of-type 选择每个p元素是其父级的第一个p元素 3
:last-of-type p:last-of-type 选择每个p元素是其父级的最后一个p元素 3
:only-of-type p:only-of-type 选择每个p元素是其父级的唯一p元素 3
:only-child p:only-child 选择每个p元素是其父级的唯一子元素 3
:nth-child(n) p:nth-child(2) 选择每个p元素是其父级的第二个子元素 3
:nth-last-child(n) p:nth-last-child(2) 选择每个p元素的是其父级的第二个子元素,从最后一个子项计数 3
:nth-of-type(n) p:nth-of-type(2) 选择每个p元素是其父级的第二个p元素 3
:nth-last-of-type(n) p:nth-last-of-type(2) 选择每个p元素的是其父级的第二个p元素,从最后一个子项计数 3
:last-child p:last-child 选择每个p元素是其父级的最后一个子级。 3
:root :root 选择文档的根元素 3
:empty p:empty 选择每个没有任何子级的p元素(包括文本节点) 3
:target #news:target 选择当前活动的#news元素(包含该锚名称的点击的URL) 3
:enabled input:enabled 选择每一个已启用的输入元素 3
:disabled input:disabled 选择每一个禁用的输入元素 3
:checked input:checked 选择每个选中的输入元素 3
:not(selector) :not(p) 选择每个并非p元素的元素 3
::selection ::selection 匹配元素中被用户选中或处于高亮状态的部分 3
:out-of-range :out-of-range 匹配值在指定区间之外的input元素 3
:in-range :in-range 匹配值在指定区间之内的input元素 3
:read-write :read-write 用于匹配可读及可写的元素 3
:read-only :read-only 用于匹配设置 "readonly"(只读) 属性的元素 3
:optional :optional 用于匹配可选的输入元素 3
:required :required 用于匹配设置了 "required" 属性的元素 3
:valid :valid 用于匹配输入值为合法的元素 3
:invalid :invalid 用于匹配输入值为非法的元素 3
2)xpath选择器
提取a标签
response.xpath('//a')
提取class="inp"的a标签的href内容
response.xpath('//a[@class="inp"]/@href')
提取class="inp"的a标签的文本内容
response.xpath('//a[@class="inp"]/text()')
路径表达式 结果
/bookstore/book[1] 选取属于 bookstore 子元素的第一个 book 元素。
/bookstore/book[last()] 选取属于 bookstore 子元素的最后一个 book 元素。
/bookstore/book[last()-1] 选取属于 bookstore 子元素的倒数第二个 book 元素。
/bookstore/book[position()<3] 选取最前面的两个属于 bookstore 元素的子元素的 book 元素。
//title[@lang] 选取所有拥有名为 lang 的属性的 title 元素。
//title[@lang='eng'] 选取所有 title 元素,且这些元素拥有值为 eng 的 lang 属性。
/bookstore/book[price>35.00] 选取 bookstore 元素的所有 book 元素,且其中的 price 元素的值须大于 35.00。
/bookstore/book[price>35.00]/title 选取 bookstore 元素中的 book 元素的所有 title 元素,且其中的 price 元素的值须大于 35.00。
路径表达式 结果
bookstore 选取 bookstore 元素的所有子节点。
/bookstore
选取根元素 bookstore。
注释:假如路径起始于正斜杠( / ),则此路径始终代表到某元素的绝对路径!
bookstore/book 选取属于 bookstore 的子元素的所有 book 元素。
//book 选取所有 book 子元素,而不管它们在文档中的位置。
bookstore//book 选择属于 bookstore 元素的后代的所有 book 元素,而不管它们位于 bookstore 之下的什么位置。
//@lang 选取名为 lang 的所有属性。
XPath 通配符可用来选取未知的 HTML元素。
通配符 描述
- 匹配任何元素节点。
-
匹配任何属性节点。
de() 匹配任何类型的节点。
在下面的表格中,我们列出了一些路径表达式,以及这些表达式的结果:
表达式 结果
ookstore/* 选取 bookstore 元素的所有子元素。
- 选取文档中的所有元素。
itle[@*] 选取所有带有属性的 title 元素。
3)正则
response.css("a").re('href="(.*?)"')
4)extract()、extract_first()
# 将符合要求的数据提取到列表中
response.css("a").re('href="(.*?)"').extract()
# 将符合要求的数据的第一条提取出来
response.css("a").re('href="(.*?)"').extract()
5、运行爬虫
首先进入项目根目录,然后运行命令:scrapy crawl tzc就可以启动爬虫了。 这个命令会启动我们刚才创建的那个名为tzc的爬虫,你会在屏幕上得到类似左图的输出。
scrapy crawl tzc
# 无日志运行
scrapy crawl tzc --nolog
# 查看本工程中有哪些spider
scrapy list
6、追踪链接
#爬取多页数据,返回一个scrapy.Request对象
import scrapy
class TzcSpider(scrapy.Spider):
name = 'tzc'
# allowed_domains = ['www.']
start_urls = ['https://ke.qq.com/course/list?mt=1001/']
def parse(self, response):
url = response.url
next_url = response.xpath('//a[@class="page-next-btn icon-font i-v-right"]/@href').extract_first()
# 返回一个request请求
return scrapy.Request(next_url,callback=self.parse)
# -*- coding: utf-8 -*-
import scrapy
class TanzhouSpiderSpider(scrapy.Spider):
name = 'tanzhou_spider'
# allowed_domains = ['www']
start_urls = ['https://www.qiushibaike.com/hot']
def parse(self, response):
next_url = response.xpath('//ul[@class="pagination"]/li/a/span[text()="\n下一页\n"]').xpath('../@href').extract_first()
detial_urls = response.xpath('//div[@id="content-left"]/div/a/@href').extract()
if detial_urls:
detial_urls = list(set(detial_urls))
for detial_url in detial_urls:
detial_url = 'https://www.qiushibaike.com' + detial_url
print(detial_url)
yield scrapy.Request(detial_url, callback=self.detial_parse)
if next_url:
next_url = 'https://www.qiushibaike.com'+ next_url
print(next_url)
return scrapy.Request(next_url, callback=self.parse)
def detial_parse(self,response):
print(response.url)
7、定义管道
parse函数在解析出我们需要的信息之后,可以将这些信息打包成一个字典对象或scray.Item对象(一般都是item对象,下面我们再讲),然后返回。这个对象会被发送到item管道,该管道会通过顺序执行几个组件处理它。每个item管道组件是一个实现简单方法的Python类。他们收到一个item并对其执行操作,同时决定该item是否应该继续通过管道或者被丢弃并且不再处理。
item管道的典型用途是:
清理HTML数据
验证已删除的数据(检查项目是否包含某些字段)
检查重复项(并删除它们)
将已爬取的item进行数据持久化
items.py
import scrapy
class MyspiderItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
url = scrapy.Field()
next_url = scrapy.Field()
pass
pipelines.py
# 编写管道类
import json
class MyspiderPipeline(object):
def open_spider(self,spider):
self.f = open("course.txt","w",encoding="utf-8")
# pass
def process_item(self, item, spider):
self.f.write(json.dumps(dict(item),ensure_ascii=False))
self.f.write("\n")
return item
def close_spider(self,spider):
self.f.close()
# pass
激活管道组件 settings.py,类后面的数字代表优先等级
ITEM_PIPELINES = {
'MySpider.pipelines.MyspiderPipeline': 300,
}
# 君子协议
# Obey robots.txt rules
ROBOTSTXT_OBEY = False
8、数据写入文件、数据库
class SomethingPipeline(object):
def __init__(self):
# 可选实现,做参数初始化等
# doing something
def process_item(self, item, spider):
# item (Item 对象) – 被爬取的item
# spider (Spider 对象) – 爬取该item的spider
# 这个方法必须实现,每个item pipeline组件都需要调用该方法,
# 这个方法必须返回一个 Item 对象,被丢弃的item将不会被之后的pipeline组件所处理。
return item
def open_spider(self, spider):
# spider (Spider 对象) – 被开启的spider
# 可选实现,当spider被开启时,这个方法被调用。
def close_spider(self, spider):
# spider (Spider 对象) – 被关闭的spider
# 可选实现,当spider被关闭时,这个方法被调用
写入文件
import json
class ItcastJsonPipeline(object):
def __init__(self):
self.file = open('teacher.json', 'wb')
def process_item(self, item, spider):
content = json.dumps(dict(item), ensure_ascii=False) + "\n"
self.file.write(content)
return item
def close_spider(self, spider):
self.file.close()
写入mysql
# 将item写入数据库
# 小数据可以使用同步写入
import pymysql
class MysqlPipeine(object):
def __init__(self):
self.db_config = {
'user': 'aliyun',
'password': '123456',
'db': 'class37',
'charset': 'utf8',
}
# 建立连接
self.conn = pymysql.connect(**self.db_config)
# 创建游标
self.cursor = self.conn.cursor()
# 处理item的函数
def process_item(self,item,spider):
# 准备sql语句
sql = 'insert into student value(item)'
self.cursor.execute(sql)
self.conn.commit()
# 关闭数据库连接
def close_spider(self,spider):
self.cursor.close()
self.conn.close()
二、运行流程
上图显示了Scrapy框架的体系结构及其组件,以及系统内部发生的数据流(由红色的箭头显示。)
Scrapy中的数据流由执行引擎控制,流程如下:
1、运行流程
首先从爬虫获取初始的请求
将请求放入调度模块,然后获取下一个需要爬取的请求
调度模块返回下一个需要爬取的请求给引擎
引擎将请求发送给下载器,依次穿过所有的下载中间件
一旦页面下载完成,下载器会返回一个响应包含了页面数据,然后再依次穿过所有的下载中间件。
引擎从下载器接收到响应,然后发送给爬虫进行解析,依次穿过所有的爬虫中间件
爬虫处理接收到的响应,然后解析出item和生成新的请求,并发送给引擎
引擎将已经处理好的item发送给管道组件,将生成好的新的请求发送给调度模块,并请求下一个请求
该过程重复,直到调度程序不再有请求为止。
2、组件介绍
Scrapy引擎
引擎负责控制系统所有组件之间的数据流,并在发生某些操作时触发事件。
调度程序
调度程序接收来自引擎的请求,将它们排入队列,以便稍后引擎请求它们。
下载器
下载程序负责获取web页面并将它们提供给引擎,引擎再将它们提供给spider。
爬虫
爬虫是由用户编写的自定义的类,用于解析响应,从中提取数据,或其他要抓取的请求。
item管道
管道负责在数据被爬虫提取后进行后续处理。典型的任务包括清理,验证和持久性(如将数据存储在数据库中)框架。它使用非阻塞(也成为异步)代码实现并发。
下载中间件
下载中间件是位于引擎和下载器之间的特定的钩子,它们处理从引擎传递到下载器的请求,以及下载器传递到引擎的响应。
如果你要执行以下操作之一,请使用Downloader中间件:
在请求发送到下载程序之前处理请求(即在scrapy将请求发送到网站之前)
在响应发送给爬虫之前
直接发送新的请求,而不是将收到的响应传递给蜘蛛
将响应传递给爬行器而不获取web页面;
默默的放弃一些请求
爬虫中间件
爬虫中间件是位于引擎和爬虫之间的特定的钩子,能够处理传入的响应和传递出去的item和请求。
如果你需要以下操作请使用爬虫中间件:
处理爬虫回调之后的 请求或item
处理start_requests
处理爬虫异常
根据响应内容调用errback而不是回调请求
事件驱动的网络
scrapy 是用Twisted编写的,Twisted是一个流行的事件驱动的Python网络框架。它使用非阻塞(也成为异步)代码实现并发。
3、编写流程
scrapy爬虫编写流程
1、创建爬虫项目
scrapy startproject mingyan
2、创建spider文件
scrapy genspider tzc_spider www.shiguangkey.com
3、编写items文件
知道需要爬取的数据
4、解析数据
提取Request或者items
5、保存数据
pipelines.py中保存数据
settings.py 激活管道
三、媒体管道
1、媒体管道特性
媒体管道都实现了以下特性:
避免重新下载最近下载的媒体
指定存储位置(文件系统目录,Amazon S3 bucket,谷歌云存储bucket)
图像管道具有一些额外的图像处理功能:
将所有下载的图片转换为通用格式(JPG)和模式(RGB)
生成缩略图
检查图像的宽度/高度,进行最小尺寸过滤
2、工作流程
它的工作流是这样的:
1.在爬虫中,您可以返回一个item,并将所需的url放入file_urls字段。
2.item从爬虫返回并进入item管道。
3.当item到达文件管道时,file_urls字段中的url将使用标准的Scrapy调度器和下载程序(这意味着将重用调度器和下载程序中间件)计划下载, 但是具有更高的优先级,在其他页面被爬取之前处理它们。在文件下载完成(或由于某种原因失败)之前,该项在特定管道阶段保持“锁定”状态。
4.下载文件后,将使用另一个字段(files)填充results。这个字段将包含一个包含有关下载文件信息的dicts列表,例如下载的路径、原始的剪贴url(从file_urls字段中获得)和文件校验和。文件字段列表中的文件将保持原来file_urls字段的顺序。如果某些文件下载失败,将记录一个错误,文件将不会出现在files字段中。
3、API
# 在媒体管道中,我们可以重写的方法:
# get_media_requests(item, info) 根据item中的file_urls/image_urls生成请求
def get_media_requests(self, item, info):
for file_url in item['file_urls']:
yield scrapy.Request(file_url)
# item_completed(requests, item, info) 当item里的 所有媒体文件请求完成调用
from scrapy.exceptions import DropItem
def item_completed(self, results, item, info):
file_paths = [x['path'] for ok, x in results if ok]
if not file_paths:
raise DropItem("Item contains no files")
item['file_paths'] = file_paths
return item
# file_path 可以变保存文件的路径
def file_path(self, request, response=None, info=None):
# 提取url前面名称作为图片名。
image_guid = request.url.split('/')[-1]
# 接收上面meta传递过来的图片名称
name = request.meta['name']
# 分文件夹存储的关键:{0}对应着name;{1}对应着image_guid
filename = u'{0}/{1}'.format(name, image_guid)
return filename
4、媒体管道的设置
ITEM_PIPELINES = {'scrapy.pipelines.images.ImagesPipeline': 1} 启用
FILES_STORE = '/path/to/valid/dir' 文件管道存放位置
IMAGES_STORE = '/path/to/valid/dir' 图片管道存放位置
FILES_URLS_FIELD = 'field_name_for_your_files_urls' 自定义文件url字段
FILES_RESULT_FIELD = 'field_name_for_your_processed_files' 自定义结果字段
IMAGES_URLS_FIELD = 'field_name_for_your_images_urls' 自定义图片url字段
IMAGES_RESULT_FIELD = 'field_name_for_your_processed_images' 结果字段
FILES_EXPIRES = 90 文件过期时间 默认90天
IMAGES_EXPIRES = 90 图片过期时间 默认90天
IMAGES_THUMBS = {'small': (50, 50), 'big':(270, 270)} 缩略图尺寸
IMAGES_MIN_HEIGHT = 110 过滤最小高度
IMAGES_MIN_WIDTH = 110 过滤最小宽度
MEDIA_ALLOW_REDIRECTS = True 是否重定向
5、自定义图片管道
pipelines.py
from scrapy.pipelines.images import ImagesPipeline
from scrapy import Request
# 继承 ImagesPipeline
class DownloadimagePipeline(ImagesPipeline):
# 需要注释掉process_item,否则不能执行get_media_requests()
# def process_item(self, item, spider):
# print(item,'=================')
# return item
def get_media_requests(self, item, info):
# 循环每一张图片地址下载,若传过来的不是集合则无需循环直接yield
for image_url in item['img_urls']:
# 将 img_names值通过meta传到后面
yield Request(image_url, meta={'name': item['img_names']})
def file_path(self, request, response=None, info=None):
# 提取url前面名称作为图片名。
image_guid = request.url.split('/')[-1]
# 接收上面meta传递过来的图片名称
name = request.meta['name']
# 分文件夹存储的关键:{0}对应着name;{1}对应着image_guid
filename = u'{0}/{1}'.format(name, image_guid)
return filename
settings.py
# Configure item pipelines
# See https://doc.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
'DownloadImage.pipelines.DownloadimagePipeline': 300,
}
import os
#配置保存本地的地址
project_dir=os.path.abspath(os.path.dirname(__file__)) #获取当前爬虫项目的绝对路径
IMAGES_STORE=os.path.join(project_dir,'images') #组装新的图片路径
# IMAGES_STORE = 'D:\ImageSpider'
6、自定义文件管道
pipelines.py
import scrapy
from scrapy.pipelines.files import FilesPipeline
class Mp3Pipeline(FilesPipeline):
'''自定义管道'''
def get_media_requests(self, item, info):
# 循环每一首歌地址下载,若传过来的不是集合则无需循环直接yield
for music,name in zip(item['music_urls'],item['music_names']):
# 将 img_names值通过meta传到后面
yield scrapy.Request(music, meta={'title': item['music_title'],'name':name})
def file_path(self, request, response=None, info=None):
title = request.meta['title']
name = request.meta['name']
filename = u'{0}/{1}.mp3'.format(title, name)
return filename
settings.py
# Configure item pipelines
# See https://doc.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
# 'LuoWang.pipelines.LuowangPipeline': 300,
'LuoWang.pipelines.Mp3Pipeline': 300,
}
import os
#配置保存本地的地址
project_dir=os.path.abspath(os.path.dirname(__file__)) #获取当前爬虫项目的绝对路径
FILES_STORE=os.path.join(project_dir,'落网音乐') #组装新的文件路径
# FILES_STORE = 'D:/luowang/'
四、Scrapy shell
1、启动 Scrapy shell
scrapy shell [option][url|file]
命令行输入:
scrapy shell http://httpbin.org/get
2、设置 shell
Scrapy 的shell是基于运行环境中的python 解释器shell
本质上就是通过命令调用shell,并在启动的时候预定义需要使用的对象
scrapy允许通过在项目配置文件”scrapy.cfg”中进行配置来指定解释器shell
# 例如:
[settings]
shell = ipython
[settings]
default = login_spider.settings
shell = ipython
[deploy]
#url = http://localhost:6800/
project = login_spider
3、shell的使用
Scrapy shell 本质上就是个普通的python shell
只不过提供了一些需要使用的对象,快捷方法便于我们调试。
快捷方法:
shelp() # 打印可用的对象和方法
fetch(url[,redirect=True]) # 爬取新的 URL 并更新相关对象
fetch(request) # 通过 request 爬取,并更新相关对象
view(response) # 使用本地浏览器打开爬取的页面
scrapy 对象:
crawler # Crawler 对象
spider # 爬取使用的 spider
request # 请求
response # 响应
settings # 设置
实例
$ scrapy shell 'http://scrapy.org' --nolog
[s] Available Scrapy objects:
[s] scrapy scrapy module (contains scrapy.Request, scrapy.Selector, etc)
[s] crawler
[s] item {}
[s] request
[s] response <200 https://scrapy.org/>
[s] settings
[s] spider
[s] Useful shortcuts:
[s] fetch(url[, redirect=True]) Fetch URL and update local objects (by default, redirects are followed)
[s] fetch(req) Fetch a scrapy.Request and update local objects
[s] shelp() Shell help (print this help)
[s] view(response) View response in a browser
In [1]: response.xpath('//title/text()').extract_first()
Out[1]: 'Scrapy | A Fast and Powerful Scraping and Web Crawling Framework'
In [2]: fetch("http://reddit.com")
In [3]: response.xpath('//title/text()').extract()
Out[3]: ['reddit: the front page of the internet']
In [4]: request = request.replace(method="POST")
In [5]: fetch(request)
In [6]: response.status
Out[6]: 404
4、在 spider 内调用 shell
使用 scrapy.shell.inspect_response
函数:
import scrapy
class MySpider(scrapy.Spider):
name = "myspider"
start_urls = [
"http://example.com",
"http://example.org",
"http://example.net",
]
def parse(self, response):
# We want to inspect one specific response.
if ".org" in response.url:
from scrapy.shell import inspect_response
inspect_response(response, self)
# Rest of parsing code.
启动爬虫,将会在执行到inspect_response
时进入 shell,当处使用完使用Ctrl-D
退出 shell,爬虫会恢复运行。
五、scrapy.Spider
1、运行流程
首先生成初始请求以爬取第一个URL,并指定要使用从这些请求下载的响应调用的回调函数。
在回调函数中,解析响应(网页)并返回,Item对象, Request对象或这些对象的可迭代的dicts。
最后,从蜘蛛返回的项目通常会持久保存到数据库(在某些项目管道中)或使用Feed导出写入文件。
在回调函数中,通常使用选择器解析页面内容 (但您也可以使用BeautifulSoup,lxml或您喜欢的任何机制)并使用解析的数据生成item。
2、详解
1)name
spider的名称
一个字符串,用于定义此蜘蛛的名称。蜘蛛名称是Scrapy如何定位(并实例化)蜘蛛,因此它必须是唯一的。这是最重要的蜘蛛属性,它是必需的
2)start_urls
起始urls
蜘蛛将开始爬取的URL列表。因此,下载的第一页将是此处列出的页面。后续Request将从起始URL中包含的数据连续生成。
3)customer_settings
customer_settings 自定义设置
运行此蜘蛛时将覆盖项目范围的设置。必须将其定义为类属性,因为在实例化之前更新了设置。
4)logger 日志
使用Spider创建的Python日志器。您可以使用它来发送日志消息。
5)from_crawler
创建spider的类方法
这是Scrapy用于创建spider的类方法。一般不用覆盖。
6)start_requests
开始请求
此方法必须返回一个iterable,其中包含第一个要爬网的请求。它只会被调用一次
7) parse(response)
默认回调函数
这是Scrapy在其请求未指定回调时处理下载的响应时使用的默认回调
8) close
关闭spider
spider关闭时调用
六、CrawlSpider
1、创建CrawlSpider
scrapy genspider -t crawl hr.tencent hr.tencent.com
url 就是你想要爬取的网址
注意:分析本地文件是一定要带上路径,scrapy shell默认当作url
scrapy genspider -t crawl mycrawlspider hr.tencent.com
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
class MycrawlspiderSpider(CrawlSpider):
name = 'mycrawlspider'
allowed_domains = ['www']
start_urls = ['http://www/']
rules = (
Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True),
)
def parse_item(self, response):
item = {}
#item['domain_id'] = response.xpath('//input[@id="sid"]/@value').get()
#item['name'] = response.xpath('//div[@id="name"]').get()
#item['description'] = response.xpath('//div[@id="description"]').get()
return item
2、Rule
Rule用来定义CrawlSpider的爬取规则
参数:
link_extractor Link Extractor对象,它定义如何从每个已爬网页面中提取链接。
callback 回调函数
cb_kwargs 是一个包含要传递给回调函数的关键字参数的dict
follow 它指定是否应该从使用此规则提取的每个响应中跟踪链接。
process_links 用于过滤连接的回调函数
process_request 用于过滤请求的额回调函数
3、LinkExtractor
LinkExractor也是scrapy框架定义的一个类
它唯一的目的是从web页面中提取最终将被跟踪的额连接。
我们也可定义我们自己的链接提取器,只需要提供一个名为
extract_links的方法,它接收Response对象
并返回scrapy.link.Link对象列表。
七、CrawlSpider url去重
八、Scrapy url去重
九、Request
1、Scrapy.http.Request
Scrapy.http.Request类是scrapy框架中request的基类。它的参数如下:
url(字符串) - 此请求的URL
callback(callable)- 回调函数
method(string) - 此请求的HTTP方法。默认为'GET'。
meta(dict) - Request.meta属性的初始值。
body(str 或unicode) - 请求体。如果没有传参,默认为空字符串。
headers(dict) - 此请求的请求头。
cookies - 请求cookie。
encoding(字符串) - 此请求的编码(默认为'utf-8')此编码将用于对URL进行百分比编码并将body转换为str(如果给定unicode)。
priority(int) - 此请求的优先级(默认为0)。
dont_filter(boolean) - 表示调度程序不应过滤此请求。
errback(callable) - 在处理请求时引发任何异常时将调用的函数。
flags(list) - 发送给请求的标志,可用于日志记录或类似目的。
2、属性和方法
url 包含此请求的URL的字符串。该属性是只读的。更改请求使用的URL replace()。
method 表示请求中的HTTP方法的字符串。
headers 类似字典的对象,包含请求头。
body 包含请求正文的str。该属性是只读的。更改请求使用的URL replace()。
meta 包含此请求的任意元数据的字典。
copy() 返回一个新的请求,改请求是此请求的副本。
replace([ URL,method,headers,body,cookies,meta,encoding,dont_filter,callback,errback] ) 返回一个更新对的request
3、利用request.meta传递参数
4、FormRequest
get请求和post请求是最常见的请求。scrapy框架内置了一个FormRequest类
它扩展了基类Request,具有处理HTML表单的功能。
它使用lxml.html表单,来预先填充表单字段,其中包含来自Response对象的表单数据。
5、from_response()
参数:
response(Responseobject) - 包含HTML表单的响应
formname(string) - 如果给定,将使用name属性设置为此值的表单。
formid(string) - 如果给定,将使用id属性设置为此值的表单。
formxpath(string) - 如果给定,将使用与xpath匹配的第一个表单。
formcss(string) - 如果给定,将使用与css选择器匹配的第一个表单。
formnumber(整数) - 当响应包含多个表单时要使用的表单数。
formdata(dict) - 要在表单数据中覆盖的字段。。
clickdata(dict) - 用于查找单击控件的属性。
dont_click(boolean) - 如果为True,将提交表单数据而不单击任何元素。
十、Response
参数:
url(字符串) - 此响应的URL
status(整数) - 响应的HTTP状态。默认为200。
headers(dict) - 此响应的响应头。dict值可以是字符串(对于单值标头)或列表(对于多值标头)。
body(字节) - 响应主体。要将解码后的文本作为str(Python 2中的unicode)访问,您可以使用response.text来自编码感知的 Response子类,例如TextResponse。
flags(列表) - 是包含Response.flags属性初始值的列表 。如果给定,列表将被浅层复制。
request(Requestobject) - Response.request属性的初始值。这表示Request生成此响应的内容。
属性和方法:
url
status
headers
body
request
meta
flags
copy()
replace([ url,status,headers,body,request,flags,cls ] )
urljoin(url )
follow(url)
十一、日志配置和使用
Scrapy logger 在每个Spider实例中提供了一个可以访问和使用的实例,使用方法,见下图
当然可以通过python的logging来记录。
比如:logging.warning('This is a warning!')
但是为了后期维护方面,我们可以创建不同的记录器来封装消息。
并且使用组件或函数的名称进行命名,见下图案例:
这些设置可用于配置日志记录:
LOG_FILE 日志输出文件,如果为None,就打印在控制台
LOG_ENABLED 是否启用日志,默认True
LOG_ENCODING 日期编码,默认utf-8
LOG_LEVEL 日志等级,默认debug
LOG_FORMAT 日志格式
LOG_DATEFORMAT 日志日期格式
LOG_STDOUT 日志标准输出,默认False,如果True所有标准输出都将写入日志中
LOG_SHORT_NAMES 短日志名,默认为False,如果True将不输出组件名
项目中一般设置:
LOG_FILE = 'logfile_name'
LOG_LEVEL = 'INFO'
十二、模拟登陆
分析
捕捉获取登陆时需要上传的form,认为构造formdata,使用FormRequest请求
# -*- coding: utf-8 -*-
import scrapy
class MyLoginSpiderSpider(scrapy.Spider):
name = 'my_login_spider'
# allowed_domains = ['www']
start_urls = ['http://example.webscraping.com/places/default/user/login']
def parse(self, response):
data = {
'email':'[email protected]',
'password':'12345678'
}
data['_next'] = response.xpath('//input[@name="_next"]/@value').extract_first()
data['_formkey'] = response.xpath('//input[@name="_formkey"]/@value').extract_first()
data['_formname'] = response.xpath('//input[@name="_formname"]/@value').extract_first()
# print(data)
# return scrapy.Request(
url='http://example.webscraping.com/places/default/user/login',
method='POST',
meta=data,
callback=self.detail_parse)
yield scrapy.FormRequest(
url='http://example.webscraping.com/places/default/user/login',
formdata=data,
callback=self.detail_parse)
# pass
def detail_parse(self,response):
# print(response.body)
login_msg = response.xpath('//div[@class="flash"]/text()').extract_first()
if login_msg == 'Logged in':
print('模拟登陆成功')
else:
print('模拟登陆失败')
# pass
十三、下载中间件
下载中间件是一个用来hooks进Scrapy的request/response处理过程的框架。
它是一个轻量级的底层系统,用来全局修改scrapy的request和response。
scrapy框架中的下载中间件,是实现了特殊方法的类。
scrapy系统自带的中间件被放在DOWNLOADER_MIDDLEWARES_BASE设置中
用户自定义的中间件需要在DOWNLOADER_MIDDLEWARES中进行设置
改设置是一个dict,键是中间件类路径,期值是中间件的顺序,是一个正整数0-1000.越小越靠近引擎。
1、API
process_request(request,spider) 处理请求,对于通过中间件的每个请求调用此方法 请求前
返回None---scrapy 直接返回这个Request
返回Response---scrapy 直接返回这个Response
返回Request ---scrapy 重新调度返回的Request请求
返回异常 ---scrapy 调用 process_exception
process_response(request, response, spider) 处理响应,对于通过中间件的每个响应,调用此方法 请求后
process_exception(request, exception, spider) 处理请求时发生了异常调用
from_crawler(cls,crawler )
2、内置下载中间件
常用内置中间件:
CookieMiddleware 支持cookie,通过设置COOKIES_ENABLED 来开启和关闭
HttpProxyMiddleware HTTP代理,通过设置request.meta['proxy']的值来设置
UserAgentMiddleware 与用户代理中间件。
3、下载中间件的使用
用户代理池
# 定义代理池
USER_AGENT =[
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3641.400 QQBrowser/10.4.3284.400",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3641.400 QQBrowser/10.4.3284.400",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3641.400 QQBrowser/10.4.3284.400",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3641.400 QQBrowser/10.4.3284.400"
]
import random
class UserAgentMiddleware(object):
# 用户代理中间件
def process_request(self,request,spider):
'''
对request添加cookies信息
:param request:
:param spider:
:return:
'''
# 在USER_AGENT中随机一个代理
request.headers['User-Agent'] = random.choice(USER_AGENT)
print(request.headers,'=================')
def process_response(self,request,response,spider):
if response.status ==403:
print('请求失败...')
return request
return response
十四、scrapy 常用设置
1、设置优先级
命令行选项(优先级最高)
设置per-spider
项目设置模块
各命令默认设置
默认全局设置(低优先级)
如下:
# 项目名
BOT_NAME = 'Baidu'
# 项目路径
SPIDER_MODULES = ['Baidu.spiders']
NEWSPIDER_MODULE = 'Baidu.spiders'
# 用户代理
# Crawl responsibly by identifying yourself (and your website) on the user-agent
#USER_AGENT = 'Baidu (+http://www.yourdomain.com)'
# 君子协议
# Obey robots.txt rules
ROBOTSTXT_OBEY = False
# 最大并发值
# Configure maximum concurrent requests performed by Scrapy (default: 16)
#CONCURRENT_REQUESTS = 32
# item处理最大并发数,默认100
CONCURRENT_ITEMS
# 下载最大并发数
CONCURRENT_REQUESTS
# 单个域名最大并发数
CONCURRENT_REQUESTS_PER_DOMAIN
# 单个ip最大并发数
CONCURRENT_REQUESTS_PER_IP
# 请求延时
# Configure a delay for requests for the same website (default: 0)
# See https://doc.scrapy.org/en/latest/topics/settings.html#download-delay
# See also autothrottle settings and docs
#DOWNLOAD_DELAY = 3
# 禁用COOKIES
# Disable cookies (enabled by default)
#COOKIES_ENABLED = False
# HEADERS请求头
# Override the default request headers:
#DEFAULT_REQUEST_HEADERS = {
# 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
# 'Accept-Language': 'en',
#}
# 下载中间件
# Enable or disable downloader middlewares
# See https://doc.scrapy.org/en/latest/topics/downloader-middleware.html
DOWNLOADER_MIDDLEWARES = {
'Baidu.middlewares.BaiduDownloaderMiddleware': 543,
'Baidu.middlewares.UserAgentMiddleware': 543,
}
# Pipeline 管道
# Configure item pipelines
# See https://doc.scrapy.org/en/latest/topics/item-pipeline.html
#ITEM_PIPELINES = {
# 'Baidu.pipelines.BaiduPipeline': 300,
#}
十五、Scrapy对接Selenium
chrome的webdriver: http://chromedriver.storage.googleapis.com/index.html
Firefox Firefox驱动下载地址为:https://github.com/mozilla/geckodriver/releases/
IE浏览器驱动下载地址为:http://selenium-release.storage.googleapis.com/index.html (不推荐,没人用)
根据操作系统,以及浏览器版本,下载相应的驱动
并且将下载的webdriver的路径设置到环境变量中
1、Selenium的使用
# 导入模块
from selenium import webdriver
import time
# 模拟调用Chrome浏览器
drvier = webdriver.Chrome()
# 访问网站
drvier.get('https://www.baidu.com')
# 延时5s
time.sleep(5)
# 获取js渲染之后的数据
print(drvier.page_source)
2、使用中间件
from selenium import webdriver
import time
import scrapy
class UserAgentMiddleware(object):
def process_request(self, request, spider):
# 打开Chrome浏览器
self.driver = webdriver.Chrome()
# 打开相应的网站
self.driver.get(request.url)
# 延时
time.sleep(5)
# 获取js渲染后的内容
html = self.driver.page_source
# 关闭driver
self.driver.quit()
# 返回Response对象
return scrapy.http.HtmlResponse(url=request.url,body=html,encoding='utf-8',request=request)
十六、Scrapy项目部署
1、scrapyd
Scrapyd是一个运行Scrapy spider的开源应用程序。它提供了一个带有HTTP API的服务器,能够运行和监控Scrapy蜘蛛。要将spider部署到Scrapyd,可以使用由Scrapyd客户端包提供的Scrapyd -deploy工具。
Scrapyd安装
Scrapyd依赖于以下库,但安装过程负责安装缺少的库:
Python2.6以上
Twisted8.0以上
Scrapy0.17以上
如何安装Scrapy取决于您正在使用的平台。通用的方法是从PyPI安装它:
pip install scrapyd
安装之后 通过scrapyd命令启动即可:scrapyd
scrapyd带有一个最小的Web界面,启动后,通过访问http://localhost:6800。如左图:
2、部署流程
命令:scrapyd 启动一个web服务
访问:127.0.0.1:6800 http://192.168.32.129:6800/
在本地不能打开127.0.0.1:6800网页,需要配置端口转发:6800 修改配置文件 0.0.0.0
进入到目录:cd /usr/local/lib/python3.6/dist-packages/scrapyd
进入文件:sudo vim default_scrapyd.conf
修改 bind_address=127.0.0.1 修改为0.0.0.0
第一步:启动scrapyd
第二步:修改scrapy.cfg文件
[settings]
default = DownloadImage.settings
[deploy]
url = http://127.0.0.1:6800/ # scrapyd 服务器地址
project = DownloadImage
第三步:上传 scrapyd-deploy -p
第四步:启动 curl http://localhost:6800/schedule.json -d project= -d spider=
第五步:停止爬虫 curl http://localhost:6800/cancel.json -d project=tutorial -d job=4fc26e4209da11e9b344000c292b839
3、API
上传
scrapyd-deploy -p
例子:scrapyd-deploy -p DownloadImage
启动
curl http://localhost:6800/schedule.json -d project= -d spider=
停止
curl http://localhost:6800/cancel.json -d project= -d job=jobid
删除项目
curl http://localhost:6800/delproject.json -d project=
列出项目
curl http://localhost:6800/listproject.json
列出爬虫
curl http://localhost:6800/listspider.json?project=myproject
官方文档
https://scrapyd.readthedocs.io/en/latest/api.html
scrapyd设置
Scrapyd在以下位置搜索配置文件,并按顺序解析它们,最新的配置文件具有更高的优先级:
/etc/scrapyd/scrapyd.conf (Unix)
c:\scrapyd\scrapyd.conf (Windows)
/etc/scrapyd/conf.d/* (in alphabetical order, Unix)
scrapyd.conf
~/.scrapyd.conf (users home directory)
scrapyd部署
使用官方提供的工具scrapyd 分为服务端和客户端
在测试服务器上开启scrapyd服务
在客户端 通过scrapy-client 上传本地的代码到服务端
4、window下部署
python absolute_dir/scrapyd-deploy -p
十七、scrapy-redis
scrapy-redis是scrapy框架基于redis数据库的组件,用于scrapy项目的分布式开发和部署。
特征:分布式爬取
您可以启动多个spider工程,相互之间共享单个redis的requests队列。最适合广泛的多个域名网站的内容爬取。
分布式数据处理
爬取到的scrapy的item数据可以推入到redis队列中,这意味着你可以根据需求启动尽可能多的处理程序来共享item的队列,进行item数据持久化处理
Scrapy即插即用组件
Scheduler调度器 + Duplication复制 过滤器,Item Pipeline,基本spider
1、request请求共享
首先Slaver端从Master端拿任务(Request、url)进行数据抓取,Slaver抓取数据的同时,产生新任务的Request便提交给 Master 处理;
Master端只有一个Redis数据库,负责将未处理的Request去重和任务分配,将处理后的Request加入待爬队列,并且存储爬取的数据。
Scrapy-Redis默认使用的就是这种策略,我们实现起来很简单,因为任务调度等工作Scrapy-Redis都已经帮我们做好了,我们只需要继承RedisSpider、指定redis_key就行了。
缺点是,Scrapy-Redis调度的任务是Request对象,里面信息量比较大(不仅包含url,还有callback函数、headers等信息),可能导致的结果就是会降低爬虫速度、而且会占用Redis大量的存储空间,所以如果要保证效率,那么就需要一定硬件水平。
2、安装
通过pip 安装: pip install scrapy-redis
官方文档:https://scrapy-redis.readthedocs.io/en/stable/
源码位置:https://github.com/rmax/scrapy-redis
博客:https://www.cnblogs.com/kylinlin/p/5198233.html
3、配置文件
1(必须). 使用了scrapy_redis的去重组件,在redis数据库里做去重
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
2(必须). 使用了scrapy_redis的调度器,在redis里分配请求
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
3(可选). 在redis中保持scrapy-redis用到的各个队列,从而True允许暂停和暂停后恢复,也就是不清理redis queues
SCHEDULER_PERSIST = True
4(必须). 通过配置RedisPipeline将item写入key为 spider.name : items 的redis的list中,供后面的分布式处理item
这个已经由 scrapy-redis 实现,不需要我们写代码,直接使用即可
ITEM_PIPELINES = {
'scrapy_redis.pipelines.RedisPipeline': 100
}
5(必须). 指定redis数据库的连接参数
REDIS_HOST = '127.0.0.1'
REDIS_PORT = 6379
4、redis键名介绍
1、 “项目名:items”
list 类型,保存爬虫获取到的数据item
内容是 json 字符串
2、 “项目名:dupefilter”
set类型,用于爬虫访问的URL去重
内容是 40个字符的 url 的hash字符串
3、 “项目名: start_urls”
List 类型,用于获取spider启动时爬取的第一个url
4、 “项目名:requests”
zset类型,用于scheduler调度处理 requests
内容是 request 对象的序列化 字符串
十八、分布式爬虫
scrapy-redis 改写爬虫
1、dlimg_spider.py
修改爬虫文件 dlimg_spider.py
from scrapy_redis.spiders import RedisSpider
class DlimgSpiderSpider(RedisSpider):
name = 'dlimg_spider'
# allowed_domains = ['sss']
# start_urls = ['http://lab.scrapyd.cn/archives/55.html', 'http://lab.scrapyd.cn/archives/57.html']
redis_key = 'DownloadImage:start_url'
2、settings.py
修改爬虫文件 settings.py
# scrapy_redis 去重
DUPEFILTER_CLASS = 'scrapy_redis.dupefilter.RFPDupefilter'
# scrapy_redis的调度器
SCHEDULER = 'scrapy_redis.scheduler.Scheduler'
# 保持队列 允许暂停和恢复
SCHEDULER_PERSIST = True
# redis的配置
REDIS_HOST = '127.0.0.1'
REDIS_PORT = 6379
ITEM_PIPELINES = {
# redis的Pipeline,尽量不要把数据存到redis,会让服务器卡
'scrapy_redis.pipelines.RedisPipeline':400,
# 'DownloadImage.pipelines.DownloadimagePipeline': 300,
}
3、运行爬虫
在不同的机器上运行此爬虫
4、键入start_url
通过redis数据库输入start_url
lpush DownloadImage:start_url + start_url