Python爬虫之scrapy框架基础理解

1 scrapy

1.1 简介

scrapy框架Python编写 ,是 一个快速、高层次的屏幕抓取和 web 抓取框架,用于抓取 web 站点并从页面中提取结构化的数据。
Scrapy 用途广泛,可以用于数据挖掘、监测和自动化测试,还有高性能的持久化存储,异步的数据下载,高性能的数据解析,分布式等等

1.1.1 scrapy原理

scrapy有五大核心组件:

  • 引擎:用来处理整个系统的数据流处理,触发事务(框架核心)
  • 调度器:用来接受引擎发过来的请求,压入队列中,并在引擎再次请求的时候返回,可以想象成一个URL的优先队列,由它来决定下一个喜欢的网址是什么,同时去除重复的网址
  • 下载器:用于下载网页内容,并将网页内容返回,scrapy下载是建立在twisted这个高效的异步模型上的
  • 爬虫:爬虫主要干活的,用于从特定网页中提取自己想要的信息,用户也可以从中爬取出链接,让Scrapy爬取下一个页面
  • 管道:负责处理页面中提取的实体,主要功能是持久化实体,验证实体的有效性,清除不需要信息,当页面被爬虫解析后,将解析发送到项目管道,并经过几个特定次序处理数据

1.2 环境安装

