Scrapy中的Selector

当抓取网页时,最常见的任务是从HTML源码中提取数据,用Beautiful Soup或lxml都可以。Beautiful Soup是基于HTML代码的结构来构造一个Python对象,对不良标记的处理也很合理,缺点就是慢。而lxml是基于ElementTree(不是Python标准库的一部分)的Python化的XML解析库,也可以解析HTML。

Scrapy提取数据的机制被称为选择器(Selector),通过特定的XPath或者CSS表达式来选择HTML文件中的某部分。Scrapy选择器构建于lxml之上,因此,选择器的速度与解析准确性都与lxml相似。

1.Selector对象

选择器的实例是对选择某些内容响应的封装。

class scrapy.selector.Selector(response=None, text=None, type=None, root=None, **kwargs):
参数:
response:HtmlResponse或XmlResponse的对象,被用来选择和提取数据。
text:response不可用时的一个unicode字符串或utf-8编码的文本。与response一起用是未定义行为。
type:选择器的类型,可以为html,xml或None,None为默认。如不指定,HtmlResponse对应html,XmlResponse对应xml,其它为html。如果设定,不检测,而是强制用指定的。
root:根。
kwargs:其实的字典类型参数。

方法和属性:
xpath(query, namespaces=None, **kwargs):找到匹配query的节点,返回SelectorList对象
css(query):应用给定的css选择器,返回SelectorList对象。
get():将匹配的节点通过一个unicode字符串进行串行化并返回。
attrib:属性字典。
re(regex, replace_entities=True):用给定的正则表达式来返回匹配的unicode字符串列表。
re_first(regex, default=None, replace_entities=True):用给定的正则表达式来返回第一个匹配的unicode字符串。如果没有匹配的,返回default。
register_namespace(prefix, uri):注册给定的命名空间,此命名空间将会在选择器中使用。
remove_namespaces():移除所有的命名空间。
__bool__():如果选择了真实的内容,返回True,否则返回False。即选择器的布尔值通过它选择的内容决定。
getall():将匹配的节点通过只有一个元素列表的unicode字符串进行串行化并返回。

2.SelectorList对象

Python内建list类的子类,只是提供了一些额外的方法。

class scrapy.selector.SelectorList

方法和属性:
xpath(xpath, namespaces=None, **kwargs):对列表中每个元素调用xpath()方法。
css(query):对列表中每个元素调用css()方法。
getall():对列表中每个元素调用getall()方法。
get(default=None):对列表中第一个元素调用get()方法。
re(regex, replace_entities=True):对列表中每个元素调用re()方法。
re_first(regex, default=None, replace_entities=True):对列表中每个元素调用re_first()方法。
attrib:返回列表中第一个元素的属性字典。

3.构造选择器

可以通过文本或TextResponse构造。

def construct_selector():
    # 通过文本构建
    body = '<html><body><span>good</span></body></html>'
    s = Selector(text=body)
    print s.xpath('//span/text()').extract()
    print s.xpath('//span/text()').get()

    # 通过TextResponse构建,HtmlResponse是TextResponse的一个子类
    response = HtmlResponse(url='http://example.com', body=body)
    s = Selector(response=response)
    print s.xpath('//span/text()').extract()
    print s.xpath('//span/text()').get()

    # response对象的selector属性
    print response.selector.xpath('//span/text()').extract()
    print response.selector.xpath('//span/text()').get()

4.使用选择器

Scrapy提供两个快捷方式使用选择器:xpath(), css()

def using_selector():
    text = '''
    <html>
     <head>
      <base href="http://example.com/" />
      <title>Example website</title>
     </head>
     <body>
      <div id="images">
       <a href="image1.html">Name: My image 1 <br /><img src="image1_thumb.jpg" /></a>
       <a href="image2.html">Name: My image 2 <br /><img src="image2_thumb.jpg" /></a>
       <a href="image3.html">Name: My image 3 <br /><img src="image3_thumb.jpg" /></a>
       <a href="image4.html">Name: My image 4 <br /><img src="image4_thumb.jpg" /></a>
       <a href="image5.html">Name: My image 5 <br /><img src="image5_thumb.jpg" /></a>
      </div>
     </body>
    </html>
    '''
    s = Selector(text=text)  # 构建选择器

    # title标签的内容,返回选择器列表
    print s.xpath('//title/text()')
    print s.css('title::text')

    # 提取title标签的内容
    print s.xpath('//title/text()').extract()
    print s.css('title::text').extract()

    # base标签的href属性
    print s.xpath('//base/@href').extract()
    print s.css('base::attr(href)').extract()

    # 具有href属性,且属性值包含image的a标签的href属性
    print s.xpath('//a[contains(@href, "image")]/@href').extract()
    print s.css('a[href*=image]::attr(href)').extract()

    # 具有href属性,且属性值包含image的a标签下的img标签的src属性
    print s.xpath('//a[contains(@href, "image")]/img/@src').extract()
    print s.css('a[href*=image] img::attr(src)').extract()

    # img标签的src属性
    print s.css('img').xpath('@src').getall()  # 返回列表
    print s.css('img').attrib['src']  # 只返回第一个

