美文网首页
Scrapy(一)- 基本使用,爬虫实例

Scrapy(一)- 基本使用,爬虫实例

作者: Zoulf | 来源:发表于2018-02-28 23:19 被阅读0次

    Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。 其可以应用在数据挖掘,信息处理或存储历史数据等一系列的程序中。

    其最初是为了页面抓取 (更确切来说, 网络抓取 )所设计的, 也可以应用在获取API所返回的数据(例如 Amazon Associates Web Services ) 或者通用的网络爬虫。Scrapy用途广泛,可以用于数据挖掘、监测和自动化测试。

    Scrapy 使用了 Twisted异步网络库来处理网络通讯。整体架构大致如下:


    Scrapy主要包括了以下组件(结合上图):

    • 引擎(Scrapy)
      用来处理整个系统的数据流处理, 触发事务(框架核心)
    • 调度器(Scheduler)
      用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL(抓取网页的网址或者说是链接)的优先队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址
    • 下载器(Downloader)
      用于下载网页内容, 并将网页内容返回给蜘蛛(Scrapy下载器是建立在twisted这个高效的异步模型上的)
    • 爬虫( Spiders )
      爬虫是主要干活的, 用于从特定的网页中提取自己需要的信息, 即所谓的实体(Item)。用户也可以从中提取出链接,让Scrapy继续抓取下一个页面
    • 项目管道(Pipeline)
      负责处理爬虫从网页中抽取的实体,主要的功能是持久化实体、验证实体的有效性、清除不需要的信息。当页面被爬虫解析后,将被发送到项目管道,并经过几个特定的次序处理数据。
    • 下载器中间件(Downloader Middlewares)
      位于Scrapy引擎和下载器之间的框架,主要是处理Scrapy引擎与下载器之间的请求及响应。
    • 爬虫中间件(Spider Middlewares)
      介于Scrapy引擎和爬虫之间的框架,主要工作是处理蜘蛛的响应输入和请求输出。
    • 调度中间件(Scheduler Middewares)
      介于Scrapy引擎和调度之间的中间件,从Scrapy引擎发送到调度的请求和响应。

     
    Scrapy运行流程大概如下(结合上图):

    • 每个 Spiders 都会有一个初始start_urls。(第一次运行)
      引擎第一次运行时把 Spiders 里的start_urls全放到调度器里
    • 然后引擎从调度器中取出一个链接(URL)用于接下来的抓取
    • 引擎把URL封装成一个请求(Request)传给下载器
    • 下载器把资源下载下来,并封装成应答包(Response)
    • 爬虫解析Response
    • 解析出实体(Item),则交给实体管道进行进一步的处理
    • 解析出的是链接(URL),则把URL交给调度器等待抓取

    Scrapy 安装

    Linux
          pip3 install scrapy
      
    Windows
          a. pip3 install wheel
          b. 下载twisted http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
          c. 进入下载目录,执行 pip3 install Twisted‑17.1.0‑cp35‑cp35m‑win_amd64.whl
          d. pip3 install scrapy
          e. 下载并安装pywin32:https://sourceforge.net/projects/pywin32/files/
    

    Scrapy 基本使用

    基本命令(cmd):

    1. scrapy startproject 项目名称
       - 在当前目录中创建中创建一个项目文件(类似于Django)
     
    2. scrapy genspider [-t template] <name> <domain>
       - 创建爬虫应用
       如:
          scrapy gensipider -t basic oldboy oldboy.com
          scrapy gensipider -t xmlfeed autohome autohome.com.cn
       PS:
          查看所有命令:scrapy gensipider -l
          查看模板命令:scrapy gensipider -d 模板名称
     
    3. scrapy list
       - 展示爬虫应用列表
     
    4. scrapy crawl 爬虫应用名称
       - 运行单独爬虫应用
    
       如果想在通过main.py运行,则新建main.py然后
       from scrapy import cmdline
       cmdline.execute("scrapy crawl chouti --nolog".split())
    

     
    项目结构以及爬虫应用简介:

    project_name/
       scrapy.cfg
       project_name/
           __init__.py
           items.py
           pipelines.py
           settings.py
           spiders/
               __init__.py
               爬虫1.py
               爬虫2.py
               爬虫3.py
    

    文件说明:

    • scrapy.cfg 项目的主配置信息。(真正爬虫相关的配置信息在settings.py文件中)
    • items.py 设置数据存储模板,用于结构化数据,如:Django的Model
    • pipelines 数据处理行为,如:一般结构化的数据持久化
    • settings.py 配置文件,如:递归的层数、并发数,延迟下载等
    • spiders 爬虫目录,如:创建文件,编写爬虫规则
    运行 scrapy genspider zhihu zhihu.com 后生成的.py文件
    
    import scrapy
     
    class ZhiHuSpider(scrapy.spiders.Spider):
        name = "zhihu"                            # 爬虫名称 *****
        allowed_domains = ["zhihu.com"]  # 允许的域名
        start_urls = [
            "https://www.zhihu.com/",   # 起始 URL
        ]
     
        def parse(self, response):
            # 访问起始URL并获取结果后的回调函数
    
    # 如果在Windows里遇到编码问题,输入下面代码
    import sys,os
    sys.stdout=io.TextIOWrapper(sys.stdout.buffer,encoding='gb18030')
    

    xpath语法

    nodename    选取此节点的所有子节点。
    /   从根节点选取。
    //  从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。
    .   选取当前节点。
    ..  选取当前节点的父节点。
    @   选取属性。
    如:
    //book  选取所有 book 子元素,而不管它们在文档中的位置。
    bookstore//book 选择属于 bookstore 元素的后代的所有 book 元素,而不管它们位于 bookstore 之下的什么位置。
    
    /div[@id="i1"] 儿子中id="i1"的div
    /div[@id="i1"]/text() 获取当前标签(div)的文本
    /div[@id="i1"]/@href  获取当前标签(div)的href属性
    obj.extract() 列表中的每一个对象转换成字符串,返回一个列表
    obj.extract_first() 列表中的每一个对象转换成字符串,返回列表第一个元素
    
    # a[starts-with(@属性(href), "link"),获取以link开头的
    hxs = Selector(response=response).xpath('//a[starts-with(@href, "/all/hot/recent/")]')
    # 也可以用正则表达式a[re:test(@href, "...")]
    hxs = Selector(response=response).xpath('//a[re:test(@href, "/all/hot/recent/\d+")]')
    
    # contains代表包含link就行
    # hxs = Selector(response=response).xpath('//a[contains(@href, "link")]')
    

    低级去重方式

    我们通过爬虫获取到的 url 肯定会有重复的,所以要去重,假如我们用集合去重(低级方式):

    # 定义静态字段
    visited_urls = set()
    ...
    def parse(self, response):
        for url in hxs:
            if url in self.visited_urls:
                print('已经存在', url)
            else:
                print(url)
                self.visited_urls.add(url)
    

    这样就去重了,但还有问题,当我们存储时(无论存在数据库还是内存/缓存中),如果url的长度过大,就会导致内存的浪费,所以我们需要通过md5或者其他加密算法,将url加密,这样就固定了存储url的长度。

    首先定义加密方法:

    def md5(self, url):
        import hashlib
        obj = hashlib.md5()
        obj.update(bytes(url, enco
        return obj.hexdigest()
    

    然后将上面的改成:

    hxs = Selector(response=response).xpath('//a[starts-with(@href, "/all/hot/recent/")]')
    for url in hxs:
        md5_url = self.md5(url)
        if md5_url in self.visited_urls:
            print('已经存在', url)
        else:
            self.visited_urls.add(md5_url)
            real_url = "http://dig.chouti.com/%s"%(url)
            print(real_url)
    

    运用 Request 发送请求

    获取当前页的所有页码已经完成了,这时需求改了,要获取所有的页码怎么做?
    获取所有页,就是从当前页跳转,然后再到跳转的页继续找,重复这个动作,这样就像是递归,而在Scrapy里,为我们提供了一个Request对象。用来请求数据(跳转就是请求新的页码)。

    from scrapy.http import Request
        ...
        else:
            self.visited_urls.add(md5_url)
            real_url = "http://dig.chouti.com%s"%(url)
            print(real_url)
            # 将新要访问的url添加到调度器
            # 必须写yield,写了yield引擎才能把Request发给调度器
            yield Request(url=real_url, callback=self.parse)
    

    这样就能找到所有的页码,如果我们不想要这么多,就可以在settings.py里设置递归深度,如

    DEPTH_LIMIT = 2
    # DEPTH_LIMIT = 0 表示无限制
    

    数据持久化:

    Scrapy里数据的持久化是在Pipeline和item里做的。
    首先在items.py里,将爬取的东西作为一个对象

    class ChoutiItem(scrapy.Item):
        title = scrapy.Field()
        href = scrapy.Field()
    

    然后chouti.py爬的时候存数据:

    xs_item_list = Selector(response=response).xpath('//div[@id="content-list"]/div[@class="item"]')
            for item in hxs_item_list:
                title = item.xpath('.//a[@class="show-content color-chag"]/text()').extract_first().strip()
                href = item.xpath('.//a[@class="show-content color-chag"]/@href').extract_first().strip()
                # 存数据
                item = ChoutiItem(title=title, href=href)
                # 通过yield 发送给Pipeline
                yield item
    

    然后再Pipeline里进行持久化:

    pieplines.py
    class ScrapyLearnPipeline(object):
        # process 处理
        def process_item(self, item, spider):
            # 因为所有爬虫爬的数据都放在pipeline里做持久化,所以可以根据spider.name来做区分
            if spider.name == 'chouti':
                # 从item里取数据通过item['xxx']
                tmp = "%s\n%s\n\n"%(item['title'], item['href'])
                with open('data.json', 'a') as f:
                    f.write(tmp)
    

    还需要注意的是,要在settings.py中注册Pipeline:

    ITEM_PIPELINES = {
       'scrapy_learn.pipelines.ScrapyLearnPipeline': 300,
    }
    300是权重,权重大,则先持久化
    

    去重的新方式

    • 自定义类去重
    • 用scrapy自带的类(RFPDupeFilter),他是存在文件中,然后通过文件来判断的。(from scrapy.dupefilters import RFPDupeFilter)

    自定义类:

    新建duplication.py
    class RepeatFilter(object):
        def __init__(self):
            # 自定义的类可以存在内存,缓存,数据库,文件
            self.visited_set = set()
    
        @classmethod
        def from_settings(cls, settings):
            # 创建RepeatFilter对象 - cls()
            return cls()
    
        # 检查是否访问过
        def request_seen(self, request):
            if request.url in self.visited_set:
                return True
            self.visited_set.add(request.url)
            return False
    
        def open(self):  # can return deferred
            # print('open') 开始爬取
            pass
    
        def close(self, reason):  # can return a deferred
            # print('close') 结束爬取
            pass
    
        def log(self, request, spider):  # log that a request has been filtered
            # print('log....') 打印日志
            pass
    

    执行顺序是 from_settings -> __init__ -> open -> request_seen -> close
    自定义类还需要更改下配置文件:

    DUPEFILTER_CLASS = "day96.duplication.RepeatFilter"
    // 默认是 DUPEFILTER_CLASS = "scrapy.dupefilters.RFPDupeFilter",即scrapy自带的去重
    

    Pipeline深入分析

    Pipeline除了process_item()还有from_crawler,open_spider,close_spider方法。
    from_crawler类似自定义去重类中的from_settings,也是一个类方法

    class ScrapyLearnPipeline(object):  
        def __init__(self,conn_str):
            """
            初始化数据
            """
            self.conn_str = conn_str
    
        @classmethod
        def from_crawler(cls, crawler):
            """
            初始化时调用,用于创建pipeline对象,得到配置文件
            还可以进行数据库配置(数据库配置都是在settings.py里进行的)
            """
            conn_str = crawler.settings.get('DB') # 注意配置文件中的变量都要大写
            return cls(conn_str)
    
        def open_spider(self,spider):
            """
            爬虫开始执行时,调用,如果存储用的是文件/数据库等可以在里面打开文件,这样就实现了打开一次文件,写入完后再关闭文件
            """
            self.conn = open(self.conn_str, 'a')
    
        def close_spider(self,spider):
            """
            爬虫关闭时,被调用
            """
            self.conn.close()
    
        def process_item(self, item, spider):
            """
            每当数据需要持久化时,就会被调用
            """
            # if spider.name == 'chouti'
            tpl = "%s\n%s\n\n" %(item['title'],item['href'])
            self.conn.write(tpl)
    
            # 注意如果有多个方法,一定要return,这样才能交给下一个pipeline处理
            return item
    
            # 当不需要交个下一个pipeline处理时,请务必这样写(from scrapy.exceptions import DropItem)
            # raise DropItem()
    

    Cookie问题

    当爬取的网站需要登录时,需要携带请求头,请求体,Cookie。

    登录流程:
    访问当前页面,拿到返回的GPSD,然后带着GPSD去登录,然后服务器就会将我们的GPSD授权,授权后GPSD就可以一直用了。具体操作看实例。


    实例

    自动给抽屉点赞

    /spiders/chouti.py
    
    import scrapy
    from scrapy.selector import Selector, HtmlXPathSelector
    from scrapy.http import Request
    from scrapy.http.cookies import CookieJar
    
    class ChoutiSpider(scrapy.Spider):
        name = 'chouti'
        # allowed_domains = ['chouti.com/']
        start_urls = ['http://dig.chouti.com/']
        # 存储cookie
        cookie_dict = None
    
        def parse(self, response):
            # 由于有start_urls的存在,所以会自动访问一次页面
            # 创建cookie对象,现在里面还什么都没有,只是一个容器
            cookie_obj = CookieJar()
    
            # extract_cookies()需要两个参数,response和request,而reponse里包含request,这一步得到授权的cookie
            cookie_obj.extract_cookies(response, response.request)
    
            # cookie_obj._cookies 从cookie对象中拿到cookie
            cookie_dict = cookie_obj._cookies
    
            yield Request(
                # 发起登录请求
                url="http://dig.chouti.com/login",
                method="POST",
                headers={'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'},
                # 请求体写法:= 和 &
                body="phone=86xxx&password=xxx&oneMonth=1",
                cookies=self.cookie_dict,
                callback=check_login
            )
    
        def check_login(self, response):
            yield Request(url='http://dig.chouti.com/', callback=self.like)
    
        # 点赞
        def like(self, response):
            # 拿到赞的id列表
            id_list = Selector(response).xpath('//div[@share-linkid]/@share-linkid').extract()
            for nid in id_list:
                # 每个赞的url
                url - 'http://dig.chouti.com/link/vote?linksId=%s'%(nid)
                yield Request(
                    url=url,
                    method='POST',
                    # 点赞需要携带cookie
                    cookies=self.cookie_dict,
                    callback=self.success
                )
    
            # 查找下一页
            page_urls = Selector(response).xpath('//div[@id="dig_lcpage"]//a/@href').extract()
            for page in page_urls:
                url = 'http://dig.chouti.com%s'%(page)
                yield Request(url=url, callback=self.like)
    
        def success(self, response):
            print(response.text)
    

    具体源码:猛戳这里

    相关文章

      网友评论

          本文标题:Scrapy(一)- 基本使用,爬虫实例

          本文链接:https://www.haomeiwen.com/subject/wdrwxftx.html