zoukankan      html  css  js  c++  java
  • Scrapy框架

    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运行流程大概如下:

    1. 引擎从调度器中取出一个链接(URL)用于接下来的抓取
    2. 引擎把URL封装成一个请求(Request)传给下载器
    3. 下载器把资源下载下来,并封装成应答包(Response)
    4. 爬虫解析Response
    5. 解析出实体(Item),则交给实体管道进行进一步的处理
    6. 解析出的是链接(URL),则把URL交给调度器等待抓取

    一、安装

    1
    pip install Scrapy

    注:windows平台需要依赖pywin32,请根据自己系统32/64位选择下载安装,https://sourceforge.net/projects/pywin32/

    二、基本使用

    1、创建项目

    运行命令:

    1
    scrapy startproject your_project_name

    自动创建目录:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    project_name/
       scrapy.cfg
       project_name/
           __init__.py
           items.py
           pipelines.py
           settings.py
           spiders/
               __init__.py

    文件说明:

    • scrapy.cfg  项目的配置信息,主要为Scrapy命令行工具提供一个基础的配置信息。(真正爬虫相关的配置信息在settings.py文件中)
    • items.py    设置数据存储模板,用于结构化数据,如:Django的Model
    • pipelines    数据处理行为,如:一般结构化的数据持久化
    • settings.py 配置文件,如:递归的层数、并发数,延迟下载等
    • spiders      爬虫目录,如:创建文件,编写爬虫规则

    注意:一般创建爬虫文件时,以网站域名命名

    2、编写爬虫

    在spiders目录中新建 xiaohuar_spider.py 文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    import scrapy
     
    class XiaoHuarSpider(scrapy.spiders.Spider):
        name = "xiaohuar"
        allowed_domains = ["xiaohuar.com"]
        start_urls = [
            "http://www.xiaohuar.com/hua/",
        ]
     
        def parse(self, response):
            # print(response, type(response))
            # from scrapy.http.response.html import HtmlResponse
            # print(response.body_as_unicode())
     
            current_url = response.url
            body = response.body
            unicode_body = response.body_as_unicode()

    3、运行

    进入project_name目录,运行命令

    1
    scrapy crawl spider_name --nolog

    4、递归的访问

    以上的爬虫仅仅是爬去初始页,而我们爬虫是需要源源不断的执行下去,直到所有的网页被执行完毕

    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
    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    import scrapy
    from scrapy.http import Request
    from scrapy.selector import HtmlXPathSelector
    import re
    import urllib
    import os
     
     
    class XiaoHuarSpider(scrapy.spiders.Spider):
        name = "xiaohuar"
        allowed_domains = ["xiaohuar.com"]
        start_urls = [
            "http://www.xiaohuar.com/list-1-1.html",
        ]
     
        def parse(self, response):
            # 分析页面
            # 找到页面中符合规则的内容(校花图片),保存
            # 找到所有的a标签,再访问其他a标签,一层一层的搞下去
     
            hxs = HtmlXPathSelector(response)
     
            # 如果url是 http://www.xiaohuar.com/list-1-d+.html
            if re.match('http://www.xiaohuar.com/list-1-d+.html', response.url):
                items = hxs.select('//div[@class="item_list infinite_scroll"]/div')
                for i in range(len(items)):
                    src = hxs.select('//div[@class="item_list infinite_scroll"]/div[%d]//div[@class="img"]/a/img/@src' % i).extract()
                    name = hxs.select('//div[@class="item_list infinite_scroll"]/div[%d]//div[@class="img"]/span/text()' % i).extract()
                    school = hxs.select('//div[@class="item_list infinite_scroll"]/div[%d]//div[@class="img"]/div[@class="btns"]/a/text()' % i).extract()
                    if src:
                        ab_src = "http://www.xiaohuar.com" + src[0]
                        file_name = "%s_%s.jpg" % (school[0].encode('utf-8'), name[0].encode('utf-8'))
                        file_path = os.path.join("/Users/wupeiqi/PycharmProjects/beauty/pic", file_name)
                        urllib.urlretrieve(ab_src, file_path)
     
            # 获取所有的url,继续访问,并在其中寻找相同的url
            all_urls = hxs.select('//a/@href').extract()
            for url in all_urls:
                if url.startswith('http://www.xiaohuar.com/list-1-'):
                    yield Request(url, callback=self.parse)

    以上代码将符合规则的页面中的图片保存在指定目录,并且在HTML源码中找到所有的其他 a 标签的href属性,从而“递归”的执行下去,直到所有的页面都被访问过为止。以上代码之所以可以进行“递归”的访问相关URL,关键在于parse方法使用了 yield Request对象。

    注:可以修改settings.py 中的配置文件,以此来指定“递归”的层数,如: DEPTH_LIMIT = 1


    from scrapy.selector import Selector
    from scrapy.http import HtmlResponse
    html = """<!DOCTYPE html>
    <html>
    <head lang="en">
        <meta charset="UTF-8">
        <title></title>
    </head>
    <body>
        <li class="item-"><a href="link.html">first item</a></li>
        <li class="item-0"><a href="link1.html">first item</a></li>
        <li class="item-1"><a href="link2.html">second item</a></li>
    </body>
    </html>
    """
    response = HtmlResponse(url='http://example.com', body=html,encoding='utf-8')
    ret = Selector(response=response).xpath('//li[re:test(@class, "item-d*")]//@href').extract()
    print(ret)
     

     
    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    
    import scrapy
    import hashlib
    from tutorial.items import JinLuoSiItem
    from scrapy.http import Request
    from scrapy.selector import HtmlXPathSelector
    
    
    class JinLuoSiSpider(scrapy.spiders.Spider):
        count = 0
        url_set = set()
    
        name = "jluosi"
        domain = 'http://www.jluosi.com'
        allowed_domains = ["jluosi.com"]
    
        start_urls = [
            "http://www.jluosi.com:80/ec/goodsDetail.action?jls=QjRDNEIzMzAzOEZFNEE3NQ==",
        ]
    
        def parse(self, response):
            md5_obj = hashlib.md5()
            md5_obj.update(response.url)
            md5_url = md5_obj.hexdigest()
            if md5_url in JinLuoSiSpider.url_set:
                pass
            else:
                JinLuoSiSpider.url_set.add(md5_url)
                hxs = HtmlXPathSelector(response)
                if response.url.startswith('http://www.jluosi.com:80/ec/goodsDetail.action'):
                    item = JinLuoSiItem()
                    item['company'] = hxs.select('//div[@class="ShopAddress"]/ul/li[1]/text()').extract()
                    item['link'] = hxs.select('//div[@class="ShopAddress"]/ul/li[2]/text()').extract()
                    item['qq'] = hxs.select('//div[@class="ShopAddress"]//a/@href').re('.*uin=(?P<qq>d*)&')
                    item['address'] = hxs.select('//div[@class="ShopAddress"]/ul/li[4]/text()').extract()
    
                    item['title'] = hxs.select('//h1[@class="goodsDetail_goodsName"]/text()').extract()
    
                    item['unit'] = hxs.select('//table[@class="R_WebDetail_content_tab"]//tr[1]//td[3]/text()').extract()
                    product_list = []
                    product_tr = hxs.select('//table[@class="R_WebDetail_content_tab"]//tr')
                    for i in range(2,len(product_tr)):
                        temp = {
                            'standard':hxs.select('//table[@class="R_WebDetail_content_tab"]//tr[%d]//td[2]/text()' %i).extract()[0].strip(),
                            'price':hxs.select('//table[@class="R_WebDetail_content_tab"]//tr[%d]//td[3]/text()' %i).extract()[0].strip(),
                        }
                        product_list.append(temp)
    
                    item['product_list'] = product_list
                    yield item
    
                current_page_urls = hxs.select('//a/@href').extract()
                for i in range(len(current_page_urls)):
                    url = current_page_urls[i]
                    if url.startswith('http://www.jluosi.com'):
                        url_ab = url
                        yield Request(url_ab, callback=self.parse)
     

    def parse(self, response):
        from scrapy.http.cookies import CookieJar
        cookieJar = CookieJar()
        cookieJar.extract_cookies(response, response.request)
        print(cookieJar._cookies)

    更多选择器规则:http://scrapy-chs.readthedocs.io/zh_CN/latest/topics/selectors.html

    5、格式化处理

    上述实例只是简单的图片处理,所以在parse方法中直接处理。如果对于想要获取更多的数据(获取页面的价格、商品名称、QQ等),则可以利用Scrapy的items将数据格式化,然后统一交由pipelines来处理。

    在items.py中创建类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    # -*- coding: utf-8 -*-
     
    # Define here the models for your scraped items
    #
    # See documentation in:
    # http://doc.scrapy.org/en/latest/topics/items.html
     
    import scrapy
     
    class JieYiCaiItem(scrapy.Item):
     
        company = scrapy.Field()
        title = scrapy.Field()
        qq = scrapy.Field()
        info = scrapy.Field()
        more = scrapy.Field()

    上述定义模板,以后对于从请求的源码中获取的数据同意按照此结构来获取,所以在spider中需要有一下操作:

     
    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    
    import scrapy
    import hashlib
    from beauty.items import JieYiCaiItem
    from scrapy.http import Request
    from scrapy.selector import HtmlXPathSelector
    from scrapy.spiders import CrawlSpider, Rule
    from scrapy.linkextractors import LinkExtractor
    
    
    class JieYiCaiSpider(scrapy.spiders.Spider):
        count = 0
        url_set = set()
    
        name = "jieyicai"
        domain = 'http://www.jieyicai.com'
        allowed_domains = ["jieyicai.com"]
    
        start_urls = [
            "http://www.jieyicai.com",
        ]
    
        rules = [
            #下面是符合规则的网址,但是不抓取内容,只是提取该页的链接(这里网址是虚构的,实际使用时请替换)
            #Rule(SgmlLinkExtractor(allow=(r'http://test_url/test?page_index=d+'))),
            #下面是符合规则的网址,提取内容,(这里网址是虚构的,实际使用时请替换)
            #Rule(LinkExtractor(allow=(r'http://www.jieyicai.com/Product/Detail.aspx?pid=d+')), callback="parse"),
        ]
    
        def parse(self, response):
            md5_obj = hashlib.md5()
            md5_obj.update(response.url)
            md5_url = md5_obj.hexdigest()
            if md5_url in JieYiCaiSpider.url_set:
                pass
            else:
                JieYiCaiSpider.url_set.add(md5_url)
                
                hxs = HtmlXPathSelector(response)
                if response.url.startswith('http://www.jieyicai.com/Product/Detail.aspx'):
                    item = JieYiCaiItem()
                    item['company'] = hxs.select('//span[@class="username g-fs-14"]/text()').extract()
                    item['qq'] = hxs.select('//span[@class="g-left bor1qq"]/a/@href').re('.*uin=(?P<qq>d*)&')
                    item['info'] = hxs.select('//div[@class="padd20 bor1 comard"]/text()').extract()
                    item['more'] = hxs.select('//li[@class="style4"]/a/@href').extract()
                    item['title'] = hxs.select('//div[@class="g-left prodetail-text"]/h2/text()').extract()
                    yield item
    
                current_page_urls = hxs.select('//a/@href').extract()
                for i in range(len(current_page_urls)):
                    url = current_page_urls[i]
                    if url.startswith('/'):
                        url_ab = JieYiCaiSpider.domain + url
                        yield Request(url_ab, callback=self.parse)
     

    此处代码的关键在于:

    • 将获取的数据封装在了Item对象中
    • yield Item对象 (一旦parse中执行yield Item对象,则自动将该对象交个pipelines的类来处理)
    # -*- coding: utf-8 -*-
    
    # Define your item pipelines here
    #
    # Don't forget to add your pipeline to the ITEM_PIPELINES setting
    # See: http://doc.scrapy.org/en/latest/topics/item-pipeline.html
    
    import json
    from twisted.enterprise import adbapi
    import MySQLdb.cursors
    import re
    
    mobile_re = re.compile(r'(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}')
    phone_re = re.compile(r'(d+-d+|d+)')
    
    class JsonPipeline(object):
    
        def __init__(self):
            self.file = open('/Users/wupeiqi/PycharmProjects/beauty/beauty/jieyicai.json', 'wb')
    
    
        def process_item(self, item, spider):
            line = "%s  %s
    " % (item['company'][0].encode('utf-8'), item['title'][0].encode('utf-8'))
            self.file.write(line)
            return item
    
    class DBPipeline(object):
    
        def __init__(self):
            self.db_pool = adbapi.ConnectionPool('MySQLdb',
                                                 db='DbCenter',
                                                 user='root',
                                                 passwd='123',
                                                 cursorclass=MySQLdb.cursors.DictCursor,
                                                 use_unicode=True)
    
        def process_item(self, item, spider):
            query = self.db_pool.runInteraction(self._conditional_insert, item)
            query.addErrback(self.handle_error)
            return item
    
        def _conditional_insert(self, tx, item):
            tx.execute("select nid from company where company = %s", (item['company'][0], ))
            result = tx.fetchone()
            if result:
                pass
            else:
                phone_obj = phone_re.search(item['info'][0].strip())
                phone = phone_obj.group() if phone_obj else ' '
    
                mobile_obj = mobile_re.search(item['info'][1].strip())
                mobile = mobile_obj.group() if mobile_obj else ' '
    
                values = (
                    item['company'][0],
                    item['qq'][0],
                    phone,
                    mobile,
                    item['info'][2].strip(),
                    item['more'][0])
                tx.execute("insert into company(company,qq,phone,mobile,address,more) values(%s,%s,%s,%s,%s,%s)", values)
    
        def handle_error(self, e):
            print 'error',e
     

    上述中的pipelines中有多个类,到底Scapy会自动执行那个?哈哈哈哈,当然需要先配置了,不然Scapy就蒙逼了。。。

    在settings.py中做如下配置:

    1
    2
    3
    4
    5
    ITEM_PIPELINES = {
        'beauty.pipelines.DBPipeline': 300,
        'beauty.pipelines.JsonPipeline': 100,
    }
    # 每行后面的整型值,确定了他们运行的顺序,item按数字从低到高的顺序,通过pipeline,通常将这些数字定义在0-1000范围内。

    更多请参见Scrapy文档:http://scrapy-chs.readthedocs.io/zh_CN/latest/index.html

  • 相关阅读:
    LeetCode Path Sum II
    LeetCode Longest Palindromic Substring
    LeetCode Populating Next Right Pointers in Each Node II
    LeetCode Best Time to Buy and Sell Stock III
    LeetCode Binary Tree Maximum Path Sum
    LeetCode Find Peak Element
    LeetCode Maximum Product Subarray
    LeetCode Intersection of Two Linked Lists
    一天一个设计模式(1)——工厂模式
    PHP迭代器 Iterator
  • 原文地址:https://www.cnblogs.com/wangyuxing/p/10028616.html
Copyright © 2011-2022 走看看