scrapy 笔记

scrapy是python最有名的爬虫框架之一,可以很方便的进行web抓取,并且提供了很强的定制型,这里记录简单学习的过程和在实际应用中会遇到的一些常见问题

一、安装

在安装scrapy之前有一些依赖需要安装,否则可能会安装失败,scrapy的选择器依赖于lxml,还有Twisted网络引擎,下面是ubuntu下安装的过程

linux下安装

# 1. 安装xml依赖库
$ sudo apt-get install libxml2 libxml2-dev
$ sudo apt-get install libxslt1-dev
$ sudo apt-get install python-libxml2
 
# 2. 安装lxml
$ sudo pip install lxml
 
# 3. 安装Twisted(版本可以换成最新的),用pip也可以,如果失败的话下载源码安装,如下
$ wget https://pypi.python.org/packages/6b/23/8dbe86fc83215015e221fbd861a545c6ec5c9e9cd7514af114d1f64084ab/Twisted-16.4.1.tar.bz2#md5=c6d09bdd681f538369659111f079c29d
$ tar xjf Twisted-16.4.1.tar.bz2
$ cd Twisted-16.4.1
$ sudo python setup.py install
 
# 3. 安装scrapy
$ sudo pip install scrapy

windows 下的安装

pip install scrapy   # py2  

二、基本使用

1. 初始化scrapy项目

我们可以使用命令行初始化一个项目

html = """
        <html>
            <body>
                <span>good</span>
                <span>buy</span>
                <ul>
                    <li class="video_part_lists">aa<li>
                    <li class="video_part_lists">bb<li>
                    <li class="audio_part_lists">cc<li>
                    <li class="video_part_lists">
                        <a href="/">主页</a>
                    <li>
                </ul>
            </body>
        </html>
        """
sel = Selector(text=html)
 
# 选择class为video_part_lists的li节点
lis = sel.css('li.video_part_lists')
 
for li in lis:
    # 选择a节点的属性
    print li.css('a::attr(href)').extract()

五、Item类

上面我们只是爬取了网页的html文本,对于爬虫,我们需要明确我们需要爬取的结构化数据,我们定义一个item存储分类信息,scrapy的item继承自scrapy.Item。注:此处类似于Django中的model可以参考Django官方文档

https://docs.djangoproject.com/en/1.9/ref/models/

from scrapy import Item, Field
 
class DmozItem(Item):
    title = Field()
    link = Field()
    desc = Field()

scrapy.Item的用法与python中的字典用法基本一样,只是做了一些安全限制,属性定义使用Field,这里只是进行了声明,而不是真正的属性,使用的时候通过键值对操作,不支持属性访问

在进行写入本地时一般借用json.dumps  后面会说明抓取的数据保存到MongoDB中

修改DmozSpider的parse方法

class DmozSpider(scrapy.Spider):
    ...
    def parse(self, response):
        for sel in response.xpath('//ul/li'):
            dmoz_item = DmozItem()
            dmoz_item['title'] = sel.xpath('a/text()').extract()
            dmoz_item['link'] = sel.xpath('a/@href').extract()
            dmoz_item['desc'] = sel.xpath('text()').extract()
            print dmoz_item   #此处可以使用return 也可使用yield  ,使用return 表示不把数据返回给pipelines进行处理

六、Pipeline

spider负责爬虫的配置,item负责声明结构化数据,而对于数据的处理,在scrapy中使用管道的方式进行处理,只要注册过的管道都可以处理item数据(处理,过滤,保存)

下面看看管道的声明方式,这里定义一个预处理管道PretreatmentPipeline.py,如果item的title为None,则设置为空字符串

class PretreatmentPipeline(object):
    def process_item(self, item, spider):
        if item['title']:
            # 不让title为空
            item['title'] = ''
        return item

再定义一个过滤重复数据的管道DuplicatesPipeline.py,当link重复,则丢弃

from scrapy.exceptions import DropItem
 
class DuplicatesPipeline(object):
    def __init__(self):
        self.links = set()
 
    def process_item(self, item, spider):
        if item['link'] in self.links:
            # 跑出DropItem表示丢掉数据
            raise DropItem("Duplicate item found: %s" % item)
        else:
            self.links.add(item['link'])
            return item

最后可以定义一个保存数据的管道,可以把数据保存到数据库中

from scrapy.exceptions import DropItem
from Database import Database
 
class DatabasePipeline(object):
    def __init__(self):
        self.db = Database
 
    def process_item(self, item, spider):
        if self.db.item_exists(item['id']):
            self.db.update_item(item)
        else:
            self.db.insert_item(item)