mac或者linux系统直接:pip install scrapy
windows系统,会比较麻烦:

  • 首先安装wheelpip install wheel

  • 下载twistedhttps://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted,下载对应的whl文件。如下图,cp后面是python版本,amd64代表64位,划线的是我要下载的,点击下载
    在这里插入图片描述
  • 在下载后的twisted文件夹内执行pip命令来安装:pip install Twisted-20.3.0-cp38-cp38-win_amd64.whl
    如果安装报错:

  • ERROR: Exception:
    Traceback (most recent call last):
    File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\cli\base_command.py", line 173, in _main
    status = self.run(options, args)
    File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\cli\req_command.py", line 203, in wrapper
    return func(self, options, args)
    File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\commands\install.py", line 315, in run
    requirement_set = resolver.resolve(
    File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\resolution\resolvelib\resolver.py", line 75, in resolve
    collected = self.factory.collect_root_requirements(root_reqs)
    File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\resolution\resolvelib\factory.py", line 471, in collect_root_requirements
    req = self._make_requirement_from_install_req(
    File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\resolution\resolvelib\factory.py", line 433, in _make_requirement_from_install_req
    cand = self._make_candidate_from_link(
    File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\resolution\resolvelib\factory.py", line 204, in _make_candidate_from_link
    self._link_candidate_cache[link] = LinkCandidate(
    File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\resolution\resolvelib\candidates.py", line 295, in init
    super().init(
    File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\resolution\resolvelib\candidates.py", line 156, in init
    self.dist = self._prepare()
    File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\resolution\resolvelib\candidates.py", line 227, in _prepare
    dist = self._prepare_distribution()
    File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\resolution\resolvelib\candidates.py", line 305, in _prepare_distribution
    return self._factory.preparer.prepare_linked_requirement(
    File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\operations\prepare.py", line 508, in prepare_linked_requirement
    return self._prepare_linked_requirement(req, parallel_builds)
    File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\operations\prepare.py", line 570, in _prepare_linked_requirement
    dist = _get_prepared_distribution(
    File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\operations\prepare.py", line 61, in _get_prepared_distribution
    return abstract_dist.get_pkg_resources_distribution()
    File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\distributions\wheel.py", line 26, in get_pkg_resources_distribution
    with ZipFile(self.req.local_file_path, allowZip64=True) as z:
    File "zipfile.py", line 1257, in init
    File "zipfile.py", line 1324, in _RealGetContents
    zipfile.BadZipFile: File is not a zip file

    可能是文件受损,重新下载执行上述命令即可成功

    • 安装pywin32pip install pywin32
    • 安装scrapypip install scrayp

    最后测试是否安装成功,在DOS终端里录入scrapy指令,没有报错即表示安装成功

    1.3 使用scrapy

    1.3.1 使用步骤

    • 创建工程:scrapy startproject ProjectName
    • 创建爬虫文件
      先进入到工程目录中,然后执行命令scrapy genspider spiderFileName www.xxx.com,就会在spriders子目录中创建一个爬虫文件
      例如执行:scrapy genspider firstSpiderFile www.xxx.com会生成一个文件firstSpiderFile.py 且内部有后缀为Spider的类
      allowed_domains:允许的域名,限定start_urls列表中哪些url可以发送请求,如果注释掉,会全部发送
    import scrapy
    
    class FirstspiderfileSpider(scrapy.Spider):
        #爬虫文件名字,爬虫源文件一个唯一标识
        name = 'firstSpiderFile'
        #允许的域名,限定start_urls列表中哪些url可以发送请求,如果注释掉,会全部发送
        #allowed_domains = ['www.baidu.com']
        #起始的url列表:列表中的存放的url被scrapy会自动发送请求
        start_urls = ['http://www.baidu.com/']
    
        #用于数据解析:response参数表示请求成功后对应的响应对象,如果解析返回对象有多个,那么这个pass方法就要调用多次
        def parse(self, response):
            print(response)
    
    
    • 执行工程,输入爬虫文件spiderFileName scrapy crawl spiderFileName
    • 在爬虫文件中添加相关代码

    注意:在爬虫文件使用xpath解析时,返回可能不是字符串,而是Selector对象,就可以用extract()方法解析`

    1.4 持久化存储

    1.4.1 基于终端指令

    基于终端指令,只可以将parse方法的返回值存储到本地的文本文件中,但是只支持一下几种格式:'json', 'jsonlines', 'jl', 'csv', 'xml', 'marshal', 'pickle'
    使用如下命令,就是在执行命令增加一个-o参数:

    scrapy crawl spiderFileName -o 文件路径+文件名

    import scrapy
    
    class FirstspiderfileSpider(scrapy.Spider):
        #爬虫文件名字,爬虫源文件一个唯一标识
        name = 'firstSpiderFile'
        #允许的域名,限定start_urls列表中哪些url可以发送请求,如果注释掉,会全部发送
        #allowed_domains = ['www.baidu.com']
        #起始的url列表:列表中的存放的url被scrapy会自动发送请求
        start_urls = ['https://www.test.com/chaxun/zuozhe/77.html']
    
        #用于数据解析:response参数表示请求成功后对应的响应对象,如果解析返回对象有多个,那么这个pass方法就要调用多次
        def parse(self, response):
            div_list = response.xpath('//div[@class="shici_list_main"]')
            all_data=[]
            for div in div_list:
                #此处使用相对路径
                shici_name=div.xpath("h3/a/text()")[0].extract()
                # print(shici_name)
                shici_text_list=div.xpath("div//text()").extract()
                shici_text_list = "".join(shici_text_list)
                dict={
                    'title':shici_name,
                    "content":shici_text_list
                }
                all_data.append(dict)
    
            return all_data
    

    注意:在爬虫文件的parse方法需要有返回值来保存文件

    1.4.2 基于管道

    1.4.2.1 基于管道步骤

    基于管道大致步骤:(即把要写的代码写到items.py文件中)

    • item类中定义相关的属性(items.py)
    • 把要解析的数据封装存储到item类型对象(items.py)
    • item类的数据对象items.py提交给管道pipelines.py进行持久化存储操作
    • 在管道类的process_item中药将其接收到item对象中存储的数据进行持久化存储操作
    • 在配置文件settings.py中开启管道

    1.4.2.2 基于管道操作

    item类中定义相关的属性

    import scrapy
    class FirstItem(scrapy.Item):
        # define the fields for your item here like:
        # name = scrapy.Field()
        title=scrapy.Field()
        content=scrapy.Field()
    

    把要解析的数据封装存储到item类型对象
    注意:此处的爬虫文件是不需要return但是需要yield

    import scrapy
    from first.items import FirstItem
    
    class FirstspiderfileSpider(scrapy.Spider):
        #爬虫文件名字,爬虫源文件一个唯一标识
        name = 'firstSpiderFile'
        #允许的域名,限定start_urls列表中哪些url可以发送请求,如果注释掉,会全部发送
        #allowed_domains = ['www.baidu.com']
        #起始的url列表:列表中的存放的url被scrapy会自动发送请求
        start_urls = ['https://www.test.com/chaxun/zuozhe/77.html']
    
        #用于数据解析:response参数表示请求成功后对应的响应对象,如果解析返回对象有多个,那么这个pass方法就要调用多次
        def parse(self, response):
            div_list = response.xpath('//div[@class="shici_list_main"]')
            all_data=[]
            for div in div_list:
                #此处使用相对路径
                shici_name=div.xpath("h3/a/text()")[0].extract()
                # print(shici_name)
                shici_text_list=div.xpath("div//text()").extract()
                shici_text_list = "".join(shici_text_list)
                
                item = FirstItem()
                item['title']=shici_name
                item['content']=shici_text_list
                yield item
    

    pipelines.py开始持久化操作
    注意:process_item中的return item,会把item传递给下一个即将被执行的管道类,不管后面有没有管道尽量都return

    from itemadapter import ItemAdapter
    
    
    class FirstPipeline(object):
        fp =None
        #重写父类一个方法:该方法只在开始爬虫的时候被调用一次
        def open_spider(self,spider):
            print("开始爬虫..............")
            self.fp=open('./test123.txt','w',encoding='utf-8')
    
        #专门用来处理item类型对象
        #可以接受爬虫文件体积过来的item对象
        #该方法每接受一个item对象就会被调用一次
        def process_item(self, item, spider):
            title=item['title']
            content=item['content']
            self.fp.write(title+":"+content+'\n')
    
            return item
    
        #继续重写父类方法
        def close_spider(self,spider):
            print('结束爬虫打印................')
            self.fp.close()
    

    在配置文件settings.py中开启管道

    ITEM_PIPELINES = {
        #300表示执行优先级,数值越小,优先级越高
       'first.pipelines.FirstPipeline': 300,
    }
    

    1.4.3 多渠道存储

    如果要把爬取的数据分别存在本地和数据库中,那么就在pipelines.py中写多个管道操作类
    pipelines.py文件中增加如下类

    class MysqlPipeline(object):
        conn =None
        curcos=None
        #重写父类一个方法:该方法只在开始爬虫的时候被调用一次
        def open_spider(self,spider):
            print("mysql开始爬虫..............")
            self.conn=pymysql.Connect(host='127.0.0.1',port=3306,user='root',password='root',db='test',charset='utf-8')
    
        #专门用来处理item类型对象
        #可以接受爬虫文件体积过来的item对象
        #该方法每接受一个item对象就会被调用一次
        def process_item(self, item, spider):
            title=item['title']
            content=item['content']
            self.curcos=self.conn.cursor()
            self.curcos.execute('insert into test values("%s","%s")%(item["author"],item["content"]) ')
            self.curcos.commit()
            # 会把item传递给下一个即将被执行的管道类
            return item
    
        #继续重写父类方法
        def close_spider(self,spider):
            print('结束mysql爬虫...............')
            self.curcos.close()
            self.conn.close()
    

    修改settings.py文件

    ITEM_PIPELINES = {
        #300表示执行优先级,数值越小,优先级越高
       'first.pipelines.FirstPipeline': 300,
       'first.pipelines.MysqlPipeline': 301,
    }
    

    1.5 全站数据爬取

    全站数据爬取就是将网站中某个板块下的全部页码对应的页码数据进行爬取
    实现方式:

    • 将所有页面url添加到start_urls列表中
    • 自行手动请求,需要手动发送请求:yield scrapy.Request(url,callback)其中的callback是专门用于数据解析的

    具体操作在爬虫文件中如下:

    class XiaohuaSpider(scrapy.Spider):
        name='xiaohua'
        start_urls=['http://test.com/meinvxiaohua/']
    	url='http://test.com/meinvxiaohua/?query=python&page=%d.html'
        def parse(self, response):
            li_list=response.xpath('//*[@id="content"]/div[2]')
            for li in li_list:
                img_name=li.xpath('./a[2]/b/text()').extract_first()
    
            if self.page_num<=3:#爬取前三页
                new_url=format(self.url%self.page_num)#替换新页面连接
                self.page_num+=1
                #手动发送请求:callback回调函数是专门用作数据解析
                yield scrapy.Request(url=new_url,callback=self.parse)#发送新的连接,同时还是使用parse方法进行解析
    
    posted @   上善若泪  阅读(314)  评论(0编辑  收藏  举报
    编辑推荐:
    · 探究高空视频全景AR技术的实现原理
    · 理解Rust引用及其生命周期标识(上)
    · 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
    · 没有源码,如何修改代码逻辑?
    · 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
    阅读排行:
    · 分享4款.NET开源、免费、实用的商城系统
    · 全程不用写代码,我用AI程序员写了一个飞机大战
    · Obsidian + DeepSeek:免费 AI 助力你的知识管理,让你的笔记飞起来!
    · MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
    · 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
    点击右上角即可分享
    微信分享提示