5.XPath的使用

def working_with_xpath():
    text = '''
        <html>
         <body>
          <p>out</p>
          <div>
            <p>one</p>
          </div>
          <div>
            <p>two</p>
            <div>
                <p>three</p>
            </div>
          </div>
         </body>
        </html>
        '''
    s = Selector(text=text)  # 构建选择器

    # 相对XPaths
    divs = s.xpath('//div')
    for p in divs.xpath('//p'):  # 错误用法,仍然会从整个文档中取得p标签
        print p.get()
    print '-' * 50
    for p in divs.xpath('.//p'):  # div里面的p标签
        print p.get()
    print '-' * 50
    for p in divs.xpath('p'):  # div直系的p标签
        print p.get()

    # 如果由类查询,用css
    s = Selector(text='<div class="hero shout"><time datetime="2014-07-23 19:00">Special date</time></div>')
    print s.css('.shout').xpath('./time/@datetime').getall()

    # //node[1]与(//node)[1]的区别
    s = Selector(text='''
        <ul class="list">
            <li>1</li>
            <li>2</li>
            <li>3</li>
        </ul>
        <ul class="list">
            <li>4</li>
            <li>5</li>
            <li>6</li>
        </ul>
    ''')
    print s.xpath('//li[1]').getall()  # 所有父标签下的li的第一个
    print s.xpath('(//li)[1]').getall()  # 文档里的第一个li
    print s.xpath('//ul/li[1]').getall()  # 所有ul标签下的第一个li
    print s.xpath('(//ul/li)[1]').getall()  # 文档里的第一个ul下的第一个li

    # 文本节点,避免使用.//text()来传递给XPath的string函数,应使用.,因为.//text()返回一个节点集,只传递节点集中的第一个
    s = Selector(text='<a href="#">Click here to go to the <strong>Next Page</strong></a>')
    print s.xpath('//a//text()').getall()  # 返回节点集
    print s.xpath('string(//a[1]//text())').getall()  # 转换成string,只传递了节点集的第一个值
    print s.xpath("//a[1]").getall()  # 第一个节点
    print s.xpath('string(//a[1])').getall()  # 子孙节点的文本放一起传递
    print s.xpath('string(//a)').getall()  # 子孙节点的文本放一起传递
    print s.xpath("//a[contains(.//text(), 'Next Page')]").getall()  # 使用.//text()返回的是空列表
    print s.xpath("//a[contains(., 'Next Page')]").getall()  # 使用.,返回当前节点

    # XPath中的变量
    text = '''
        <html>
         <head>
          <base href="http://example.com/" />
          <title>Example website</title>
         </head>
         <body>
          <div id="images">
           <a href="image1.html">Name: My image 1 <br /><img src="image1_thumb.jpg" /></a>
           <a href="image2.html">Name: My image 2 <br /><img src="image2_thumb.jpg" /></a>
           <a href="image3.html">Name: My image 3 <br /><img src="image3_thumb.jpg" /></a>
           <a href="image4.html">Name: My image 4 <br /><img src="image4_thumb.jpg" /></a>
           <a href="image5.html">Name: My image 5 <br /><img src="image5_thumb.jpg" /></a>
          </div>
         </body>
        </html>
    '''
    s = Selector(text=text)
    print s.xpath('//div[@id=$val]/a/text()', val='images').get()  # $val是变量,通过参数val来传递
    print s.xpath('//div[count(a)=$cnt]/@id', cnt=5).get()  # $cnt是变量,通过参数cnt来传递

    # 移除命名空间(看不出区别)
    html = requests.get(url='https://feeds.feedburner.com/PythonInsider')
    response = HtmlResponse(url='https://feeds.feedburner.com/PythonInsider', body=html.text, encoding='utf-8')
    print response.xpath('//link')
    response.selector.remove_namespaces()  # 移除命名空间
    print response.xpath('//link')