定义好管道之后我们需要配置到爬虫上,我们在settings.py模块中配置,后面的数字表示管道的顺序

ITEM_PIPELINES = {
    'pipelines.DuplicatesPipeline.DuplicatesPipeline': 1,
    'pipelines.PretreatmentPipeline.PretreatmentPipeline': 2,
}
#此处的数字越小越先执行

除了process_item方法外,pipeline还有open_spiderspider_closed两个方法,在爬虫启动和关闭的时候调用

七、Rule

爬虫的通常需要在一个网页里面爬去其他的链接,然后一层一层往下爬,scrapy提供了LinkExtractor类用于对网页链接的提取,使用LinkExtractor需要使用CrawlSpider爬虫类中,CrawlSpiderSpider相比主要是多了rules,可以添加一些规则,先看下面这个例子,爬取链家网的链接

from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor
 
class LianjiaSpider(CrawlSpider):
    name = "lianjia"
 
    allowed_domains = ["lianjia.com"]
 
    start_urls = [
        "http://bj.lianjia.com/ershoufang/"
    ]
 
    rules = [
        # 匹配正则表达式,处理下一页
        Rule(LinkExtractor(allow=(r'http://bj.lianjia.com/ershoufang/pg\s+$',)), callback='parse_item'),
 
        # 匹配正则表达式,结果加到url列表中,设置请求预处理函数
        # Rule(FangLinkExtractor(allow=('http://www.lianjia.com/client/', )), follow=True, process_request='add_cookie')
    ]
 
    def parse_item(self, response):
        # 这里与之前的parse方法一样,处理
        pass

1. Rule对象

Role对象有下面参数

  • link_extractor:链接提取规则
  • callback:link_extractor提取的链接的请求结果的回调
  • cb_kwargs:附加参数,可以在回调函数中获取到
  • follow:表示提取的链接请求完成后是否还要应用当前规则(boolean),如果为False则不会对提取出来的网页进行进一步提取,默认为False
  • process_links:处理所有的链接的回调,用于处理从response提取的links,通常用于过滤(参数为link列表)
  • process_request:链接请求预处理(添加header或cookie等)

2. LinkExtractor

