网络爬虫之scrapy框架详解

ぐ巨炮叔叔 提交于 2019-12-06 16:11:43

twisted介绍

Twisted是用Python实现的基于事件驱动的网络引擎框架,scrapy正是依赖于twisted,

它是基于事件循环的异步非阻塞网络框架,可以实现爬虫的并发。

twisted是什么以及和requests的区别:

  1. request是一个python实现的可以伪造浏览器发送Http请求的模块,它封装了socket发送请求
  2. 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} 即可!

twisted介绍

Twisted是用Python实现的基于事件驱动的网络引擎框架,scrapy正是依赖于twisted,

它是基于事件循环的异步非阻塞网络框架,可以实现爬虫的并发。

twisted是什么以及和requests的区别:

  1. request是一个python实现的可以伪造浏览器发送Http请求的模块,它封装了socket发送请求
  2. 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} 即可!

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!