6.使用EXSLT扩展

# 使用EXSLT扩展
def using_exslt_extension():
    # 正则表达式操作
    doc = '''
        <div>
            <ul>
                <li class="item-0"><a href="link1.html">first item</a></li>
                <li class="item-1"><a href="link2.html">second item</a></li>
                <li class="item-inactive"><a href="link3.html">third item</a></li>
                <li class="item-1"><a href="link4.html">fourth item</a></li>
                <li class="item-0"><a href="link5.html">fifth item</a></li>
            </ul>
        </div>
    '''
    s = Selector(text=doc, type='html')
    print s.xpath('//li//@href').getall()
    # 当starts-with()或contains()失效时,使用test()函数
    print s.xpath('//li[re:test(@class, "tem-\d$")]//@href').getall()  # 类名以数字结尾

    # 集合操作
    doc = '''
        <div itemscope itemtype="http://schema.org/Product">
            <span itemprop="name">Kenmore White 17" Microwave</span>
            <img src="kenmore-microwave-17in.jpg" alt='Kenmore 17" Microwave' />
            <div itemprop="aggregateRating"
                itemscope itemtype="http://schema.org/AggregateRating">
                Rated <span itemprop="ratingValue">3.5</span>/5
                based on <span itemprop="reviewCount">11</span> customer reviews
            </div>
            
            <div itemprop="offers" itemscope itemtype="http://schema.org/Offer">
                <span itemprop="price">$55.00</span>
                <link itemprop="availability" href="http://schema.org/InStock" />In stock
            </div>
            
            Product description:
            <span itemprop="description">0.7 cubic feet countertop microwave.
            Has six preset cooking categories and convenience features like
            Add-A-Minute and Child Lock.</span>
            
            Customer reviews:
            
            <div itemprop="review" itemscope itemtype="http://schema.org/Review">
                <span itemprop="name">Not a happy camper</span> -
                by <span itemprop="author">Ellie</span>,
                <meta itemprop="datePublished" content="2011-04-01">April 1, 2011
                <div itemprop="reviewRating" itemscope itemtype="http://schema.org/Rating">
                    <meta itemprop="worstRating" content = "1">
                    <span itemprop="ratingValue">1</span>/
                    <span itemprop="bestRating">5</span>stars
                </div>
                <span itemprop="description">The lamp burned out and now I have to replace
                it. </span>
            </div>
            
            <div itemprop="review" itemscope itemtype="http://schema.org/Review">
                <span itemprop="name">Value purchase</span> -
                by <span itemprop="author">Lucas</span>,
                <meta itemprop="datePublished" content="2011-03-25">March 25, 2011
                <div itemprop="reviewRating" itemscope itemtype="http://schema.org/Rating">
                    <meta itemprop="worstRating" content = "1"/>
                    <span itemprop="ratingValue">4</span>/
                    <span itemprop="bestRating">5</span>stars
                </div>
                <span itemprop="description">Great microwave for the price. It is small and
                fits in my apartment.</span>
            </div>
            ...
        </div>
    '''
    s = Selector(text=doc, type='html')
    for scope in s.xpath('//div[@itemscope]'):
        print u'当前scope:', scope.xpath('@itemtype').getall()
        props = scope.xpath('set:difference(./descendant::*/@itemprop, .//*[@itemscope]/*/@itemprop)')
        print u'    属性:%s' % props.getall()

    # 其它XPath扩展
    text = '''
        <p class="foo bar-baz">First</p>
        <p class="foo">Second</p>
        <p class="bar">Third</p>
        <p>Fourth</p>
    '''
    s = Selector(text=text)
    print s.xpath('//p[has-class("foo")]')  # 有foo这个类的p标签
    print s.xpath('//p[has-class("foo", "bar-baz")]')  # 同时有foo,bar-baz这两个类的p标签
    print s.xpath('//p[has-class("foo", "bar")]')  # 同时有foo,bar这两个类的p标签

源码可于github下载:https://github.com/gkimeeq/WebCrawler

猜你喜欢

转载自www.cnblogs.com/Ooman/p/11223806.html
今日推荐