zoukankan      html  css  js  c++  java
  • scrapy爬虫框架

    Scrapy不是一个函数功能库,而是一个爬虫框架
    爬虫框架是实现爬虫功能的一个软件结构和功能组件集合。
    爬虫框架是一个半成品,能够帮助用户实现专业网络爬虫。

    1.scrapy爬虫框架结构

    数据流向步骤1:

    1 EngineSpider处获得爬取请求(Request)
    2 Engine将爬取请求转发给Scheduler,用于调度

    数据流向步骤2:

    3 EngineScheduler处获得下一个要爬取的请求

    4 Engine将爬取请求通过中间件发送给Downloader

    5 爬取网页后,Downloader形成响应(Response通过中间件发给Engine

    6 Engine将收到的响应通过中间件发送给Spider处理

    数据流向步骤3:

    7 Spider处理响应后产生爬取项(scraped Item和新的爬取请求(Requests)给Engine

    8 Engine将爬取项发送给Item Pipeline(框架出口)
    9 Engine将爬取请求发送给Scheduler

    Engine控制各模块数据流,不间断从Scheduler
    获得爬取请求,直至请求为空
    框架入口:Spider的初始爬取请求
    框架出口:Item Pipeline
    engine、scheduler、Downloader是已有实现,spiders和pipline需要编写。

    Engine
    (1) 控制所有模块之间的数据流
    (2) 根据条件触发事件

    Downloader
    根据请求下载网页

    Scheduler

    对所有爬取请求进行调度管理 

    Downloader Middleware

    目的:实施EngineSchedulerDownloader之间进行用户可配置的控制

    功能:修改、丢弃、新增请求或响应
    用户可以编写配置代码

    Spider
    (1) 解析Downloader返回的响应(Response
    (2) 产生爬取项(scraped item
    (3) 产生额外的爬取请求(Request

    用户可以编写配置代码

    Item Pipelines
    (1) 以流水线方式处理Spider产生的爬取项
    (2) 由一组操作顺序组成,类似流水线,每个操作是一个Item Pipeline类型
    (3) 可能操作包括:清理、检验和查重爬取项中的HTML数据、将数据存储到数据库
    需要用户编写配置代码

    Spider Middleware
    目的:对请求和爬取项的再处理
    功能:修改、丢弃、新增请求或爬取项
    2.scrapy常用命令

    startproject   创建一个新工程  scrapy startproject <name> [dir]
    genspider    创建一个爬虫  scrapy genspider [options] <name> <domain>
    settings      获得爬虫配置信息  scrapy settings [options]
    crawl         运行一个爬虫  scrapy crawl <spider>
    list             列出工程中所有爬虫  scrapy list
    shell         启动URL调试命令行  scrapy shell [url]        

    3.创建scrapy

    应用Scrapy爬虫框架主要是编写配置型代码

    步骤1:建立一个Scrapy爬虫工程
    选取一个目录(D:,然后执行如下命令

    scrapy startproject <name> [dir]
    生成的目录结构


    # -*- coding: utf-8 -*-
    import scrapy
    import sys
    import io
    from scrapy.http import Request
    from scrapy.selector import Selector, HtmlXPathSelector
    from ..items import ChoutiItem
    
    sys.stdout = io.TextIOWrapper(sys.stdout.buffer,encoding='gb18030')
    
    class ChoutiSpider(scrapy.Spider):
        name = "chouti"
        allowed_domains = ["chouti.com"]
        start_urls = ['http://dig.chouti.com/']
    
        visited_urls =set()
    
        # def start_requests(self):
        #     for url in self.start_urls:
        #         yield Request(url,callback=self.parse)
    
        def parse(self, response):
            # content = str(response.body,encoding='utf-8')
            # 找到文档中所有A标签
            # hxs = Selector(response=response).xpath('//a') # 标签对象列表
            # for i in hxs:
            #     print(i) # 标签对象
    
            # 对象转换为字符串
            # hxs = Selector(response=response).xpath('//div[@id="content-list"]/div[@class="item"]').extract()  # 标签对象列表
            # hxs = Selector(response=response).xpath('//div[@id="content-list"]/div[@class="item"]')  # 标签对象列表
            # for obj in hxs:
            #     a = obj.xpath('.//a[@class="show-content"]/text()').extract_first()
            #     print(a.strip())
            # 选择器:
            """
            //   表示子孙中
            .//  当前对象的子孙中
            /    儿子
            /div 儿子中的div标签
            /div[@id="i1"]  儿子中的div标签且id=i1
            /div[@id="i1"]  儿子中的div标签且id=i1
            obj.extract()         # 列表中的每一个对象转换字符串 =》 []
            obj.extract_first()   # 列表中的每一个对象转换字符串 => 列表第一个元素
            //div/text()    获取某个标签的文本
    
           """
    
            # 获取当前页的所有页码
    
            # hxs = Selector(response=response).xpath('//div[@id="dig_lcpage"]//a/text()')
            # hxs0 = Selector(response=response).xpath('//div[@id="dig_lcpage"]//a/@href').extract()
            # for item in hxs0:
            #     if item in self.visited_urls:
            #         print('已经存在', item)
            #     else:
            #         self.visited_urls.add(item)
            #         print(item)
    
            # hxs2 = Selector(response=response).xpath('//div[@id="dig_lcpage"]//a/@href').extract()
            # hxs2 = Selector(response=response).xpath('//a[starts-with(@href, "/all/hot/recent/")]/@href').extract()
        #     hxs2 = Selector(response=response).xpath('//a[re:test(@href, "/all/hot/recent/d+")]/@href').extract()
        #     for url in hxs2:
        #         md5_url = self.md5(url)
        #         if md5_url in self.visited_urls:
        #             pass
        #             # print('已经存在', url)
        #         else:
        #             self.visited_urls.add(md5_url)
        #             print(url)
        #             url = "http://dig.chouti.com%s" %url
        #             # 将新要访问的url添加到调度器
        #             yield Request(url=url,callback=self.parse)
        #             # a/@href  获取属性
        #             # //a[starts-with(@href, "/all/hot/recent/")]/@href'  已xx开始
        #             # //a[re:test(@href, "/all/hot/recent/d+")]          正则
        #             # yield Request(url=url,callback=self.parse)          # 将新要访问的url添加到调度器
        #             # 重写start_requests,指定最开始处理请求的方法
        #
        #             # def show(self,response):
        #             #     print(response.text)
        #
        # def md5(self, url):
        #     import hashlib
        #     obj = hashlib.md5()
        #     obj.update(bytes(url, encoding='utf-8'))
        #     return obj.hexdigest()
    
    
            # hxs = HtmlXPathSelector(response)
            # response
            hxs1 = Selector(response=response).xpath('//div[@id="content-list"]/div[@class="item"]')  # 标签对象列表
            for obj in hxs1:
                title = obj.xpath('.//a[@class="show-content color-chag"]/text()').extract_first().strip()
                href =  obj.xpath('.//a[@class="show-content color-chag"]/@href').extract_first().strip()
                # print(title)
                # print(href)
                item_obj = ChoutiItem(title=title,href=href)
                # 将item对象传递给pipeline
                yield item_obj
    初试scrapy

    parse()用于处理响应,解析内容形成字典,发现新的URL爬取请求 
    配置产生的spider爬虫:(1)初始URL地址 (2)获取页面后的解析方式 

    yield 生成器
    生成器每次产生一个值(yield语句),函数被冻结,被唤醒后再产生一个值,生成器是一个不断产生值的函数
    生成器相比一次列出所有内容的优势
    1)更节省存储空间
    2)响应更迅速
    3)使用更灵活
    4.scrapy数据类型

    request类:

    class scrapy.http.Request()
    Request对象表示一个HTTP请求
    Spider生成,由Downloader执行

    属性或方法 说明
    .url Request对应的请求URL地址
    .method 对应的请求方法,'GET' 'POST'等
    .headers 字典类型风格的请求头
    .body 请求内容主体,字符串类型
    .meta 用户添加的扩展信息,在Scrapy内部模块间传递信息使用
    .copy() 复制该请求

    response类:

    class scrapy.http.Response()
    Response对象表示一个HTTP响应
    Downloader生成,由Spider处理

    属性或方法 说明
    .url Response对应的URL地址
    .status HTTP状态码,默认是200
    .headers Response对应的头部信息
    .body Response对应的内容信息,字符串类型
    .flags 一组标记
    .request 产生Response类型对应的Request对象
    .copy() 复制该响应

    Item类

    class scrapy.item.Item()
    Item对象表示一个从HTML页面中提取的信息内容
    Spider生成,由Item Pipeline处理
    Item类似字典类型,可以按照字典类型操作 

    Scrapy爬虫支持多种HTML信息爬取方法:

    Beautiful Soup
    lxml
    re
    XPath Selector
    CSS Selector
    settings.py配置并发连接选项
    选项 说明
    CONCURRENT_REQUESTS Downloader最大并发请求下载数量,默认32
    CONCURRENT_ITEMS Item Pipeline最大并发ITEM处理数量,默认100
    CONCURRENT_REQUESTS_PER_DOMAIN 每个目标域名最大的并发请求数量,默认8
    CONCURRENT_REQUESTS_PER_IP 每个目标IP最大的并发请求数量,默认0,非0有效

    5.格式化处理

    import scrapy
    import sys
    import io
    from scrapy.http import Request
    from scrapy.selector import Selector, HtmlXPathSelector
    from ..items import ChoutiItem
    
    
    sys.stdout = io.TextIOWrapper(sys.stdout.buffer,encoding='gb18030')
    
    class ChoutiSpider(scrapy.Spider):
        name = "chouti"
        allowed_domains = ["chouti.com"]
        start_urls = ['http://dig.chouti.com/']
    
        visited_urls =set()
     
            hxs1 = Selector(response=response).xpath('//div[@id="content-list"]/div[@class="item"]')  # 标签对象列表
            for obj in hxs1:
                title = obj.xpath('.//a[@class="show-content"]/text()').extract_first().strip()
                href =  obj.xpath('.//a[@class="show-content"]/@href').extract_first().strip()
                item_obj = ChoutiItem(title=title,href=href)
    
                # 将item对象传递给pipeline
                yield item_obj
    # -*- 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 ChoutiItem(scrapy.Item):
        # define the fields for your item here like:
        title = scrapy.Field()
        href = scrapy.Field()
    Item
    class Day96Pipeline(object):
        def process_item(self, item, spider):
            print(spider, item)
            # if spider.name == 'chouti'
            tpl = "%s
    %s
    
    " %(item['title'],item['href'])
            f = open('news.json', 'a')
            f.write(tpl)
            f.close()
    pipline
    ITEM_PIPELINES = {
       # 'day96.pipelines.Day96Pipeline': 300,
       # 'day96.pipelines.Day96Pipeline': 300,
       'day96.pipelines.Day96Pipeline': 300,
    }
    settings

    自定义pipeline

    from scrapy.exceptions import DropItem
    
    class Day96Pipeline(object):
    
        def __init__(self,conn_str):
            self.conn_str = conn_str
    
        @classmethod
        def from_crawler(cls, crawler):
            """
            初始化时候,用于创建pipeline对象
            :param crawler:
            :return:
            """
            conn_str = crawler.settings.get('DB')
            return cls(conn_str)
    
        def open_spider(self,spider):
            """
            爬虫开始执行时,调用
            :param spider:
            :return:
            """
            self.conn = open(self.conn_str, 'a')
    
        def close_spider(self,spider):
            """
            爬虫关闭时,被调用
            :param spider:
            :return:
            """
            self.conn.close()
    
        def process_item(self, item, spider):
            """
            每当数据需要持久化时,就会被调用
            :param item:
            :param spider:
            :return:
            """
            # if spider.name == 'chouti'
            tpl = "%s
    %s
    
    " %(item['title'],item['href'])
            self.conn.write(tpl)
            # 交给下一个pipeline处理
            return item
            # 丢弃item,不交给
            # raise DropItem()
    自定义pipeline

    6.cookies

    from scrapy.http.cookies import CookieJar
            cookie_obj = CookieJar()
            cookie_obj.extract_cookies(response,response.request)
            print(cookie_obj._cookies)
    # -*- coding: utf-8 -*-
    import scrapy
    import sys
    import io
    from scrapy.http import Request
    from scrapy.selector import Selector, HtmlXPathSelector
    from ..items import ChoutiItem
    
    
    sys.stdout = io.TextIOWrapper(sys.stdout.buffer,encoding='gb18030')
    from scrapy.http.cookies import CookieJar
    class ChoutiSpider(scrapy.Spider):
        name = "chouti"
        allowed_domains = ["chouti.com",]
        start_urls = ['http://dig.chouti.com/']
    
        cookie_dict = None
        def parse(self, response):
            print("spider.reponse",response)
    
            cookie_obj = CookieJar()
            cookie_obj.extract_cookies(response,response.request)
            self.cookie_dict = cookie_obj._cookies
            # 带上用户名密码+cookie
            yield Request(
                url="http://dig.chouti.com/login",
                method='POST',
                body = "phone=8615131255089&password=woshiniba&oneMonth=1",
                headers={'Content-Type': "application/x-www-form-urlencoded; charset=UTF-8"},
                cookies=cookie_obj._cookies,
                callback=self.check_login
            )
    
        def check_login(self,response):
            print(response.text)
            yield Request(url="http://dig.chouti.com/",callback=self.good)
    
    
        def good(self,response):
            id_list = Selector(response=response).xpath('//div[@share-linkid]/@share-linkid').extract()
            for nid in id_list:
                print(nid)
                url = "http://dig.chouti.com/link/vote?linksId=%s" % nid
                yield Request(
                    url=url,
                    method="POST",
                    cookies=self.cookie_dict,
                    callback=self.show
                )
    
            page_urls = Selector(response=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.good)
    
    
        def show(self,response):
            print(response.text)
    带cookie登录

    7.自定义扩展

    from scrapy import signals
    class MyExtend:
    
        def __init__(self,crawler):
            self.crawler = crawler
            # 钩子上挂障碍物
            # 在指定信号上注册操作
            crawler.signals.connect(self.start, signals.engine_started)
            crawler.signals.connect(self.close, signals.spider_closed)
    
        @classmethod
        def from_crawler(cls, crawler):
            return cls(crawler)
    
        def start(self):
            print('signals.engine_started.start')
    
        def close(self):
            print('signals.spider_closed.close')
    extension

    8.证书

      from scrapy.contrib.downloadermiddleware.httpproxy import HttpProxyMiddleware
        
        方式一:使用默认
            os.environ
            {
                http_proxy:http://root:@ip:port/
                https_proxy:http://ip:port/
            }
        方式二:使用自定义下载中间件
        
        def to_bytes(text, encoding=None, errors='strict'):
            if isinstance(text, bytes):
                return text
            if not isinstance(text, six.string_types):
                raise TypeError('to_bytes must receive a unicode, str or bytes '
                                'object, got %s' % type(text).__name__)
            if encoding is None:
                encoding = 'utf-8'
            return text.encode(encoding, errors)
            
        class ProxyMiddleware(object):
            def process_request(self, request, spider):
                PROXIES = [
                    {'ip_port': '111.11.228.75:80', 'user_pass': ''},
                    {'ip_port': '120.198.243.22:80', 'user_pass': ''},
                    {'ip_port': '111.8.60.9:8123', 'user_pass': ''},
                    {'ip_port': '101.71.27.120:80', 'user_pass': ''},
                    {'ip_port': '122.96.59.104:80', 'user_pass': ''},
                    {'ip_port': '122.224.249.122:8088', 'user_pass': ''},
                ]
                proxy = random.choice(PROXIES)
                if proxy['user_pass'] is not None:
                    request.meta['proxy'] = to_bytes("http://%s" % proxy['ip_port'])
                    encoded_user_pass = base64.encodestring(to_bytes(proxy['user_pass']))
                    request.headers['Proxy-Authorization'] = to_bytes('Basic ' + encoded_user_pass)
                    print "**************ProxyMiddleware have pass************" + proxy['ip_port']
                else:
                    print "**************ProxyMiddleware no pass************" + proxy['ip_port']
                    request.meta['proxy'] = to_bytes("http://%s" % proxy['ip_port'])
        
        DOWNLOADER_MIDDLEWARES = {
           'step8_king.middlewares.ProxyMiddleware': 500,
        }
    certification

    9.中间件

    class SpiderMiddleware(object):
    
        def process_spider_input(self,response, spider):
            """
            下载完成,执行,然后交给parse处理
            :param response: 
            :param spider: 
            :return: 
            """
            pass
    
        def process_spider_output(self,response, result, spider):
            """
            spider处理完成,返回时调用
            :param response:
            :param result:
            :param spider:
            :return: 必须返回包含 Request 或 Item 对象的可迭代对象(iterable)
            """
            return result
    
        def process_spider_exception(self,response, exception, spider):
            """
            异常调用
            :param response:
            :param exception:
            :param spider:
            :return: None,继续交给后续中间件处理异常;含 Response 或 Item 的可迭代对象(iterable),交给调度器或pipeline
            """
            return None
    
    
        def process_start_requests(self,start_requests, spider):
            """
            爬虫启动时调用
            :param start_requests:
            :param spider:
            :return: 包含 Request 对象的可迭代对象
            """
            return start_requests
    爬虫中间件
    class DownMiddleware1(object):
        def process_request(self, request, spider):
            """
            请求需要被下载时,经过所有下载器中间件的process_request调用
            :param request: 
            :param spider: 
            :return:  
                None,继续后续中间件去下载;
                Response对象,停止process_request的执行,开始执行process_response
                Request对象,停止中间件的执行,将Request重新调度器
                raise IgnoreRequest异常,停止process_request的执行,开始执行process_exception
            """
            pass
    
    
    
        def process_response(self, request, response, spider):
            """
            spider处理完成,返回时调用
            :param response:
            :param result:
            :param spider:
            :return: 
                Response 对象:转交给其他中间件process_response
                Request 对象:停止中间件,request会被重新调度下载
                raise IgnoreRequest 异常:调用Request.errback
            """
            print('response1')
            return response
    
        def process_exception(self, request, exception, spider):
            """
            当下载处理器(download handler)或 process_request() (下载中间件)抛出异常
            :param response:
            :param exception:
            :param spider:
            :return: 
                None:继续交给后续中间件处理异常;
                Response对象:停止后续process_exception方法
                Request对象:停止中间件,request将会被重新调用下载
            """
            return None
    下载中间件

     10.自定制命令

    scrapy除了命令行,还可以在程序中启动爬虫,由于scrapy实在Twisted异步网络库上构建的,因此必须在Twisted reactor里运行。

    from scrapy.commands import ScrapyCommand
    from scrapy.utils.project import get_project_settings
    
    
    class Command(ScrapyCommand):
    
        requires_project = True
    
        def syntax(self):
            return '[options]'
    
        def short_desc(self):
            return 'Runs all of the spiders'
    
        def run(self, args, opts):
            # 找到所有的爬虫名称
            print(type(self.crawler_process))
            #<class 'scrapy.crawler.CrawlerProcess'>
            from scrapy.crawler import CrawlerProcess
            # 1. 执行CrawlerProcess构造方法
            # 2. CrawlerProcess对象含有配置文件的spiders
                # 2.1,为每个爬虫创建一个Crawler
                # 2.2,执行 d = Crawler.crawl(...)   # ************************ #
                #           d.addBoth(_done)
                # 2.3, CrawlerProcess对象._active = {d,}
    
            # 3. dd = defer.DeferredList(self._active)
            #    dd.addBoth(self._stop_reactor)  # self._stop_reactor ==> reactor.stop()
            #    reactor.run
    
            # 获取当前所有爬虫的名称
            spider_list = self.crawler_process.spiders.list()
            # spider_list = ["chouti",'cnblogs']
    
            for name in spider_list:
                self.crawler_process.crawl(name, **opts.__dict__)
            self.crawler_process.start()
    commandrun
  • 相关阅读:
    Java中的I/O 线程 网络
    20169214 2016-2017-2 《网络攻防实践》第六周学习总结
    20169214 2016-2017-2 《网络攻防实践》第五周学习总结
    20169214 2016-2017-2 《移动平台开发实践》第五周学习总结
    20169214 2016-2017-2 《移动平台开发实践》第四周学习总结
    20169214 2016-2017-2 《网络攻防实践》第四周学习总结
    20169214 2016-2017-2 《网络攻防实践》第三周学习总结
    20169214 2016-2017-2 《移动平台开发实践》第三周学习总结
    20169214 2016-2017-2《网络攻防实践》第二周学习总结
    Android开发设计 实验报告
  • 原文地址:https://www.cnblogs.com/ningxin18/p/7860711.html
Copyright © 2011-2022 走看看