• scrapy


    Scrapy 内部集成了Twisted异步网络框架,可以加快我们的下载速度。

    未使用scrapy框架之前的爬虫

    使用之后

    1 爬虫中起始的url构造成request对象-->爬虫中间件-->引擎-->调度器
    2 调度器把request-->引擎-->下载中间件--->下载器
    3 下载器给互联网发送请求,获取response响应---->下载中间件---->引擎--->爬虫中间件--->爬虫
    4 爬虫提取url地址,组装成request对象---->爬虫中间件--->引擎--->调度器,重复步骤2
    5 爬虫提取数据--->引擎--->管道处理和保存数据

    图中绿色线条的表示数据的传递
    注意图中中间件的位置,决定了其作用,中间件类似于请求钩子,把请求拦截,做处理再返回
    注意其中引擎的位置,所有的模块之前相互独立,只和引擎进行交互

    爬虫中间件和下载中间件只是运行逻辑的位置不同,作用是重复的:如替换UA等

    由于scrapy已经帮我们封装了很多的功能,我们只需要对爬虫和管道部分做些补充就好
    引擎将定义的类实例化运行

    基本流程


    安装
    sudo apt-get install scrapy 或者:pip install scrapy

    1 创建scrapy项目

    2 创建爬虫


    完善spider
    image.png

    /myspider/myspider/spiders/itcast.py
    
    
    import scrapy
    
    class ItcastSpider(scrapy.Spider):  # 继承scrapy.spider
        # 爬虫名字 
        name = 'itcast' 
    
        # 允许爬取的范围
        allowed_domains = ['itcast.cn'] 
    
        # 开始爬取的url地址
        start_urls = ['http://www.itcast.cn/channel/teacher.shtml']
    
        # 数据提取的方法,接受下载中间件传过来的response
     def parse(self, response):
            # scrapy的response对象可以直接进行xpath
            names = response.xpath('//div[@class="tea_con"]//li/div/h3/text()') 
            print(names)
    
            # 获取具体数据文本的方式如下
            # 分组
            li_list = response.xpath('//div[@class="tea_con"]//li') 
            for li in li_list:
                # 创建一个数据字典
                item = {}
                # 利用scrapy封装好的xpath选择器定位元素,并通过extract()或extract_first()来获取结果
                item['name'] = li.xpath('.//h3/text()').extract_first() # 老师的名字
                item['level'] = li.xpath('.//h4/text()').extract_first() # 老师的级别
                item['text'] = li.xpath('.//p/text()').extract_first() # 老师的介绍
                print(item)
    
    完善spider
    class MaitianSpider(scrapy.Spider):
        #爬虫名字
        name = 'maitian'
        #抓取的域名范围,控制范围
        allowed_domains = ['maitian.cn']
        #
        start_urls = ['http://bj.maitian.cn/esfall']
    
        #解析方法
        def parse(self, response):
    
            #构建一个容器
            item = MiantianItem()
            item['title'] = response.xpath('/html/body/section[2]/div[2]/div[2]/ul/li[1]/div[2]/h1/a/text()').extract_first().strip()
            item['size_info'] = response.xpath('/html/body/section[2]/div[2]/div[2]/ul/li[1]/div[2]/p/text()').extract()[0].strip()
            item['price'] = response.xpath('/html/body/section[2]/div[2]/div[2]/ul/li[1]/div[2]/div/ol/strong/span/text()').extract()[0].strip()
            item['location'] = response.xpath('/html/body/section[2]/div[2]/div[2]/ul/li[1]/div[2]/dl/dd/mark[1]/text()').extract()[0]
            # print(response.body.decode())
            print(item)
    
    	#将item返回给engine,engine将数据传给管道
     return item
    

    3 运行爬虫

    4 解析数据

    image.png

    class MiantianItem(scrapy.Item):
        # define the fields for your item here like:
        # name = scrapy.Field()
        title=scrapy.Field()
        size_info=scrapy.Field()
        price=scrapy.Field()
        location=scrapy.Field()
    

    5 数据存储 管道

    image.png

    修改pipelines.py文件,完善process_item函数

    import json
    
    class ItcastPipeline(object):
        # 爬虫文件中提取数据的方法每yield一次item,就会运行一次
        def __init__(self):
            self.file.open('xxx.json','w')
    
        def process_item(self, item, spider):
    
            #将处理后的数据写入文件
            str_data = jaon.dumps(item) + ',
    '   
    	#当写入文件出现乱码时,可以在dumps后加入encode_ascii=False参数            
            self.file.write(str_data)
    
    	管道处理完item后必须将item对象返回给引擎。(当定义多个管道类的时候,将item返回,引擎自动下发给下一个管道类。)	
          return item
    def __del__(self):
             self.file.close()
    

    mongo  
    
    class MongoPipeline(object):
    
        def open_spider(self, spider):
            # spider中记录了爬虫名,可以根据爬虫名决定是否使用管道
            self.cli = MongoClient("127.0.0.1",27017)
            self.col = self.cli.python19.qq
    
        # process_item方法负责定义对数据的处理
        def process_item(self, item, spider):
    
            # 将item对象转换成Python字典
            item = dict(item)
    
            self.col.insert(item)
    
            # 管道处理完item之后必须将item对象返回给引擎
            return item
    
        def close_spider(self, spider):
    
            self.cli.close()
    

    在settings.py设置开启pipeline
    ITEM_PIPELINES = { 'myspider.pipelines.ItcastPipeline': 400, 优先级,根据权重值决定各个管道的先后顺序 }
    优先级范围从0-1000 优先级越小,优先执行

    spider类,item类,中间类,都是写成的类,他们在引擎中被调用,生成实例进行爬取数据

    其他操作


    中间件

    自定义中间件
    class UaMiddlewares(object):
        def __int__(self):
            self.user_agent_list=[
                'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.96 Safari/537.36',
                'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3626.96 Safari/537.36' ,
    。。。。。。。。
            ]
    
        def process_request(self,request,spider):
    
            user_random_agent=random.choice(self.user_agent_list)
            request.headers['User-Agent']=user_random_agent
    
    设置header属性
        def process_request(self,request,spider):
    
            proxy='http://188.188.188.188:8080'
            request.mete['proxy']=proxy
    
    设置meta属性
        def process_request(self,request,spider):
    
            proxy='http://188.188.188.188:8080'
            request.mete['proxy']=proxy
    

    cookie

    模拟登录 
    
    
    class ItloginSpider(scrapy.Spider):
        name = 'itlogin'
        allowed_domains = ['boxuegu.com']
        #登页面  找齐需要的参数
        start_urls = ['http://tlias-stu.boxuegu.com/#/login']
    
        def parse(self, response):
    
            #代码登陆
            formdata={
                'loginName':'A180803802',
                'password':'yy245088'
            }
            login_url = 'http://tlias-stu.boxuegu.com/user/login'
            #发送post请求
    
            #方法1
            # yield scrapy.FormRequest(
            #     login_url,
            #     formdata=formdata,
            #     callback=self.parser_login
            # )
    
            #方法2
            yield scrapy.Request(login_url,body=json.dumps(formdata),method='POST',callback=self.parser_login)
    
        def parser_login(self,response):
            #解析获取的cookie,让scrapy携带着有效的cookie继续请求目标url
            target_url='http://tlias-stu.boxuegu.com/#/setinfo'
            yield scrapy.Request(target_url,callback=self.parser_info)
        def parser_info(self,response):
            with open('1.html','wb') as f:
                f.write(response.body)
    

    meta

    scrapy构造并发送请求

    构造Request对象,发送请求

    class TencentItem(scrapy.Item): 
        name = scrapy.Field() # 招聘标题
        address = scrapy.Field() # 工作地址
        time = scrapy.Field() # 发布时间
        job_content = scrapy.Field() # 工作职责
    

    crawlspider

    子主题
    
    class LxmlLinkExtractor(FilteringLinkExtractor):
    
        def __init__(self, allow=(), deny=(), allow_domains=(), deny_domains=(), restrict_xpaths=(),
                     tags=('a', 'area'), attrs=('href',), canonicalize=False,
                     unique=True, process_value=None, deny_extensions=None, restrict_css=(),
                     strip=True):
            tags, attrs = set(arg_to_iter(tags)), set(arg_to_iter(attrs))
            tag_func = lambda x: x in tags
            attr_func = lambda x: x in attrs
            lx = LxmlParserLinkExtractor(
                tag=tag_func,
                attr=attr_func,
                unique=unique,
                process=process_value,
                strip=strip,
                canonicalized=canonicalize
            )
    
            super(LxmlLinkExtractor, self).__init__(lx, allow=allow, deny=deny,
                allow_domains=allow_domains, deny_domains=deny_domains,
                restrict_xpaths=restrict_xpaths, restrict_css=restrict_css,
                canonicalize=canonicalize, deny_extensions=deny_extensions)
    
        def extract_links(self, response):
            base_url = get_base_url(response)
            if self.restrict_xpaths:
                docs = [subdoc
                        for x in self.restrict_xpaths
                        for subdoc in response.xpath(x)]
            else:
                docs = [response.selector]
            all_links = []
            for doc in docs:
                links = self._extract_links(doc, response.url, response.encoding, base_url)
                all_links.extend(self._process_links(links))
            return unique_list(all_links)
    
    
    
    获取所有翻页url的实现
    class ErshouSpider(CrawlSpider):
        name = 'ershou'
        allowed_domains = ['chouti.com']
        start_urls = ['https://dig.chouti.com/']
    
        rules = (                        #找到翻页的href的正则进行匹配
            Rule(LinkExtractor(allow='/all/hot/recent'), callback='parse_item', follow=True),
        )
    
        def parse_item(self, response):
            print(response.url)
    
    解析方法
        def parse_item(self, response):
            item = {}
            #item['domain_id'] = response.xpath('//input[@id="sid"]/@value').get()
            #item['name'] = response.xpath('//div[@id="name"]').get()
            #item['description'] = response.xpath('//div[@id="description"]').get()
            return item
    

    scapy_redis 分布式



    scrapy_splash


    import scrapy
    from scrapy_splash import SplashRequest # 使用scrapy_splash包提供的request对象
    
    class WithSplashSpider(scrapy.Spider):
        name = 'with_splash'
        allowed_domains = ['baidu.com']
        start_urls = ['https://www.baidu.com/s?wd=13161933309']
    
        def start_requests(self):
            yield SplashRequest(self.start_urls[0],
                                callback=self.parse_splash,
                                args={'wait': 10}, # 最大超时时间,单位:秒
                                endpoint='render.html') # 使用splash服务的固定参数
    
        def parse_splash(self, response):
            with open('with_splash.html', 'w') as f:
                f.write(response.body.decode())
    
     SPLASH_URL = 'http://127.0.0.1:8050'
     DOWNLOADER_MIDDLEWARES = {
         'scrapy_splash.SplashCookiesMiddleware': 723,
         'scrapy_splash.SplashMiddleware': 725,
         'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 810,
     }
     DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter'
     HTTPCACHE_STORAGE = 'scrapy_splash.SplashAwareFSCacheStorage'
    

    配置




    scrapy_redis和scrapy_splash配合使用

    重写dupefilter去重类
    from __future__ import absolute_import
    
    from copy import deepcopy
    
    from scrapy.utils.request import request_fingerprint
    from scrapy.utils.url import canonicalize_url
    
    from scrapy_splash.utils import dict_hash
    
    from scrapy_redis.dupefilter import RFPDupeFilter
    
    
    def splash_request_fingerprint(request, include_headers=None):
        """ Request fingerprint which takes 'splash' meta key into account """
    
        fp = request_fingerprint(request, include_headers=include_headers)
        if 'splash' not in request.meta:
            return fp
    
        splash_options = deepcopy(request.meta['splash'])
        args = splash_options.setdefault('args', {})
    
        if 'url' in args:
            args['url'] = canonicalize_url(args['url'], keep_fragments=True)
    
        return dict_hash(splash_options, fp)
    
    
    class SplashAwareDupeFilter(RFPDupeFilter):
        """
        DupeFilter that takes 'splash' meta key in account.
        It should be used with SplashMiddleware.
        """
        def request_fingerprint(self, request):
            return splash_request_fingerprint(request)
    
    
    
    settings中设置
    # 渲染服务的url
    SPLASH_URL = 'http://127.0.0.1:8050'
    # 下载器中间件
    DOWNLOADER_MIDDLEWARES = {
        'scrapy_splash.SplashCookiesMiddleware': 723,
        'scrapy_splash.SplashMiddleware': 725,
        'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 810,
    }
    # 使用Splash的Http缓存
    HTTPCACHE_STORAGE = 'scrapy_splash.SplashAwareFSCacheStorage'
    
    # 去重过滤器
    # DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter'
    # DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" # 指纹生成以及去重类
    DUPEFILTER_CLASS = 'test_splash.spiders.splash_and_redis.SplashAwareDupeFilter' # 混合去重类的位置
    
    SCHEDULER = "scrapy_redis.scheduler.Scheduler" # 调度器类
    SCHEDULER_PERSIST = True # 持久化请求队列和指纹集合, scrapy_redis和scrapy_splash混用使用splash的DupeFilter!
    ITEM_PIPELINES = {'scrapy_redis.pipelines.RedisPipeline': 400} # 数据存入redis的管道
    REDIS_URL = "redis://127.0.0.1:6379" # redis的url
    
    代码中使用
    from scrapy_redis.spiders import RedisSpider
    from scrapy_splash import SplashRequest
    
    
    class SplashAndRedisSpider(RedisSpider):
        name = 'splash_and_redis'
        allowed_domains = ['baidu.com']
    
        # start_urls = ['https://www.baidu.com/s?wd=13161933309']
        redis_key = 'splash_and_redis'
        # lpush splash_and_redis 'https://www.baidu.com'
    
        # 分布式的起始的url不能使用splash服务!
        # 需要重写dupefilter去重类!
    
        def parse(self, response):
            yield SplashRequest('https://www.baidu.com/s?wd=13161933309',
                                callback=self.parse_splash,
                                args={'wait': 10}, # 最大超时时间,单位:秒
                                endpoint='render.html') # 使用splash服务的固定参数
    
        def parse_splash(self, response):
            with open('splash_and_redis.html', 'w') as f:
                f.write(response.body.decode())
    

    京东体会

    1 爬出来大分类底下的小分类的url很多事相对路径,需要在解析方法中使用response.urljoin(子类的url)来进行拼接成一个完整的url
    item['link']=response.urljoin(book.xpath("./div[1]/a/@href").extract_first())

    2 假如第二个解析方法要使用第一个解析方法的数据,则在第一个解析方法 中给引擎返回请求的时候添加meta参数,是一个字典,达到数据传输的效果
    yield scrapy.Request(url,callback=第二个解析方法,meta={'任意的键名':data})
    在第二个解析方法中用response.meta['键名']取出传送的data
    image.png

    3在爬取的时候现在element中找,此时的数据时经过浏览器渲染好的,
    假如在element中找不到东西了,爬取出现None的情况,就去源码中查找,例如作者名,图片的src在element中跟在源码中是不一样的
    item['cover']=book.xpath("./div[1]/a/img/@src|./div[1]/a/img/@data-lazy-img").extract_first()

    4 有些数据在源码中都没有就很有可能是通过JS访问响应的接口来获取的,例如这里的价格就是通过访问后端固定的端口获得的,这里就需要用到post请求

    5 在访问价格接口的时候,由于在爬虫创建的时候对域做了约束,将会访问不到,这时候需要对allow_domain做添加

    6 做爬虫,去大部分很好取,但是涉及到一丁点不同,大的规则就会失效,就需要对规则进行补充,
    查找缺失的地方,从小到大一步一步查,直到找到所缺的东西

    7 一般在测试的时候在大的节点列表选择时进行切片处理下,少发点请求。

    8 对于response.body 得到的是bytes数据,需要对其进行decode才能进行json.loads(),必须是个字符串

    9 在编写管道的时候, 主要作用域process_item函数

    import json
    from pymongo import MongoClient
    from scrapy.exporters import JsonItemExporter
    
    class JdbookPipeline(object):
    
        def __init__(self):
            self.file=open('1111.json', 'w')
    
        def process_item(self, item, spider):
            # 将处理后的数据写入文件
            str_data = json.dumps(dict(item),ensure_ascii=False) + ',
    '
            # 当写入文件出现乱码时,可以在dumps后加入ensure_ascii=False参数
            self.file.write(str_data)
    
            # 管道处理完item后必须将item对象返回给引擎。(当定义多个管道类的时候,将item返回,引擎自动下发给下一个管道类。)
            return item
    
        def __del__(self):
            self.file.close()
    
    class ItcastMongoPipeline(object):
        def open_spider(self, spider):  # 在爬虫开启的时候仅执行一次
    
            # 也可以使用isinstanc函数来区分爬虫类:
            con = MongoClient(host='127.0.0.1', port=27017) # 实例化mongoclient
            self.collection = con.JD.jd # 创建数据库名为itcast,集合名为teachers的集合操作对象
    
        def process_item(self, item, spider):
    
            self.collection.insert(dict(item))
                # 此时item对象必须是一个字典,再插入
                # 如果此时item是BaseItem则需要先转换为字典:dict(BaseItem)
            # 不return的情况下,另一个权重较低的pipeline将不会获得item
            return item
    

    10 在改分布式爬虫的时候,allow_domains可以留的,也可以吧他放在def __inti__中

  • 相关阅读:
    [No00009C]Visual Studio在 解决方案资源管理器 里同步定位打开的文件
    [No00009B]win10快捷键大全
    [No000099]软件版本命名规范
    [No000098]SVN学习笔记5-分支,合并,属性,补丁,锁,分支图
    [No000097]程序员面试题集【下】
    [No000096]程序员面试题集【上】
    [No000095].Net入门级逆向工程-1.SpreadsheetGear汉化
    [No000093]按住Alt 再按数字键敲出任意汉字和字符!
    [No000092]SVN学习笔记3-Import/Checkout(迁入/迁出),GetLock(加锁)
    ubuntu 查看端口被占用并处理
  • 原文地址:https://www.cnblogs.com/cizao/p/11482047.html
Copyright © 2020-2023  润新知