twisted介绍
Twisted是用Python实现的基于事件驱动的网络引擎框架,scrapy正是依赖于twisted,
它是基于事件循环的异步非阻塞网络框架,可以实现爬虫的并发。
twisted是什么以及和requests的区别:
- request是一个python实现的可以伪造浏览器发送Http请求的模块,它封装了socket发送请求
- twisted是基于时间循环的异步非阻塞的网络框架,它也封装了socket发送请求,但是他可以单线程的完成并发请求。
twisted的特点是:
- 非阻塞:不等待
- 异步:回调
- 事件循环:一直循环去检查状态
scrapy的pipeline文件和items文件
这两个文件有什么作用
先看看我们上篇的示例:
在这个示例中,虽然我们已经通过chouti.py一个文件中的parse方法实现了爬去抽屉网的新闻并将之保存在文件中的功能,
但是我们会发现有两个问题:
1、在循环爬去每一页的时候,每次都需要重新打开然后再关闭文件,如果数据量庞大的话,这对性能有很大的影响。
2、我们将解析和数据持久化都放在了同一个文件的同一个方法中,没有做到分工明确
如果要解决这两个问题,则需要用到scrapy自动为我们生成的pipeline文件和items文件
这两个文件怎么用
如果我们要使用这两个文件从而解决问题,则需要有四部操作:
a.编写pipeline文件中的类,格式如下:
1
2
3
|
class XXXPipeline( object ): def process_item( self , item, spider): return item |
b.编写items文件中的类,格式如下:
1
2
3
|
class XXXItem(scrapy.Item): href = scrapy.Field() title = scrapy.Field() |
c.配置settings文件
1
2
3
4
|
ITEM_PIPELINES = { ‘xxx.pipelines.XXXPipeline‘ : 300 , # ‘xxx.pipelines.XXXPipeline2‘: 600, # 后面的数字为优先级,数字越大,优先级月底 } |
d.在parse方法中yield一个Item对象
1
2
3
4
5
|
from xxx.items import XXXItem def parse( self , response): ... yield XXXItem(text = text,href = href) |
执行流程为:
当我们在执行爬虫中的parse方法的时候,scrapy一旦解析到有yield XXXitem的语句,就会到配置文件中找
ITEM_PIPELINES的配置项,进而找到XXXPipeline类,然后执行其中的方法,我们就可以在方法中做很多操作
当然,pipeline中不止process_item一个方法。
Pipeline中的方法详解
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
|
class FilePipeline( object ): def __init__( self ,path): self .f = None self .path = path @classmethod def from_crawler( cls , crawler): """ 初始化时候,用于创建pipeline对象 :param crawler: :return: """ # 从配置文件中获取配置好的文件存放目录 path = crawler.settings.get( ‘HREF_FILE_PATH‘ ) return cls (path) def open_spider( self ,spider): """ 爬虫开始执行时,调用 :param spider: :return: """ self .f = open ( self .path, ‘a+‘ ) def process_item( self , item, spider): # 在这里做持久化 self .f.write(item[ ‘href‘ ] + ‘\n‘ ) return item # 交给下一个pipeline的process_item方法 # raise DropItem()# 如果写上这一句,后续的 pipeline的process_item方法不再执行 def close_spider( self ,spider): """ 爬虫关闭时,被调用 :param spider: :return: """ self .f.close() |
去重
scrapy内部实现的去重
从上一篇的例子我们可以看出,其实scrapy内部在循环爬去页码的时候,已经帮我们做了去重功能的,
因为我们在首页可以看到1,2,3,4,5,6,7,8,9,10页的页码以及连接,当爬虫爬到第二页的时候,
还是可以看到这10个页面及连接,然后它并没有再重新把第一页爬一遍。
它内部实现去重的原理是,将已爬去的网址存入一个set集合里,每次爬取新页面的时候就先看一下是否在集合里面
如果在,就不再爬去,如果不在就爬取,然后再添加入到set里。当然,这个集合存放的不是原网址,
而是将链接通过request_fingerprint()方法将它变成一个类似于md5的值,这样可以节省存储空间
自定义去重
虽然scrapy已经帮我们实现了去重,但是有时候不足以满足我们的需求,这样就需要我们自定义去重了
自定义去重分两步
1、编写DupeFilter类
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
|
from scrapy.dupefilter import BaseDupeFilter from scrapy.utils.request import request_fingerprint class XXXDupeFilter(BaseDupeFilter): def __init__( self ): ‘‘‘初始化一个集合,用来存放爬去过的网址‘‘‘ self .visited_fd = set () @classmethod def from_settings( cls , settings): ‘‘‘ 如果我们自定义了DupeFilter类并且重写了父类的该方法, scrapy会首先执行该方法,获取DupeFilter对象, 如果没有定义,则会执行init方法来获取对象 ‘‘‘ return cls () def request_seen( self , request): ‘‘‘在此方法中做操作,判断以及添加网址到set里‘‘‘ # 将request里的url转换下,然后判断是否在set里 fd = request_fingerprint(request = request) # 循环set集合,如果已经在集合里,则返回True,爬虫将不会继续爬取该网址 if fd in self .visited_fd: return True self .visited_fd.add(fd) def open ( self ): # can return deferred ‘‘‘开始前执行此方法‘‘‘ print ( ‘开始‘ ) def close( self , reason): # can return a deferred ‘‘‘结束后执行此方法‘‘‘ print ( ‘结束‘ ) def log( self , request, spider): # log that a request has been filtered ‘‘‘在此方法中可以做日志操作‘‘‘ print ( ‘日志‘ ) |
2.配置settings文件
1
2
3
|
# 修改默认的去重规则 # DUPEFILTER_CLASS = ‘scrapy.dupefilter.RFPDupeFilter‘ DUPEFILTER_CLASS = ‘xxx.dupefilters.XXXDupeFilter‘ |
深度
深度就是爬虫所要爬取的层级
限制深度只需要配置一下即可
1
2
|
# 限制深度 DEPTH_LIMIT = 3 |
cookie
获取上一次请求之后获得的cookie
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
from scrapy.http.cookies import CookieJar class ChoutiSpider(scrapy.Spider): name = ‘chouti‘ allowed_domains = [ ‘chouti.com‘ ] start_urls = [ ‘https://dig.chouti.com/‘ ] cookie_dict = {} def parse( self , response): # 去响应头中获取cookie,cookie保存在cookie_jar对象 cookie_jar = CookieJar() cookie_jar.extract_cookies(response, response.request) # 去对象中将cookie解析到字典 for k, v in cookie_jar._cookies.items(): for i, j in v.items(): for m, n in j.items(): self .cookie_dict[m] = n.value |
再次请求的时候携带cookie
1
2
3
4
5
6
7
8
9
10
|
yield Request( url = ‘https://dig.chouti.com/login‘ , method = ‘POST‘ , body = "phone=861300000000&password=12345678&oneMonth=1" , # cookies = self .cookie_dict, headers = { ‘Content-Type‘ : ‘application/x-www-form-urlencoded; charset=UTF-8‘ }, callback = self .check_login ) |
是不是感觉很麻烦?
那么,呵呵,其实,嘿嘿,
你只需要在Request对象的参数中加入 meta={‘cookiejar‘: True} 即可!