美文网首页
爬虫精进(十二) ------ Scrapy架构

爬虫精进(十二) ------ Scrapy架构

作者: 千喜Ya | 来源:发表于2019-07-27 22:43 被阅读0次

    一.结构


    上面的这张图是Scrapy的整个结构。你可以把整个Scrapy框架看成是一家爬虫公司。最中心位置的Scrapy Engine(引擎)就是这家爬虫公司的大boss,负责统筹公司的4大部门,每个部门都只听从它的命令,并只向它汇报工作。
    1.Scheduler(调度器)部门主要负责处理引擎发送过来的requests对象(即网页请求的相关信息集合,包括params,data,cookies,request headers…等),会把请求的url以有序的方式排列成队,并等待引擎来提取(功能上类似于gevent库的queue模块)。
    2.Downloader(下载器)部门则是负责处理引擎发送过来的requests,进行网页爬取,并将返回的response(爬取到的内容)交给引擎。它对应的是爬虫流程【获取数据】这一步。
    3.Spiders(爬虫)部门是公司的核心业务部门,主要任务是创建requests对象和接受引擎发送过来的response(Downloader部门爬取到的内容),从中解析并提取出有用的数据。它对应的是爬虫流程【解析数据】和【提取数据】这两步。
    4.Item Pipeline(数据管道)部门则是公司的数据部门,只负责存储和处理Spiders部门提取到的有用数据。这个对应的是爬虫流程【存储数据】这一步。
    5.Downloader Middlewares(下载中间件)的工作相当于下载器部门的秘书,比如会提前对引擎大boss发送的诸多requests做出处理。
    6.Spider Middlewares(爬虫中间件)的工作则相当于爬虫部门的秘书,比如会提前接收并处理引擎大boss发送来的response,过滤掉一些重复无用的东西。

    二.工作原理


    上图展现出的也是Scrapy框架的工作原理——引擎是中心,其他组成部分由引擎调度。
    在Scrapy里,整个爬虫程序的流程都不需要我们去操心,且Scrapy中的程序全部都是异步模式,所有的请求或返回的响应都由引擎自动分配去处理。

    三.代码实现

    需求 : 爬取豆瓣Top250图书信息

    1.创建项目(MAC版)

    首先要安装 Scrapy库,参考Pycham安装第三方模块
    PyCharm中打开Terminal ,输入指令 “scrapy startproject module”,其中module为模块名,看到如下的信息表示成功创建scrapy项目:


    参考 : PyCharm创建Scrapy项目
    创建之后的结构 :
    2.编辑爬虫

    spiders是放置爬虫的目录。我们可以在spiders这个文件夹里创建爬虫文件。我们来把这个文件,命名为top250。后面的大部分代码都需要在这个top250.py文件里编写。放置位置如下:



    爬虫基本结构 :

    import scrapy
    import bs4
    from ..items import DoubanItem
    
    class DoubanSpider(scrapy.Spider):
    #定义一个爬虫类DoubanSpider,继承自scrapy.Spider类。
        name = 'douban'
        #定义爬虫的名字为douban。这个名字是爬虫的唯一标识。name = 'douban'意思是定义爬虫的名字为douban。等会我们启动爬虫的时候,要用到这个名字。
        allowed_domains = ['book.douban.com']
        #定义爬虫爬取网址的域名。:`allowed_domains`是定义允许爬虫爬取的网址域名(不需要加https://)。如果网址的域名不在这个列表里,就会被过滤掉。
        #为什么会有这个设置呢?当你在爬取大量数据时,经常是从一个URL开始爬取,然后关联爬取更多的网页。
        #比如,假设我们今天的爬虫目标不是爬书籍信息,而是要爬豆瓣top250,每本书的书评。我们会先爬取书单,再找到每本书的URL,再进入每本书的详情页面去抓取评论。
        #`allowed_domains就限制了,我们这种关联爬取的URL,一定在book.douban.com这个域名之下,不会跳转到某个奇怪的广告页面。`
        start_urls = []
        #start_urls是定义起始网址,就是爬虫从哪个网址开始抓取。在此,allowed_domains的设定对start_urls里的网址不会有影响。
        for x in range(3):
            url = 'https://book.douban.com/top250?start=' + str(x * 25)
            start_urls.append(url)
            #把豆瓣Top250图书的前3页网址添加进start_urls。
    
        def parse(self, response):
        #parse是默认处理response的方法,中文意思是解析。
            bs = bs4.BeautifulSoup(response.text,'html.parser')
            #用BeautifulSoup解析response。
            datas = bs.find_all('tr',class_="item")
            #用find_all提取<tr class="item">元素,这个元素里含有书籍信息。
            for data in  datas:
            #遍历data。
                title = data.find_all('a')[1]['title']
                #提取出书名。
                publish = data.find('p',class_='pl').text
                #提取出出版信息。
                score = data.find('span',class_='rating_nums').text
                #提取出评分。
                print([title,publish,score])
                #打印上述信息。
    
    3.定义数据

    我们要爬取的数据是书名、出版信息和评分,我们来看看如何在items.py里定义这些数据。

    import scrapy
    #导入scrapy
    class DoubanItem(scrapy.Item):
    #定义一个类DoubanItem,它继承自scrapy.Item
    #这样,有许多好用属性和方法,就能够直接使用。比如到后面,引擎能将item类的对象发给Item Pipeline(数据管道)处理。
        title = scrapy.Field()
        #定义书名的数据属性
        publish = scrapy.Field()
        #定义出版信息的数据属性
        score = scrapy.Field()
        #定义评分的数据属性
        #定义了书名、出版信息和评分三种数据。scrapy.Field()这行代码实现的是,让数据能以类似字典的形式记录。
    

    你会看到打印出来的结果的确和字典非常相像,但它却并不是dict,它的数据类型是我们定义的DoubanItem,属于“自定义的Python字典”。我们可以利用类似上述代码的样式,去重新写top250.py。如下所示:

    from scrapy import Spider
    import bs4
    from ..items import DoubanItem
    # 需要引用DoubanItem,它在items里面。因为是items在top250.py的上一级目录,所以要用..items,这是一个固定用法。
    
    class DoubanSpider(Spider):
    #定义一个爬虫类DoubanSpider。
        name = 'douban'
        #定义爬虫的名字为douban。
        allowed_domains = ['book.douban.com']
        #定义爬虫爬取网址的域名。
        start_urls = []
        #定义起始网址。
        for x in range(3):
            url = 'https://book.douban.com/top250?start=' + str(x * 25)
            start_urls.append(url)
            #把豆瓣Top250图书的前3页网址添加进start_urls。
    
        def parse(self, response):
        #parse是默认处理response的方法。
            bs = bs4.BeautifulSoup(response.text,'html.parser')
            #用BeautifulSoup解析response。
            datas = bs.find_all('tr',class_="item")
            #用find_all提取<tr class="item">元素,这个元素里含有书籍信息。
            for data in  datas:
            #遍历data。
                item = DoubanItem()
                #实例化DoubanItem这个类。
                item['title'] = data.find_all('a')[1]['title']
                #提取出书名,并把这个数据放回DoubanItem类的title属性里。
                item['publish'] = data.find('p',class_='pl').text
                #提取出出版信息,并把这个数据放回DoubanItem类的publish里。
                item['score'] = data.find('span',class_='rating_nums').text
                #提取出评分,并把这个数据放回DoubanItem类的score属性里。
                print(item['title'])
                #打印书名。
                yield item
                #yield item是把获得的item传递给引擎。
    

    当我们每一次,要记录数据的时候,比如前面在每一个最小循环里,都要记录“书名”,“出版信息”,“评分”。我们会实例化一个item对象,利用这个对象来记录数据。
    每一次,当数据完成记录,它会离开spiders,来到Scrapy Engine(引擎),引擎将它送入Item Pipeline(数据管道)处理。这里,要用到yield语句。
    yield可以简单理解为:它有点类似return,不过它和return不同的点在于,它不会结束函数,且能多次返回信息。


    如果用可视化的方式来呈现程序运行的过程,就如同上图所示:爬虫(Spiders)会把豆瓣的10个网址封装成requests对象,引擎会从爬虫(Spiders)里提取出requests对象,再交给调度器(Scheduler),让调度器把这些requests对象排序处理。
    然后引擎再把经过调度器处理的requests对象发给下载器(Downloader),下载器会立马按照引擎的命令爬取,并把response返回给引擎。
    紧接着引擎就会把response发回给爬虫(Spiders),这时爬虫会启动默认的处理response的parse方法,解析和提取出书籍信息的数据,使用item做记录,返回给引擎。引擎将它送入Item Pipeline(数据管道)处理。
    请注意DoubanItem 属性赋值的写法,千万不要用.来访问
    4.设置

    到这里,我们就用代码编写好了一个爬虫。不过,实际运行的话,可能还是会报错。
    原因在于Scrapy里的默认设置没被修改。点击settings.py文件

    ①.USER _AGENT的注释取消(删除#),然后替换掉user-agent的内容,修改请求头。
    ②.遵守robots协议换成无需遵从robots协议

    把ROBOTSTXT_OBEY=True改成ROBOTSTXT_OBEY=False

    5.保存数据
    ①.存储成csv文件

    在settings.py文件里,添加如下的代码 :

    FEED_URI='./storage/data/%(name)s.csv'
    FEED_FORMAT='CSV'
    FEED_EXPORT_ENCODING='ansi'
    

    FEED_URI是导出文件的路径。'./storage/data/%(name)s.csv',就是把存储的文件放到与settings.py文件同级的storage文件夹的data子文件夹里。
    FEED_FORMAT 是导出数据格式,写CSV就能得到CSV格式。
    FEED_EXPORT_ENCODING 是导出文件编码,ansi是一种在windows上的编码格式,你也可以把它变成utf-8用在mac电脑上。

    ②.存储成Excel文件

    先在setting.py里设置启用ITEM_PIPELINES,设置方法如下:

    #需要修改`ITEM_PIPELINES`的设置代码:
    
    # Configure item pipelines
    # See https://doc.scrapy.org/en/latest/topics/item-pipeline.html
    ITEM_PIPELINES = {
         'jobuitest.pipelines.JobuitestPipeline': 300,
     }
    

    只要取消ITEM_PIPELINES的注释(删掉#)即可。
    编辑pipelines.py文件。存储为Excel文件,我们依旧是用openpyxl模块来实现,代码如下,注意阅读注释:

    import openpyxl
    
    class JobuiPipeline(object):
    #定义一个JobuiPipeline类,负责处理item
        def __init__(self):
        #初始化函数 当类实例化时这个方法会自启动
            self.wb =openpyxl.Workbook()
            #创建工作薄
            self.ws = self.wb.active
            #定位活动表
            self.ws.append(['公司', '职位', '地址', '招聘信息'])
            #用append函数往表格添加表头
            
        def process_item(self, item, spider):
        #process_item是默认的处理item的方法,就像parse是默认处理response的方法
            line = [item['company'], item['position'], item['address'], item['detail']]
            #把公司名称、职位名称、工作地点和招聘要求都写成列表的形式,赋值给line
            self.ws.append(line)
            #用append函数把公司名称、职位名称、工作地点和招聘要求的数据都添加进表格
            return item
            #将item丢回给引擎,如果后面还有这个item需要经过的itempipeline,引擎会自己调度
    
        def close_spider(self, spider):
        #close_spider是当爬虫结束运行时,这个方法就会执行
            self.wb.save('./jobui.xlsx')
            #保存文件
            self.wb.close()
            #关闭文件
    

    取消DOWNLOAD_DELAY = 0这行的注释(删掉#)。DOWNLOAD_DELAY翻译成中文是下载延迟的意思,这行代码可以控制爬虫的速度。因为这个项目的爬取速度不宜过快,我们要把下载延迟的时间改成0.5秒。

    6.运行

    在最外层的大文件夹里新建一个main.py文件(与scrapy.cfg同级)。



    我们只需要在这个main.py文件里,输入以下代码,点击运行,Scrapy的程序就会启动。

    from scrapy import cmdline
    #导入cmdline模块,可以实现控制终端命令行。导入了这个模块,我们就能操控终端。
    cmdline.execute(['scrapy','crawl','douban'])
    #用execute()方法,输入运行scrapy的命令。我们想输入运行Scrapy的代码scrapy crawl douban,就需要写成['scrapy','crawl','douban']这样。
    

    值得一提的是,在本关卡中为了教学方便理解,先写了爬虫,再定义数据。但是,在实际项目实战中,常常顺序却是相反的——先定义数据,再写爬虫。所以,流程图应如下:


    四.练习

    请使用Scrapy,爬取当当网2018年图书销售榜单前3页的数据(图书名、作者和书的价格)。

    项目结构如下 :
    1.scaItem
    import scrapy
    class scaItem(scrapy.Item):
        # define the fields for your item here like:
        # name = scrapy.Field()
        bookindex = scrapy.Field()
        bookname = scrapy.Field()
        bookauthor = scrapy.Field()
        bookprice = scrapy.Field()
        pass
    
    2.BookSpider.py
    from scrapy import Spider
    import bs4
    from ..items import scaItem
    
    class BookSpider(Spider):
        name = 'book'
        allowed_domains = ['http://bang.dangdang.com']
        start_urls=[]
        for i in range(1,4):
            url = 'http://bang.dangdang.com/books/bestsellers/01.00.00.00.00.00-year-2018-0-1-' + str(i)
            start_urls.append(url)
    
    
        def parse(self,response):
            bs = bs4.BeautifulSoup(response.text,'html.parser')
            datas = bs.find('ul',class_ = 'bang_list clearfix bang_list_mode').find_all('li')
            for data in datas:
                item = scaItem()
                item['bookindex'] = data.find('div', class_='list_num').text
                item['bookname']= data.find('div',class_ = 'name').find('a')['title']
                item['bookname'] = data.find('div',class_ = 'publisher_info').find('a').text
                item['bookprice'] = data.find('div',class_ = 'price').find('span',class_='price_n').text
                yield item
    
    3.ScaPipeline
    import csv
    class ScaPipeline(object):
        def process_item(self, item, spider):
            csv_file = open('book.csv', 'a')
            writer = csv.writer(csv_file)
            writer.writerow([item['bookindex'],item['bookname'],item['bookname'], item['bookprice']])
            print(item)
            return item
    
    4.setting
    # Crawl responsibly by identifying yourself (and your website) on the user-agent
    USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142'
    
    # Obey robots.txt rules
    ROBOTSTXT_OBEY = False
    
    5.main
    from scrapy import cmdline
    #导入cmdline模块,可以实现控制终端命令行。导入了这个模块,我们就能操控终端。
    cmdline.execute(['scrapy','crawl','book'])
    #用execute()方法,输入运行scrapy的命令。我们想输入运行Scrapy的代码scrapy crawl douban,就需要写成['scrapy','crawl','douban']这样。
    
    

    相关文章

      网友评论

          本文标题:爬虫精进(十二) ------ Scrapy架构

          本文链接:https://www.haomeiwen.com/subject/snrjrctx.html