• 学习scrapy框架爬小说


    一、背景:近期学习python爬虫技术,感觉挺有趣。由于手动自制爬虫感觉效率低,了解到爬虫界有先进的工具可用,尝试学学scrapy爬虫框架的使用。

    二、环境:centos7,python3.7,scrapy1.7.3

    三、scrapy原理简述:

    1、scrapy框架组成:引擎、调度器、下载器(含下载器中间件)、爬虫组件(spider,含爬虫中间件)、输出管道(item pipelines)

    2、scrapy工作过程:

    (1)引擎发起爬虫请求,提交给调度器安排任务排序。

    (2)调度器安排的下载任务通过引擎提交下载器开展下载任务(发出Quests请求)。

    (3)下载器获得的Response通过引擎回到爬虫组件进行内容爬取的处理。(这一步就是人为工作的重点,爬取什么内容,如何爬取等设计需要人为设定控制。)

    (4)爬虫组件处理后的数据通过item pipelines组件进行输出,可输出为json、数据库、csv等等格式。(这一步需要根据具体需求,可人为控制输出方案。)

    (5)上述过程循环往复,直到预定爬取任务完成。

    四、scrapy工程构建过程(以爬取新笔趣阁网站www.xbiquge.com上的《汉乡》小说为例)

    1、新建爬虫工程

    (base) [python@ELK ~]$ scrapy startproject hanxiang
    New Scrapy project 'hanxiang', using template directory '/home/python/miniconda3/lib/python3.7/site-packages/scrapy/templates/project', created in:
        /home/python/hanxiang

    You can start your first spider with:
        cd hanxiang
        scrapy genspider example example.com

    工程目录结构:
    (base) [python@ELK ~]$ tree hanxiang
    hanxiang
    ├── hanxiang
    │   ├── __init__.py
    │   ├── items.py
    │   ├── middlewares.py
    │   ├── pipelines.py
    │   ├── __pycache__
    │   ├── settings.py
    │   └── spiders
    │       ├── __init__.py
    │       └── __pycache__
    └── scrapy.cfg

    2、进入爬虫工程目录并生成爬虫程序文件

    (base) [python@ELK ~]$ cd hanxiang
    (base) [python@ELK hanxiang]$ scrapy genspider Hanxiang www.xbiquge.la/15/15158   #注意,爬虫名称不能与工程名称重名;链接不要加http://或https://关键字,链接尾部不要有/符号。
    Created spider 'Hanxiang' using template 'basic' in module:
      hanxiang.spiders.Hanxiang

    (base) [python@ELK hanxiang]$ tree
    .
    ├── hanxiang
    │   ├── __init__.py
    │   ├── items.py
    │   ├── middlewares.py
    │   ├── pipelines.py
    │   ├── __pycache__
    │   │   ├── __init__.cpython-37.pyc
    │   │   └── settings.cpython-37.pyc
    │   ├── settings.py
    │   └── spiders
    │       ├── Hanxiang.py
    │       ├── __init__.py
    │       └── __pycache__
    │           └── __init__.cpython-37.pyc
    └── scrapy.cfg
    在爬虫工程中新生成的文件中,重点是Hanxiang.py的爬虫程序文件。

    3、编制爬虫文件和相关的配置文件

    (1)修改settings.py文件

    (base) [python@ELK hanxiang]$ vi hanxiang/settings.py

    # -*- coding: utf-8 -*-
    # Scrapy settings for hanxiang project

    BOT_NAME = 'hanxiang'
    SPIDER_MODULES = ['hanxiang.spiders']
    NEWSPIDER_MODULE = 'hanxiang.spiders'

    ROBOTSTXT_OBEY = False   #爬取不受网站限制

    ...

    # Configure item pipelines
    # See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
    ITEM_PIPELINES = {
        'hanxiang.pipelines.HanxiangPipeline': 300,                      #启用pipelines功能,300是优先级配置,其取值范围0-1000
    }
    ...


    FEED_EXPORT_ENCODING = 'utf-8'     #输出编码设置

    (2)修改items.py配置(确定需要爬取的内容)

    (base) [python@ELK hanxiang]$ vi hanxiang/items.py

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

    # Define here the models for your scraped items
    #
    # See documentation in:
    # https://docs.scrapy.org/en/latest/topics/items.html
    import scrapy
    class HanxiangItem(scrapy.Item):
        # define the fields for your item here like:
        # name = scrapy.Field()
        url = scrapy.Field()                         #需要获取小说的章节链接
        preview_page = scrapy.Field()       #小说的上一页章节链接
        next_page = scrapy.Field()            #小说的下一页章节链接
        content = scrapy.Field()                 #小说的章节内容

    (3)编制pipelines.py程序,控制输出内容。(目标:把爬取的四个字段内容输出到mysql数据库,以方便后续处理。)

    (base) [python@ELK hanxiang]$ vi hanxiang/pipelines.py
    # -*- coding: utf-8 -*-
    # Define your item pipelines here
    #
    # Don't forget to add your pipeline to the ITEM_PIPELINES setting
    # See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html
    import os
    import pymysql
    from twisted.enterprise import adbapi
    from pymysql import cursors

    class HanxiangPipeline(object):             #类名是自动生成的,不用更改。

        def __init__(self):                              #定义类初始化动作,包括连接数据库novels和创建hanxiang数据表。
            dbparams = {
                'host': '127.0.0.1',
                'port': 3306,
                'user': 'root',
                'password': 'password',
                'database': 'novels',
                'charset': 'utf8'
            }
            self.conn = pymysql.connect(**dbparams)
            self.cursor = self.conn.cursor()
            self._sql = None

        #建表

        def createtable(self):
            self.cursor.execute("drop table if exists hanxiang")
            self.cursor.execute("create table hanxiang (id int unsigned auto_increment not null primary key, url varchar(50) not null, preview_page varchar(50), next_page varchar(50), content TEXT not null) charset=utf8")

            return

         #爬取内容写入数据表
        def process_item(self, item, spider):        #此方法名字也是自动生成,不能更改。
            self.cursor.execute(self.sql, (item['url'], item['preview_page'], item['next_page'], item['content']))   #执行sql命令,向hanxiang数据表写入爬取的数据。
            self.conn.commit()    #需执行commit,数据表内容才会更新。
            return item

        @property
        def sql(self):
            if not self._sql:
                self._sql = """
                    insert into hanxiang(id, url, preview_page, next_page, content) values(null, %s, %s, %s, %s)
                    """
                return self._sql
            return self._sql

        #从数据库取小说章节内容写入txt文件
        def content2txt(self):
            self.cursor.execute("select count(*) from hanxiang")
            record_num = self.cursor.fetchall()[0][0]  #取数据表的总记录数,注意fetchall反馈的是元组
            #counts=5  #测试用
            counts=record_num
            url_c = """ + "http://www.xbiquge.la/15/15158/6873206.html" + """  #第一章的链接变量,需要注意带上双引号,以便提供给sql查询
            start_time=time.time()  #获取提取小说内容程序运行的起始时间
            f = open("汉乡.txt", mode='w', encoding='utf-8')
            for i in range(counts):
                sql_c = "select content from hanxiang where url=" + url_c  #组合获取小说章节内容的sql命令
                self.cursor.execute(sql_c)
                record_content_c2a0=self.cursor.fetchall()[0][0]  #获取小说章节内容
                record_content=record_content_c2a0.replace(u'xa0', u'')  #消除特殊字符xc2xa0
                #print(record_content)
                f.write(' ')
                f.write(record_content + ' ')
                f.write(' ')
                sql_n = "select next_page from hanxiang where url=" + url_c   #组合获取下一章链接的sql命令
                self.cursor.execute(sql_n)
                url_c = """ + self.cursor.fetchall()[0][0] + """  #下一章链接地址赋值给url_c,准备下一次循环。

            #print(record_content[0][0])
            f.close()
            print(time.time()-start_time)
            return

        #爬虫结束,调用content2txt方法,生成txt文件
        def close_spider(self,spider):
            self.content2txt()
            return


    (4)编制爬虫主程序Hanxiang.py,爬取设定内容。

    (base) [python@ELK hanxiang]$ vi hanxiang/spiders/Hanxiang.py

    # -*- coding: utf-8 -*-
    import scrapy
    from hanxiang.items import HanxiangItem

    class HanxiangSpider(scrapy.Spider):       #自动生成的爬虫类名
        name = 'Hanxiang'
        #allowed_domains = ['www.xbiquge.la/15/15158']    #自动生成的爬取页面控制范围(域)
        allowed_domains = ['xbiquge.la']     #放宽爬取页面限制,否则,就需要在更深一级发出的Request方法中使用dont_filter=True参数。
        def start_requests(self):   #方法名称不能变
            start_urls = ['http://www.xbiquge.la/15/15158/']            #启动爬取页面应以list方式存放,变量名称不能改变。
            for url in start_urls:
                yield scrapy.Request(url=url, callback=self.parse)       #生成器模式(yield)调用爬虫处理方法parse,效率很高。
        def parse(self, response):    #方法名称不能变
            dl = response.css('#list dl dd')     #提取章节链接相关信息
            for dd in dl:
                self.url_c = "http://www.xbiquge.la" + dd.css('a::attr(href)').extract()[0]   #组合形成小说的各章节链接
                #print(self.url_c)
                #yield scrapy.Request(self.url_c, callback=self.parse_c,dont_filter=True )
                yield scrapy.Request(self.url_c, callback=self.parse_c)    #以生成器模式(yield)调用parse_c方法获得各章节链接、上一页链接、下一页链接和章节内容信息。
                #print(self.url_c)
        def parse_c(self, response):
            item = HanxiangItem()
            item['url'] = response.url
            item['preview_page'] = "http://www.xbiquge.la" + response.css('div .bottem1 a::attr(href)').extract()[1]
            item['next_page'] = "http://www.xbiquge.la" + response.css('div .bottem1 a::attr(href)').extract()[3]
            title = response.css('.con_top::text').extract()[4]
            contents = response.css('#content::text').extract()
            text=''
            for content in contents:
                text = text + content
            #print(text)
            item['content'] = title + " " + text.replace('15', ' ')     #各章节标题和内容组合成content数据,15是^M的八进制表示,需要替换为换行符。
            yield item     #以生成器模式(yield)输出Item对象的内容给pipelines模块。

    (5)编制mysql数据库建表程序()

    (base) [python@ELK hanxiang]$ vi createtable.py
    # -*-coding:utf-8-*-
    from hanxiang.pipelines import HanxiangPipeline

    HanxiangPipeline().createtable()

    (6)编制txt文件生成程序(此程序可手工运行单独生成txt文件)

    (base) [python@ELK hanxiang]$ vi spider2txt.py
    # -*- coding:utf-8 -*-
    #调用pipelines.py中模块HanxiangPipeline的content2txt方法,生成txt文件
    from  hanxiang.pipelines import HanxiangPipeline

    HanxiangPipeline().content2txt()

    4、运行爬虫:

    (1)运行建表程序(生成mysql数据表)

    (base) [python@ELK hanxiang]$ python createtable.py

    (2)运行爬虫主程序(爬取数据写入mysql数据库,并在爬虫结束时,从数据库提取数据按章节顺序生成txt文件。)

    (base) [python@ELK hanxiang]$ scrapy runspider hanxiang/spiders/Hanxiang.py

     5、运行结果显示

    ...

     'next_page': 'http://www.xbiquge.la/15/15158/6873262.html',
     'preview_page': 'http://www.xbiquge.la/15/15158/6873260.html',
     'url': 'http://www.xbiquge.la/15/15158/6873261.html'}
    2019-08-24 23:50:22 [scrapy.core.engine] INFO: Closing spider (finished)
    7.578139305114746
    2019-08-24 23:50:30 [scrapy.statscollectors] INFO: Dumping Scrapy stats:
    {'downloader/request_bytes': 412373,
     'downloader/request_count': 1492,
     'downloader/request_method_count/GET': 1492,
     'downloader/response_bytes': 10242909,
     'downloader/response_count': 1492,
     'downloader/response_status_count/200': 1492,
     'elapsed_time_seconds': 170.554676,
     'finish_reason': 'finished',
     'finish_time': datetime.datetime(2019, 8, 24, 15, 50, 30, 547474),
     'item_scraped_count': 1491,
     'log_count/DEBUG': 2983,
     'log_count/INFO': 12,
     'log_count/WARNING': 1,
     'memusage/max': 70201344,
     'memusage/startup': 44478464,
     'request_depth_max': 1,
     'response_received_count': 1492,
     'scheduler/dequeued': 1492,
     'scheduler/dequeued/memory': 1492,
     'scheduler/enqueued': 1492,
     'scheduler/enqueued/memory': 1492,
     'start_time': datetime.datetime(2019, 8, 24, 15, 47, 39, 992798)}
    2019-08-24 23:50:30 [scrapy.core.engine] INFO: Spider closed (finished)

    (base) [python@ELK hanxiang]$ ll

    ...

    -rw-rw-r--. 1 python python 11889462 8月  24 23:50 汉乡.txt

  • 相关阅读:
    python面对对象编程------3:写集合类的三种方法
    python面对对象编程----2:__init__
    python面对对象编程----1:BlackJack(21点)
    wxpython下的桥梁信息管理系统
    python运行时间计算之timeit
    python单元测试之unittest
    python 下的数据结构与算法---8:哈希一下【dict与set的实现】
    python 下的数据结构与算法---7:查找
    python 下的数据结构与算法---6:6大排序算法
    分布式一致性算法Raft
  • 原文地址:https://www.cnblogs.com/sfccl/p/11401782.html
Copyright © 2020-2023  润新知