LinkExtractor常用的参数有:

  • allow:提取满足正则表达式的链接
  • deny:排除正则表达式匹配的链接(优先级高于allow
  • allow_domains:允许的域名(可以是strlist
  • deny_domains:排除的域名(可以是strlist
  • restrict_xpaths:提取满足XPath选择条件的链接(可以是strlist
  • restrict_css:提取满足css选择条件的链接(可以是strlist
  • tags:提取指定标签下的链接,默认从aarea中提取(可以是strlist
  • attrs:提取满足拥有属性的链接,默认为href(类型为list
  • unique:链接是否去重(类型为boolean
  • process_value:值处理函数(优先级大于allow

关于LinkExtractor的详细参数介绍见官网

注意:如果使用rules规则,请不要覆盖或重写CrawlSpiderparse方法,否则规则会失效,可以使用parse_start_urls方法

 

八、Middleware

从最开始的流程图可以看到,爬去一个资源链接的流程,首先我们配置spider相关的爬取信息,在启动爬取实例后,scrapy_engine从Spider取出Request(经过SpiderMiddleware),然后丢给Scheduler(经过SchedulerMiddleware),Scheduler接着把请求丢给Downloader(经过DownloadMiddlware),Downloader把请求结果丢还给Spider,然后Spider把分析好的结构化数据丢给Pipeline,Pipeline进行分析保存或丢弃,这里面有4个角色

scrapy有下面三种middlewares

  • SpiderMiddleware:通常用于配置爬虫相关的属性,引用链接设置,Url长度限制,成功状态码设置,爬取深度设置,爬去优先级设置等
  • DownloadMiddlware:通常用于处理下载之前的预处理,如请求Header(Cookie,User-Agent),登录验证处理,重定向处理,代理服务器处理,超时处理,重试处理等
  • SchedulerMiddleware(已经废弃):为了简化框架,调度器中间件已经被废弃,使用另外两个中间件已经够用了

1. SpiderMiddleware

爬虫中间件有下面几个方法

  • process_spider_input:当response通过spider的时候被调用,返回None(继续给其他中间件处理)或抛出异常(不会给其他中间件处理,当成异常处理)
  • process_spider_output:当spider有item或Request输出的时候调动
  • process_spider_exception:处理出现异常时调用
  • process_start_requests:spider当开始请求Request的时候调用

下面是scrapy自带的一些中间件(在scrapy.spidermiddlewares命名空间下)

  • UrlLengthMiddleware
  • RefererMiddleware
  • OffsiteMiddleware
  • HttpErrorMiddleware
  • DepthMiddleware

2. DownloaderMiddleware

下载中间件有下面几个方法

  • process_request:请求通过下载器的时候调用
  • process_response:请求完成后调用
  • process_exception:请求发生异常时调用
  • from_crawler:从crawler构造的时候调用
  • from_settings:从settings构造的时候调用

更多详细的参数解释见这里

在爬取网页的时候,使用不同的User-Agent可以提高请求的随机性,定义一个随机设置User-Agent的中间件RandomUserAgentMiddleware

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

# Define here the models for your spider middleware
#
# See documentation in:
# http://doc.scrapy.org/en/latest/topics/spider-middleware.html

from scrapy import signals


class RenrenSpiderMiddleware(object):
    # Not all methods need to be defined. If a method is not defined,
    # scrapy acts as if the spider middleware does not modify the
    # passed objects.

    @classmethod
    def from_crawler(cls, crawler):
        # This method is used by Scrapy to create your spiders.
        s = cls()
        crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
        return s

    def process_spider_input(self, response, spider):
        # Called for each response that goes through the spider
        # middleware and into the spider.

        # Should return None or raise an exception.
        return None

    def process_spider_output(self, response, result, spider):
        # Called with the results returned from the Spider, after
        # it has processed the response.

        # Must return an iterable of Request, dict or Item objects.
        for i in result:
            yield i

    def process_spider_exception(self, response, exception, spider):
        # Called when a spider or process_spider_input() method
        # (from other spider middleware) raises an exception.

        # Should return either None or an iterable of Response, dict
        # or Item objects.
        pass

    def process_start_requests(self, start_requests, spider):
        # Called with the start requests of the spider, and works
        # similarly to the process_spider_output() method, except
        # that it doesn’t have a response associated.

        # Must return only requests (not items).
        for r in start_requests:
            yield r

    def spider_opened(self, spider):
        spider.logger.info('Spider opened: %s' % spider.name)

settings.py设置USER_AGENTS参数

 

USER_AGENTS = [
    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; AcooBrowser; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
    "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; Acoo Browser; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506)",
    "Mozilla/4.0 (compatible; MSIE 7.0; AOL 9.5; AOLBuild 4337.35; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
    "Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)",
    "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)",
    "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)",
    "Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.2; .NET CLR 3.0.04506.30)",
    "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/523.15 (KHTML, like Gecko, Safari/419.3) Arora/0.3 (Change: 287 c9dfb30)",
    "Mozilla/5.0 (X11; U; Linux; en-US) AppleWebKit/527+ (KHTML, like Gecko, Safari/419.3) Arora/0.6",
    "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.2pre) Gecko/20070215 K-Ninja/2.1.1",
    "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9) Gecko/20080705 Firefox/3.0 Kapiko/3.0",
    "Mozilla/5.0 (X11; Linux i686; U;) Gecko/20070322 Kazehakase/0.4.5",
    "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.8) Gecko Fedora/1.9.0.8-1.fc10 Kazehakase/0.5.6",
    "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.20 (KHTML, like Gecko) Chrome/19.0.1036.7 Safari/535.20",
    "Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; fr) Presto/2.9.168 Version/11.52",
]
#此处可添加IP代理,防止我们的IP被封

PROXIES = [
  {"ip_port" :"121.42.140.113:16816", "user_passwd" : "xxxxxxxx:xxxx"},   #"xxxxxxxx:xxxx" 表示账号密码
  {'ip_port':'153.35.206.77:8118','user_passwd':''},
  {"ip_port":'124.92.199.28:80','user_passwd':''},
]

 配置爬虫中间件的方式与pipeline类似,第二个参数表示优先级

# 配置爬虫中间件
SPIDER_MIDDLEWARES = {
    'myproject.middlewares.CustomSpiderMiddleware': 543,
    # 如果想禁用默认的中间件的话,可以设置其优先级为None
    'scrapy.spidermiddlewares.offsite.OffsiteMiddleware': None,
}
 
# 配置下载中间件
DOWNLOADER_MIDDLEWARES = {
    'myproject.middlewares.RandomUserAgentMiddleware': 543,
    'scrapy.contrib.downloadermiddleware.useragent.UserAgentMiddleware': None,
}
#后面的案例中会有使用到

九、缓存

scrapy默认已经自带了缓存的功能,通常我们只需要配置即可,打开settings.py

posted @ 2018-02-03 17:51  随风无义  阅读(395)  评论(0)    收藏  举报