网络爬虫之scrapy框架详解,scrapy框架设置代理

twisted介绍

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

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

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

  1. request是一个python实现的可以伪造浏览器发送Http请求的模块,它封装了socket发送请求
  2. twisted是基于时间循环的异步非阻塞的网络框架,它也封装了socket发送请求,但是他可以单线程的完成并发请求。

twisted的特点是:

  • 非阻塞:不等待
  • 异步:回调
  • 事件循环:一直循环去检查状态

scrapy的pipeline文件和items文件

这两个文件有什么作用

先看看我们上篇的示例:

# -*- coding: utf-8 -*-

import scrapy

 

 

class ChoutiSpider(scrapy.Spider):

    ‘‘‘

    爬去抽屉网的帖子信息

    ‘‘‘

    name = ‘chouti‘

    allowed_domains = [‘chouti.com‘]

    start_urls = [‘http://chouti.com/‘]

 

    def parse(self, response):

        # 获取帖子列表的父级div

        content_div = response.xpath(‘//div[@id="content-list"]‘)

 

        # 获取帖子item的列表

        items_list = content_div.xpath(‘.//div[@class="item"]‘)

 

        # 打开一个文件句柄,目的是为了将获取的东西写入文件

        with open(‘articles.log‘,‘a+‘,encoding=‘utf-8‘) as f:

            # 循环item_list

            for item in items_list:

                # 获取每个item的第一个a标签的文本和url链接

                text = item.xpath(‘.//a/text()‘).extract_first()

                href = item.xpath(‘.//a/@href‘).extract_first()

                # print(href, text.strip())

                # print(‘-‘*100)

                f.write(href+‘\n‘)

                f.write(text.strip()+‘\n‘)

                f.write(‘-‘*100+‘\n‘)

 

        # 获取分页的页码,然后让程序循环爬去每个链接

        # 页码标签对象列表

        page_list = response.xpath(‘//div[@id="dig_lcpage"]‘)

        # 循环列表

        for page in page_list:

            # 获取每个标签下的a标签的url,即每页的链接

            page_a_url = page.xpath(‘.//a/@href‘).extract()

            # 将域名和url拼接起来

            page_url = ‘https://dig.chouti.com‘ + page_a_url

 

            # 重要的一步!!!!

            # 导入Request模块,然后实例化一个Request对象,然后yield它

            # 就会自动执行Request对象的callback方法,爬去的是url参数中的链接

            from scrapy.http import Request

            yield Request(url=page_url,callback=self.parse)

  在这个示例中,虽然我们已经通过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} 即可!

网络爬虫之scrapy框架设置代理

前戏

os.environ()简介

os.environ()可以获取到当前进程的环境变量,注意,是当前进程。

如果我们在一个程序中设置了环境变量,另一个程序是无法获取设置的那个变量的。

环境变量是以一个字典的形式存在的,可以用字典的方法来取值或者设置值。

os.environ() key字段详解

windows:


1

2

3

4

5

6

os.environ[‘HOMEPATH‘]:当前用户主目录。

os.environ[‘TEMP‘]:临时目录路径。

os.environ[PATHEXT‘]:可执行文件。

os.environ[‘SYSTEMROOT‘]:系统主目录。

os.environ[‘LOGONSERVER‘]:机器名。

os.environ[‘PROMPT‘]:设置提示符。

linux:


1

2

3

4

5

os.environ[‘USER‘]:当前使用用户。

os.environ[‘LC_COLLATE‘]:路径扩展的结果排序时的字母顺序。

os.environ[‘SHELL‘]:使用shell的类型。

os.environ[‘LAN‘]:使用的语言。

os.environ[‘SSH_AUTH_SOCK‘]:ssh的执行路径。

内置的方式

原理

scrapy框架内部已经实现了设置代理的方法,它的原理是从环境变量中取出设置的代理,然后再使用,

所以我们只需要在程序执行前将代理以键值对的方式设置到环境变量中即可。

代码

第一种方式:直接添加键值对的方式


1

2

3

4

5

6

7

8

9

10

11

12

class ChoutiSpider(scrapy.Spider):

    name = ‘chouti‘

    allowed_domains = [‘chouti.com‘]

    start_urls = [‘https://dig.chouti.com/‘]

    cookie_dict = {}

    def start_requests(self):

        import os

        os.environ[‘HTTPS_PROXY‘= "http://username:[email protected]:9999/"

        os.environ[‘HTTP_PROXY‘= ‘19.11.2.32‘,

        for url in self.start_urls:

            yield Request(url=url,callback=self.parse)

第二种方式:设置meta参数的方式


1

2

3

4

5

6

7

8

9

class ChoutiSpider(scrapy.Spider):

    name = ‘chouti‘

    allowed_domains = [‘chouti.com‘]

    start_urls = [‘https://dig.chouti.com/‘]

    cookie_dict = {}

    def start_requests(self):

        for url in self.start_urls:

            yield Request(url=url,callback=self.parse,meta={‘proxy‘:‘"http://username:[email protected]:9999/"‘})

自定义方式

原理

我们可以根据内部实现的添加代理的类(中间件)的实现方法,来对它进行升级,比如内部的方式一次只能使用一个代理,

我们可以弄一个列表,装很多代理地址,然后随机选取一个代理,这样可以防止请求过多被封ip

代码


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

42

43

44

45

46

47

48

49

50

class ChoutiSpider(scrapy.Spider):

    name = ‘chouti‘

    allowed_domains = [‘chouti.com‘]

    start_urls = [‘https://dig.chouti.com/‘]

    cookie_dict = {}

    def start_requests(self):

        for url in self.start_urls:

            yield Request(url=url,callback=self.parse,meta={‘proxy‘:‘"http://username:[email protected]:9999/"‘})

            import base64

            import random

            from six.moves.urllib.parse import unquote

            try:

                from urllib2 import _parse_proxy

            except ImportError:

                from urllib.request import _parse_proxy

            from six.moves.urllib.parse import urlunparse

            from scrapy.utils.python import to_bytes

            class XXProxyMiddleware(object):

                def _basic_auth_header(self, username, password):

                    user_pass = to_bytes(

                        ‘%s:%s‘ % (unquote(username), unquote(password)),

                        encoding=‘latin-1‘)

                    return base64.b64encode(user_pass).strip()

                def process_request(self, request, spider):

                    PROXIES = [

                        "http://username:[email protected]:9999/",

                        "http://username:[email protected]:9999/",

                        "http://username:[email protected]:9999/",

                        "http://username:[email protected]:9999/",

                        "http://username:[email protected]:9999/",

                        "http://username:[email protected]:9999/",

                    ]

                    url = random.choice(PROXIES)

                    orig_type = ""

                    proxy_type, user, password, hostport = _parse_proxy(url)

                    proxy_url = urlunparse((proxy_type or orig_type, hostport, ‘‘, ‘‘, ‘‘, ‘‘))

                    if user:

                        creds = self._basic_auth_header(user, password)

                    else:

                        creds = None

                    request.meta[‘proxy‘= proxy_url

                    if creds:

                        request.headers[‘Proxy-Authorization‘= b‘Basic ‘ + creds

写完类之后需要在settings文件里配置一下:


1

2

3

DOWNLOADER_MIDDLEWARES = {

   ‘spider.xxx.XXXProxyMiddleware‘543,

}

  

原文地址:https://www.cnblogs.com/xyhh/p/10860873.html

时间: 2024-10-06 16:55:04

网络爬虫之scrapy框架详解,scrapy框架设置代理的相关文章

第三百五十五节,Python分布式爬虫打造搜索引擎Scrapy精讲—scrapy信号详解

第三百五十五节,Python分布式爬虫打造搜索引擎Scrapy精讲-scrapy信号详解 信号一般使用信号分发器dispatcher.connect(),来设置信号,和信号触发函数,当捕获到信号时执行一个函数 dispatcher.connect()信号分发器,第一个参数信号触发函数,第二个参数是触发信号, signals.engine_started当Scrapy引擎启动爬取时发送该信号.该信号支持返回deferreds.signals.engine_stopped当Scrapy引擎停止时发送

hadoop 学习笔记:mapreduce框架详解

hadoop 学习笔记:mapreduce框架详解 开始聊mapreduce,mapreduce是hadoop的计算框架,我 学hadoop是从hive开始入手,再到hdfs,当我学习hdfs时候,就感觉到hdfs和mapreduce关系的紧密.这个可能是我做技术研究的 思路有关,我开始学习某一套技术总是想着这套技术到底能干什么,只有当我真正理解了这套技术解决了什么问题时候,我后续的学习就能逐步的加快,而学习 hdfs时候我就发现,要理解hadoop框架的意义,hdfs和mapreduce是密不

iOS 开发之照片框架详解之二 —— PhotoKit 详解(下)

这里接着前文<iOS 开发之照片框架详解之二 —— PhotoKit 详解(上)>,主要是干货环节,列举了如何基于 PhotoKit 与 AlAssetLibrary 封装出通用的方法. 三. 常用方法的封装 虽然 PhotoKit 的功能强大很多,但基于兼容 iOS 8.0 以下版本的考虑,暂时可能仍无法抛弃 ALAssetLibrary,这时候一个比较好的方案是基于 ALAssetLibrary 和 PhotoKit 封装出一系列模拟系统 Asset 类的自定义类,然后在其中封装好兼容 A

iOS 开发之照片框架详解之二 —— PhotoKit 详解(上)

一. 概况 本文接着 iOS 开发之照片框架详解,侧重介绍在前文中简单介绍过的 PhotoKit 及其与 ALAssetLibrary 的差异,以及如何基于 PhotoKit 与 AlAssetLibrary 封装出通用的方法. 这里引用一下前文中对 PhotoKit 基本构成的介绍: PHAsset: 代表照片库中的一个资源,跟 ALAsset 类似,通过 PHAsset 可以获取和保存资源 PHFetchOptions: 获取资源时的参数,可以传 nil,即使用系统默认值 PHAssetCo

(转) IOS ASI http 框架详解

(转) IOS ASI http 框架详解 ASIHTTPRequest对CFNetwork API进行了封装,并且使用起来非常简单,用Objective-C编写,可以很好的应用在Mac OS X系统和iOS平台的应用程序中.ASIHTTPRequest适用于基本的HTTP请求,和基于REST的服务之间的交互. ASIHTTPRequest功能很强大,主要特色如下: l 通过简单的接口,即可完成向服务端提交数据和从服务端获取数据的工作 l 下载的数据,可存储到内存中或直接存储到磁盘中 l 能上传

Python网络请求urllib和urllib3详解

Python网络请求urllib和urllib3详解 urllib是Python中请求url连接的官方标准库,在Python2中主要为urllib和urllib2,在Python3中整合成了urllib. 而urllib3则是增加了连接池等功能,两者互相都有补充的部分. urllib urllib作为Python的标准库,基本上涵盖了基础的网络请求功能. urllib.request urllib中,request这个模块主要负责构造和发起网络请求,并在其中加入Headers.Proxy等. 发

MTK平台LCD驱动框架详解(一)

许多学习嵌入式的进入MTK开发平台,很多东西都会感到很陌生.在MTK平台上你可以简简单单几分钟就点亮一块屏.加上MTK快速开发的节奏,也很少有时间自己整理学习.如果不思进取,不加班加点学习.很容易就慢慢--.这也难怪有些人说MTK造就了一批懒人,毁掉了一批工程师.但其实都是基于linux开发,核心的东西都是一样一样的.我刚入行业,在迷茫之际,自己整理跟踪源码.想慢慢找回自己熟悉的感觉,掌握MTK的整体框架.也希望能给有需要的人带来些帮助.好吧!前话说到这,开始正题. 本文肯定有不少地方会出现错误

boost asio异步读写网络聊天程序客户端 实例详解

// // chat_client.cpp // ~~~~~~~~~~~~~~~ // // Copyright (c) 2003-2013 Christopher M. Kohlhoff (chris at kohlhoff dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://ww

jQuery Validate验证框架详解

jQuery校验官网地址:http://bassistance.de/jquery-plugins/jquery-plugin-validation 一.导入js库 <script type="text/javascript" src="<%=path %>/validate/jquery-1.6.2.min.js"></script> <script type="text/javascript" src