Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。 其能够应用在数据挖掘,信息处理或存储历史数据等一系列的程序中。
其最初是为了页面抓取 (更确切来讲, 网络抓取 )所设计的, 也能够应用在获取API所返回的数据(例如 Amazon Associates Web Services ) 或者通用的网络爬虫。Scrapy用途普遍,能够用于数据挖掘、监测和自动化测试。html
Scrapy 使用了 Twisted异步网络库来处理网络通信。总体架构大体以下:python
Scrapy主要包括了如下组件:web
Scrapy运行流程大概以下:算法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
Linux:
pip3 install scrapy
window:
注:本人Python版本是
64
位,如果
32
位,请到官网下载对应版本文件!
1
、pip3 install wheel 有请跳过;
2
、安装关联模块pypiwin32:pip3 install pypiwin32 有请跳过;
3
、在http:
/
/
www.lfd.uci.edu
/
~gohlke
/
pythonlibs
/
#twisted 下载 而后pip3 install 文件路径 安装 twisted
4
、pip3 install scrapy
5
、python
import
scrapy 报错:
from
..
import
etree ImportError: DLL load failed: 找不到指定的程序。
解决办法:因为本地缺乏lxml文件或是lxml文件不符
pip3 uninstall lxml 先卸载已经安装的lxml
官网下载新的lxml:http:
/
/
www.lfd.uci.edu
/
~gohlke
/
pythonlibs
/
#lxml 下载 而后pip3 install 文件路径 安装 lxml
如果有其余报错,查看少哪一个模块,pip安装便可,例如:缺乏cryptography模块;pip3 install cryptography
|
文件下载,请猛击: Twisted 64位下载 lxml 64位下载数据库
1、基本命令json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
#在当前目录下操做
#建立项目:
scrapy startproject 项目名
例:scrapy startproject sp1
sp1项目结构介绍
-
sp1
-
spiders 目录
-
middlewares.py 中间件
-
items.py 格式化
-
pipelines.py 持久化
-
settings.cfg 配置
-
scrapy.cfg 配置
#建立爬虫:
cd sp1
scrapy genspider 爬虫名 域名
例如:scrapy genspider example example.com
scrapy genspider baidu baidu.com
scrapy genspider xiaohuar xiaohuar.com
scrapy genspider jiandan jandan.net
/
ooxx
PS:
查看全部命令:scrapy gensipider
-
l
查看模板命令:scrapy gensipider
-
d 模板名称
#注意:通常建立爬虫文件时,以网站域名命名
#展现爬虫应用列表
-
scrapy
list
#单独执行某爬虫,进入project
-
scrapy crawl 爬虫名
例如:scrapy crawl baidu
# 有执行日志流程
-
scrapy crawl baidu
-
-
nolog
# 无执行日志
#可能会遇到执行报错 forbidden 的问题,这是由于在爬取数据的时候,须要遵循爬取协议,若是对方服务器不容许爬取那就会返回这个错误。
#真的须要暴力爬取的话,那就在settings配置文件中,把协议改为False。这样就能获取到数据!
#打印爬取内容:
在你建立的爬虫项目下,有一个以项目名命名的py文件【个人是baidu.py】,这里边有一个爬虫的类,类下有个parse方法,用于接收返回值response
在函数下添加以下代码:
print
(response.text)。表明执行完毕以后打印爬取的内容!
response.text
# 字符串类型数据
response.body
# 字节类型数据
|
文件说明:
scrapy.cfg 项目的主配置信息。(真正爬虫相关的配置信息在settings.py文件中)
items.py 设置数据存储模板,用于结构化数据,如:Django的Model
pipelines 数据处理行为,如:通常结构化的数据持久化
settings.py 配置文件,如:递归的层数、并发数,延迟下载等
spiders 爬虫目录,如:建立文件,编写爬虫规则
2、小试牛刀 缓存
既然咱们已经熟悉了基本操做,那咱们就来扒拉一下校花网。服务器
1
2
3
4
5
|
建立scrapy项目
-
scrapy startproject sp1
建立爬虫 (一个项目下能够建立多个爬虫)
-
scrapy genspider xiaohuar xiaohuar.com
建立完成以后,能够用pycharm打开建立的项目,欣赏一下本身的杰做!
|
在 spiders 文件下,会有一个建立的爬虫文件 - xiaohuar.py。文件内部代码以下:cookie
# -*- coding: utf-8 -*- import scrapy class XiaohuarSpider(scrapy.Spider): name = 'xiaohuar' #爬虫名称 allowed_domains = ['xiaohuar.com'] #容许的域名 start_urls = ['http://www.xiaohuar.com/hua/'] #起始URL def parse(self, response): # 访问起始URL并获取结果后的回调函数 pass
咱们发现,回调函数parse中没有任何代码,咱们能够自定义操做,如:咱们写个 print(response.text) 意思是打印返回的结果。[自行添加测试哈]网络
关于编码的问题:
咱们爬取下来的信息,可能由于字符编码的问题,致使打印的结果是乱码,解决方法是把以下代码粘贴到爬虫文件的头部。
1
2
|
import
sys,os
sys.stdout
=
io.TextIOWrapper(sys.stdout.
buffer
,encoding
=
'gb18030'
)
|
执行此爬虫文件,则在终端进入项目目录执行以下命令:
1
|
scrapy crawl xiaohuar
-
-
nolog
|
3、scrapy框架内部方法介绍
一、选择器
当爬虫爬取下来全部页面,可是咱们仅须要某一段标签,或是超连接地址,再或者是须要某张照片or文本,这怎么办呢?scrapy框架内部为咱们封装了Selector方法,方便咱们去截取。
引用方法:
1
|
from
scrapy.selector
import
Selector
|
应用:
#处理返回的字符串,转换成标签 hxs = Selector(response=response) # hxs.xpath(条件) #经过条件过滤查找,相似与CSS中的选择器 #lists = hxs.xpath(条件).extract() #获取条件查找到的全部标签,返回结果是一个列表,列表内都是找到的一个个对应的标签 #lists = hxs.xpath(条件).extract_first() #获取条件查找到的第一个标签 #关于条件: 经常使用操做: // 表明子子孙孙中查找 / 儿子中查找 标签名[@属性名="属性值"] 定位直接找到某一类标签 支持正则匹配查找,语法结构以下: 标签名[re:test(@属性名,"正则匹配信息语法")] /@属性名 获取某个标签内的属性 /text() 获取标签的文本 contains 包含 starts-with 起始 也支持链式操做,及查找的时候能够找当前标签下的子标签或是子孙标签 标签名[@属性名="属性值"]/a/@href 子标签a标签的href属性 标签名[@属性名="属性值"]//a/@href 子孙标签a标签的href属性 特殊的,也支持在当前标签再作一次过滤,语法不变但查找是须要指定当前位置也就是点(.); hxs = Selector(response=response) user_list = hxs.xpath('//div[@class="item masonry_brick"]') for item in user_list: price = item.xpath('.//span[@class="price"]/text()').extract_first() url = item.xpath('//div[@class="item_t"]/div[@class="img"]//a/@href').extract_first() print(price, url) #item.xpath(.//) 相对于当前 子孙 中找 #item.xpath(./) 相对于当前 子代 中找 #item.xpath(*/) 相对于当前 子代 中找 #item.xpath('a') 相对于当前 找子代的a标签
# hxs = Selector(response=response).xpath('//a') # print(hxs) # hxs = Selector(response=response).xpath('//a[2]') # print(hxs) # hxs = Selector(response=response).xpath('//a[@id]') # print(hxs) # hxs = Selector(response=response).xpath('//a[@id="i1"]') # print(hxs) # hxs = Selector(response=response).xpath('//a[@href="link.html"][@id="i1"]') # print(hxs) # hxs = Selector(response=response).xpath('//a[contains(@href, "link")]') # print(hxs) # hxs = Selector(response=response).xpath('//a[starts-with(@href, "link")]') # print(hxs) # hxs = Selector(response=response).xpath('//a[re:test(@id, "i\d+")]') # print(hxs) # hxs = Selector(response=response).xpath('//a[re:test(@id, "i\d+")]/text()').extract() # print(hxs) # hxs = Selector(response=response).xpath('//a[re:test(@id, "i\d+")]/@href').extract() # print(hxs) # hxs = Selector(response=response).xpath('/html/body/ul/li/a/@href').extract() # print(hxs) # hxs = Selector(response=response).xpath('//body/ul/li/a/@href').extract_first() # print(hxs) # ul_list = Selector(response=response).xpath('//body/ul/li') # for item in ul_list: # v = item.xpath('./a/span') # # 或 # # v = item.xpath('a/span') # # 或 # # v = item.xpath('*/a/span') # print(v)
scrapy框架内部,也封装了Request方法,与Python内置的requests的应用一致。
引入方法:
1
|
from
scrapy.http
import
Request
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
分析HTTP请求
-
请求方式
-
URL
-
Http经常使用请求头
各个参数对应的不一样名称:
user
-
agent: 当前用户访问使用的设备信息
content
-
type
:xxxx 请求体数据类型
host:请求访问的主地址
Referer:访问请求来自哪一个页面
cookies:关键信息
PS:POST请求,不一样的请求体数据类型对应着不一样的请求头
数据是:Form Data,请求头类型为:Content
-
Type
:application
/
x
-
www
-
form
-
urlencoded;
数据是:request payload 请求头类型为:content
-
type
: application
/
json
|
class Request(object_ref): def __init__(self, url, callback=None, method='GET', headers=None, body=None, cookies=None, meta=None, encoding='utf-8', priority=0, dont_filter=False, errback=None, flags=None): ......... #url = 请求路由地址 #method 请求方法,默认GET #headers 请求头 #body 传值 #cookies cookies #dont_filter 是否过滤,默认False url请求过滤
import scrapy from scrapy.selector import HtmlXPathSelector from scrapy.http.request import Request class DigSpider(scrapy.Spider): # 爬虫应用的名称,经过此名称启动爬虫命令 name = "dig" # 容许的域名 allowed_domains = ["chouti.com"] # 起始URL start_urls = [ 'http://dig.chouti.com/', ] has_request_set = {} def parse(self, response): print(response.url) hxs = HtmlXPathSelector(response) page_list = hxs.select('//div[@id="dig_lcpage"]//a[re:test(@href, "/all/hot/recent/\d+")]/@href').extract() for page in page_list: page_url = 'http://dig.chouti.com%s' % page key = self.md5(page_url) if key in self.has_request_set: pass else: self.has_request_set[key] = page_url obj = Request(url=page_url, method='GET', callback=self.parse) yield obj @staticmethod def md5(val): import hashlib ha = hashlib.md5() ha.update(bytes(val, encoding='utf-8')) key = ha.hexdigest() return key
# -*- coding: utf-8 -*- import scrapy from scrapy.selector import Selector from scrapy.http import Request class XiaohuarSpider(scrapy.Spider): name = 'xiaohuar' #爬虫名称 allowed_domains = ['xiaohuar.com'] #容许的域名 start_urls = ['http://www.xiaohuar.com/hua/'] #起始URL def parse(self, response): # 访问起始URL并获取结果后的回调函数 #处理返回的字符串,转换成标签 hxs = Selector(response=response) user_list = hxs.xpath('//div[@class="item masonry_brick"]') for item in user_list: price = item.xpath('.//span[@class="price"]/text()').extract_first() url = item.xpath('//div[@class="item_t"]/div[@class="img"]//a/@href').extract_first() print(price, url) # 跨页获取 result = hxs.xpath('.//a[re:test(@href,"http://www.xiaohuar.com/list-1-\d+.html")]/@href') print(result) # 规则,固定写法,在配置文件中指定递归层数 for url in result: yield Request(url=url, callback=self.parse)
注意:Request是一个封装用户请求的类,请求回来都会调用参数为 callback 的回调函数,默认为None,咱们能够经过callback = 函数名 指定请求回来执行某个函数。在回调函数中须要使用 yield该对象 表示才继续访问。在请求过程当中咱们不知道何时请求返回信息,因此咱们就须要利用回调函数去响应!
yield 的意义及在于,把当前须要继续请求的函数交给爬虫引擎,再次放入队列执行。
注意:settings.py中设置DEPTH_LIMIT =n n来指定“递归”的层数。
4、自定义scrapy框架方法
一、自定义起始URL 执行的回调函数
当咱们建立一个爬虫文件,都会在类下生成一个parse函数,只要是执行调用爬虫就会默认执行这个parse函数的方法。咱们知道建立的爬虫类继承的是 Spider类的方法,那这里边是否是有相关默认执行的操做呢?咱们查看源码发现,确实是有个函数 start_requests ,用于指定起始URL默认执行的回调函数。因此咱们能够重写这个函数,以实现自定义起始URL,执行咱们定义的回调函数。代码以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
import
scrapy
from
scrapy.http
import
Request
class
ChoutiSpider(scrapy.Spider):
name
=
'chouti'
allowed_domains
=
[
'chouti.com'
]
start_urls
=
[
'http://chouti.com/'
]
def
start_requests(
self
):
"""
自定义起始url
"""
for
url
in
self
.start_urls:
yield
Request(url, dont_filter
=
True
,callback
=
self
.parse1)
def
parse1(
self
, response):
"""
自定义执行的回调函数
"""
pass
|
二、POST请求,请求头相关问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
关于请求体数据类型的转换:把字典类型的数据转换成 k1
=
v1&k2
=
v2....
import
urllib.parse
data
=
urllib.parse.urlencode({
'k1'
:
'v1'
,
'k2'
:
'v2'
})
GET请求:
url,
method
=
'GET'
,
headers
=
{},
cookies
=
{}, cookiejar
POST请求:
url,
method
=
'GET'
,
headers
=
{},
cookies
=
{}, cookiejar
body
=
None
,
请求头:application
/
x
-
www
-
form
-
urlencoded; charset
=
UTF
-
8
要传递数据类型:
"phone=86155fa&password=asdf&oneMonth=1"
form_data
=
{
'user'
:
'alex'
,
'pwd'
:
123
}
import
urllib.parse
data
=
urllib.parse.urlencode({
'k1'
:
'v1'
,
'k2'
:
'v2'
})
请求头:application
/
json; charset
=
UTF
-
8
要传递数据类型:
"{k1:'v1','k2':'v2'}"
json.dumsp()
|
2.五、硬插一杠,获取cookies
咱们知道在对网站请求的时候,须要携带cookies,那在scrapy框架返回的结果中怎么获取cookies呢?以下方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
from
scrapy.http.cookies
import
CookieJar
#引入
#转换成cookies的对象
cookies_jar
=
CookieJar()
#对象,内部封装了cookies,scrapy框架能够直接使用,打印的话须要以下转换
cookies_jar.extract_cookies(response,response.request)
#去响应中获取cookies
#可视化字典类型的cookies
cookie_dict
=
{}
for
k, v
in
cookies_jar._cookies.items():
for
i, j
in
v.items():
for
m, n
in
j.items():
cookie_dict[m]
=
n.value
#注意:在scrapy框架中,Request方法可使用 cookies的对象,也可使用字典类型的cookies
|
# -*- coding: utf-8 -*- import scrapy from scrapy.http import Request from scrapy.selector import Selector from scrapy.http.cookies import CookieJar import urllib.parse as urlps class ChoutiSpider(scrapy.Spider): name = 'chouti' allowed_domains = ['chouti.com'] start_urls = ['http://chouti.com/'] #获取cookies cookie_dict = {} def start_requests(self): """ 自定义起始url,执行访问主页 :return: """ for url in self.start_urls: yield Request(url, dont_filter=True,callback=self.parse_login) def parse_login(self, response): #访问登陆页面,登陆并拿到新的cookies cookies_jar = CookieJar() #对象,内部封装了cookies,scrapy框架能够直接使用,打印的话须要以下转换 cookies_jar.extract_cookies(response,response.request) #去响应中获取cookies for k, v in cookies_jar._cookies.items(): for i, j in v.items(): for m, n in j.items(): self.cookie_dict[m] = n.value url_info = "http://dig.chouti.com/login" post_dict = { 'phone':'8613581945998', 'password':'zyj13032025071', 'oneMonth':1, } #POST登陆 yield Request( url=url_info, method="POST", cookies=self.cookie_dict, body=urlps.urlencode(post_dict), headers={"Content-Type":"application/x-www-form-urlencoded; charset=UTF-8",}, callback=self.get_message, ) def get_message(self,response): # 获取消息列表 print(response.text) yield Request( url="http://dig.chouti.com/", method="GET", cookies=self.cookie_dict, callback=self.parse_UP, ) def parse_UP(self,response): #找到标签,点赞 #找div,class = part2, 获取share-linkid print(11111) hxs = Selector(response) link_id_list = hxs.xpath('//div[@class="part2"]/@share-linkid').extract() print(link_id_list) for link_id in link_id_list: #获取每个ID去点赞(仅在第一页点赞) base_url = "http://dig.chouti.com/link/vote?linksId=%s"%(link_id,) yield Request( url=base_url, method="POST", cookies=self.cookie_dict, callback=self.return_infos, ) #对全部的页面点赞 page_list = hxs.xpath('//div[@id="dig_lcpage"]//a/@href').extract() for page in page_list: page_url = "http://dig.chouti.com%s"%(page,) yield Request(url=page_url,method="GET",dont_filter=False,callback=self.parse_UP) page_list.remove(page) def return_infos(self,response): #点赞以后,获取返回信息 print(response.text)
三、持久化
上述实例只是简单的处理,因此能够在parse方法中直接处理。若是对于想要获取更多的数据处理保存在本地,则就要利用Scrapy的items将数据格式化,而后统一交由pipelines来处理持久化,保存在本地。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
须要先在item.py文件中结构化,而后再经过pipeline持久化
item.py文件的类下能够自定义字段。
class
Sp1Item(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
"""
注意只有Field字段
"""
name
=
scrapy.Field()
url
=
scrapy.Field()
pipeline执行的前提:
-
spider爬虫中要
yield
Items对象
-
settings中注册
ITEM_PIPELINES
=
{
'sp2.pipelines.Sp2Pipeline'
:
300
,
#数字表明优先级,越小优先级越大
'sp2.pipelines.Sp3Pipeline'
:
100
,
}
# 每行后面的整型值,肯定了他们运行的顺序,item按数字从低到高的顺序,经过pipeline,一般将这些数字定义在0-1000范围内。
编写pipeline 持久化操做
"""
检测 Sp2Pipeline类中是否有 from_crawler方法:
若是有:
obj = 类.from_crawler()
若是没有:
obj = 类()
实例化完对象以后,会调用如下方法:
obj.open_spider() #爬虫开始执行调用
而后会循环
爬虫运行,而且执行parse各类各样的方法,yield item
obj.process_item() #把获取的信息写入文件或是数据库
循环结束,关闭
obj.close_spider() #爬虫关闭时调用
注意:pipeline在爬虫开始初始化执行的时候,除了process_item()类下的其余方法都已经执行完成【实例化对象,链接数据库或是打开文件】,
而process_item()会被爬虫代码循环调用执行写入信息,直至爬虫代码执行结束!
"""
|
# -*- coding: utf-8 -*- import scrapy from scrapy.selector import Selector from scrapy.http import Request from items import Sp1Item class JandanSpider(scrapy.Spider): name = 'jandan' allowed_domains = ['jandan.net'] start_urls = ['http://jandan.net/ooxx/'] def start_requests(self): for url in self.start_urls: yield Request(url=url,dont_filter=True,callback=self.parse1) def parse1(self, response): hxs = Selector(response) text_list = hxs.xpath('//div[@class="indexs"]/h2/a/text()').extract() for item in text_list: yield Sp1Item(text=item,name="jiandan")
import scrapy class Sp1Item(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field() """ 注意只有Field字段 """ name = scrapy.Field() text = scrapy.Field()
class Sp2Pipeline(object): def __init__(self): self.f = None self.val = None def process_item(self, item, spider): """ :param item: 爬虫中yield回来的对象 :param spider: 爬虫对象 obj = JianDanSpider() :return: """ print(item) self.f.write('....') #将item 传递给下一个pipeline 的 process_item 方法 return item #把对象传递给下一个pipeline # 若是不想让下一个pipline中process_item方法不执行,使用以下,抛出异常再也不执行 # from scrapy.exceptions import DropItem # raise DropItem() 下一个pipeline的process_item方法不在执行 @classmethod def from_crawler(cls, crawler): """ 初始化时候,用于建立pipeline对象 :param crawler: 爬虫相关的全部信息,能够去配置文件取值 :return: """ #从配置文件中取值,实例化传给__init__方法。【通常是经过这种方式链接数据库】 # val = crawler.settings.get('MMMM') print('执行pipeline的from_crawler,进行实例化对象') return cls() def open_spider(self,spider): """ 爬虫开始执行时,调用 :param spider: 爬虫对象 :return: """ print('打开爬虫') #打开文件 self.f = open('a.log','a+') #链接数据库 self.val = 数据库链接操做 def close_spider(self,spider): """ 爬虫关闭时,被调用 :param spider: :return: """ #关闭文件 self.f.close() #关闭数据库
注意:
0、items结构化的文件能够自定义编写;可是只有一个字段名
一、Pipeline类内最多能够构建5个方法,以下:__init__ ,open_spider,close_spider,from_crawler,process_item。
二、PipeLine.py文件内,能够定义多个类,可是爬虫会顺序执行不一样类内 process_item 这个方法。
执行缘由:process_item方法下,若是最后有个 return item ,就会把对象传递给下一个pipeline。
#将item 传递给下一个pipeline 的 process_item 方法
return item #把对象传递给下一个pipeline
# 若是不想让下一个pipline中process_item方法不执行,使用以下,抛出异常再也不执行
# from scrapy.exceptions import DropItem
# raise DropItem() 下一个pipeline的process_item方法不在执行
三、多个执行process_item方法的的好处就是能够把数据保存到多地;
四、PipeLine是全局生效,全部爬虫均可以调用执行;调用方法:【yield Item对象】。
五、若是个别爬虫须要作特殊操做:
能够在process_item方法下,经过spider.name获取爬虫的名字,经过这个名字进行判断单独的应用在这个爬虫上,从而作特殊的操做!
四、自定义去重规则(避免重复访问)
咱们都知道scrapy框架内部是有去重规则的,调用方式默认是在setting.py文件中存在的。
scrapy默认使用 scrapy.dupefilter.RFPDupeFilter 进行去重,相关配置有:
1
2
3
|
DUPEFILTER_CLASS
=
'scrapy.dupefilter.RFPDupeFilter'
DUPEFILTER_DEBUG
=
False
JOBDIR
=
"保存范文记录的日志路径,如:/root/"
# 最终路径为 /root/requests.seen
|
咱们自定义的话,须要先在当前项目下建立一个py文件,剩下的就两点:
- 定义一个类;
- 配置文件中指定
DUPEFILTER_CLASS = 'sp2.rep.RepeatUrl' #文件的相对路径+类名!
class RepeatUrl: def __init__(self): #能够把要检测的url写在数据库 or 放在另外一台服务器的内存,或是缓存 self.visited_url = set()#结合 # @classmethod def from_settings(cls, settings): """ 初始化时,调用 :param settings:配置文件,能够取东西 :return: """ return cls() def request_seen(self, request): """ 检测当前请求是否已经被访问过 :param request:请求 :return: True表示已经访问过;False表示未访问过 """ if request.url in self.visited_url: return True self.visited_url.add(request.url) return False def open(self): """ 开始爬去请求时,调用 :return: """ print('open replication') def close(self, reason): """ 结束爬虫爬取时,调用 :param reason: :return: """ print('close replication') def log(self, request, spider): """ 记录日志 :param request: :param spider: :return: """ print('repeat', request.url)
五、自定义扩展【基于信号】
自定义扩展时,利用信号在指定位置注册制定操做。
应用的话有两点:
一、须要在当前项目下建立一个py文件。在文件中写入自定义扩展的操做。
二、须要在配置文件中注册
EXTENSIONS = {
# 'scrapy.extensions.telnet.TelnetConsole': None,
# '文件路径': 数字表明优先级,
}
from scrapy import signals class MyExtension(object): def __init__(self, value): self.value = value @classmethod def from_crawler(cls, crawler): val = crawler.settings.getint('MMMM') ext = cls(val) # 在scrapy中注册信号: ext.opened 注册触发信号时执行的函数,爬虫执行自动触发 crawler.signals.connect(ext.opened, signal=signals.spider_opened) # 在scrapy中注册信号: 同上 crawler.signals.connect(ext.closed, signal=signals.spider_closed) return ext #自定义触发信号时执行的函数 def opened(self, spider): print('open') def closed(self, spider): print('close')
应用:在执行爬虫,若想实现其余的功能或是操做,不用多想,此时就须要自定义扩展!
六、中间件
咱们在学习Django框架已经学习了中间件,爬虫的中间件功能与其一致,也是为了实现批量处理。爬虫的中间件分为:爬虫中间件(做用于爬虫文件与引擎之间)、下载中间件(做用于引擎与下载器之间)。在中间件中咱们能够自定义方法,对全部要执行的爬虫或是在下载以前,进行处理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
-
下载中间件
在process_request方法中,能够修改 请求方法 或是 请求头信息;加入代理。
注意:咱们能够在这个方法内,自定义请求地址 获取下载的信息,而后放入response响应体中,无论爬虫爬取的是哪一个网站的数据返回的结果都是咱们自定义的下载信息。
在process_response方法中,能够自定义响应信息的处理方式。
-
引用的话,须要在配置文件中注册:
# Enable or disable spider middlewares
# See http://scrapy.readthedocs.org/en/latest/topics/spider-middleware.html
SPIDER_MIDDLEWARES
=
{
'sp2.middlewares.Sp2SpiderMiddleware'
:
543
,
}
-
爬虫中间件
-
start_requests 可迭代对象
-
引用的话,须要在配置文件中注册:
# Enable or disable downloader middlewares
# See http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html
DOWNLOADER_MIDDLEWARES
=
{
'sp2.middlewares.MyCustomDownloaderMiddleware'
:
543
,
}
返回的响应信息中,存在发起请求的request信息,获取的方法:response.request
|
class SpiderMiddleware(object): def process_spider_input(self,response, spider): """ 下载完成,执行,而后交给parse处理 :param response: :param spider: :return: """ pass def process_spider_output(self,response, result, spider): """ spider处理完成,返回时调用 :param response: :param result: :param spider: :return: 必须返回包含 Request 或 Item 对象的可迭代对象(iterable) """ return result def process_spider_exception(self,response, exception, spider): """ 异常调用 :param response: :param exception: :param spider: :return: None,继续交给后续中间件处理异常;含 Response 或 Item 的可迭代对象(iterable),交给调度器或pipeline """ return None def process_start_requests(self,start_requests, spider): """ 爬虫启动时调用 :param start_requests: :param spider: :return: 包含 Request 对象的可迭代对象 """ return start_requests
class DownMiddleware1(object): def process_request(self, request, spider): """ 请求须要被下载时,通过全部下载器中间件的process_request调用 :param request: :param spider: :return: None,继续后续中间件去下载; Response对象,中止process_request的执行,开始执行process_response Request对象,中止中间件的执行,将Request从新调度器 raise IgnoreRequest异常,中止process_request的执行,开始执行process_exception """ """ #自定义请求方式,添加或是修改请求头 from scrapy.http import Request # print(request) # request.method = "POST" request.headers['proxy'] = "{'ip_port': '111.11.228.75:80', 'user_pass': ''}," return None """ """ #自定义请求地址,返回响应信息 from scrapy.http import Response import requests v = request.get('http://www.baidu.com') data = Response(url='xxxxxxxx',body=v.content,request=request) return data """ pass def process_response(self, request, response, spider): """ spider处理完成,返回时调用 :param response: :param result: :param spider: :return: Response 对象:转交给其余中间件process_response Request 对象:中止中间件,request会被从新调度下载 raise IgnoreRequest 异常:调用Request.errback """ print('response1') #在响应回来时,咱们能够对响应体进行某些处理 # from scrapy.http import Response # response.encoding = 'utf-8' return response def process_exception(self, request, exception, spider): """ 当下载处理器(download handler)或 process_request() (下载中间件)抛出异常 :param response: :param exception: :param spider: :return: None:继续交给后续中间件处理异常; Response对象:中止后续process_exception方法 Request对象:中止中间件,request将会被从新调用下载 """ return None
七、自定义命令
- 让全部爬虫都开始工做
from scrapy.commands import ScrapyCommand from scrapy.utils.project import get_project_settings class Command(ScrapyCommand): requires_project = True def syntax(self): return '[options]' def short_desc(self): return 'Runs all of the spiders' def run(self, args, opts): # 爬虫列表 spider_list = self.crawler_process.spiders.list() for name in spider_list: # 初始化爬虫 self.crawler_process.crawl(name, **opts.__dict__) # 开始执行全部的爬虫 self.crawler_process.start()
八、scrapy 配置文件
# Scrapy settings for step8_king project # # For simplicity, this file contains only settings considered important or # commonly used. You can find more settings consulting the documentation: # # http://doc.scrapy.org/en/latest/topics/settings.html # http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html # http://scrapy.readthedocs.org/en/latest/topics/spider-middleware.html # 1. 爬虫名称 BOT_NAME = 'step8_king' # 2. 爬虫应用路径 SPIDER_MODULES = ['step8_king.spiders'] NEWSPIDER_MODULE = 'step8_king.spiders' # Crawl responsibly by identifying yourself (and your website) on the user-agent # 3. 客户端 user-agent请求头 # USER_AGENT = 'step8_king (+http://www.yourdomain.com)' # Obey robots.txt rules # 4. 禁止爬虫配置 # ROBOTSTXT_OBEY = False # Configure maximum concurrent requests performed by Scrapy (default: 16) # 5. 并发请求数 # CONCURRENT_REQUESTS = 4 # Configure a delay for requests for the same website (default: 0) # See http://scrapy.readthedocs.org/en/latest/topics/settings.html#download-delay # See also autothrottle settings and docs # 6. 延迟下载秒数 # DOWNLOAD_DELAY = 2 # The download delay setting will honor only one of: # 7. 单域名访问并发数,而且延迟下次秒数也应用在每一个域名 # CONCURRENT_REQUESTS_PER_DOMAIN = 2 # 单IP访问并发数,若是有值则忽略:CONCURRENT_REQUESTS_PER_DOMAIN,而且延迟下次秒数也应用在每一个IP # CONCURRENT_REQUESTS_PER_IP = 3 # Disable cookies (enabled by default) # 8. 是否支持cookie,cookiejar进行操做cookie # COOKIES_ENABLED = True # COOKIES_DEBUG = True # Disable Telnet Console (enabled by default) # 9. Telnet用于查看当前爬虫的信息,操做爬虫等... # 使用telnet ip port ,而后经过命令操做 # TELNETCONSOLE_ENABLED = True # TELNETCONSOLE_HOST = '127.0.0.1' # TELNETCONSOLE_PORT = [6023,] # 10. 默认请求头 # 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', # } # Configure item pipelines # See http://scrapy.readthedocs.org/en/latest/topics/item-pipeline.html # 11. 定义pipeline处理请求 # ITEM_PIPELINES = { # 'step8_king.pipelines.JsonPipeline': 700, # 'step8_king.pipelines.FilePipeline': 500, # } # 12. 自定义扩展,基于信号进行调用 # Enable or disable extensions # See http://scrapy.readthedocs.org/en/latest/topics/extensions.html # EXTENSIONS = { # # 'step8_king.extensions.MyExtension': 500, # } # 13. 爬虫容许的最大深度,能够经过meta查看当前深度;0表示无深度 # DEPTH_LIMIT = 3 # 14. 爬取时,0表示深度优先Lifo(默认);1表示广度优先FiFo # 后进先出,深度优先 # DEPTH_PRIORITY = 0 # SCHEDULER_DISK_QUEUE = 'scrapy.squeue.PickleLifoDiskQueue' # SCHEDULER_MEMORY_QUEUE = 'scrapy.squeue.LifoMemoryQueue' # 先进先出,广度优先 # DEPTH_PRIORITY = 1 # SCHEDULER_DISK_QUEUE = 'scrapy.squeue.PickleFifoDiskQueue' # SCHEDULER_MEMORY_QUEUE = 'scrapy.squeue.FifoMemoryQueue' # 15. 调度器队列 # SCHEDULER = 'scrapy.core.scheduler.Scheduler' # from scrapy.core.scheduler import Scheduler # 16. 访问URL去重 # DUPEFILTER_CLASS = 'step8_king.duplication.RepeatUrl' # Enable and configure the AutoThrottle extension (disabled by default) # See http://doc.scrapy.org/en/latest/topics/autothrottle.html """ 17. 自动限速算法 from scrapy.contrib.throttle import AutoThrottle 自动限速设置 1. 获取最小延迟 DOWNLOAD_DELAY 2. 获取最大延迟 AUTOTHROTTLE_MAX_DELAY 3. 设置初始下载延迟 AUTOTHROTTLE_START_DELAY 4. 当请求下载完成后,获取其"链接"时间 latency,即:请求链接到接受到响应头之间的时间 5. 用于计算的... AUTOTHROTTLE_TARGET_CONCURRENCY target_delay = latency / self.target_concurrency new_delay = (slot.delay + target_delay) / 2.0 # 表示上一次的延迟时间 new_delay = max(target_delay, new_delay) new_delay = min(max(self.mindelay, new_delay), self.maxdelay) slot.delay = new_delay """ # 开始自动限速 # AUTOTHROTTLE_ENABLED = True # The initial download delay # 初始下载延迟 # AUTOTHROTTLE_START_DELAY = 5 # The maximum download delay to be set in case of high latencies # 最大下载延迟 # AUTOTHROTTLE_MAX_DELAY = 10 # The average number of requests Scrapy should be sending in parallel to each remote server # 平均每秒并发数 # AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0 # Enable showing throttling stats for every response received: # 是否显示 # AUTOTHROTTLE_DEBUG = True # Enable and configure HTTP caching (disabled by default) # See http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html#httpcache-middleware-settings """ 18. 启用缓存 目的用于将已经发送的请求或相应缓存下来,以便之后使用【没有网络,可是数据已经缓存下来,能够测试使用】 from scrapy.downloadermiddlewares.httpcache import HttpCacheMiddleware from scrapy.extensions.httpcache import DummyPolicy from scrapy.extensions.httpcache import FilesystemCacheStorage """ # 是否启用缓存策略 # HTTPCACHE_ENABLED = True # 缓存策略:全部请求均缓存,下次在请求直接访问原来的缓存便可 # HTTPCACHE_POLICY = "scrapy.extensions.httpcache.DummyPolicy" # 缓存策略:根据Http响应头:Cache-Control、Last-Modified 等进行缓存的策略 # HTTPCACHE_POLICY = "scrapy.extensions.httpcache.RFC2616Policy" # 缓存超时时间 # HTTPCACHE_EXPIRATION_SECS = 0 # 缓存保存路径 # HTTPCACHE_DIR = 'httpcache' # 缓存忽略的Http状态码 # HTTPCACHE_IGNORE_HTTP_CODES = [] # 缓存存储的插件 # HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'
""" 19. 代理,须要在环境变量中设置 from scrapy.contrib.downloadermiddleware.httpproxy import HttpProxyMiddleware 方式一:使用默认 os.environ { http_proxy:http://root:woshiniba@192.168.11.11:9999/ https_proxy:http://192.168.11.11:9999/ } 例如:咱们能够在自定义命令中引入,能够在爬虫执行的时候,使用代理IP。 from scrapy.contrib.downloadermiddleware.httpproxy import HttpProxyMiddleware import os os.environ['http_proxy'] = "http://root:woshiniba@192.168.11.11:9999/" os.environ['https_proxy'] = "http://192.168.11.11:9999/" os.environ['xx_proxy'] = "http://192.168.11.11:9999/" 方式二:使用自定义下载中间件 def to_bytes(text, encoding=None, errors='strict'): if isinstance(text, bytes): return text if not isinstance(text, six.string_types): raise TypeError('to_bytes must receive a unicode, str or bytes ' 'object, got %s' % type(text).__name__) if encoding is None: encoding = 'utf-8' return text.encode(encoding, errors) class ProxyMiddleware(object): def process_request(self, request, spider): PROXIES = [ {'ip_port': '111.11.228.75:80', 'user_pass': ''}, {'ip_port': '120.198.243.22:80', 'user_pass': ''}, {'ip_port': '111.8.60.9:8123', 'user_pass': ''}, {'ip_port': '101.71.27.120:80', 'user_pass': ''}, {'ip_port': '122.96.59.104:80', 'user_pass': ''}, {'ip_port': '122.224.249.122:8088', 'user_pass': ''}, ] proxy = random.choice(PROXIES) if proxy['user_pass'] is not None: request.meta['proxy'] = to_bytes("http://%s" % proxy['ip_port']) encoded_user_pass = base64.encodestring(to_bytes(proxy['user_pass'])) request.headers['Proxy-Authorization'] = to_bytes('Basic ' + encoded_user_pass) print "**************ProxyMiddleware have pass************" + proxy['ip_port'] else: print "**************ProxyMiddleware no pass************" + proxy['ip_port'] request.meta['proxy'] = to_bytes("http://%s" % proxy['ip_port']) # 配置文件中注册 DOWNLOADER_MIDDLEWARES = { 'step8_king.middlewares.ProxyMiddleware': 500, } """
""" 20. Https访问 Https访问时有两种状况: 1. 要爬取网站使用的可信任证书(默认支持) DOWNLOADER_HTTPCLIENTFACTORY = "scrapy.core.downloader.webclient.ScrapyHTTPClientFactory" DOWNLOADER_CLIENTCONTEXTFACTORY = "scrapy.core.downloader.contextfactory.ScrapyClientContextFactory" 2. 要爬取网站使用的自定义证书 DOWNLOADER_HTTPCLIENTFACTORY = "scrapy.core.downloader.webclient.ScrapyHTTPClientFactory" DOWNLOADER_CLIENTCONTEXTFACTORY = "step8_king.https.MySSLFactory" # https.py from scrapy.core.downloader.contextfactory import ScrapyClientContextFactory from twisted.internet.ssl import (optionsForClientTLS, CertificateOptions, PrivateCertificate) class MySSLFactory(ScrapyClientContextFactory): def getCertificateOptions(self): from OpenSSL import crypto v1 = crypto.load_privatekey(crypto.FILETYPE_PEM, open('/Users/wupeiqi/client.key.unsecure', mode='r').read()) v2 = crypto.load_certificate(crypto.FILETYPE_PEM, open('/Users/wupeiqi/client.pem', mode='r').read()) return CertificateOptions( privateKey=v1, # pKey对象 certificate=v2, # X509对象 verify=False, method=getattr(self, 'method', getattr(self, '_ssl_method', None)) ) 其余: 相关类 scrapy.core.downloader.handlers.http.HttpDownloadHandler scrapy.core.downloader.webclient.ScrapyHTTPClientFactory scrapy.core.downloader.contextfactory.ScrapyClientContextFactory 相关配置 DOWNLOADER_HTTPCLIENTFACTORY DOWNLOADER_CLIENTCONTEXTFACTORY """
""" 21. 爬虫中间件 class SpiderMiddleware(object): def process_spider_input(self,response, spider): ''' 下载完成,执行,而后交给parse处理 :param response: :param spider: :return: ''' pass def process_spider_output(self,response, result, spider): ''' spider处理完成,返回时调用 :param response: :param result: :param spider: :return: 必须返回包含 Request 或 Item 对象的可迭代对象(iterable) ''' return result def process_spider_exception(self,response, exception, spider): ''' 异常调用 :param response: :param exception: :param spider: :return: None,继续交给后续中间件处理异常;含 Response 或 Item 的可迭代对象(iterable),交给调度器或pipeline ''' return None def process_start_requests(self,start_requests, spider): ''' 爬虫启动时调用 :param start_requests: :param spider: :return: 包含 Request 对象的可迭代对象 ''' return start_requests 内置爬虫中间件: 'scrapy.contrib.spidermiddleware.httperror.HttpErrorMiddleware': 50, 'scrapy.contrib.spidermiddleware.offsite.OffsiteMiddleware': 500, 'scrapy.contrib.spidermiddleware.referer.RefererMiddleware': 700, 'scrapy.contrib.spidermiddleware.urllength.UrlLengthMiddleware': 800, 'scrapy.contrib.spidermiddleware.depth.DepthMiddleware': 900, """ # from scrapy.contrib.spidermiddleware.referer import RefererMiddleware # Enable or disable spider middlewares # See http://scrapy.readthedocs.org/en/latest/topics/spider-middleware.html SPIDER_MIDDLEWARES = { # 'step8_king.middlewares.SpiderMiddleware': 543, } """ 22. 下载中间件 class DownMiddleware1(object): def process_request(self, request, spider): ''' 请求须要被下载时,通过全部下载器中间件的process_request调用 :param request: :param spider: :return: None,继续后续中间件去下载; Response对象,中止process_request的执行,开始执行process_response Request对象,中止中间件的执行,将Request从新调度器 raise IgnoreRequest异常,中止process_request的执行,开始执行process_exception ''' pass def process_response(self, request, response, spider): ''' spider处理完成,返回时调用 :param response: :param result: :param spider: :return: Response 对象:转交给其余中间件process_response Request 对象:中止中间件,request会被从新调度下载 raise IgnoreRequest 异常:调用Request.errback ''' print('response1') return response def process_exception(self, request, exception, spider): ''' 当下载处理器(download handler)或 process_request() (下载中间件)抛出异常 :param response: :param exception: :param spider: :return: None:继续交给后续中间件处理异常; Response对象:中止后续process_exception方法 Request对象:中止中间件,request将会被从新调用下载 ''' return None 默认下载中间件 { 'scrapy.contrib.downloadermiddleware.robotstxt.RobotsTxtMiddleware': 100, 'scrapy.contrib.downloadermiddleware.httpauth.HttpAuthMiddleware': 300, 'scrapy.contrib.downloadermiddleware.downloadtimeout.DownloadTimeoutMiddleware': 350, 'scrapy.contrib.downloadermiddleware.useragent.UserAgentMiddleware': 400, 'scrapy.contrib.downloadermiddleware.retry.RetryMiddleware': 500, 'scrapy.contrib.downloadermiddleware.defaultheaders.DefaultHeadersMiddleware': 550, 'scrapy.contrib.downloadermiddleware.redirect.MetaRefreshMiddleware': 580, 'scrapy.contrib.downloadermiddleware.httpcompression.HttpCompressionMiddleware': 590, 'scrapy.contrib.downloadermiddleware.redirect.RedirectMiddleware': 600, 'scrapy.contrib.downloadermiddleware.cookies.CookiesMiddleware': 700, 'scrapy.contrib.downloadermiddleware.httpproxy.HttpProxyMiddleware': 750, 'scrapy.contrib.downloadermiddleware.chunked.ChunkedTransferMiddleware': 830, 'scrapy.contrib.downloadermiddleware.stats.DownloaderStats': 850, 'scrapy.contrib.downloadermiddleware.httpcache.HttpCacheMiddleware': 900, } """ # from scrapy.contrib.downloadermiddleware.httpauth import HttpAuthMiddleware # Enable or disable downloader middlewares # See http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html # DOWNLOADER_MIDDLEWARES = { # 'step8_king.middlewares.DownMiddleware1': 100, # 'step8_king.middlewares.DownMiddleware2': 500, # }
补充:
爬虫也支持telnet终端操做,telnet 链接上 正在处理的爬虫,能够经过est() 查看正在执行的全部操做列表。
1
|
telnet localhost
6023
est()
|
更对文档请参考:官方文档