zoukankan      html  css  js  c++  java
  • 自学Python十二 战斗吧Scrapy!

      初窥Scrapy

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

          还是先推荐几个学习的教程:Scrapy 0.25文档  Scrapy快速入门教程 这些教程里面有关于Scrapy的安装,创建项目,爬取实例等等,如果一个全新的东西扔给你首先要看文档,初看文档我也是蒙蒙的,后来一层一层的去摸索才大概懂了个皮毛。我们就试着将之前的爬虫福利改写成用Scrapy框架的爬虫,在实践中学习。 战斗吧 Scrapy!

      安装Scrapy

      如果配置好了pip或者easy_install 可以直接pip install scrapy (从https://pip.pypa.io/en/latest/installing.html 安装 pip

      还需要从 http://sourceforge.net/projects/pywin32/ 安装 pywin32 (注:此处要注意了,这里pywin32的版本要跟你python的完全一致,比如你在64位系统安装的32位的python2.7 那么你也需要安装2.7 32位的pywin32) 否则遇到:Scrapy [twisted] CRITICAL:Unhandled error in Deferred

      新建项目

      因为我们要重写之前的项目,我们新建一个scrapy项目,命名为rosi: scrapy startproject rosi

      可以看到目录里面包含:

     1 rosi/
     2     scrapy.cfg    #项目的配置文件
     3     rosi/            #该项目的Python模块,代码全在这里面
     4         __init__.py
     5         items.py    #放多个model的地方
     6         pipelines.py    #顾名思义 管道,处理items结果的地方
     7         settings.py    #配置文件
     8         spiders/        #爬虫代码
     9             __init__.py
    10             ...

      好了。。。说了这么多废话,接下来让我们深入基层!新建rosi项目,然后在rosi/rosi/spiders下面新建rosi_spider.py

    import scrapy
    
    class RosiSpider(scrapy.spiders.Spider):
        name = "rosi"    #爬虫名字 唯一
        allowed_domains = ["baidu.com"]    #白名单
        start_urls = ["http://www.baidu.com"]    #爬取起始页面
        
        def parse(self,response):#回调函数
            print response.url

      上面的代码就是一直简单的爬虫,默认爬取了百度首页。你如果问我,怎么爬取的,什么原理,怎么会爬取了,我只能这么回答你:我表达不出来,因为我也是刚学现在还一团浆糊,我现在只明白怎么用,至于原理,我想等我用的熟了,需要去更深的应用的时候我就会懂了,如果能看的下去可以去看看源码。。。不过我可以引用官方文档中的话来回答你:Scrapy为start_urls属性中的每个url都创建了一个Request对象,并将parse方法最为回调函数(callback)赋值给了Request。Request对象经过调度,执行生成scrapy.http.Response对象并返回给parse方法。

      执行该爬虫:scrapy crawl rosi

      我们既然知道了返回的是response,我们可以试着将里面我们需要的东西匹配读取保存下来,比如文字,比如图片。在Scrapy中呢他拥有自己的Selectors。使用了一种基于XPath和css的机制。深入的东西还是看官方文档:Selector文档 简单介绍介绍几个官方文档的例子:

    • /html/head/title: 选择HTML文档中 <head> 标签内的 <title> 元素
    • /html/head/title/text(): 选择上面提到的 <title> 元素的文字
    • //td: 选择所有的 <td> 元素
    • //div[@class="mine"]: 选择所有具有 class="mine" 属性的 div 元素

      Selector有4个基本方法:

    • xpath(): 传入xpath表达式,返回该表达式所对应的所有节点的selector list列表 。
    • css(): 传入CSS表达式,返回该表达式所对应的所有节点的selector list列表.
    • extract(): 序列化该节点为unicode字符串并返回list。
    • re(): 根据传入的正则表达式对数据进行提取,返回unicode字符串list列表。

      这里可以自行尝试一下利用XPath取出百度首页的title文字等等等等。

      好了,重点来了。Scrapy中的BaseSpider爬虫类只能抓取start_urls中提供的链接,而利用Scrapy提供的crawlSpider类可以很方便的自动解析网页上符合要求的链接,从而达到爬虫自动抓取的功能。要利用crawSpider和BaseSpider的区别在于crawSpider提供了一组Rule对象列表,这些Rule对象规定了爬虫抓取链接的行为,Rule规定的链接才会被抓取,交给相应的callback函数去处理。

      在rules中通过SmglLinkExtractor提取希望获取的链接。比如:

    1 rules = (
    2              Rule(SgmlLinkExtractor(allow = ('detail_d{4}_d{5}.html')),callback = 'parse_image',follow=True),
    3              )

      这里要解释下,Rule就是一组对象列表,在这里我们设置要过滤的地址。SmglLinkExtractor的主要参数:

    1. allow:满足括号中“正则表达式”的值会被提取,如果为空,则全部匹配。
    2. deny:与这个正则表达式(或正则表达式列表)不匹配的URL一定不提取。
    3. allow_domains:会被提取的链接的domains。
    4. deny_domains:一定不会被提取链接的domains。
    5. restrict_xpaths:使用xpath表达式,和allow共同作用过滤链接。
    6. follow 指定这些通过规则匹配出来的链接是否需要继续,如果callback是None,follow默认为False,否则follow是True。通俗点讲呢就是如果设置为false 那么就访问了这个网站为止不再根据Rule判断该网址,如果设置为True 则继续从该网址里面选择符合Rule的网址出来继续访问。(举个例子:网站有25页,但是首页上提供的页面跳转的标号只有从1-10 后面的隐藏了,平常我们点开10页 页面标号是10-20 如果我们follow为false 我们只能得到1-10页的url 如果设置为True 则每次得到一页都去取标号,我们能得到所有的页码1-25.说的太乱了,一会儿代码中说。)

      我们尝试着从首页得到符合规则的rosi跳转页面:

     1 import scrapy
     2 from scrapy.contrib.spiders import CrawlSpider,Rule
     3 from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor
     4 class RosiSpider(CrawlSpider):
     5     name = "rosi"    
     6     allowed_domains = ["5442.com"]    
     7     start_urls = ["http://www.5442.com/tag/rosi.html"]    
     8     rules = (Rule(SgmlLinkExtractor(allow=('rosi/[d]+.html', )),callback='parse_href',),)
     9     def parse_href(self,response):#注意 回调函数不要命名为parse 否则出bug
    10         print response.url

      得到的结果如下:

      天杀的,明明是1-25页好不好,怎么只有这么几个,上面说了如果不设置follow的话默认为false,所以访问了这个就不继续了,我们设置为True就对了。

      我们还是要分析一下这个流程。我们从起始页面:http://www.5442.com/tag/rosi.html 我们需要得到符合条件为tag/rosi/[0-9]+/.html的所有页面,然后访问这些页面得到所有图片集的地址如:http://www.5442.com/meinv/20150904/27062.html和http://www.5442.com/meinv/20150904/27062_2.html,分析可得[0-9_]+.html。这样我们就得到了所有包含我们需要下载图片url的地址,我们就可以根据XPath得到图片url进行下载。所以我们的爬虫Rule是这样的:

     1 import scrapy,re,urllib2
     2 from scrapy.http import Request
     3 from scrapy.contrib.spiders import CrawlSpider,Rule
     4 from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor
     5 from scrapy.selector import Selector
     6 from scrapydemo.items import *
     7 
     8 class RosiSpider(CrawlSpider):
     9     name = "rosi"
    10     number = 0
    11     allowed_domains = ["5442.com"]
    12     start_urls = ["http://www.5442.com/tag/rosi.html"]
    13     rules = (Rule(SgmlLinkExtractor(allow=('rosi/[d]+.html', )),follow=True),
    14             Rule(SgmlLinkExtractor(allow=('[0-9_]+.html', )),callback='parse_img',follow=True)
    15         )

      第一条Rule我们得到了rosi的所有页面的信息,在访问这些页面的时候我们并不需要进行处理,所以我们不需要回调函数,然后我们从这些页面信息中提取出了所有的图片集页面,然后我们将图片集页面的返回值response给回调函数进行处理:

     1 def parse_img(self,response):
     2         #print response.url
     3         sel = Selector(response)
     4         src = sel.xpath("//div[@class='arcBody']//p[@id='contents']//a//img/@src").extract()
     5         for item in src:
     6             self.saveimg(item)
     7 
     8     def saveimg(self,url):
     9         savePath = '%d.jpg'%(self.number)
    10         print url
    11         self.number += 1
    12         try:
    13             u = urllib2.urlopen(url)
    14             r = u.read()
    15             downloadFile = open(savePath,'wb')
    16             downloadFile.write(r)
    17             u.close()
    18             downloadFile.close()
    19         except:
    20             print savePath,'can not download.'

      可能我们要问了,这就完了? items.py 和 pipeline.py咋没用上呢。那就来谈谈这两个:

      Items

      爬取的主要目标就是从非结构性的数据源提取结构性数据,例如网页。 Scrapy提供 Item类来满足这样的需求。Item 对象是种简单的容器,保存了爬取到得数据。 其提供了 类似于词典(dictionary-like) 的API以及用于声明可用字段的简单语法。

    1 import scrapy
    2 
    3 class Product(scrapy.Item):
    4     name = scrapy.Field()
    5     price = scrapy.Field()
    6     stock = scrapy.Field()

      他就是一个model,我们可以在回调函数中通过XPath得到内容 然后新建一个Item对象,赋值给他,

    1 def parse_href(self,response):
    2     items = []
    3     item = Product()
    4     item["name"] = "xxx"
    5     item["price"] = "xxx"
    6     items.append(item)
    7     return items

      注意,这里我们返回了一个items!!!当Item在Spider中被收集之后,它将会被传递到Item Pipeline,一些组件会按照一定的顺序执行对Item的处理。每个item pipeline组件(有时称之为“Item Pipeline”)是实现了简单方法的Python类。他们接收到Item并通过它执行一些行为,同时也决定此Item是否继续通过pipeline,或是被丢弃而不再进行处理。

      以下是item pipeline的一些典型应用:

    • 清理HTML数据
    • 验证爬取的数据(检查item包含某些字段)
    • 查重(并丢弃)
    • 将爬取结果保存到数据库中

      我们可以在pipelines.py中编写自己的itempipeline方法。你必须实现process_item(self,item,spider)方法。更多内容 看官方文档。。。

      让我们来看一下以下这个假设的pipeline,它为那些不含税(price_excludes_vat 属性)的item调整了price 属性,同时丢弃了那些没有价格的item:

     1 from scrapy.exceptions import DropItem
     2 
     3 class PricePipeline(object):
     4 
     5     vat_factor = 1.15
     6 
     7     def process_item(self, item, spider):
     8         if item['price']:
     9             if item['price_excludes_vat']:
    10                 item['price'] = item['price'] * self.vat_factor
    11             return item
    12         else:
    13             raise DropItem("Missing price in %s" % item)

      以下pipeline将所有(从所有spider中)爬取到的item,存储到一个独立地 items.jl 文件,每行包含一个序列化为JSON格式的item:

     1 import json
     2 
     3 class JsonWriterPipeline(object):
     4 
     5     def __init__(self):
     6         self.file = open('items.jl', 'wb')
     7 
     8     def process_item(self, item, spider):
     9         line = json.dumps(dict(item)) + "
    "
    10         self.file.write(line)
    11         return item

      好了,今天就到这儿吧。。。其实我现在也蒙蒙的,接下来就是在实际应用中去提升了,毕竟熟能生巧!!战斗吧 Scrapy!

  • 相关阅读:
    POJ 3672 水题......
    POJ 3279 枚举?
    STL
    241. Different Ways to Add Parentheses
    282. Expression Add Operators
    169. Majority Element
    Weekly Contest 121
    927. Three Equal Parts
    910. Smallest Range II
    921. Minimum Add to Make Parentheses Valid
  • 原文地址:https://www.cnblogs.com/jixin/p/5158177.html
Copyright © 2011-2022 走看看